Repository: jclouds
Updated Branches:
  refs/heads/master 655aa444d -> 85a1a8c1d


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/85a1a8c1
Tree: http://git-wip-us.apache.org/repos/asf/jclouds/tree/85a1a8c1
Diff: http://git-wip-us.apache.org/repos/asf/jclouds/diff/85a1a8c1

Branch: refs/heads/master
Commit: 85a1a8c1dd3de5f107ddf8e66c22b2fb3410a4ba
Parents: 655aa44
Author: Pasi Niemi <[email protected]>
Authored: Wed Mar 19 22:58:02 2014 +0200
Committer: Andrew Phillips <[email protected]>
Committed: Thu Mar 27 09:04:34 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/85a1a8c1/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/85a1a8c1/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/85a1a8c1/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/85a1a8c1/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/85a1a8c1/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/85a1a8c1/drivers/jsch/pom.xml
----------------------------------------------------------------------
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 @@
       <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/85a1a8c1/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/85a1a8c1/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/85a1a8c1/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/85a1a8c1/drivers/sshj/pom.xml
----------------------------------------------------------------------
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 @@
         </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/85a1a8c1/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/85a1a8c1/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/85a1a8c1/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/85a1a8c1/project/pom.xml
----------------------------------------------------------------------
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 @@
                 <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 -->

Reply via email to