From 3e3f4de222538d905b0244b4fba1458e5639f697 Mon Sep 17 00:00:00 2001 From: Pasi Niemi Date: Wed, 19 Mar 2014 22:58:02 +0200 Subject: [PATCH] JCLOUDS-516: Add ssh agent support via sch agentproxy --- .../CreateSshClientOncePortIsListeningOnNode.java | 5 ++- .../src/main/java/org/jclouds/ssh/SshClient.java | 3 +- .../internal/BaseComputeServiceLiveTest.java | 2 - core/src/main/java/org/jclouds/crypto/Pems.java | 1 + .../java/org/jclouds/domain/LoginCredentials.java | 10 +++++ drivers/jsch/pom.xml | 10 +++++ .../java/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 +++++ .../java/org/jclouds/sshj/SSHClientConnection.java | 52 +++++++++++++++++++--- .../main/java/org/jclouds/sshj/SshjSshClient.java | 17 ++++--- .../jclouds/sshj/config/SshjSshClientModule.java | 23 +++++++++- project/pom.xml | 17 +++++++ 14 files changed, 182 insertions(+), 32 deletions(-) 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 @@ @Inject(optional = true) SshClient.Factory sshFactory; - + private final OpenSocketFinder openSocketFinder; private final long timeoutMs; @@ -65,7 +66,7 @@ public SshClient apply(NodeMetadata node) { 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); 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 @@ public interface SshClient { interface Factory { - SshClient create(HostAndPort socket, LoginCredentials credentials); - + boolean isAgentAvailable(); } String getUsername(); 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 void testAScriptExecutionAfterBootWithBasicTemplate() throws Exception { NodeMetadata node = get(nodes, 0); LoginCredentials good = node.getCredentials(); assert good.identity != null : nodes; - assert good.credential != null : nodes; for (Entry response : client.runScriptOnNodesMatching( runningInGroup(group), "hostname", @@ -507,7 +506,6 @@ protected void checkNodes(Iterable nodes, String group, assertNotNull(node.getCredentials()); if (node.getCredentials().identity != null) { assertNotNull(node.getCredentials().identity); - assertNotNull(node.getCredentials().credential); sshPing(node, taskName); } } 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 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 implements ByteProcessor { private interface ResultParser { 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 @@ 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 String getPrivateKey() { } /** + * @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 diff --git a/drivers/jsch/pom.xml b/drivers/jsch/pom.xml index f5c9652..8835ea6 100644 --- a/drivers/jsch/pom.xml +++ b/drivers/jsch/pom.xml @@ -86,6 +86,16 @@ jsch compile + + com.jcraft + jsch.agentproxy.jsch + 0.0.7 + + + com.jcraft + jsch.agentproxy.connector-factory + 0.0.7 + 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.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.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 void close() throws IOException { public JschSshClient(ProxyConfig proxyConfig, BackoffLimitedRetryHandler backoffLimitedRetryHandler, HostAndPort socket, - LoginCredentials loginCredentials, int timeout) { + LoginCredentials loginCredentials, int timeout, Optional 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 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.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 { + + private Optional agentConnector; + public static Builder builder() { return new Builder(); } @@ -47,6 +51,7 @@ public static Builder builder() { private Optional proxy = Optional.absent(); private int connectTimeout; private int sessionTimeout; + private Optional agentConnector; /** * @see SessionConnection#getHostAndPort() @@ -114,7 +119,7 @@ public Builder sessionTimeout(int sessionTimeout) { } 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 Builder from(SessionConnection in) { .connectTimeout(in.connectTimeout).sessionTimeout(in.sessionTimeout); } + public Builder agentConnector(Optional agentConnector) { + this.agentConnector = agentConnector; + return this; + } + } private SessionConnection(HostAndPort hostAndPort, LoginCredentials loginCredentials, Optional proxy, - int connectTimeout, int sessionTimeout) { + int connectTimeout, int sessionTimeout, Optional 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 Session create() throws Exception { 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"); 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.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 @@ protected void configure() { @Inject(optional = true) int timeout = 60000; + Optional agentConnector = getAgentConnector(); + + Optional 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 Factory(ProxyConfig proxyConfig, BackoffLimitedRetryHandler backoffLimite @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(); + } } } diff --git a/drivers/sshj/pom.xml b/drivers/sshj/pom.xml index 33125bf..bc69e89 100644 --- a/drivers/sshj/pom.xml +++ b/drivers/sshj/pom.xml @@ -104,6 +104,16 @@ + + com.jcraft + jsch.agentproxy.sshj + 0.0.7 + + + com.jcraft + jsch.agentproxy.connector-factory + 0.0.7 + 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 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 { + private Optional agentConnector; + public static Builder builder() { return new Builder(); } @@ -46,6 +59,7 @@ public static Builder builder() { protected LoginCredentials loginCredentials; protected int connectTimeout; protected int sessionTimeout; + protected Optional agentConnector; /** * @see SSHClientConnection#getHostAndPort() @@ -79,8 +93,16 @@ public Builder sessionTimeout(int sessionTimeout) { return this; } + /** + * @see SSHClientConnection#getAgentConnector() + */ + public Builder agentConnector(Optional 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 @@ protected Builder fromSSHClientConnection(SSHClientConnection in) { } private SSHClientConnection(HostAndPort hostAndPort, LoginCredentials loginCredentials, int connectTimeout, - int sessionTimeout) { - this.hostAndPort = hostAndPort; - this.loginCredentials = loginCredentials; + int sessionTimeout, Optional 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 SSHClient create() throws Exception { 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 int getSessionTimeout() { } /** + * + * @return Ssh agent connector + */ + public Optional getAgentConnector() { + return agentConnector; + } + + /** * * @return the current ssh or {@code null} if not connected */ @@ -206,4 +240,12 @@ public String toString() { "sessionTimeout", sessionTimeout).toString(); } + private static List getAuthMethods(AgentProxy agent) throws BufferException { + ImmutableList.Builder identities = ImmutableList.builder(); + for (Identity identity : agent.getIdentities()) { + identities.add(new AuthAgent(agent, identity)); + } + return identities.build(); + } + } 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.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.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 void close() throws IOException { private final BackoffLimitedRetryHandler backoffLimitedRetryHandler; public SshjSshClient(BackoffLimitedRetryHandler backoffLimitedRetryHandler, HostAndPort socket, - LoginCredentials loginCredentials, int timeout) { + LoginCredentials loginCredentials, int timeout, Optional 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 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.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 @@ protected void configure() { 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 agentConnector = getAgentConnector(); + + Optional 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 Factory(BackoffLimitedRetryHandler backoffLimitedRetryHandler, Injector i @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(); + } + } } diff --git a/project/pom.xml b/project/pom.xml index 79d31f9..e548aea 100644 --- a/project/pom.xml +++ b/project/pom.xml @@ -469,6 +469,23 @@ com.google + + + + com.jcraft + jsch.agentproxy.core + 0.0.7 + + + com.jcraft + jsch.agentproxy.connector-factory + 0.0.7 + + + + com.jcraft.jsch.agentproxy + + -- 1.8.5.5