mirror of
https://github.com/TermoraDev/termora.git
synced 2026-01-16 18:32:58 +08:00
fix: SSH proxy not working in jump hosts (#435)
This commit is contained in:
@@ -18,6 +18,7 @@ import org.apache.sshd.client.channel.ClientChannelEvent
|
|||||||
import org.apache.sshd.client.config.hosts.HostConfigEntry
|
import org.apache.sshd.client.config.hosts.HostConfigEntry
|
||||||
import org.apache.sshd.client.config.hosts.HostConfigEntryResolver
|
import org.apache.sshd.client.config.hosts.HostConfigEntryResolver
|
||||||
import org.apache.sshd.client.config.hosts.KnownHostEntry
|
import org.apache.sshd.client.config.hosts.KnownHostEntry
|
||||||
|
import org.apache.sshd.client.future.ConnectFuture
|
||||||
import org.apache.sshd.client.kex.DHGClient
|
import org.apache.sshd.client.kex.DHGClient
|
||||||
import org.apache.sshd.client.keyverifier.KnownHostsServerKeyVerifier
|
import org.apache.sshd.client.keyverifier.KnownHostsServerKeyVerifier
|
||||||
import org.apache.sshd.client.keyverifier.ModifiedServerKeyAcceptor
|
import org.apache.sshd.client.keyverifier.ModifiedServerKeyAcceptor
|
||||||
@@ -29,7 +30,13 @@ import org.apache.sshd.common.SshException
|
|||||||
import org.apache.sshd.common.channel.PtyChannelConfiguration
|
import org.apache.sshd.common.channel.PtyChannelConfiguration
|
||||||
import org.apache.sshd.common.config.keys.KeyRandomArt
|
import org.apache.sshd.common.config.keys.KeyRandomArt
|
||||||
import org.apache.sshd.common.config.keys.KeyUtils
|
import org.apache.sshd.common.config.keys.KeyUtils
|
||||||
|
import org.apache.sshd.common.future.CloseFuture
|
||||||
|
import org.apache.sshd.common.future.SshFutureListener
|
||||||
import org.apache.sshd.common.global.KeepAliveHandler
|
import org.apache.sshd.common.global.KeepAliveHandler
|
||||||
|
import org.apache.sshd.common.io.IoConnectFuture
|
||||||
|
import org.apache.sshd.common.io.IoConnector
|
||||||
|
import org.apache.sshd.common.io.IoServiceEventListener
|
||||||
|
import org.apache.sshd.common.io.IoSession
|
||||||
import org.apache.sshd.common.kex.BuiltinDHFactories
|
import org.apache.sshd.common.kex.BuiltinDHFactories
|
||||||
import org.apache.sshd.common.keyprovider.KeyIdentityProvider
|
import org.apache.sshd.common.keyprovider.KeyIdentityProvider
|
||||||
import org.apache.sshd.common.util.net.SshdSocketAddress
|
import org.apache.sshd.common.util.net.SshdSocketAddress
|
||||||
@@ -199,6 +206,10 @@ object SshClients {
|
|||||||
entry.username = host.username
|
entry.username = host.username
|
||||||
entry.hostName = host.host
|
entry.hostName = host.host
|
||||||
entry.setProperty("Middleware", middleware.toString())
|
entry.setProperty("Middleware", middleware.toString())
|
||||||
|
entry.setProperty("Host", host.id)
|
||||||
|
|
||||||
|
// 设置代理
|
||||||
|
// configureProxy(entry, host, client)
|
||||||
|
|
||||||
// ssh-agent
|
// ssh-agent
|
||||||
if (host.authentication.type == AuthenticationType.SSHAgent) {
|
if (host.authentication.type == AuthenticationType.SSHAgent) {
|
||||||
@@ -285,11 +296,12 @@ object SshClients {
|
|||||||
fun openClient(host: Host): SshClient {
|
fun openClient(host: Host): SshClient {
|
||||||
val builder = ClientBuilder.builder()
|
val builder = ClientBuilder.builder()
|
||||||
builder.globalRequestHandlers(listOf(KeepAliveHandler.INSTANCE))
|
builder.globalRequestHandlers(listOf(KeepAliveHandler.INSTANCE))
|
||||||
.factory { JGitSshClient() }
|
.factory { MyJGitSshClient() }
|
||||||
|
|
||||||
val keyExchangeFactories = ClientBuilder.setUpDefaultKeyExchanges(true).toMutableList()
|
val keyExchangeFactories = ClientBuilder.setUpDefaultKeyExchanges(true).toMutableList()
|
||||||
|
|
||||||
// https://github.com/TermoraDev/termora/issues/123
|
// https://github.com/TermoraDev/termora/issues/123
|
||||||
|
@Suppress("DEPRECATION")
|
||||||
keyExchangeFactories.addAll(
|
keyExchangeFactories.addAll(
|
||||||
listOf(
|
listOf(
|
||||||
DHGClient.newFactory(BuiltinDHFactories.dhg1),
|
DHGClient.newFactory(BuiltinDHFactories.dhg1),
|
||||||
@@ -345,26 +357,6 @@ object SshClients {
|
|||||||
|
|
||||||
sshClient.setKeyPasswordProviderFactory { IdentityPasswordProvider(CredentialsProvider.getDefault()) }
|
sshClient.setKeyPasswordProviderFactory { IdentityPasswordProvider(CredentialsProvider.getDefault()) }
|
||||||
|
|
||||||
if (host.proxy.type != ProxyType.No) {
|
|
||||||
sshClient.setProxyDatabase {
|
|
||||||
if (host.proxy.authenticationType == AuthenticationType.No) ProxyData(
|
|
||||||
Proxy(
|
|
||||||
if (host.proxy.type == ProxyType.SOCKS5) Proxy.Type.SOCKS else Proxy.Type.HTTP,
|
|
||||||
InetSocketAddress(host.proxy.host, host.proxy.port)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
else
|
|
||||||
ProxyData(
|
|
||||||
Proxy(
|
|
||||||
if (host.proxy.type == ProxyType.SOCKS5) Proxy.Type.SOCKS else Proxy.Type.HTTP,
|
|
||||||
InetSocketAddress(host.proxy.host, host.proxy.port)
|
|
||||||
),
|
|
||||||
host.proxy.username,
|
|
||||||
host.proxy.password.toCharArray(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
sshClient.start()
|
sshClient.start()
|
||||||
return sshClient
|
return sshClient
|
||||||
}
|
}
|
||||||
@@ -497,5 +489,129 @@ object SshClients {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
private class MyJGitSshClient : JGitSshClient() {
|
||||||
|
companion object {
|
||||||
|
private val HOST_CONFIG_ENTRY: AttributeRepository.AttributeKey<HostConfigEntry> by lazy {
|
||||||
|
JGitSshClient::class.java.getDeclaredField("HOST_CONFIG_ENTRY").apply { isAccessible = true }
|
||||||
|
.get(null) as AttributeRepository.AttributeKey<HostConfigEntry>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun createConnector(): IoConnector {
|
||||||
|
return MyIoConnector(this, super.createConnector())
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 加上 synchronized ,因为默认代理是全局的(需要在连接时动态修改),所以这里需要一个个连接
|
||||||
|
*/
|
||||||
|
override fun connect(
|
||||||
|
hostConfig: HostConfigEntry?,
|
||||||
|
context: AttributeRepository?,
|
||||||
|
localAddress: SocketAddress?
|
||||||
|
): ConnectFuture {
|
||||||
|
synchronized(this) {
|
||||||
|
return super.connect(hostConfig, context, localAddress)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class MyIoConnector(private val sshClient: SshClient, private val ioConnector: IoConnector) :
|
||||||
|
IoConnector {
|
||||||
|
override fun close(immediately: Boolean): CloseFuture {
|
||||||
|
return ioConnector.close(immediately)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun addCloseFutureListener(listener: SshFutureListener<CloseFuture>?) {
|
||||||
|
return ioConnector.addCloseFutureListener(listener)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun removeCloseFutureListener(listener: SshFutureListener<CloseFuture>?) {
|
||||||
|
return ioConnector.removeCloseFutureListener(listener)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun isClosed(): Boolean {
|
||||||
|
return ioConnector.isClosed
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun isClosing(): Boolean {
|
||||||
|
return ioConnector.isClosing
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getIoServiceEventListener(): IoServiceEventListener {
|
||||||
|
return ioConnector.ioServiceEventListener
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun setIoServiceEventListener(listener: IoServiceEventListener?) {
|
||||||
|
return ioConnector.setIoServiceEventListener(listener)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getManagedSessions(): MutableMap<Long, IoSession> {
|
||||||
|
return ioConnector.managedSessions
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun connect(
|
||||||
|
targetAddress: SocketAddress,
|
||||||
|
context: AttributeRepository?,
|
||||||
|
localAddress: SocketAddress?
|
||||||
|
): IoConnectFuture {
|
||||||
|
var tAddress = targetAddress
|
||||||
|
val entry = context?.getAttribute(HOST_CONFIG_ENTRY)
|
||||||
|
if (entry != null) {
|
||||||
|
val host = hostManager.getHost(entry.getProperty("Host") ?: StringUtils.EMPTY)
|
||||||
|
if (host != null) {
|
||||||
|
tAddress = configureProxy(host, tAddress)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val proxyConnector = sshClient.clientProxyConnector
|
||||||
|
val future = ioConnector.connect(tAddress, context, localAddress)
|
||||||
|
|
||||||
|
// 代理是一次性的
|
||||||
|
// 如果 tAddress != targetAddress 为 true 那么表示进行代理了
|
||||||
|
if (proxyConnector != null && tAddress != targetAddress) {
|
||||||
|
future.addListener {
|
||||||
|
if (it.isDone) {
|
||||||
|
if (sshClient.clientProxyConnector == proxyConnector) {
|
||||||
|
sshClient.clientProxyConnector = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return future
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun configureProxy(host: Host, targetAddress: SocketAddress): SocketAddress {
|
||||||
|
if (host.proxy.type == ProxyType.No) return targetAddress
|
||||||
|
if (targetAddress.toString().contains(SshdSocketAddress.LOCALHOST_IPV4)) return targetAddress
|
||||||
|
|
||||||
|
val proxyData = ProxyData(
|
||||||
|
if (host.proxy.type == ProxyType.HTTP) {
|
||||||
|
Proxy(Proxy.Type.HTTP, InetSocketAddress(host.proxy.host, host.proxy.port))
|
||||||
|
} else {
|
||||||
|
Proxy(Proxy.Type.SOCKS, InetSocketAddress(host.proxy.host, host.proxy.port))
|
||||||
|
},
|
||||||
|
host.proxy.username.ifBlank { null },
|
||||||
|
if (host.proxy.password.isBlank()) null else host.proxy.password.toCharArray(),
|
||||||
|
)
|
||||||
|
|
||||||
|
// 反射调用
|
||||||
|
val configureProxy = JGitSshClient::class.java.getDeclaredMethod(
|
||||||
|
"configureProxy",
|
||||||
|
ProxyData::class.java,
|
||||||
|
InetSocketAddress::class.java
|
||||||
|
)
|
||||||
|
configureProxy.isAccessible = true
|
||||||
|
val address = configureProxy.invoke(sshClient, proxyData, InetSocketAddress(host.host, host.port))
|
||||||
|
if (address is InetSocketAddress) {
|
||||||
|
return address
|
||||||
|
}
|
||||||
|
|
||||||
|
return targetAddress
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,4 +3,7 @@ RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories
|
|||||||
&& apk update && apk add wget gcc g++ git make zsh htop stress-ng inetutils-telnet xclock xorg-server xinit && wget https://ohse.de/uwe/releases/lrzsz-0.12.20.tar.gz \
|
&& apk update && apk add wget gcc g++ git make zsh htop stress-ng inetutils-telnet xclock xorg-server xinit && wget https://ohse.de/uwe/releases/lrzsz-0.12.20.tar.gz \
|
||||||
&& tar -xf lrzsz-0.12.20.tar.gz && cd lrzsz-0.12.20 && ./configure && make && make install \
|
&& tar -xf lrzsz-0.12.20.tar.gz && cd lrzsz-0.12.20 && ./configure && make && make install \
|
||||||
&& ln -s /usr/local/bin/lrz /usr/local/bin/rz && ln -s /usr/local/bin/lsz /usr/local/bin/sz
|
&& ln -s /usr/local/bin/lrz /usr/local/bin/rz && ln -s /usr/local/bin/lsz /usr/local/bin/sz
|
||||||
|
RUN sed -i 's/#AllowAgentForwarding yes/AllowAgentForwarding yes/g' /etc/ssh/sshd_config
|
||||||
|
RUN sed -i 's/AllowTcpForwarding no/AllowTcpForwarding yes/g' /etc/ssh/sshd_config
|
||||||
|
RUN sed -i 's/GatewayPorts no/GatewayPorts yes/g' /etc/ssh/sshd_config
|
||||||
|
RUN sed -i 's/X11Forwarding no/X11Forwarding yes/g' /etc/ssh/sshd_config
|
||||||
|
|||||||
Reference in New Issue
Block a user