Repository: jclouds Updated Branches: refs/heads/1.7.x 59c10e18e -> 63d6d9755
JCLOUDS-516: Add ssh agent support via sch agentproxy Project: http://git-wip-us.apache.org/repos/asf/jclouds/repo Commit: http://git-wip-us.apache.org/repos/asf/jclouds/commit/63d6d975 Tree: http://git-wip-us.apache.org/repos/asf/jclouds/tree/63d6d975 Diff: http://git-wip-us.apache.org/repos/asf/jclouds/diff/63d6d975 Branch: refs/heads/1.7.x Commit: 63d6d97553524598d9096234b824f1ccdedc4ccd Parents: 59c10e1 Author: Pasi Niemi <[email protected]> Authored: Wed Mar 19 22:58:02 2014 +0200 Committer: Andrew Phillips <[email protected]> Committed: Thu Mar 27 11:04:14 2014 +0100 ---------------------------------------------------------------------- ...reateSshClientOncePortIsListeningOnNode.java | 5 +- .../main/java/org/jclouds/ssh/SshClient.java | 3 +- .../internal/BaseComputeServiceLiveTest.java | 2 - core/src/main/java/org/jclouds/crypto/Pems.java | 1 + .../org/jclouds/domain/LoginCredentials.java | 10 ++++ drivers/jsch/pom.xml | 10 ++++ .../org/jclouds/ssh/jsch/JschSshClient.java | 19 ++++--- .../org/jclouds/ssh/jsch/SessionConnection.java | 24 ++++++--- .../ssh/jsch/config/JschSshClientModule.java | 21 +++++++- drivers/sshj/pom.xml | 10 ++++ .../org/jclouds/sshj/SSHClientConnection.java | 52 ++++++++++++++++++-- .../java/org/jclouds/sshj/SshjSshClient.java | 17 ++++--- .../sshj/config/SshjSshClientModule.java | 23 ++++++++- project/pom.xml | 17 +++++++ 14 files changed, 182 insertions(+), 32 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/jclouds/blob/63d6d975/compute/src/main/java/org/jclouds/compute/functions/CreateSshClientOncePortIsListeningOnNode.java ---------------------------------------------------------------------- diff --git a/compute/src/main/java/org/jclouds/compute/functions/CreateSshClientOncePortIsListeningOnNode.java b/compute/src/main/java/org/jclouds/compute/functions/CreateSshClientOncePortIsListeningOnNode.java index 0ba1818..ce06167 100644 --- a/compute/src/main/java/org/jclouds/compute/functions/CreateSshClientOncePortIsListeningOnNode.java +++ b/compute/src/main/java/org/jclouds/compute/functions/CreateSshClientOncePortIsListeningOnNode.java @@ -17,6 +17,7 @@ package org.jclouds.compute.functions; 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 java.util.concurrent.TimeUnit; @@ -49,7 +50,7 @@ public class CreateSshClientOncePortIsListeningOnNode implements Function<NodeMe @Inject(optional = true) SshClient.Factory sshFactory; - + private final OpenSocketFinder openSocketFinder; private final long timeoutMs; @@ -65,7 +66,7 @@ public class CreateSshClientOncePortIsListeningOnNode implements Function<NodeMe checkState(sshFactory != null, "ssh requested, but no SshModule configured"); 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().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()); HostAndPort socket = openSocketFinder.findOpenSocketOnNode(node, node.getLoginPort(), timeoutMs, TimeUnit.MILLISECONDS); http://git-wip-us.apache.org/repos/asf/jclouds/blob/63d6d975/compute/src/main/java/org/jclouds/ssh/SshClient.java ---------------------------------------------------------------------- diff --git a/compute/src/main/java/org/jclouds/ssh/SshClient.java b/compute/src/main/java/org/jclouds/ssh/SshClient.java index 0620e46..2c94c6b 100644 --- a/compute/src/main/java/org/jclouds/ssh/SshClient.java +++ b/compute/src/main/java/org/jclouds/ssh/SshClient.java @@ -29,9 +29,8 @@ import com.google.common.net.HostAndPort; public interface SshClient { interface Factory { - SshClient create(HostAndPort socket, LoginCredentials credentials); - + boolean isAgentAvailable(); } String getUsername(); http://git-wip-us.apache.org/repos/asf/jclouds/blob/63d6d975/compute/src/test/java/org/jclouds/compute/internal/BaseComputeServiceLiveTest.java ---------------------------------------------------------------------- diff --git a/compute/src/test/java/org/jclouds/compute/internal/BaseComputeServiceLiveTest.java b/compute/src/test/java/org/jclouds/compute/internal/BaseComputeServiceLiveTest.java index 2400598..dbdba20 100644 --- a/compute/src/test/java/org/jclouds/compute/internal/BaseComputeServiceLiveTest.java +++ b/compute/src/test/java/org/jclouds/compute/internal/BaseComputeServiceLiveTest.java @@ -228,7 +228,6 @@ public abstract class BaseComputeServiceLiveTest extends BaseComputeServiceConte NodeMetadata node = get(nodes, 0); LoginCredentials good = node.getCredentials(); assert good.identity != null : nodes; - assert good.credential != null : nodes; for (Entry<? extends NodeMetadata, ExecResponse> response : client.runScriptOnNodesMatching( runningInGroup(group), "hostname", @@ -507,7 +506,6 @@ public abstract class BaseComputeServiceLiveTest extends BaseComputeServiceConte assertNotNull(node.getCredentials()); if (node.getCredentials().identity != null) { assertNotNull(node.getCredentials().identity); - assertNotNull(node.getCredentials().credential); sshPing(node, taskName); } } http://git-wip-us.apache.org/repos/asf/jclouds/blob/63d6d975/core/src/main/java/org/jclouds/crypto/Pems.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/jclouds/crypto/Pems.java b/core/src/main/java/org/jclouds/crypto/Pems.java index 65afc42..b343f82 100644 --- a/core/src/main/java/org/jclouds/crypto/Pems.java +++ b/core/src/main/java/org/jclouds/crypto/Pems.java @@ -73,6 +73,7 @@ public class Pems { public static final String CERTIFICATE_X509_MARKER = "-----BEGIN CERTIFICATE-----"; 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 PROC_TYPE_ENCRYPTED = "Proc-Type: 4,ENCRYPTED"; private static class PemProcessor<T> implements ByteProcessor<T> { private interface ResultParser<T> { http://git-wip-us.apache.org/repos/asf/jclouds/blob/63d6d975/core/src/main/java/org/jclouds/domain/LoginCredentials.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/jclouds/domain/LoginCredentials.java b/core/src/main/java/org/jclouds/domain/LoginCredentials.java index a1eca0a..d94acd7 100644 --- a/core/src/main/java/org/jclouds/domain/LoginCredentials.java +++ b/core/src/main/java/org/jclouds/domain/LoginCredentials.java @@ -19,6 +19,7 @@ package org.jclouds.domain; import static org.jclouds.crypto.Pems.PRIVATE_PKCS1_MARKER; import static org.jclouds.crypto.Pems.PRIVATE_PKCS8_MARKER; +import org.jclouds.crypto.Pems; import org.jclouds.javax.annotation.Nullable; import com.google.common.base.Optional; @@ -156,6 +157,15 @@ public class LoginCredentials extends Credentials { } /** + * @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 */ @Nullable http://git-wip-us.apache.org/repos/asf/jclouds/blob/63d6d975/drivers/jsch/pom.xml ---------------------------------------------------------------------- diff --git a/drivers/jsch/pom.xml b/drivers/jsch/pom.xml index f4ace6f..5b441f5 100644 --- a/drivers/jsch/pom.xml +++ b/drivers/jsch/pom.xml @@ -86,6 +86,16 @@ <artifactId>jsch</artifactId> <scope>compile</scope> </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> <profiles> http://git-wip-us.apache.org/repos/asf/jclouds/blob/63d6d975/drivers/jsch/src/main/java/org/jclouds/ssh/jsch/JschSshClient.java ---------------------------------------------------------------------- diff --git a/drivers/jsch/src/main/java/org/jclouds/ssh/jsch/JschSshClient.java b/drivers/jsch/src/main/java/org/jclouds/ssh/jsch/JschSshClient.java index 5e58b48..aa7f9d4 100644 --- a/drivers/jsch/src/main/java/org/jclouds/ssh/jsch/JschSshClient.java +++ b/drivers/jsch/src/main/java/org/jclouds/ssh/jsch/JschSshClient.java @@ -56,6 +56,7 @@ import org.jclouds.util.Closeables2; import org.jclouds.util.Strings2; import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Optional; import com.google.common.base.Predicate; import com.google.common.base.Predicates; import com.google.common.base.Splitter; @@ -66,6 +67,7 @@ import com.jcraft.jsch.ChannelExec; import com.jcraft.jsch.ChannelSftp; import com.jcraft.jsch.JSchException; import com.jcraft.jsch.Session; +import com.jcraft.jsch.agentproxy.Connector; /** * 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, - LoginCredentials loginCredentials, int timeout) { + LoginCredentials loginCredentials, int timeout, Optional<Connector> agentConnector) { this.user = checkNotNull(loginCredentials, "loginCredentials").getUser(); this.host = checkNotNull(socket, "socket").getHostText(); checkArgument(socket.getPort() > 0, "ssh port must be greater then zero" + socket.getPort()); - checkArgument(loginCredentials.getPassword() != null || loginCredentials.getPrivateKey() != null, - "you must specify a password or a key"); + checkArgument(loginCredentials.getPassword() != null || loginCredentials.hasUnencryptedPrivateKey() || agentConnector.isPresent(), + "you must specify a password, a key or an SSH agent needs to be available"); this.backoffLimitedRetryHandler = checkNotNull(backoffLimitedRetryHandler, "backoffLimitedRetryHandler"); - if (loginCredentials.getPrivateKey() == null) { + if (loginCredentials.getPassword() != null) { this.toString = String.format("%s:pw[%s]@%s:%d", loginCredentials.getUser(), base16().lowerCase().encode(md5().hashString(loginCredentials.getPassword(), UTF_8).asBytes()), host, socket.getPort()); - } else { + } else if (loginCredentials.hasUnencryptedPrivateKey()) { String fingerPrint = fingerprintPrivateKey(loginCredentials.getPrivateKey()); String sha1 = sha1PrivateKey(loginCredentials.getPrivateKey()); 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( - loginCredentials).proxy(checkNotNull(proxyConfig, "proxyConfig")).connectTimeout(timeout).sessionTimeout(timeout).build(); + loginCredentials).proxy(checkNotNull(proxyConfig, "proxyConfig")).connectTimeout(timeout).sessionTimeout(timeout) + .agentConnector(agentConnector).build(); } @Override http://git-wip-us.apache.org/repos/asf/jclouds/blob/63d6d975/drivers/jsch/src/main/java/org/jclouds/ssh/jsch/SessionConnection.java ---------------------------------------------------------------------- diff --git a/drivers/jsch/src/main/java/org/jclouds/ssh/jsch/SessionConnection.java b/drivers/jsch/src/main/java/org/jclouds/ssh/jsch/SessionConnection.java index 66d05d1..f6465cb 100644 --- a/drivers/jsch/src/main/java/org/jclouds/ssh/jsch/SessionConnection.java +++ b/drivers/jsch/src/main/java/org/jclouds/ssh/jsch/SessionConnection.java @@ -17,7 +17,6 @@ package org.jclouds.ssh.jsch; 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 org.jclouds.domain.Credentials; @@ -34,8 +33,13 @@ import com.jcraft.jsch.Proxy; import com.jcraft.jsch.ProxyHTTP; import com.jcraft.jsch.ProxySOCKS5; import com.jcraft.jsch.Session; +import com.jcraft.jsch.agentproxy.Connector; +import com.jcraft.jsch.agentproxy.RemoteIdentityRepository; public final class SessionConnection implements Connection<Session> { + + private Optional<Connector> agentConnector; + public static Builder builder() { return new Builder(); } @@ -47,6 +51,7 @@ public final class SessionConnection implements Connection<Session> { private Optional<Proxy> proxy = Optional.absent(); private int connectTimeout; private int sessionTimeout; + private Optional<Connector> agentConnector; /** * @see SessionConnection#getHostAndPort() @@ -114,7 +119,7 @@ public final class SessionConnection implements Connection<Session> { } 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) { @@ -122,15 +127,21 @@ public final class SessionConnection implements Connection<Session> { .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, - int connectTimeout, int sessionTimeout) { + int connectTimeout, int sessionTimeout, Optional<Connector> agentConnector) { this.hostAndPort = checkNotNull(hostAndPort, "hostAndPort"); this.loginCredentials = checkNotNull(loginCredentials, "loginCredentials for %", hostAndPort); this.connectTimeout = connectTimeout; this.sessionTimeout = sessionTimeout; this.proxy = checkNotNull(proxy, "proxy for %", hostAndPort); + this.agentConnector = checkNotNull(agentConnector, "agentConnector for %", hostAndPort); } private static final byte[] emptyPassPhrase = new byte[0]; @@ -160,11 +171,12 @@ public final class SessionConnection implements Connection<Session> { session.setTimeout(sessionTimeout); if (loginCredentials.getPrivateKey() == null) { session.setPassword(loginCredentials.getPassword()); - } else { - checkArgument(!loginCredentials.getPrivateKey().contains("Proc-Type: 4,ENCRYPTED"), - "JschSshClientModule does not support private keys that require a passphrase"); + } else if (loginCredentials.hasUnencryptedPrivateKey()) { byte[] privateKey = loginCredentials.getPrivateKey().getBytes(); 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(); config.put("StrictHostKeyChecking", "no"); http://git-wip-us.apache.org/repos/asf/jclouds/blob/63d6d975/drivers/jsch/src/main/java/org/jclouds/ssh/jsch/config/JschSshClientModule.java ---------------------------------------------------------------------- diff --git a/drivers/jsch/src/main/java/org/jclouds/ssh/jsch/config/JschSshClientModule.java b/drivers/jsch/src/main/java/org/jclouds/ssh/jsch/config/JschSshClientModule.java index 729da6d..dfb75a0 100644 --- a/drivers/jsch/src/main/java/org/jclouds/ssh/jsch/config/JschSshClientModule.java +++ b/drivers/jsch/src/main/java/org/jclouds/ssh/jsch/config/JschSshClientModule.java @@ -28,11 +28,15 @@ import org.jclouds.ssh.SshClient; import org.jclouds.ssh.config.ConfiguresSshClient; import org.jclouds.ssh.jsch.JschSshClient; +import com.google.common.base.Optional; import com.google.common.net.HostAndPort; import com.google.inject.AbstractModule; import com.google.inject.Inject; import com.google.inject.Injector; 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) 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 BackoffLimitedRetryHandler backoffLimitedRetryHandler; private final Injector injector; @@ -63,9 +77,14 @@ public class JschSshClientModule extends AbstractModule { @Override 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 return client; } + + @Override + public boolean isAgentAvailable() { + return agentConnector.isPresent(); + } } } http://git-wip-us.apache.org/repos/asf/jclouds/blob/63d6d975/drivers/sshj/pom.xml ---------------------------------------------------------------------- diff --git a/drivers/sshj/pom.xml b/drivers/sshj/pom.xml index da0c530..2098a4e 100644 --- a/drivers/sshj/pom.xml +++ b/drivers/sshj/pom.xml @@ -104,6 +104,16 @@ </exclusion> </exclusions> </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> <profiles> http://git-wip-us.apache.org/repos/asf/jclouds/blob/63d6d975/drivers/sshj/src/main/java/org/jclouds/sshj/SSHClientConnection.java ---------------------------------------------------------------------- diff --git a/drivers/sshj/src/main/java/org/jclouds/sshj/SSHClientConnection.java b/drivers/sshj/src/main/java/org/jclouds/sshj/SSHClientConnection.java index bf20378..f1f32e2 100644 --- a/drivers/sshj/src/main/java/org/jclouds/sshj/SSHClientConnection.java +++ b/drivers/sshj/src/main/java/org/jclouds/sshj/SSHClientConnection.java @@ -17,15 +17,19 @@ package org.jclouds.sshj; import static com.google.common.base.Objects.equal; +import static com.google.common.base.Preconditions.checkNotNull; import java.io.IOException; +import java.util.List; import javax.annotation.Resource; import javax.inject.Named; import net.schmizz.sshj.SSHClient; +import net.schmizz.sshj.common.Buffer.BufferException; import net.schmizz.sshj.transport.verification.PromiscuousVerifier; import net.schmizz.sshj.userauth.keyprovider.OpenSSHKeyFile; +import net.schmizz.sshj.userauth.method.AuthMethod; import org.jclouds.domain.LoginCredentials; 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.base.Objects; +import com.google.common.base.Optional; +import com.google.common.collect.ImmutableList; 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> { + private Optional<Connector> agentConnector; + public static Builder builder() { return new Builder(); } @@ -46,6 +59,7 @@ public class SSHClientConnection implements Connection<SSHClient> { protected LoginCredentials loginCredentials; protected int connectTimeout; protected int sessionTimeout; + protected Optional<Connector> agentConnector; /** * @see SSHClientConnection#getHostAndPort() @@ -79,8 +93,16 @@ public class SSHClientConnection implements Connection<SSHClient> { return this; } + /** + * @see SSHClientConnection#getAgentConnector() + */ + public Builder agentConnector(Optional<Connector> agentConnector) { + this.agentConnector = agentConnector; + return this; + } + public SSHClientConnection build() { - return new SSHClientConnection(hostAndPort, loginCredentials, connectTimeout, sessionTimeout); + return new SSHClientConnection(hostAndPort, loginCredentials, connectTimeout, sessionTimeout, agentConnector); } protected Builder fromSSHClientConnection(SSHClientConnection in) { @@ -90,11 +112,12 @@ public class SSHClientConnection implements Connection<SSHClient> { } private SSHClientConnection(HostAndPort hostAndPort, LoginCredentials loginCredentials, int connectTimeout, - int sessionTimeout) { - this.hostAndPort = hostAndPort; - this.loginCredentials = loginCredentials; + int sessionTimeout, Optional<Connector> agentConnector) { + this.hostAndPort = checkNotNull(hostAndPort, "hostAndPort"); + this.loginCredentials = checkNotNull(loginCredentials, "loginCredentials for %", hostAndPort); this.connectTimeout = connectTimeout; this.sessionTimeout = sessionTimeout; + this.agentConnector = checkNotNull(agentConnector, "agentConnector for %", hostAndPort); } @Resource @@ -136,10 +159,13 @@ public class SSHClientConnection implements Connection<SSHClient> { ssh.connect(hostAndPort.getHostText(), hostAndPort.getPortOrDefault(22)); if (loginCredentials.getPassword() != null) { ssh.authPassword(loginCredentials.getUser(), loginCredentials.getPassword()); - } else { + } else if (loginCredentials.hasUnencryptedPrivateKey()) { OpenSSHKeyFile key = new OpenSSHKeyFile(); key.init(loginCredentials.getPrivateKey(), null); ssh.authPublickey(loginCredentials.getUser(), key); + } else if (agentConnector.isPresent()) { + AgentProxy proxy = new AgentProxy(agentConnector.get()); + ssh.auth(loginCredentials.getUser(), getAuthMethods(proxy)); } return ssh; } @@ -176,6 +202,14 @@ public class SSHClientConnection implements Connection<SSHClient> { } /** + * + * @return Ssh agent connector + */ + public Optional<Connector> getAgentConnector() { + return agentConnector; + } + + /** * * @return the current ssh or {@code null} if not connected */ @@ -206,4 +240,12 @@ public class SSHClientConnection implements Connection<SSHClient> { "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(); + } + } http://git-wip-us.apache.org/repos/asf/jclouds/blob/63d6d975/drivers/sshj/src/main/java/org/jclouds/sshj/SshjSshClient.java ---------------------------------------------------------------------- diff --git a/drivers/sshj/src/main/java/org/jclouds/sshj/SshjSshClient.java b/drivers/sshj/src/main/java/org/jclouds/sshj/SshjSshClient.java index 4115517..9bfb210 100644 --- a/drivers/sshj/src/main/java/org/jclouds/sshj/SshjSshClient.java +++ b/drivers/sshj/src/main/java/org/jclouds/sshj/SshjSshClient.java @@ -68,6 +68,7 @@ import org.jclouds.util.Closeables2; import org.jclouds.util.Throwables2; import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Optional; import com.google.common.base.Predicate; import com.google.common.base.Predicates; 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.net.HostAndPort; import com.google.inject.Inject; +import com.jcraft.jsch.agentproxy.Connector; /** * This class needs refactoring. It is not thread safe. @@ -141,25 +143,28 @@ public class SshjSshClient implements SshClient { private final BackoffLimitedRetryHandler backoffLimitedRetryHandler; public SshjSshClient(BackoffLimitedRetryHandler backoffLimitedRetryHandler, HostAndPort socket, - LoginCredentials loginCredentials, int timeout) { + LoginCredentials loginCredentials, int timeout, Optional<Connector> agentConnector) { this.user = checkNotNull(loginCredentials, "loginCredentials").getUser(); this.host = checkNotNull(socket, "socket").getHostText(); checkArgument(socket.getPort() > 0, "ssh port must be greater then zero" + socket.getPort()); - checkArgument(loginCredentials.getPassword() != null || loginCredentials.getPrivateKey() != null, - "you must specify a password or a key"); + checkArgument(loginCredentials.getPassword() != null || loginCredentials.hasUnencryptedPrivateKey() || agentConnector.isPresent(), + "you must specify a password, a key or an SSH agent needs to be available"); this.backoffLimitedRetryHandler = checkNotNull(backoffLimitedRetryHandler, "backoffLimitedRetryHandler"); - if (loginCredentials.getPrivateKey() == null) { + if (loginCredentials.getPassword() != null) { this.toString = String.format("%s:pw[%s]@%s:%d", loginCredentials.getUser(), base16().lowerCase().encode(md5().hashString(loginCredentials.getPassword(), UTF_8).asBytes()), host, socket.getPort()); - } else { + } else if (loginCredentials.hasUnencryptedPrivateKey()) { String fingerPrint = fingerprintPrivateKey(loginCredentials.getPrivateKey()); String sha1 = sha1PrivateKey(loginCredentials.getPrivateKey()); this.toString = String.format("%s:rsa[fingerprint(%s),sha1(%s)]@%s:%d", loginCredentials.getUser(), 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())) - .loginCredentials(loginCredentials).connectTimeout(timeout).sessionTimeout(timeout).build(); + .loginCredentials(loginCredentials).connectTimeout(timeout).sessionTimeout(timeout).agentConnector(agentConnector).build(); } @Override http://git-wip-us.apache.org/repos/asf/jclouds/blob/63d6d975/drivers/sshj/src/main/java/org/jclouds/sshj/config/SshjSshClientModule.java ---------------------------------------------------------------------- diff --git a/drivers/sshj/src/main/java/org/jclouds/sshj/config/SshjSshClientModule.java b/drivers/sshj/src/main/java/org/jclouds/sshj/config/SshjSshClientModule.java index a50c886..4b37850 100644 --- a/drivers/sshj/src/main/java/org/jclouds/sshj/config/SshjSshClientModule.java +++ b/drivers/sshj/src/main/java/org/jclouds/sshj/config/SshjSshClientModule.java @@ -25,11 +25,15 @@ import org.jclouds.ssh.SshClient; import org.jclouds.ssh.config.ConfiguresSshClient; import org.jclouds.sshj.SshjSshClient; +import com.google.common.base.Optional; import com.google.common.net.HostAndPort; import com.google.inject.AbstractModule; import com.google.inject.Inject; import com.google.inject.Injector; 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); } + private static class Factory implements SshClient.Factory { @Named(Constants.PROPERTY_CONNECTION_TIMEOUT) @Inject(optional = true) 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 Injector injector; @@ -58,9 +73,15 @@ public class SshjSshClientModule extends AbstractModule { @Override 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 return client; } + + @Override + public boolean isAgentAvailable() { + return agentConnector.isPresent(); + } + } } http://git-wip-us.apache.org/repos/asf/jclouds/blob/63d6d975/project/pom.xml ---------------------------------------------------------------------- diff --git a/project/pom.xml b/project/pom.xml index 1fc17a4..4a08fa1 100644 --- a/project/pom.xml +++ b/project/pom.xml @@ -469,6 +469,23 @@ <package>com.google</package> </packages> </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> <ignoredResources> <!-- For all the jetty packages -->
