From c67d5b02764b379edd8fdb980430077c17ea8c4d Mon Sep 17 00:00:00 2001 From: hstyi Date: Fri, 8 Aug 2025 17:45:31 +0800 Subject: [PATCH] feat: ssh ForwardAgent --- .../plugin/internal/ssh/SSHHostOptionsPane.kt | 22 ++++++++++++++++--- .../plugin/internal/ssh/SshAgentFactory.kt | 14 ++++++++++++ .../termora/plugin/internal/ssh/SshClients.kt | 15 ++++++++----- 3 files changed, 42 insertions(+), 9 deletions(-) create mode 100644 src/main/kotlin/app/termora/plugin/internal/ssh/SshAgentFactory.kt diff --git a/src/main/kotlin/app/termora/plugin/internal/ssh/SSHHostOptionsPane.kt b/src/main/kotlin/app/termora/plugin/internal/ssh/SSHHostOptionsPane.kt index aa04582..95dce9a 100644 --- a/src/main/kotlin/app/termora/plugin/internal/ssh/SSHHostOptionsPane.kt +++ b/src/main/kotlin/app/termora/plugin/internal/ssh/SSHHostOptionsPane.kt @@ -114,7 +114,8 @@ internal class SSHHostOptionsPane(private val accountOwner: AccountOwner) : Opti ?: AltKeyModifier.EightBit.name), "keywordHighlightSetId" to ((terminalOption.highlightSetComboBox.selectedItem as? KeywordHighlight)?.id ?: "-1"), - "timeout" to (terminalOption.timeoutTextField.value ?: 60).toString() + "timeout" to (terminalOption.timeoutTextField.value ?: 60).toString(), + "forwardAgent" to tunnelingOption.forwardAgentCheckBox.isSelected.toString(), ) ) @@ -182,6 +183,7 @@ internal class SSHHostOptionsPane(private val accountOwner: AccountOwner) : Opti tunnelingOption.tunnelings.addAll(host.tunnelings) tunnelingOption.x11ForwardingCheckBox.isSelected = host.options.enableX11Forwarding tunnelingOption.x11ServerTextField.text = StringUtils.defaultIfBlank(host.options.x11Forwarding, "localhost:0") + tunnelingOption.forwardAgentCheckBox.isSelected = host.options.extras["forwardAgent"]?.toBoolean() ?: false if (host.options.jumpHosts.isNotEmpty()) { val hosts = HostManager.getInstance().hosts().associateBy { it.id } @@ -570,9 +572,10 @@ internal class SSHHostOptionsPane(private val accountOwner: AccountOwner) : Opti } } - protected inner class TunnelingOption : JPanel(BorderLayout()), Option { + private inner class TunnelingOption : JPanel(BorderLayout()), Option { val tunnelings = mutableListOf() val x11ForwardingCheckBox = JCheckBox("X DISPLAY:") + val forwardAgentCheckBox = JCheckBox("Enable ForwardAgent") val x11ServerTextField = OutlineTextField(255) private val model = object : DefaultTableModel() { @@ -649,6 +652,7 @@ internal class SSHHostOptionsPane(private val accountOwner: AccountOwner) : Opti box.add(deleteBtn) x11ForwardingCheckBox.isFocusable = false + forwardAgentCheckBox.isFocusable = false if (x11ServerTextField.text.isBlank()) { x11ServerTextField.text = "localhost:0" @@ -662,6 +666,13 @@ internal class SSHHostOptionsPane(private val accountOwner: AccountOwner) : Opti x11Forwarding.add(x11ForwardingCheckBox) x11Forwarding.add(x11ServerTextField) + val forwardAgent = Box.createHorizontalBox() + forwardAgent.border = BorderFactory.createCompoundBorder( + BorderFactory.createTitledBorder("ForwardAgent"), + BorderFactory.createEmptyBorder(4, 4, 4, 4) + ) + forwardAgent.add(forwardAgentCheckBox) + x11ServerTextField.isEnabled = x11ForwardingCheckBox.isSelected val panel = JPanel(BorderLayout()) @@ -670,8 +681,13 @@ internal class SSHHostOptionsPane(private val accountOwner: AccountOwner) : Opti panel.add(box, BorderLayout.SOUTH) panel.border = BorderFactory.createEmptyBorder(0, 0, 8, 0) + val forwardingBox = Box.createHorizontalBox() + forwardingBox.add(x11Forwarding) + forwardingBox.add(Box.createHorizontalStrut(4)) + forwardingBox.add(forwardAgent) + add(panel, BorderLayout.CENTER) - add(x11Forwarding, BorderLayout.SOUTH) + add(forwardingBox, BorderLayout.SOUTH) } diff --git a/src/main/kotlin/app/termora/plugin/internal/ssh/SshAgentFactory.kt b/src/main/kotlin/app/termora/plugin/internal/ssh/SshAgentFactory.kt new file mode 100644 index 0000000..3baf42c --- /dev/null +++ b/src/main/kotlin/app/termora/plugin/internal/ssh/SshAgentFactory.kt @@ -0,0 +1,14 @@ +package app.termora.plugin.internal.ssh + +import org.apache.sshd.agent.local.ChannelAgentForwardingFactory +import org.apache.sshd.common.FactoryManager +import org.apache.sshd.common.channel.ChannelFactory +import org.eclipse.jgit.internal.transport.sshd.agent.JGitSshAgentFactory +import org.eclipse.jgit.transport.sshd.agent.ConnectorFactory +import java.io.File + +internal class SshAgentFactory(factory: ConnectorFactory, homeDir: File?) : JGitSshAgentFactory(factory, homeDir) { + override fun getChannelForwardingFactories(manager: FactoryManager?): List { + return listOf(ChannelAgentForwardingFactory.OPENSSH, ChannelAgentForwardingFactory.IETF) + } +} \ No newline at end of file diff --git a/src/main/kotlin/app/termora/plugin/internal/ssh/SshClients.kt b/src/main/kotlin/app/termora/plugin/internal/ssh/SshClients.kt index 1cab829..2bcaa88 100644 --- a/src/main/kotlin/app/termora/plugin/internal/ssh/SshClients.kt +++ b/src/main/kotlin/app/termora/plugin/internal/ssh/SshClients.kt @@ -56,7 +56,6 @@ import org.apache.sshd.server.forward.AcceptAllForwardingFilter import org.apache.sshd.server.forward.RejectAllForwardingFilter import org.eclipse.jgit.internal.transport.sshd.JGitClientSession import org.eclipse.jgit.internal.transport.sshd.JGitSshClient -import org.eclipse.jgit.internal.transport.sshd.agent.JGitSshAgentFactory import org.eclipse.jgit.internal.transport.sshd.agent.connector.PageantConnector import org.eclipse.jgit.internal.transport.sshd.agent.connector.UnixDomainSocketConnector import org.eclipse.jgit.internal.transport.sshd.proxy.AbstractClientProxyConnector @@ -112,6 +111,8 @@ object SshClients { env.putAll(host.options.envs()) val channel = session.createShellChannel(configuration, env) + channel.isAgentForwarding = host.options.extras["forwardAgent"]?.toBoolean() == true + if (host.options.enableX11Forwarding) { if (channel is app.termora.x11.ChannelShell) { channel.xForwarding = true @@ -386,7 +387,7 @@ object SshClients { val channelFactories = mutableListOf() channelFactories.addAll(ClientBuilder.DEFAULT_CHANNEL_FACTORIES) - channelFactories.add(X11ChannelFactory.Companion.INSTANCE) + channelFactories.add(X11ChannelFactory.INSTANCE) builder.channelFactories(channelFactories) val sshClient = builder.build() as JGitSshClient @@ -395,12 +396,14 @@ object SshClients { // JGit 会尝试读取本地的私钥或缓存的私钥 sshClient.keyIdentityProvider = KeyIdentityProvider { mutableListOf() } + // https://github.com/TermoraDev/termora/issues/1001 + if (host.authentication.type == AuthenticationType.SSHAgent || host.options.extras["forwardAgent"]?.toBoolean() == true) { + // ssh-agent + sshClient.agentFactory = SshAgentFactory(ConnectorFactory.getDefault(), null) + } + // 设置优先级 if (host.authentication.type == AuthenticationType.PublicKey || host.authentication.type == AuthenticationType.SSHAgent) { - if (host.authentication.type == AuthenticationType.SSHAgent) { - // ssh-agent - sshClient.agentFactory = JGitSshAgentFactory(ConnectorFactory.getDefault(), null) - } CoreModuleProperties.PREFERRED_AUTHS.set( sshClient, listOf(