4278 |
20 Dec 16 |
nicklas |
1 |
package net.sf.basedb.opengrid; |
4278 |
20 Dec 16 |
nicklas |
2 |
|
4278 |
20 Dec 16 |
nicklas |
3 |
import java.io.IOException; |
4278 |
20 Dec 16 |
nicklas |
4 |
|
4278 |
20 Dec 16 |
nicklas |
5 |
import org.slf4j.LoggerFactory; |
4278 |
20 Dec 16 |
nicklas |
6 |
|
4742 |
09 Apr 18 |
nicklas |
7 |
import net.schmizz.sshj.Config; |
4278 |
20 Dec 16 |
nicklas |
8 |
import net.schmizz.sshj.SSHClient; |
4742 |
09 Apr 18 |
nicklas |
9 |
import net.schmizz.sshj.common.Factory; |
4742 |
09 Apr 18 |
nicklas |
10 |
import net.schmizz.sshj.common.SSHException; |
4278 |
20 Dec 16 |
nicklas |
11 |
import net.schmizz.sshj.transport.verification.PromiscuousVerifier; |
4742 |
09 Apr 18 |
nicklas |
12 |
import net.schmizz.sshj.userauth.keyprovider.FileKeyProvider; |
4742 |
09 Apr 18 |
nicklas |
13 |
import net.schmizz.sshj.userauth.keyprovider.KeyFormat; |
4742 |
09 Apr 18 |
nicklas |
14 |
import net.schmizz.sshj.userauth.keyprovider.KeyProvider; |
4742 |
09 Apr 18 |
nicklas |
15 |
import net.schmizz.sshj.userauth.keyprovider.KeyProviderUtil; |
4742 |
09 Apr 18 |
nicklas |
16 |
import net.schmizz.sshj.userauth.password.PasswordFinder; |
4742 |
09 Apr 18 |
nicklas |
17 |
import net.schmizz.sshj.userauth.password.PasswordUtils; |
4450 |
10 Apr 17 |
nicklas |
18 |
import net.sf.basedb.core.FileServer; |
4278 |
20 Dec 16 |
nicklas |
19 |
import net.sf.basedb.opengrid.config.ConnectionInfo; |
7075 |
27 Mar 23 |
nicklas |
20 |
import net.sf.basedb.opengrid.service.OpenGridService; |
7075 |
27 Mar 23 |
nicklas |
21 |
import net.sf.basedb.util.extensions.logging.ExtensionsLog; |
7075 |
27 Mar 23 |
nicklas |
22 |
import net.sf.basedb.util.extensions.logging.ExtensionsLogger; |
4278 |
20 Dec 16 |
nicklas |
23 |
|
4278 |
20 Dec 16 |
nicklas |
24 |
/** |
4278 |
20 Dec 16 |
nicklas |
An abstract host that can be reached via SSH. |
4278 |
20 Dec 16 |
nicklas |
26 |
|
4278 |
20 Dec 16 |
nicklas |
@author nicklas |
4278 |
20 Dec 16 |
nicklas |
@since 1.0 |
4278 |
20 Dec 16 |
nicklas |
@param <T> The type of sessions that are created when connecting to the host |
4278 |
20 Dec 16 |
nicklas |
30 |
*/ |
4278 |
20 Dec 16 |
nicklas |
31 |
public abstract class AbstractHost<T extends AbstractSession<?>> |
4278 |
20 Dec 16 |
nicklas |
32 |
{ |
4278 |
20 Dec 16 |
nicklas |
33 |
|
7075 |
27 Mar 23 |
nicklas |
34 |
private static final ExtensionsLogger logger = |
7075 |
27 Mar 23 |
nicklas |
35 |
ExtensionsLog.getLogger(OpenGridService.ID, true).wrap(LoggerFactory.getLogger(AbstractHost.class)); |
4278 |
20 Dec 16 |
nicklas |
36 |
|
4278 |
20 Dec 16 |
nicklas |
37 |
private final ConnectionInfo ci; |
4278 |
20 Dec 16 |
nicklas |
38 |
|
4278 |
20 Dec 16 |
nicklas |
39 |
protected AbstractHost(ConnectionInfo ci) |
4278 |
20 Dec 16 |
nicklas |
40 |
{ |
4278 |
20 Dec 16 |
nicklas |
41 |
this.ci = ci.lock(); |
4278 |
20 Dec 16 |
nicklas |
42 |
} |
4278 |
20 Dec 16 |
nicklas |
43 |
|
4278 |
20 Dec 16 |
nicklas |
44 |
/** |
4278 |
20 Dec 16 |
nicklas |
Get the connection information for this host. |
4278 |
20 Dec 16 |
nicklas |
46 |
*/ |
4278 |
20 Dec 16 |
nicklas |
47 |
public ConnectionInfo getConnectionInfo() |
4278 |
20 Dec 16 |
nicklas |
48 |
{ |
4278 |
20 Dec 16 |
nicklas |
49 |
return ci; |
4278 |
20 Dec 16 |
nicklas |
50 |
} |
4278 |
20 Dec 16 |
nicklas |
51 |
|
4278 |
20 Dec 16 |
nicklas |
52 |
/** |
4278 |
20 Dec 16 |
nicklas |
Connect to the cluster. The returned session can |
4278 |
20 Dec 16 |
nicklas |
be used to send commands or transfer files to/from |
4278 |
20 Dec 16 |
nicklas |
the cluster. Do not forget to {@link AbstractSession#close()} |
4278 |
20 Dec 16 |
nicklas |
the session after use. |
4278 |
20 Dec 16 |
nicklas |
57 |
|
4278 |
20 Dec 16 |
nicklas |
@param timeout Timeout in seconds for the connection to be established |
4278 |
20 Dec 16 |
nicklas |
59 |
*/ |
4278 |
20 Dec 16 |
nicklas |
60 |
public abstract T connect(int timeout); |
4278 |
20 Dec 16 |
nicklas |
61 |
|
4278 |
20 Dec 16 |
nicklas |
62 |
|
4278 |
20 Dec 16 |
nicklas |
63 |
protected SSHClient internalConnect(int timeout) |
4278 |
20 Dec 16 |
nicklas |
64 |
{ |
4278 |
20 Dec 16 |
nicklas |
65 |
if (logger.isDebugEnabled()) |
4278 |
20 Dec 16 |
nicklas |
66 |
{ |
4278 |
20 Dec 16 |
nicklas |
67 |
logger.debug("Connecting to " + ci); |
4278 |
20 Dec 16 |
nicklas |
68 |
} |
4278 |
20 Dec 16 |
nicklas |
69 |
SSHClient ssh = null; |
4278 |
20 Dec 16 |
nicklas |
70 |
try |
4278 |
20 Dec 16 |
nicklas |
71 |
{ |
4278 |
20 Dec 16 |
nicklas |
72 |
ssh = new SSHClient(SshUtil.SSH_CONFIG); |
4278 |
20 Dec 16 |
nicklas |
73 |
if (ci.getFingerPrint() == null) |
4278 |
20 Dec 16 |
nicklas |
74 |
{ |
4278 |
20 Dec 16 |
nicklas |
75 |
ssh.addHostKeyVerifier(new PromiscuousVerifier()); |
4278 |
20 Dec 16 |
nicklas |
76 |
} |
4450 |
10 Apr 17 |
nicklas |
77 |
else if (FileServer.FINGERPRINT_TYPE_MD5.equals(ci.getFingerPrintType())) |
4278 |
20 Dec 16 |
nicklas |
78 |
{ |
4278 |
20 Dec 16 |
nicklas |
79 |
ssh.addHostKeyVerifier(ci.getFingerPrint()); |
4278 |
20 Dec 16 |
nicklas |
80 |
} |
4450 |
10 Apr 17 |
nicklas |
81 |
else if (FileServer.FINGERPRINT_TYPE_SHA256.equals(ci.getFingerPrintType())) |
4450 |
10 Apr 17 |
nicklas |
82 |
{ |
4450 |
10 Apr 17 |
nicklas |
83 |
ssh.addHostKeyVerifier(new Sha256Verifier(ci.getFingerPrint())); |
4450 |
10 Apr 17 |
nicklas |
84 |
} |
4742 |
09 Apr 18 |
nicklas |
85 |
|
4278 |
20 Dec 16 |
nicklas |
86 |
ssh.setConnectTimeout(timeout * 1000); |
4278 |
20 Dec 16 |
nicklas |
87 |
ssh.connect(ci.getAddress(), ci.getPort()); |
4742 |
09 Apr 18 |
nicklas |
88 |
|
4742 |
09 Apr 18 |
nicklas |
89 |
if (ci.getPrivateKey() != null) |
4742 |
09 Apr 18 |
nicklas |
90 |
{ |
4742 |
09 Apr 18 |
nicklas |
91 |
if (logger.isDebugEnabled()) |
4742 |
09 Apr 18 |
nicklas |
92 |
{ |
4742 |
09 Apr 18 |
nicklas |
93 |
logger.debug("Using private key authentication"); |
4742 |
09 Apr 18 |
nicklas |
94 |
} |
4742 |
09 Apr 18 |
nicklas |
95 |
KeyProvider kp = createFileKeyProvider(ssh.getTransport().getConfig(), |
4742 |
09 Apr 18 |
nicklas |
96 |
ci.getPrivateKey(), ci.getPrivateKeyPassword(), ci.getPrivateKeyFormat()); |
4742 |
09 Apr 18 |
nicklas |
97 |
ssh.authPublickey(ci.getUser(), kp); |
4742 |
09 Apr 18 |
nicklas |
98 |
} |
4742 |
09 Apr 18 |
nicklas |
99 |
else if (ci.getPassword() != null) |
4742 |
09 Apr 18 |
nicklas |
100 |
{ |
4742 |
09 Apr 18 |
nicklas |
101 |
if (logger.isDebugEnabled()) |
4742 |
09 Apr 18 |
nicklas |
102 |
{ |
4742 |
09 Apr 18 |
nicklas |
103 |
logger.debug("Using password authentication"); |
4742 |
09 Apr 18 |
nicklas |
104 |
} |
4742 |
09 Apr 18 |
nicklas |
105 |
ssh.authPassword(ci.getUser(), ci.getPassword()); |
4742 |
09 Apr 18 |
nicklas |
106 |
} |
4742 |
09 Apr 18 |
nicklas |
107 |
|
4278 |
20 Dec 16 |
nicklas |
108 |
if (logger.isDebugEnabled()) |
4278 |
20 Dec 16 |
nicklas |
109 |
{ |
4278 |
20 Dec 16 |
nicklas |
110 |
logger.debug("Connected to " + ci); |
4278 |
20 Dec 16 |
nicklas |
111 |
} |
4278 |
20 Dec 16 |
nicklas |
112 |
} |
4278 |
20 Dec 16 |
nicklas |
113 |
catch (IOException ex) |
4278 |
20 Dec 16 |
nicklas |
114 |
{ |
4278 |
20 Dec 16 |
nicklas |
115 |
logger.error("Connection to " + ci + " failed", ex); |
4278 |
20 Dec 16 |
nicklas |
116 |
OpenGrid.close(ssh); |
7075 |
27 Mar 23 |
nicklas |
117 |
throw new RuntimeException("Connection to " + ci + " failed", ex); |
4278 |
20 Dec 16 |
nicklas |
118 |
} |
4278 |
20 Dec 16 |
nicklas |
119 |
catch (RuntimeException ex) |
4278 |
20 Dec 16 |
nicklas |
120 |
{ |
4278 |
20 Dec 16 |
nicklas |
121 |
logger.error("Connection to " + ci + " failed", ex); |
4278 |
20 Dec 16 |
nicklas |
122 |
OpenGrid.close(ssh); |
4278 |
20 Dec 16 |
nicklas |
123 |
throw ex; |
4278 |
20 Dec 16 |
nicklas |
124 |
} |
4278 |
20 Dec 16 |
nicklas |
125 |
return ssh; |
4278 |
20 Dec 16 |
nicklas |
126 |
} |
4278 |
20 Dec 16 |
nicklas |
127 |
|
4278 |
20 Dec 16 |
nicklas |
128 |
@Override |
4278 |
20 Dec 16 |
nicklas |
129 |
public String toString() |
4278 |
20 Dec 16 |
nicklas |
130 |
{ |
4278 |
20 Dec 16 |
nicklas |
131 |
return getClass().getSimpleName()+"["+getConnectionInfo().toString()+"]"; |
4278 |
20 Dec 16 |
nicklas |
132 |
} |
4278 |
20 Dec 16 |
nicklas |
133 |
|
4742 |
09 Apr 18 |
nicklas |
134 |
|
4742 |
09 Apr 18 |
nicklas |
135 |
/** |
4742 |
09 Apr 18 |
nicklas |
Create and initialize a private key provider. |
4742 |
09 Apr 18 |
nicklas |
@param config Required since we get supported key file formats from this |
4742 |
09 Apr 18 |
nicklas |
@param privateKey The private key file as a string (required) |
4742 |
09 Apr 18 |
nicklas |
@param password Optional |
4742 |
09 Apr 18 |
nicklas |
@param format Optional (if not specified, auto-detection will be attempted) |
4742 |
09 Apr 18 |
nicklas |
141 |
*/ |
4742 |
09 Apr 18 |
nicklas |
142 |
private KeyProvider createFileKeyProvider(Config config, String privateKey, String password, String format) |
4742 |
09 Apr 18 |
nicklas |
143 |
throws SSHException, IOException |
4742 |
09 Apr 18 |
nicklas |
144 |
{ |
4742 |
09 Apr 18 |
nicklas |
145 |
if (format == null) |
4742 |
09 Apr 18 |
nicklas |
146 |
{ |
4742 |
09 Apr 18 |
nicklas |
147 |
if (logger.isDebugEnabled()) logger.debug("Auto-detecting private key format"); |
4742 |
09 Apr 18 |
nicklas |
// No format specified, try auto-detecting which is supported for PuTTY and OpenSSH |
4742 |
09 Apr 18 |
nicklas |
149 |
KeyFormat kf = KeyProviderUtil.detectKeyFileFormat(privateKey, true); |
4742 |
09 Apr 18 |
nicklas |
150 |
if (kf == null || kf == KeyFormat.Unknown) |
4742 |
09 Apr 18 |
nicklas |
151 |
{ |
4742 |
09 Apr 18 |
nicklas |
152 |
throw new SSHException("Could not detect format of private key"); |
4742 |
09 Apr 18 |
nicklas |
153 |
} |
4742 |
09 Apr 18 |
nicklas |
154 |
format = kf.toString(); |
4742 |
09 Apr 18 |
nicklas |
155 |
if (logger.isDebugEnabled()) logger.debug("Auto-detecting private key format: " + format); |
4742 |
09 Apr 18 |
nicklas |
156 |
} |
4742 |
09 Apr 18 |
nicklas |
157 |
|
4742 |
09 Apr 18 |
nicklas |
158 |
FileKeyProvider provider = Factory.Named.Util.create(config.getFileKeyProviderFactories(), format); |
4742 |
09 Apr 18 |
nicklas |
159 |
if (provider == null) |
4742 |
09 Apr 18 |
nicklas |
160 |
{ |
4742 |
09 Apr 18 |
nicklas |
161 |
throw new SSHException("No provider available for '" + format + "' key file format"); |
4742 |
09 Apr 18 |
nicklas |
162 |
} |
4742 |
09 Apr 18 |
nicklas |
163 |
|
4742 |
09 Apr 18 |
nicklas |
164 |
PasswordFinder pwd = password == null ? null : PasswordUtils.createOneOff(password.toCharArray()); |
4742 |
09 Apr 18 |
nicklas |
165 |
provider.init(privateKey, null, pwd); |
4742 |
09 Apr 18 |
nicklas |
166 |
try |
4742 |
09 Apr 18 |
nicklas |
167 |
{ |
4742 |
09 Apr 18 |
nicklas |
// Try to get the private key here to catch problems early and get better error messages for the user |
4742 |
09 Apr 18 |
nicklas |
169 |
provider.getPrivate(); |
4742 |
09 Apr 18 |
nicklas |
170 |
} |
4742 |
09 Apr 18 |
nicklas |
171 |
catch (RuntimeException ex) |
4742 |
09 Apr 18 |
nicklas |
172 |
{ |
4742 |
09 Apr 18 |
nicklas |
173 |
String msg = "Invalid '" + format + "' private key"; |
4742 |
09 Apr 18 |
nicklas |
174 |
if (ex.getMessage() != null) msg += " (" + ex.getMessage() + ")"; |
4742 |
09 Apr 18 |
nicklas |
175 |
throw new SSHException(msg, ex); |
4742 |
09 Apr 18 |
nicklas |
176 |
} |
4742 |
09 Apr 18 |
nicklas |
177 |
|
4742 |
09 Apr 18 |
nicklas |
178 |
return provider; |
4742 |
09 Apr 18 |
nicklas |
179 |
} |
4742 |
09 Apr 18 |
nicklas |
180 |
|
4278 |
20 Dec 16 |
nicklas |
181 |
} |