mirror of https://github.com/apache/jclouds.git
JCLOUDS-516: Add ssh agent support via sch agentproxy
This commit is contained in:
parent
655aa444d7
commit
85a1a8c1dd
|
@ -17,6 +17,7 @@
|
||||||
package org.jclouds.compute.functions;
|
package org.jclouds.compute.functions;
|
||||||
|
|
||||||
import static com.google.common.base.Preconditions.checkNotNull;
|
import static com.google.common.base.Preconditions.checkNotNull;
|
||||||
|
import static com.google.common.base.Preconditions.checkArgument;
|
||||||
import static com.google.common.base.Preconditions.checkState;
|
import static com.google.common.base.Preconditions.checkState;
|
||||||
|
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
@ -49,7 +50,7 @@ public class CreateSshClientOncePortIsListeningOnNode implements Function<NodeMe
|
||||||
|
|
||||||
@Inject(optional = true)
|
@Inject(optional = true)
|
||||||
SshClient.Factory sshFactory;
|
SshClient.Factory sshFactory;
|
||||||
|
|
||||||
private final OpenSocketFinder openSocketFinder;
|
private final OpenSocketFinder openSocketFinder;
|
||||||
|
|
||||||
private final long timeoutMs;
|
private final long timeoutMs;
|
||||||
|
@ -65,7 +66,7 @@ public class CreateSshClientOncePortIsListeningOnNode implements Function<NodeMe
|
||||||
checkState(sshFactory != null, "ssh requested, but no SshModule configured");
|
checkState(sshFactory != null, "ssh requested, but no SshModule configured");
|
||||||
checkNotNull(node.getCredentials(), "no credentials found for node %s", node.getId());
|
checkNotNull(node.getCredentials(), "no credentials found for node %s", node.getId());
|
||||||
checkNotNull(node.getCredentials().identity, "no login identity found for node %s", node.getId());
|
checkNotNull(node.getCredentials().identity, "no login identity found for node %s", node.getId());
|
||||||
checkNotNull(node.getCredentials().credential, "no credential found for %s on node %s", node
|
checkArgument(node.getCredentials().credential != null || sshFactory.isAgentAvailable(), "no credential or ssh agent found for %s on node %s", node
|
||||||
.getCredentials().identity, node.getId());
|
.getCredentials().identity, node.getId());
|
||||||
HostAndPort socket = openSocketFinder.findOpenSocketOnNode(node, node.getLoginPort(),
|
HostAndPort socket = openSocketFinder.findOpenSocketOnNode(node, node.getLoginPort(),
|
||||||
timeoutMs, TimeUnit.MILLISECONDS);
|
timeoutMs, TimeUnit.MILLISECONDS);
|
||||||
|
|
|
@ -29,9 +29,8 @@ import com.google.common.net.HostAndPort;
|
||||||
public interface SshClient {
|
public interface SshClient {
|
||||||
|
|
||||||
interface Factory {
|
interface Factory {
|
||||||
|
|
||||||
SshClient create(HostAndPort socket, LoginCredentials credentials);
|
SshClient create(HostAndPort socket, LoginCredentials credentials);
|
||||||
|
boolean isAgentAvailable();
|
||||||
}
|
}
|
||||||
|
|
||||||
String getUsername();
|
String getUsername();
|
||||||
|
|
|
@ -228,7 +228,6 @@ public abstract class BaseComputeServiceLiveTest extends BaseComputeServiceConte
|
||||||
NodeMetadata node = get(nodes, 0);
|
NodeMetadata node = get(nodes, 0);
|
||||||
LoginCredentials good = node.getCredentials();
|
LoginCredentials good = node.getCredentials();
|
||||||
assert good.identity != null : nodes;
|
assert good.identity != null : nodes;
|
||||||
assert good.credential != null : nodes;
|
|
||||||
|
|
||||||
for (Entry<? extends NodeMetadata, ExecResponse> response : client.runScriptOnNodesMatching(
|
for (Entry<? extends NodeMetadata, ExecResponse> response : client.runScriptOnNodesMatching(
|
||||||
runningInGroup(group), "hostname",
|
runningInGroup(group), "hostname",
|
||||||
|
@ -507,7 +506,6 @@ public abstract class BaseComputeServiceLiveTest extends BaseComputeServiceConte
|
||||||
assertNotNull(node.getCredentials());
|
assertNotNull(node.getCredentials());
|
||||||
if (node.getCredentials().identity != null) {
|
if (node.getCredentials().identity != null) {
|
||||||
assertNotNull(node.getCredentials().identity);
|
assertNotNull(node.getCredentials().identity);
|
||||||
assertNotNull(node.getCredentials().credential);
|
|
||||||
sshPing(node, taskName);
|
sshPing(node, taskName);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -73,6 +73,7 @@ public class Pems {
|
||||||
public static final String CERTIFICATE_X509_MARKER = "-----BEGIN CERTIFICATE-----";
|
public static final String CERTIFICATE_X509_MARKER = "-----BEGIN CERTIFICATE-----";
|
||||||
public static final String PUBLIC_X509_MARKER = "-----BEGIN PUBLIC KEY-----";
|
public static final String PUBLIC_X509_MARKER = "-----BEGIN PUBLIC KEY-----";
|
||||||
public static final String PUBLIC_PKCS1_MARKER = "-----BEGIN RSA PUBLIC KEY-----";
|
public static final String PUBLIC_PKCS1_MARKER = "-----BEGIN RSA PUBLIC KEY-----";
|
||||||
|
public static final String PROC_TYPE_ENCRYPTED = "Proc-Type: 4,ENCRYPTED";
|
||||||
|
|
||||||
private static class PemProcessor<T> implements ByteProcessor<T> {
|
private static class PemProcessor<T> implements ByteProcessor<T> {
|
||||||
private interface ResultParser<T> {
|
private interface ResultParser<T> {
|
||||||
|
|
|
@ -19,6 +19,7 @@ package org.jclouds.domain;
|
||||||
import static org.jclouds.crypto.Pems.PRIVATE_PKCS1_MARKER;
|
import static org.jclouds.crypto.Pems.PRIVATE_PKCS1_MARKER;
|
||||||
import static org.jclouds.crypto.Pems.PRIVATE_PKCS8_MARKER;
|
import static org.jclouds.crypto.Pems.PRIVATE_PKCS8_MARKER;
|
||||||
|
|
||||||
|
import org.jclouds.crypto.Pems;
|
||||||
import org.jclouds.javax.annotation.Nullable;
|
import org.jclouds.javax.annotation.Nullable;
|
||||||
|
|
||||||
import com.google.common.base.Optional;
|
import com.google.common.base.Optional;
|
||||||
|
@ -155,6 +156,15 @@ public class LoginCredentials extends Credentials {
|
||||||
return (privateKey != null) ? privateKey.orNull() : null;
|
return (privateKey != null) ? privateKey.orNull() : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return true if there is a private key attached that is not encrypted
|
||||||
|
*/
|
||||||
|
public boolean hasUnencryptedPrivateKey() {
|
||||||
|
return getPrivateKey() != null
|
||||||
|
&& !getPrivateKey().isEmpty()
|
||||||
|
&& !getPrivateKey().contains(Pems.PROC_TYPE_ENCRYPTED);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return the optional private ssh key of the user or null
|
* @return the optional private ssh key of the user or null
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -86,6 +86,16 @@
|
||||||
<artifactId>jsch</artifactId>
|
<artifactId>jsch</artifactId>
|
||||||
<scope>compile</scope>
|
<scope>compile</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.jcraft</groupId>
|
||||||
|
<artifactId>jsch.agentproxy.jsch</artifactId>
|
||||||
|
<version>0.0.7</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.jcraft</groupId>
|
||||||
|
<artifactId>jsch.agentproxy.connector-factory</artifactId>
|
||||||
|
<version>0.0.7</version>
|
||||||
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
<profiles>
|
<profiles>
|
||||||
|
|
|
@ -56,6 +56,7 @@ import org.jclouds.util.Closeables2;
|
||||||
import org.jclouds.util.Strings2;
|
import org.jclouds.util.Strings2;
|
||||||
|
|
||||||
import com.google.common.annotations.VisibleForTesting;
|
import com.google.common.annotations.VisibleForTesting;
|
||||||
|
import com.google.common.base.Optional;
|
||||||
import com.google.common.base.Predicate;
|
import com.google.common.base.Predicate;
|
||||||
import com.google.common.base.Predicates;
|
import com.google.common.base.Predicates;
|
||||||
import com.google.common.base.Splitter;
|
import com.google.common.base.Splitter;
|
||||||
|
@ -66,6 +67,7 @@ import com.jcraft.jsch.ChannelExec;
|
||||||
import com.jcraft.jsch.ChannelSftp;
|
import com.jcraft.jsch.ChannelSftp;
|
||||||
import com.jcraft.jsch.JSchException;
|
import com.jcraft.jsch.JSchException;
|
||||||
import com.jcraft.jsch.Session;
|
import com.jcraft.jsch.Session;
|
||||||
|
import com.jcraft.jsch.agentproxy.Connector;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This class needs refactoring. It is not thread safe.
|
* This class needs refactoring. It is not thread safe.
|
||||||
|
@ -124,25 +126,28 @@ public class JschSshClient implements SshClient {
|
||||||
|
|
||||||
|
|
||||||
public JschSshClient(ProxyConfig proxyConfig, BackoffLimitedRetryHandler backoffLimitedRetryHandler, HostAndPort socket,
|
public JschSshClient(ProxyConfig proxyConfig, BackoffLimitedRetryHandler backoffLimitedRetryHandler, HostAndPort socket,
|
||||||
LoginCredentials loginCredentials, int timeout) {
|
LoginCredentials loginCredentials, int timeout, Optional<Connector> agentConnector) {
|
||||||
this.user = checkNotNull(loginCredentials, "loginCredentials").getUser();
|
this.user = checkNotNull(loginCredentials, "loginCredentials").getUser();
|
||||||
this.host = checkNotNull(socket, "socket").getHostText();
|
this.host = checkNotNull(socket, "socket").getHostText();
|
||||||
checkArgument(socket.getPort() > 0, "ssh port must be greater then zero" + socket.getPort());
|
checkArgument(socket.getPort() > 0, "ssh port must be greater then zero" + socket.getPort());
|
||||||
checkArgument(loginCredentials.getPassword() != null || loginCredentials.getPrivateKey() != null,
|
checkArgument(loginCredentials.getPassword() != null || loginCredentials.hasUnencryptedPrivateKey() || agentConnector.isPresent(),
|
||||||
"you must specify a password or a key");
|
"you must specify a password, a key or an SSH agent needs to be available");
|
||||||
this.backoffLimitedRetryHandler = checkNotNull(backoffLimitedRetryHandler, "backoffLimitedRetryHandler");
|
this.backoffLimitedRetryHandler = checkNotNull(backoffLimitedRetryHandler, "backoffLimitedRetryHandler");
|
||||||
if (loginCredentials.getPrivateKey() == null) {
|
if (loginCredentials.getPassword() != null) {
|
||||||
this.toString = String.format("%s:pw[%s]@%s:%d", loginCredentials.getUser(),
|
this.toString = String.format("%s:pw[%s]@%s:%d", loginCredentials.getUser(),
|
||||||
base16().lowerCase().encode(md5().hashString(loginCredentials.getPassword(), UTF_8).asBytes()), host,
|
base16().lowerCase().encode(md5().hashString(loginCredentials.getPassword(), UTF_8).asBytes()), host,
|
||||||
socket.getPort());
|
socket.getPort());
|
||||||
} else {
|
} else if (loginCredentials.hasUnencryptedPrivateKey()) {
|
||||||
String fingerPrint = fingerprintPrivateKey(loginCredentials.getPrivateKey());
|
String fingerPrint = fingerprintPrivateKey(loginCredentials.getPrivateKey());
|
||||||
String sha1 = sha1PrivateKey(loginCredentials.getPrivateKey());
|
String sha1 = sha1PrivateKey(loginCredentials.getPrivateKey());
|
||||||
this.toString = String.format("%s:rsa[fingerprint(%s),sha1(%s)]@%s:%d", loginCredentials.getUser(),
|
this.toString = String.format("%s:rsa[fingerprint(%s),sha1(%s)]@%s:%d", loginCredentials.getUser(),
|
||||||
fingerPrint, sha1, host, socket.getPort());
|
fingerPrint, sha1, host, socket.getPort());
|
||||||
|
} else {
|
||||||
|
this.toString = String.format("%s:rsa[ssh-agent]@%s:%d", loginCredentials.getUser(), host, socket.getPort());
|
||||||
}
|
}
|
||||||
sessionConnection = SessionConnection.builder().hostAndPort(HostAndPort.fromParts(host, socket.getPort())).loginCredentials(
|
sessionConnection = SessionConnection.builder().hostAndPort(HostAndPort.fromParts(host, socket.getPort())).loginCredentials(
|
||||||
loginCredentials).proxy(checkNotNull(proxyConfig, "proxyConfig")).connectTimeout(timeout).sessionTimeout(timeout).build();
|
loginCredentials).proxy(checkNotNull(proxyConfig, "proxyConfig")).connectTimeout(timeout).sessionTimeout(timeout)
|
||||||
|
.agentConnector(agentConnector).build();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -17,7 +17,6 @@
|
||||||
package org.jclouds.ssh.jsch;
|
package org.jclouds.ssh.jsch;
|
||||||
|
|
||||||
import static com.google.common.base.Objects.equal;
|
import static com.google.common.base.Objects.equal;
|
||||||
import static com.google.common.base.Preconditions.checkArgument;
|
|
||||||
import static com.google.common.base.Preconditions.checkNotNull;
|
import static com.google.common.base.Preconditions.checkNotNull;
|
||||||
|
|
||||||
import org.jclouds.domain.Credentials;
|
import org.jclouds.domain.Credentials;
|
||||||
|
@ -34,8 +33,13 @@ import com.jcraft.jsch.Proxy;
|
||||||
import com.jcraft.jsch.ProxyHTTP;
|
import com.jcraft.jsch.ProxyHTTP;
|
||||||
import com.jcraft.jsch.ProxySOCKS5;
|
import com.jcraft.jsch.ProxySOCKS5;
|
||||||
import com.jcraft.jsch.Session;
|
import com.jcraft.jsch.Session;
|
||||||
|
import com.jcraft.jsch.agentproxy.Connector;
|
||||||
|
import com.jcraft.jsch.agentproxy.RemoteIdentityRepository;
|
||||||
|
|
||||||
public final class SessionConnection implements Connection<Session> {
|
public final class SessionConnection implements Connection<Session> {
|
||||||
|
|
||||||
|
private Optional<Connector> agentConnector;
|
||||||
|
|
||||||
public static Builder builder() {
|
public static Builder builder() {
|
||||||
return new Builder();
|
return new Builder();
|
||||||
}
|
}
|
||||||
|
@ -47,6 +51,7 @@ public final class SessionConnection implements Connection<Session> {
|
||||||
private Optional<Proxy> proxy = Optional.absent();
|
private Optional<Proxy> proxy = Optional.absent();
|
||||||
private int connectTimeout;
|
private int connectTimeout;
|
||||||
private int sessionTimeout;
|
private int sessionTimeout;
|
||||||
|
private Optional<Connector> agentConnector;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @see SessionConnection#getHostAndPort()
|
* @see SessionConnection#getHostAndPort()
|
||||||
|
@ -114,7 +119,7 @@ public final class SessionConnection implements Connection<Session> {
|
||||||
}
|
}
|
||||||
|
|
||||||
public SessionConnection build() {
|
public SessionConnection build() {
|
||||||
return new SessionConnection(hostAndPort, loginCredentials, proxy, connectTimeout, sessionTimeout);
|
return new SessionConnection(hostAndPort, loginCredentials, proxy, connectTimeout, sessionTimeout, agentConnector);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Builder from(SessionConnection in) {
|
public Builder from(SessionConnection in) {
|
||||||
|
@ -122,15 +127,21 @@ public final class SessionConnection implements Connection<Session> {
|
||||||
.connectTimeout(in.connectTimeout).sessionTimeout(in.sessionTimeout);
|
.connectTimeout(in.connectTimeout).sessionTimeout(in.sessionTimeout);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Builder agentConnector(Optional<Connector> agentConnector) {
|
||||||
|
this.agentConnector = agentConnector;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private SessionConnection(HostAndPort hostAndPort, LoginCredentials loginCredentials, Optional<Proxy> proxy,
|
private SessionConnection(HostAndPort hostAndPort, LoginCredentials loginCredentials, Optional<Proxy> proxy,
|
||||||
int connectTimeout, int sessionTimeout) {
|
int connectTimeout, int sessionTimeout, Optional<Connector> agentConnector) {
|
||||||
this.hostAndPort = checkNotNull(hostAndPort, "hostAndPort");
|
this.hostAndPort = checkNotNull(hostAndPort, "hostAndPort");
|
||||||
this.loginCredentials = checkNotNull(loginCredentials, "loginCredentials for %", hostAndPort);
|
this.loginCredentials = checkNotNull(loginCredentials, "loginCredentials for %", hostAndPort);
|
||||||
this.connectTimeout = connectTimeout;
|
this.connectTimeout = connectTimeout;
|
||||||
this.sessionTimeout = sessionTimeout;
|
this.sessionTimeout = sessionTimeout;
|
||||||
this.proxy = checkNotNull(proxy, "proxy for %", hostAndPort);
|
this.proxy = checkNotNull(proxy, "proxy for %", hostAndPort);
|
||||||
|
this.agentConnector = checkNotNull(agentConnector, "agentConnector for %", hostAndPort);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final byte[] emptyPassPhrase = new byte[0];
|
private static final byte[] emptyPassPhrase = new byte[0];
|
||||||
|
@ -160,11 +171,12 @@ public final class SessionConnection implements Connection<Session> {
|
||||||
session.setTimeout(sessionTimeout);
|
session.setTimeout(sessionTimeout);
|
||||||
if (loginCredentials.getPrivateKey() == null) {
|
if (loginCredentials.getPrivateKey() == null) {
|
||||||
session.setPassword(loginCredentials.getPassword());
|
session.setPassword(loginCredentials.getPassword());
|
||||||
} else {
|
} else if (loginCredentials.hasUnencryptedPrivateKey()) {
|
||||||
checkArgument(!loginCredentials.getPrivateKey().contains("Proc-Type: 4,ENCRYPTED"),
|
|
||||||
"JschSshClientModule does not support private keys that require a passphrase");
|
|
||||||
byte[] privateKey = loginCredentials.getPrivateKey().getBytes();
|
byte[] privateKey = loginCredentials.getPrivateKey().getBytes();
|
||||||
jsch.addIdentity(loginCredentials.getUser(), privateKey, null, emptyPassPhrase);
|
jsch.addIdentity(loginCredentials.getUser(), privateKey, null, emptyPassPhrase);
|
||||||
|
} else if (agentConnector.isPresent()) {
|
||||||
|
JSch.setConfig("PreferredAuthentications", "publickey");
|
||||||
|
jsch.setIdentityRepository(new RemoteIdentityRepository(agentConnector.get()));
|
||||||
}
|
}
|
||||||
java.util.Properties config = new java.util.Properties();
|
java.util.Properties config = new java.util.Properties();
|
||||||
config.put("StrictHostKeyChecking", "no");
|
config.put("StrictHostKeyChecking", "no");
|
||||||
|
|
|
@ -28,11 +28,15 @@ import org.jclouds.ssh.SshClient;
|
||||||
import org.jclouds.ssh.config.ConfiguresSshClient;
|
import org.jclouds.ssh.config.ConfiguresSshClient;
|
||||||
import org.jclouds.ssh.jsch.JschSshClient;
|
import org.jclouds.ssh.jsch.JschSshClient;
|
||||||
|
|
||||||
|
import com.google.common.base.Optional;
|
||||||
import com.google.common.net.HostAndPort;
|
import com.google.common.net.HostAndPort;
|
||||||
import com.google.inject.AbstractModule;
|
import com.google.inject.AbstractModule;
|
||||||
import com.google.inject.Inject;
|
import com.google.inject.Inject;
|
||||||
import com.google.inject.Injector;
|
import com.google.inject.Injector;
|
||||||
import com.google.inject.Scopes;
|
import com.google.inject.Scopes;
|
||||||
|
import com.jcraft.jsch.agentproxy.AgentProxyException;
|
||||||
|
import com.jcraft.jsch.agentproxy.Connector;
|
||||||
|
import com.jcraft.jsch.agentproxy.ConnectorFactory;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
|
@ -50,6 +54,16 @@ public class JschSshClientModule extends AbstractModule {
|
||||||
@Inject(optional = true)
|
@Inject(optional = true)
|
||||||
int timeout = 60000;
|
int timeout = 60000;
|
||||||
|
|
||||||
|
Optional<Connector> agentConnector = getAgentConnector();
|
||||||
|
|
||||||
|
Optional<Connector> getAgentConnector() {
|
||||||
|
try {
|
||||||
|
return Optional.of(ConnectorFactory.getDefault().createConnector());
|
||||||
|
} catch (final AgentProxyException e) {
|
||||||
|
return Optional.absent();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private final ProxyConfig proxyConfig;
|
private final ProxyConfig proxyConfig;
|
||||||
private final BackoffLimitedRetryHandler backoffLimitedRetryHandler;
|
private final BackoffLimitedRetryHandler backoffLimitedRetryHandler;
|
||||||
private final Injector injector;
|
private final Injector injector;
|
||||||
|
@ -63,9 +77,14 @@ public class JschSshClientModule extends AbstractModule {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public SshClient create(HostAndPort socket, LoginCredentials credentials) {
|
public SshClient create(HostAndPort socket, LoginCredentials credentials) {
|
||||||
SshClient client = new JschSshClient(proxyConfig, backoffLimitedRetryHandler, socket, credentials, timeout);
|
SshClient client = new JschSshClient(proxyConfig, backoffLimitedRetryHandler, socket, credentials, timeout, getAgentConnector());
|
||||||
injector.injectMembers(client);// add logger
|
injector.injectMembers(client);// add logger
|
||||||
return client;
|
return client;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isAgentAvailable() {
|
||||||
|
return agentConnector.isPresent();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -104,6 +104,16 @@
|
||||||
</exclusion>
|
</exclusion>
|
||||||
</exclusions>
|
</exclusions>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.jcraft</groupId>
|
||||||
|
<artifactId>jsch.agentproxy.sshj</artifactId>
|
||||||
|
<version>0.0.7</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.jcraft</groupId>
|
||||||
|
<artifactId>jsch.agentproxy.connector-factory</artifactId>
|
||||||
|
<version>0.0.7</version>
|
||||||
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
<profiles>
|
<profiles>
|
||||||
|
|
|
@ -17,15 +17,19 @@
|
||||||
package org.jclouds.sshj;
|
package org.jclouds.sshj;
|
||||||
|
|
||||||
import static com.google.common.base.Objects.equal;
|
import static com.google.common.base.Objects.equal;
|
||||||
|
import static com.google.common.base.Preconditions.checkNotNull;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
import javax.annotation.Resource;
|
import javax.annotation.Resource;
|
||||||
import javax.inject.Named;
|
import javax.inject.Named;
|
||||||
|
|
||||||
import net.schmizz.sshj.SSHClient;
|
import net.schmizz.sshj.SSHClient;
|
||||||
|
import net.schmizz.sshj.common.Buffer.BufferException;
|
||||||
import net.schmizz.sshj.transport.verification.PromiscuousVerifier;
|
import net.schmizz.sshj.transport.verification.PromiscuousVerifier;
|
||||||
import net.schmizz.sshj.userauth.keyprovider.OpenSSHKeyFile;
|
import net.schmizz.sshj.userauth.keyprovider.OpenSSHKeyFile;
|
||||||
|
import net.schmizz.sshj.userauth.method.AuthMethod;
|
||||||
|
|
||||||
import org.jclouds.domain.LoginCredentials;
|
import org.jclouds.domain.LoginCredentials;
|
||||||
import org.jclouds.logging.Logger;
|
import org.jclouds.logging.Logger;
|
||||||
|
@ -33,9 +37,18 @@ import org.jclouds.sshj.SshjSshClient.Connection;
|
||||||
|
|
||||||
import com.google.common.annotations.VisibleForTesting;
|
import com.google.common.annotations.VisibleForTesting;
|
||||||
import com.google.common.base.Objects;
|
import com.google.common.base.Objects;
|
||||||
|
import com.google.common.base.Optional;
|
||||||
|
import com.google.common.collect.ImmutableList;
|
||||||
import com.google.common.net.HostAndPort;
|
import com.google.common.net.HostAndPort;
|
||||||
|
|
||||||
|
import com.jcraft.jsch.agentproxy.AgentProxy;
|
||||||
|
import com.jcraft.jsch.agentproxy.Connector;
|
||||||
|
import com.jcraft.jsch.agentproxy.Identity;
|
||||||
|
import com.jcraft.jsch.agentproxy.sshj.AuthAgent;
|
||||||
|
|
||||||
public class SSHClientConnection implements Connection<SSHClient> {
|
public class SSHClientConnection implements Connection<SSHClient> {
|
||||||
|
private Optional<Connector> agentConnector;
|
||||||
|
|
||||||
public static Builder builder() {
|
public static Builder builder() {
|
||||||
return new Builder();
|
return new Builder();
|
||||||
}
|
}
|
||||||
|
@ -46,6 +59,7 @@ public class SSHClientConnection implements Connection<SSHClient> {
|
||||||
protected LoginCredentials loginCredentials;
|
protected LoginCredentials loginCredentials;
|
||||||
protected int connectTimeout;
|
protected int connectTimeout;
|
||||||
protected int sessionTimeout;
|
protected int sessionTimeout;
|
||||||
|
protected Optional<Connector> agentConnector;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @see SSHClientConnection#getHostAndPort()
|
* @see SSHClientConnection#getHostAndPort()
|
||||||
|
@ -79,8 +93,16 @@ public class SSHClientConnection implements Connection<SSHClient> {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see SSHClientConnection#getAgentConnector()
|
||||||
|
*/
|
||||||
|
public Builder agentConnector(Optional<Connector> agentConnector) {
|
||||||
|
this.agentConnector = agentConnector;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
public SSHClientConnection build() {
|
public SSHClientConnection build() {
|
||||||
return new SSHClientConnection(hostAndPort, loginCredentials, connectTimeout, sessionTimeout);
|
return new SSHClientConnection(hostAndPort, loginCredentials, connectTimeout, sessionTimeout, agentConnector);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected Builder fromSSHClientConnection(SSHClientConnection in) {
|
protected Builder fromSSHClientConnection(SSHClientConnection in) {
|
||||||
|
@ -90,11 +112,12 @@ public class SSHClientConnection implements Connection<SSHClient> {
|
||||||
}
|
}
|
||||||
|
|
||||||
private SSHClientConnection(HostAndPort hostAndPort, LoginCredentials loginCredentials, int connectTimeout,
|
private SSHClientConnection(HostAndPort hostAndPort, LoginCredentials loginCredentials, int connectTimeout,
|
||||||
int sessionTimeout) {
|
int sessionTimeout, Optional<Connector> agentConnector) {
|
||||||
this.hostAndPort = hostAndPort;
|
this.hostAndPort = checkNotNull(hostAndPort, "hostAndPort");
|
||||||
this.loginCredentials = loginCredentials;
|
this.loginCredentials = checkNotNull(loginCredentials, "loginCredentials for %", hostAndPort);
|
||||||
this.connectTimeout = connectTimeout;
|
this.connectTimeout = connectTimeout;
|
||||||
this.sessionTimeout = sessionTimeout;
|
this.sessionTimeout = sessionTimeout;
|
||||||
|
this.agentConnector = checkNotNull(agentConnector, "agentConnector for %", hostAndPort);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Resource
|
@Resource
|
||||||
|
@ -136,10 +159,13 @@ public class SSHClientConnection implements Connection<SSHClient> {
|
||||||
ssh.connect(hostAndPort.getHostText(), hostAndPort.getPortOrDefault(22));
|
ssh.connect(hostAndPort.getHostText(), hostAndPort.getPortOrDefault(22));
|
||||||
if (loginCredentials.getPassword() != null) {
|
if (loginCredentials.getPassword() != null) {
|
||||||
ssh.authPassword(loginCredentials.getUser(), loginCredentials.getPassword());
|
ssh.authPassword(loginCredentials.getUser(), loginCredentials.getPassword());
|
||||||
} else {
|
} else if (loginCredentials.hasUnencryptedPrivateKey()) {
|
||||||
OpenSSHKeyFile key = new OpenSSHKeyFile();
|
OpenSSHKeyFile key = new OpenSSHKeyFile();
|
||||||
key.init(loginCredentials.getPrivateKey(), null);
|
key.init(loginCredentials.getPrivateKey(), null);
|
||||||
ssh.authPublickey(loginCredentials.getUser(), key);
|
ssh.authPublickey(loginCredentials.getUser(), key);
|
||||||
|
} else if (agentConnector.isPresent()) {
|
||||||
|
AgentProxy proxy = new AgentProxy(agentConnector.get());
|
||||||
|
ssh.auth(loginCredentials.getUser(), getAuthMethods(proxy));
|
||||||
}
|
}
|
||||||
return ssh;
|
return ssh;
|
||||||
}
|
}
|
||||||
|
@ -175,6 +201,14 @@ public class SSHClientConnection implements Connection<SSHClient> {
|
||||||
return sessionTimeout;
|
return sessionTimeout;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @return Ssh agent connector
|
||||||
|
*/
|
||||||
|
public Optional<Connector> getAgentConnector() {
|
||||||
|
return agentConnector;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @return the current ssh or {@code null} if not connected
|
* @return the current ssh or {@code null} if not connected
|
||||||
|
@ -206,4 +240,12 @@ public class SSHClientConnection implements Connection<SSHClient> {
|
||||||
"sessionTimeout", sessionTimeout).toString();
|
"sessionTimeout", sessionTimeout).toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static List<AuthMethod> getAuthMethods(AgentProxy agent) throws BufferException {
|
||||||
|
ImmutableList.Builder<AuthMethod> identities = ImmutableList.builder();
|
||||||
|
for (Identity identity : agent.getIdentities()) {
|
||||||
|
identities.add(new AuthAgent(agent, identity));
|
||||||
|
}
|
||||||
|
return identities.build();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -68,6 +68,7 @@ import org.jclouds.util.Closeables2;
|
||||||
import org.jclouds.util.Throwables2;
|
import org.jclouds.util.Throwables2;
|
||||||
|
|
||||||
import com.google.common.annotations.VisibleForTesting;
|
import com.google.common.annotations.VisibleForTesting;
|
||||||
|
import com.google.common.base.Optional;
|
||||||
import com.google.common.base.Predicate;
|
import com.google.common.base.Predicate;
|
||||||
import com.google.common.base.Predicates;
|
import com.google.common.base.Predicates;
|
||||||
import com.google.common.base.Splitter;
|
import com.google.common.base.Splitter;
|
||||||
|
@ -76,6 +77,7 @@ import com.google.common.base.Throwables;
|
||||||
import com.google.common.collect.ImmutableMap;
|
import com.google.common.collect.ImmutableMap;
|
||||||
import com.google.common.net.HostAndPort;
|
import com.google.common.net.HostAndPort;
|
||||||
import com.google.inject.Inject;
|
import com.google.inject.Inject;
|
||||||
|
import com.jcraft.jsch.agentproxy.Connector;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This class needs refactoring. It is not thread safe.
|
* This class needs refactoring. It is not thread safe.
|
||||||
|
@ -141,25 +143,28 @@ public class SshjSshClient implements SshClient {
|
||||||
private final BackoffLimitedRetryHandler backoffLimitedRetryHandler;
|
private final BackoffLimitedRetryHandler backoffLimitedRetryHandler;
|
||||||
|
|
||||||
public SshjSshClient(BackoffLimitedRetryHandler backoffLimitedRetryHandler, HostAndPort socket,
|
public SshjSshClient(BackoffLimitedRetryHandler backoffLimitedRetryHandler, HostAndPort socket,
|
||||||
LoginCredentials loginCredentials, int timeout) {
|
LoginCredentials loginCredentials, int timeout, Optional<Connector> agentConnector) {
|
||||||
this.user = checkNotNull(loginCredentials, "loginCredentials").getUser();
|
this.user = checkNotNull(loginCredentials, "loginCredentials").getUser();
|
||||||
this.host = checkNotNull(socket, "socket").getHostText();
|
this.host = checkNotNull(socket, "socket").getHostText();
|
||||||
checkArgument(socket.getPort() > 0, "ssh port must be greater then zero" + socket.getPort());
|
checkArgument(socket.getPort() > 0, "ssh port must be greater then zero" + socket.getPort());
|
||||||
checkArgument(loginCredentials.getPassword() != null || loginCredentials.getPrivateKey() != null,
|
checkArgument(loginCredentials.getPassword() != null || loginCredentials.hasUnencryptedPrivateKey() || agentConnector.isPresent(),
|
||||||
"you must specify a password or a key");
|
"you must specify a password, a key or an SSH agent needs to be available");
|
||||||
this.backoffLimitedRetryHandler = checkNotNull(backoffLimitedRetryHandler, "backoffLimitedRetryHandler");
|
this.backoffLimitedRetryHandler = checkNotNull(backoffLimitedRetryHandler, "backoffLimitedRetryHandler");
|
||||||
if (loginCredentials.getPrivateKey() == null) {
|
if (loginCredentials.getPassword() != null) {
|
||||||
this.toString = String.format("%s:pw[%s]@%s:%d", loginCredentials.getUser(),
|
this.toString = String.format("%s:pw[%s]@%s:%d", loginCredentials.getUser(),
|
||||||
base16().lowerCase().encode(md5().hashString(loginCredentials.getPassword(), UTF_8).asBytes()), host,
|
base16().lowerCase().encode(md5().hashString(loginCredentials.getPassword(), UTF_8).asBytes()), host,
|
||||||
socket.getPort());
|
socket.getPort());
|
||||||
} else {
|
} else if (loginCredentials.hasUnencryptedPrivateKey()) {
|
||||||
String fingerPrint = fingerprintPrivateKey(loginCredentials.getPrivateKey());
|
String fingerPrint = fingerprintPrivateKey(loginCredentials.getPrivateKey());
|
||||||
String sha1 = sha1PrivateKey(loginCredentials.getPrivateKey());
|
String sha1 = sha1PrivateKey(loginCredentials.getPrivateKey());
|
||||||
this.toString = String.format("%s:rsa[fingerprint(%s),sha1(%s)]@%s:%d", loginCredentials.getUser(),
|
this.toString = String.format("%s:rsa[fingerprint(%s),sha1(%s)]@%s:%d", loginCredentials.getUser(),
|
||||||
fingerPrint, sha1, host, socket.getPort());
|
fingerPrint, sha1, host, socket.getPort());
|
||||||
|
} else {
|
||||||
|
this.toString = String.format("%s:rsa[ssh-agent]@%s:%d", loginCredentials.getUser(),
|
||||||
|
host, socket.getPort());
|
||||||
}
|
}
|
||||||
sshClientConnection = SSHClientConnection.builder().hostAndPort(HostAndPort.fromParts(host, socket.getPort()))
|
sshClientConnection = SSHClientConnection.builder().hostAndPort(HostAndPort.fromParts(host, socket.getPort()))
|
||||||
.loginCredentials(loginCredentials).connectTimeout(timeout).sessionTimeout(timeout).build();
|
.loginCredentials(loginCredentials).connectTimeout(timeout).sessionTimeout(timeout).agentConnector(agentConnector).build();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -25,11 +25,15 @@ import org.jclouds.ssh.SshClient;
|
||||||
import org.jclouds.ssh.config.ConfiguresSshClient;
|
import org.jclouds.ssh.config.ConfiguresSshClient;
|
||||||
import org.jclouds.sshj.SshjSshClient;
|
import org.jclouds.sshj.SshjSshClient;
|
||||||
|
|
||||||
|
import com.google.common.base.Optional;
|
||||||
import com.google.common.net.HostAndPort;
|
import com.google.common.net.HostAndPort;
|
||||||
import com.google.inject.AbstractModule;
|
import com.google.inject.AbstractModule;
|
||||||
import com.google.inject.Inject;
|
import com.google.inject.Inject;
|
||||||
import com.google.inject.Injector;
|
import com.google.inject.Injector;
|
||||||
import com.google.inject.Scopes;
|
import com.google.inject.Scopes;
|
||||||
|
import com.jcraft.jsch.agentproxy.AgentProxyException;
|
||||||
|
import com.jcraft.jsch.agentproxy.Connector;
|
||||||
|
import com.jcraft.jsch.agentproxy.ConnectorFactory;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
|
@ -42,11 +46,22 @@ public class SshjSshClientModule extends AbstractModule {
|
||||||
bind(SshClient.Factory.class).to(Factory.class).in(Scopes.SINGLETON);
|
bind(SshClient.Factory.class).to(Factory.class).in(Scopes.SINGLETON);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private static class Factory implements SshClient.Factory {
|
private static class Factory implements SshClient.Factory {
|
||||||
@Named(Constants.PROPERTY_CONNECTION_TIMEOUT)
|
@Named(Constants.PROPERTY_CONNECTION_TIMEOUT)
|
||||||
@Inject(optional = true)
|
@Inject(optional = true)
|
||||||
int timeout = 60000;
|
int timeout = 60000;
|
||||||
|
|
||||||
|
Optional<Connector> agentConnector = getAgentConnector();
|
||||||
|
|
||||||
|
Optional<Connector> getAgentConnector() {
|
||||||
|
try {
|
||||||
|
return Optional.of(ConnectorFactory.getDefault().createConnector());
|
||||||
|
} catch (final AgentProxyException e) {
|
||||||
|
return Optional.absent();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private final BackoffLimitedRetryHandler backoffLimitedRetryHandler;
|
private final BackoffLimitedRetryHandler backoffLimitedRetryHandler;
|
||||||
private final Injector injector;
|
private final Injector injector;
|
||||||
|
|
||||||
|
@ -58,9 +73,15 @@ public class SshjSshClientModule extends AbstractModule {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public SshClient create(HostAndPort socket, LoginCredentials credentials) {
|
public SshClient create(HostAndPort socket, LoginCredentials credentials) {
|
||||||
SshClient client = new SshjSshClient(backoffLimitedRetryHandler, socket, credentials, timeout);
|
SshClient client = new SshjSshClient(backoffLimitedRetryHandler, socket, credentials, timeout, getAgentConnector());
|
||||||
injector.injectMembers(client);// add logger
|
injector.injectMembers(client);// add logger
|
||||||
return client;
|
return client;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isAgentAvailable() {
|
||||||
|
return agentConnector.isPresent();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -469,6 +469,23 @@
|
||||||
<package>com.google</package>
|
<package>com.google</package>
|
||||||
</packages>
|
</packages>
|
||||||
</exception>
|
</exception>
|
||||||
|
<exception>
|
||||||
|
<conflictingDependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.jcraft</groupId>
|
||||||
|
<artifactId>jsch.agentproxy.core</artifactId>
|
||||||
|
<version>0.0.7</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.jcraft</groupId>
|
||||||
|
<artifactId>jsch.agentproxy.connector-factory</artifactId>
|
||||||
|
<version>0.0.7</version>
|
||||||
|
</dependency>
|
||||||
|
</conflictingDependencies>
|
||||||
|
<packages>
|
||||||
|
<package>com.jcraft.jsch.agentproxy</package>
|
||||||
|
</packages>
|
||||||
|
</exception>
|
||||||
</exceptions>
|
</exceptions>
|
||||||
<ignoredResources>
|
<ignoredResources>
|
||||||
<!-- For all the jetty packages -->
|
<!-- For all the jetty packages -->
|
||||||
|
|
Loading…
Reference in New Issue