|
17 | 17 |
|
18 | 18 | package org.apache.hertzbeat.collector.collect.common.ssh;
|
19 | 19 |
|
| 20 | +import java.io.InputStream; |
| 21 | +import java.security.KeyPair; |
| 22 | +import java.util.List; |
20 | 23 | import lombok.extern.slf4j.Slf4j;
|
21 | 24 | import org.apache.hertzbeat.collector.collect.common.cache.AbstractConnection;
|
22 | 25 | import org.apache.hertzbeat.collector.collect.common.cache.CacheIdentifier;
|
23 | 26 | import org.apache.hertzbeat.collector.collect.common.cache.GlobalConnectionCache;
|
24 | 27 | import org.apache.hertzbeat.collector.collect.common.cache.SshConnect;
|
25 | 28 | import org.apache.hertzbeat.collector.util.PrivateKeyUtils;
|
| 29 | +import org.apache.hertzbeat.common.entity.job.protocol.SshProtocol; |
26 | 30 | import org.apache.sshd.client.SshClient;
|
| 31 | +import org.apache.sshd.client.config.hosts.HostConfigEntry; |
27 | 32 | import org.apache.sshd.client.session.ClientSession;
|
28 | 33 | import org.apache.sshd.common.config.keys.FilePasswordProvider;
|
29 | 34 | import org.apache.sshd.common.util.security.SecurityUtils;
|
@@ -101,4 +106,101 @@ public static ClientSession getConnectSession(String host, String port, String u
|
101 | 106 | return clientSession;
|
102 | 107 | }
|
103 | 108 |
|
| 109 | + public static ClientSession getConnectSession(SshProtocol sshProtocol, int timeout, boolean reuseConnection, boolean useProxy) |
| 110 | + throws IOException, GeneralSecurityException { |
| 111 | + CacheIdentifier identifier = CacheIdentifier.builder() |
| 112 | + .ip(sshProtocol.getHost()).port(sshProtocol.getPort()) |
| 113 | + .username(sshProtocol.getUsername()).password(sshProtocol.getPassword()) |
| 114 | + .build(); |
| 115 | + ClientSession clientSession = null; |
| 116 | + // When using ProxyJump, force connection reuse: |
| 117 | + // Apache MINA SSHD will pass the proxy password error to the target host in proxy scenarios, causing the first connection to fail. |
| 118 | + // Reusing connections can skip duplicate authentication and avoid this problem. |
| 119 | + if (reuseConnection || useProxy) { |
| 120 | + Optional<AbstractConnection<?>> cacheOption = CONNECTION_COMMON_CACHE.getCache(identifier, true); |
| 121 | + if (cacheOption.isPresent()) { |
| 122 | + SshConnect sshConnect = (SshConnect) cacheOption.get(); |
| 123 | + clientSession = sshConnect.getConnection(); |
| 124 | + try { |
| 125 | + if (clientSession == null || clientSession.isClosed() || clientSession.isClosing()) { |
| 126 | + clientSession = null; |
| 127 | + CONNECTION_COMMON_CACHE.removeCache(identifier); |
| 128 | + } |
| 129 | + } catch (Exception e) { |
| 130 | + log.warn(e.getMessage()); |
| 131 | + clientSession = null; |
| 132 | + CONNECTION_COMMON_CACHE.removeCache(identifier); |
| 133 | + } |
| 134 | + } |
| 135 | + if (clientSession != null) { |
| 136 | + return clientSession; |
| 137 | + } |
| 138 | + } |
| 139 | + SshClient sshClient = CommonSshClient.getSshClient(); |
| 140 | + HostConfigEntry proxyConfig = new HostConfigEntry(); |
| 141 | + if (useProxy && StringUtils.hasText(sshProtocol.getProxyHost())) { |
| 142 | + String proxySpec = String.format("%s@%s:%d", sshProtocol.getProxyUsername(), sshProtocol.getProxyHost(), Integer.parseInt(sshProtocol.getProxyPort())); |
| 143 | + proxyConfig.setHostName(sshProtocol.getHost()); |
| 144 | + proxyConfig.setHost(sshProtocol.getHost()); |
| 145 | + proxyConfig.setPort(Integer.parseInt(sshProtocol.getPort())); |
| 146 | + proxyConfig.setUsername(sshProtocol.getUsername()); |
| 147 | + proxyConfig.setProxyJump(proxySpec); |
| 148 | + |
| 149 | + // Apache SSHD requires the password for the proxy to be preloaded into the sshClient instance before connecting |
| 150 | + if (StringUtils.hasText(sshProtocol.getProxyPassword())) { |
| 151 | + sshClient.addPasswordIdentity(sshProtocol.getProxyPassword()); |
| 152 | + log.debug("Loaded proxy server password authentication: {}@{}", sshProtocol.getProxyUsername(), sshProtocol.getProxyHost()); |
| 153 | + } |
| 154 | + if (StringUtils.hasText(sshProtocol.getProxyPrivateKey())) { |
| 155 | + proxyConfig.setIdentities(List.of(sshProtocol.getProxyPrivateKey())); |
| 156 | + log.debug("Proxy private key loaded into HostConfigEntry"); |
| 157 | + } |
| 158 | + } |
| 159 | + |
| 160 | + if (useProxy && StringUtils.hasText(sshProtocol.getProxyHost())) { |
| 161 | + try { |
| 162 | + clientSession = sshClient.connect(proxyConfig) |
| 163 | + .verify(timeout, TimeUnit.MILLISECONDS).getSession(); |
| 164 | + } |
| 165 | + finally { |
| 166 | + sshClient.removePasswordIdentity(sshProtocol.getProxyPassword()); |
| 167 | + } |
| 168 | + } else { |
| 169 | + clientSession = sshClient.connect(sshProtocol.getUsername(), sshProtocol.getHost(), Integer.parseInt(sshProtocol.getPort())) |
| 170 | + .verify(timeout, TimeUnit.MILLISECONDS).getSession(); |
| 171 | + } |
| 172 | + |
| 173 | + if (StringUtils.hasText(sshProtocol.getPassword())) { |
| 174 | + clientSession.addPasswordIdentity(sshProtocol.getPassword()); |
| 175 | + } else if (StringUtils.hasText(sshProtocol.getPrivateKey())) { |
| 176 | + var resourceKey = PrivateKeyUtils.writePrivateKey(sshProtocol.getHost(), sshProtocol.getPrivateKey()); |
| 177 | + try (InputStream keyStream = new FileInputStream(resourceKey)) { |
| 178 | + FilePasswordProvider passwordProvider = (session, resource, index) -> { |
| 179 | + if (StringUtils.hasText(sshProtocol.getPrivateKeyPassphrase())) { |
| 180 | + return sshProtocol.getPrivateKeyPassphrase(); |
| 181 | + } |
| 182 | + return null; |
| 183 | + }; |
| 184 | + Iterable<KeyPair> keyPairs = SecurityUtils.loadKeyPairIdentities(null, () -> resourceKey, keyStream, passwordProvider); |
| 185 | + if (keyPairs != null) { |
| 186 | + keyPairs.forEach(clientSession::addPublicKeyIdentity); |
| 187 | + } else { |
| 188 | + log.error("Failed to load private key pairs from: {}", resourceKey); |
| 189 | + } |
| 190 | + } catch (IOException e) { |
| 191 | + log.error("Error reading private key file: {}", e.getMessage()); |
| 192 | + } |
| 193 | + } // else auth with localhost private public key certificates |
| 194 | + |
| 195 | + // auth |
| 196 | + if (!clientSession.auth().verify(timeout, TimeUnit.MILLISECONDS).isSuccess()) { |
| 197 | + clientSession.close(); |
| 198 | + throw new IllegalArgumentException("ssh auth failed."); |
| 199 | + } |
| 200 | + if (reuseConnection || useProxy) { |
| 201 | + SshConnect sshConnect = new SshConnect(clientSession); |
| 202 | + CONNECTION_COMMON_CACHE.addCache(identifier, sshConnect); |
| 203 | + } |
| 204 | + return clientSession; |
| 205 | + } |
104 | 206 | }
|
0 commit comments