JCLOUDS-516: Add ssh agent support via sch agentproxy

This commit is contained in:
Pasi Niemi 2014-03-19 22:58:02 +02:00 committed by Andrew Phillips
parent 655aa444d7
commit 85a1a8c1dd
14 changed files with 182 additions and 32 deletions

View File

@ -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;
@ -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);

View File

@ -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();

View File

@ -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);
} }
} }

View File

@ -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> {

View File

@ -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
*/ */

View File

@ -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>

View File

@ -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

View File

@ -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");

View File

@ -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();
}
} }
} }

View File

@ -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>

View File

@ -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();
}
} }

View File

@ -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

View File

@ -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();
}
} }
} }

View File

@ -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 -->