You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@karaf.apache.org by jb...@apache.org on 2015/02/09 15:42:12 UTC

karaf git commit: [KARAF-3423] Fix and enhance the ssh:ssh client

Repository: karaf
Updated Branches:
  refs/heads/karaf-3.0.x c7b725820 -> 22df147e4


[KARAF-3423] Fix and enhance the ssh:ssh client


Project: http://git-wip-us.apache.org/repos/asf/karaf/repo
Commit: http://git-wip-us.apache.org/repos/asf/karaf/commit/22df147e
Tree: http://git-wip-us.apache.org/repos/asf/karaf/tree/22df147e
Diff: http://git-wip-us.apache.org/repos/asf/karaf/diff/22df147e

Branch: refs/heads/karaf-3.0.x
Commit: 22df147e4773cb2392196e898a87b8c0a601dee1
Parents: c7b7258
Author: Jean-Baptiste Onofré <jb...@apache.org>
Authored: Mon Feb 9 15:40:24 2015 +0100
Committer: Jean-Baptiste Onofré <jb...@apache.org>
Committed: Mon Feb 9 15:40:24 2015 +0100

----------------------------------------------------------------------
 .../karaf/instance/command/ConnectCommand.java  |  15 ++-
 .../karaf/shell/ssh/KnownHostsManager.java      |   1 +
 .../karaf/shell/ssh/ServerKeyVerifierImpl.java  |  21 ++-
 .../org/apache/karaf/shell/ssh/SshAction.java   | 128 ++++++++++++++-----
 .../karaf/shell/ssh/SshClientFactory.java       |  45 -------
 .../resources/OSGI-INF/blueprint/shell-ssh.xml  |   9 +-
 6 files changed, 126 insertions(+), 93 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/karaf/blob/22df147e/instance/command/src/main/java/org/apache/karaf/instance/command/ConnectCommand.java
----------------------------------------------------------------------
diff --git a/instance/command/src/main/java/org/apache/karaf/instance/command/ConnectCommand.java b/instance/command/src/main/java/org/apache/karaf/instance/command/ConnectCommand.java
index a2a542a..97b0def 100644
--- a/instance/command/src/main/java/org/apache/karaf/instance/command/ConnectCommand.java
+++ b/instance/command/src/main/java/org/apache/karaf/instance/command/ConnectCommand.java
@@ -33,6 +33,9 @@ public class ConnectCommand extends InstanceCommandSupport {
     @Option(name = "-p", aliases = {"--password"}, description = "Remote password", required = false, multiValued = false)
     private String password;
 
+    @Option(name = "-k", aliases = { "--keyfile" }, description = "Remote key file to use for key authentication", required = false, multiValued = false)
+    private String keyFile;
+
     @Argument(index = 0, name="name", description="The name of the container instance", required = true, multiValued = false)
     private String instance = null;
 
@@ -55,12 +58,20 @@ public class ConnectCommand extends InstanceCommandSupport {
         int port = getExistingInstance(instance).getSshPort();
         if (username != null) {
             if (password == null) {
-                session.execute("ssh:ssh -q -l " + username + " -p " + port + " localhost " + cmdStr);
+                if (keyFile == null) {
+                    session.execute("ssh:ssh -q -l " + username + " -p " + port + " localhost " + cmdStr);
+                } else {
+                    session.execute("ssh:ssh -q -l " + username + " -p " + port + " -k " + keyFile + " localhost " + cmdStr);
+                }
             } else {
                 session.execute("ssh:ssh -q -l " + username + " -P " + password + " -p " + port + " localhost " + cmdStr);
             }
         } else {
-            session.execute("ssh:ssh -q -p " + port + " localhost " + cmdStr);
+            if (keyFile == null) {
+                session.execute("ssh:ssh -q -p " + port + " localhost " + cmdStr);
+            } else {
+                session.execute("ssh:ssh -q -p " + port + " -k " + keyFile + " localhost " + cmdStr);
+            }
         }
         return null;
     }

http://git-wip-us.apache.org/repos/asf/karaf/blob/22df147e/shell/ssh/src/main/java/org/apache/karaf/shell/ssh/KnownHostsManager.java
----------------------------------------------------------------------
diff --git a/shell/ssh/src/main/java/org/apache/karaf/shell/ssh/KnownHostsManager.java b/shell/ssh/src/main/java/org/apache/karaf/shell/ssh/KnownHostsManager.java
index 0c9389d..a6ebf69 100644
--- a/shell/ssh/src/main/java/org/apache/karaf/shell/ssh/KnownHostsManager.java
+++ b/shell/ssh/src/main/java/org/apache/karaf/shell/ssh/KnownHostsManager.java
@@ -117,6 +117,7 @@ public class KnownHostsManager {
 		serverKey.getEncoded();
 		bw.append(new String(Base64.encodeBase64(serverKey.getEncoded()),
 				"UTF-8"));
+        bw.append("\n");
 	}
 
 	String getAddressString(SocketAddress address) {

http://git-wip-us.apache.org/repos/asf/karaf/blob/22df147e/shell/ssh/src/main/java/org/apache/karaf/shell/ssh/ServerKeyVerifierImpl.java
----------------------------------------------------------------------
diff --git a/shell/ssh/src/main/java/org/apache/karaf/shell/ssh/ServerKeyVerifierImpl.java b/shell/ssh/src/main/java/org/apache/karaf/shell/ssh/ServerKeyVerifierImpl.java
index 13e28a8..78b0fff 100644
--- a/shell/ssh/src/main/java/org/apache/karaf/shell/ssh/ServerKeyVerifierImpl.java
+++ b/shell/ssh/src/main/java/org/apache/karaf/shell/ssh/ServerKeyVerifierImpl.java
@@ -27,9 +27,23 @@ import org.apache.sshd.ClientSession;
 import org.apache.sshd.client.ServerKeyVerifier;
 
 public class ServerKeyVerifierImpl implements ServerKeyVerifier {
+
     private final KnownHostsManager knownHostsManager;
 	private final boolean quiet;
 
+    private final static String keyChangedMessage =
+            " @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ \n" +
+                    " @    WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED!      @ \n" +
+                    " @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ \n" +
+                    "IT IS POSSIBLE THAT SOMEONE IS DOING SOMETHING NASTY!\n" +
+                    "Someone could be eavesdropping on you right now (man-in-the-middle attack)!\n" +
+                    "It is also possible that the RSA host key has just been changed.\n" +
+                    "Please contact your system administrator.\n" +
+                    "Add correct host key in " + System.getProperty("user.home") + "/.sshkaraf/known_hosts to get rid of this message.\n" +
+                    "Offending key in " + System.getProperty("user.home") + "/.sshkaraf/known_hosts\n" +
+                    "RSA host key has changed and you have requested strict checking.\n" +
+                    "Host key verification failed.";
+
 	public ServerKeyVerifierImpl(KnownHostsManager knownHostsManager, boolean quiet) {
 		this.knownHostsManager = knownHostsManager;
 		this.quiet = quiet;
@@ -63,11 +77,12 @@ public class ServerKeyVerifierImpl implements ServerKeyVerifier {
 			return confirm;
 		}
 		
-		boolean verifed = (knownKey.equals(serverKey));
-		if (!verifed) {
+		boolean verified = (knownKey.equals(serverKey));
+		if (!verified) {
 			System.err.println("Server key for host " + remoteAddress + " does not match the stored key !! Terminating session.");
+            System.err.println(keyChangedMessage);
 		}
-		return verifed;
+		return verified;
 	}
 
 	private boolean getConfirmation() {

http://git-wip-us.apache.org/repos/asf/karaf/blob/22df147e/shell/ssh/src/main/java/org/apache/karaf/shell/ssh/SshAction.java
----------------------------------------------------------------------
diff --git a/shell/ssh/src/main/java/org/apache/karaf/shell/ssh/SshAction.java b/shell/ssh/src/main/java/org/apache/karaf/shell/ssh/SshAction.java
index ef4886a..7c31ddc 100644
--- a/shell/ssh/src/main/java/org/apache/karaf/shell/ssh/SshAction.java
+++ b/shell/ssh/src/main/java/org/apache/karaf/shell/ssh/SshAction.java
@@ -18,8 +18,9 @@
  */
 package org.apache.karaf.shell.ssh;
 
-import java.io.ByteArrayInputStream;
-import java.io.IOException;
+import java.io.*;
+import java.net.URL;
+import java.security.KeyPair;
 import java.util.List;
 
 import jline.Terminal;
@@ -34,8 +35,13 @@ import org.apache.sshd.ClientChannel;
 import org.apache.sshd.ClientSession;
 import org.apache.sshd.SshClient;
 import org.apache.sshd.agent.SshAgent;
+import org.apache.sshd.agent.local.AgentImpl;
+import org.apache.sshd.agent.local.LocalAgentFactory;
+import org.apache.sshd.client.ServerKeyVerifier;
 import org.apache.sshd.client.UserInteraction;
 import org.apache.sshd.client.channel.ChannelShell;
+import org.apache.sshd.client.future.ConnectFuture;
+import org.apache.sshd.common.keyprovider.FileKeyPairProvider;
 import org.apache.sshd.common.util.NoCloseInputStream;
 import org.apache.sshd.common.util.NoCloseOutputStream;
 import org.slf4j.Logger;
@@ -45,46 +51,35 @@ import org.slf4j.LoggerFactory;
 public class SshAction extends OsgiCommandSupport {
     private final Logger log = LoggerFactory.getLogger(getClass());
 
-    @Option(name="-l", aliases={"--username"}, description = "The user name for remote login", required = false, multiValued = false)
+    @Option(name = "-l", aliases = {"--username"}, description = "The user name for remote login", required = false, multiValued = false)
     private String username;
 
-    @Option(name="-P", aliases={"--password"}, description = "The password for remote login", required = false, multiValued = false)
+    @Option(name = "-P", aliases = {"--password"}, description = "The password for remote login", required = false, multiValued = false)
     private String password;
 
-    @Option(name="-p", aliases={"--port"}, description = "The port to use for SSH connection", required = false, multiValued = false)
+    @Option(name = "-p", aliases = {"--port"}, description = "The port to use for SSH connection", required = false, multiValued = false)
     private int port = 22;
-    
-    @Option(name="-q", description = "Quiet Mode. Do not ask for confirmations", required = false, multiValued = false)
+
+    @Option(name = "-k", aliases = {"--keyfile"}, description = "The private keyFile location when using key login, need have BouncyCastle registered as security provider using this flag", required = false, multiValued = false)
+    private String keyFile;
+
+    @Option(name = "-q", description = "Quiet Mode. Do not ask for confirmations", required = false, multiValued = false)
     private boolean quiet;
 
+    @Option(name = "-r", aliases = { "--retries" }, description = "Retry connection establishment (up to attempts times)", required = false, multiValued = false)
+    private int retries = 0;
+
     @Argument(index = 0, name = "hostname", description = "The host name to connect to via SSH", required = true, multiValued = false)
     private String hostname;
 
     @Argument(index = 1, name = "command", description = "Optional command to execute", required = false, multiValued = true)
     private List<String> command;
 
-	private ClientSession sshSession;
-
-	private SshClientFactory sshClientFactory;
+    private ClientSession sshSession;
 
-    private final static String keyChangedMessage =
-            " @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ \n" +
-            " @    WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED!      @ \n" +
-            " @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ \n" +
-            "IT IS POSSIBLE THAT SOMEONE IS DOING SOMETHING NASTY!\n" +
-            "Someone could be eavesdropping on you right now (man-in-the-middle attack)!\n" +
-            "It is also possible that the RSA host key has just been changed.\n" +
-            "Please contact your system administrator.\n" +
-            "Add correct host key in " + System.getProperty("user.home") + "/.sshkaraf/known_hosts to get rid of this message.\n" +
-            "Offending key in " + System.getProperty("user.home") + "/.sshkaraf/known_hosts\n" +
-            "RSA host key has changed and you have requested strict checking.\n" +
-            "Host key verification failed.";
 
-    public void setSshClientFactory(SshClientFactory sshClientFactory) {
-		this.sshClientFactory = sshClientFactory;
-	}
 
-	@Override
+    @Override
     protected Object doExecute() throws Exception {
 
         if (hostname.indexOf('@') >= 0) {
@@ -108,19 +103,18 @@ public class SshAction extends OsgiCommandSupport {
             }
         }
 
-        SshClient client = sshClientFactory.create(quiet);
+        SshClient client = SshClient.setUpDefaultClient();
+        setupAgent(username, keyFile, client);
+        KnownHostsManager knownHostsManager = new KnownHostsManager(new File(System.getProperty("user.home"), ".sshkaraf/known_hosts"));
+        ServerKeyVerifier serverKeyVerifier = new ServerKeyVerifierImpl(knownHostsManager, quiet);
+        client.setServerKeyVerifier(serverKeyVerifier);
         log.debug("Created client: {}", client);
-        client.start();
 
-        String agentSocket = null;
-        if (this.session.get(SshAgent.SSH_AUTHSOCKET_ENV_NAME) != null) {
-            agentSocket = this.session.get(SshAgent.SSH_AUTHSOCKET_ENV_NAME).toString();
-            client.getProperties().put(SshAgent.SSH_AUTHSOCKET_ENV_NAME,agentSocket);
-        }
         client.setUserInteraction(new UserInteraction() {
             public void welcome(String banner) {
                 System.out.println(banner);
             }
+
             public String[] interactive(String destination, String name, String instruction, String[] prompt, boolean[] echo) {
                 String[] answers = new String[prompt.length];
                 try {
@@ -132,6 +126,7 @@ public class SshAction extends OsgiCommandSupport {
                 return answers;
             }
         });
+        client.start();
 
         try {
             ClientSession sshSession = client.connect(username, hostname, port).await().getSession();
@@ -141,10 +136,11 @@ public class SshAction extends OsgiCommandSupport {
                 if (password != null) {
                     sshSession.addPasswordIdentity(password);
                 }
+
                 sshSession.auth().verify();
 
                 System.out.println("Connected");
-                this.session.put( SessionProperties.IGNORE_INTERRUPTS, Boolean.TRUE );
+                this.session.put(SessionProperties.IGNORE_INTERRUPTS, Boolean.TRUE);
 
                 StringBuilder sb = new StringBuilder();
                 if (command != null) {
@@ -176,7 +172,7 @@ public class SshAction extends OsgiCommandSupport {
                 channel.open().verify();
                 channel.waitFor(ClientChannel.CLOSED, 0);
             } finally {
-                session.put( SessionProperties.IGNORE_INTERRUPTS, oldIgnoreInterrupts );
+                session.put(SessionProperties.IGNORE_INTERRUPTS, oldIgnoreInterrupts);
                 sshSession.close(false);
             }
         } finally {
@@ -200,4 +196,66 @@ public class SshAction extends OsgiCommandSupport {
         return reader.readLine(msg, mask);
     }
 
+    private void setupAgent(String user, String keyFile, SshClient client) {
+        SshAgent agent;
+        URL url = getClass().getClassLoader().getResource("karaf.key");
+        agent = startAgent(user, url, keyFile);
+        client.setAgentFactory(new LocalAgentFactory(agent));
+        client.getProperties().put(SshAgent.SSH_AUTHSOCKET_ENV_NAME, "local");
+    }
+
+    private SshAgent startAgent(String user, URL privateKeyUrl, String keyFile) {
+        InputStream is = null;
+        try {
+            SshAgent agent = new AgentImpl();
+            is = privateKeyUrl.openStream();
+            ObjectInputStream r = new ObjectInputStream(is);
+            KeyPair keyPair = (KeyPair) r.readObject();
+            is.close();
+            agent.addIdentity(keyPair, user);
+            if (keyFile != null) {
+                String[] keyFiles = new String[]{keyFile};
+                FileKeyPairProvider fileKeyPairProvider = new FileKeyPairProvider(keyFiles);
+                for (KeyPair key : fileKeyPairProvider.loadKeys()) {
+                    agent.addIdentity(key, user);
+                }
+            }
+            return agent;
+        } catch (Throwable e) {
+            close(is);
+            System.err.println("Error starting ssh agent for: " + e.getMessage());
+            return null;
+        }
+    }
+
+    private static ClientSession connectWithRetries(SshClient client, String username, String host, int port, int maxAttempts) throws Exception, InterruptedException {
+        ClientSession session = null;
+        int retries = 0;
+        do {
+            ConnectFuture future = client.connect(username, host, port);
+            future.await();
+            try {
+                session = future.getSession();
+            } catch (Exception ex) {
+                if (retries++ < maxAttempts) {
+                    Thread.sleep(2 * 1000);
+                    System.out.println("retrying (attempt " + retries + ") ...");
+                } else {
+                    throw ex;
+                }
+            }
+        } while (session == null);
+        return session;
+    }
+
+    private void close(Closeable is) {
+        if (is != null) {
+            try {
+                is.close();
+            } catch (IOException e1) {
+                // Ignore
+            }
+        }
+    }
+
 }

http://git-wip-us.apache.org/repos/asf/karaf/blob/22df147e/shell/ssh/src/main/java/org/apache/karaf/shell/ssh/SshClientFactory.java
----------------------------------------------------------------------
diff --git a/shell/ssh/src/main/java/org/apache/karaf/shell/ssh/SshClientFactory.java b/shell/ssh/src/main/java/org/apache/karaf/shell/ssh/SshClientFactory.java
deleted file mode 100644
index adf520f..0000000
--- a/shell/ssh/src/main/java/org/apache/karaf/shell/ssh/SshClientFactory.java
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *  http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.karaf.shell.ssh;
-
-import java.io.File;
-
-import org.apache.sshd.SshClient;
-import org.apache.sshd.agent.SshAgentFactory;
-import org.apache.sshd.client.ServerKeyVerifier;
-
-public class SshClientFactory {
-
-	private SshAgentFactory agentFactory;
-	private File knownHosts;
-	
-	public SshClientFactory(SshAgentFactory agentFactory, File knownHosts) {
-		this.agentFactory = agentFactory;
-		this.knownHosts = knownHosts;
-	}
-
-	public SshClient create(boolean quiet) {
-		SshClient client = SshClient.setUpDefaultClient();
-        client.setAgentFactory(agentFactory);
-        KnownHostsManager knownHostsManager = new KnownHostsManager(knownHosts);
-		ServerKeyVerifier serverKeyVerifier = new ServerKeyVerifierImpl(knownHostsManager, quiet);
-		client.setServerKeyVerifier(serverKeyVerifier );
-		return client;
-	}
-}

http://git-wip-us.apache.org/repos/asf/karaf/blob/22df147e/shell/ssh/src/main/resources/OSGI-INF/blueprint/shell-ssh.xml
----------------------------------------------------------------------
diff --git a/shell/ssh/src/main/resources/OSGI-INF/blueprint/shell-ssh.xml b/shell/ssh/src/main/resources/OSGI-INF/blueprint/shell-ssh.xml
index 954b2cf..4de0036 100644
--- a/shell/ssh/src/main/resources/OSGI-INF/blueprint/shell-ssh.xml
+++ b/shell/ssh/src/main/resources/OSGI-INF/blueprint/shell-ssh.xml
@@ -61,9 +61,7 @@
 
     <command-bundle xmlns="http://karaf.apache.org/xmlns/shell/v1.1.0">
         <command>
-            <action class="org.apache.karaf.shell.ssh.SshAction">
-                <property name="sshClientFactory" ref="sshClientFactory" />
-            </action>
+            <action class="org.apache.karaf.shell.ssh.SshAction" />
         </command>
         <command>
             <action class="org.apache.karaf.shell.ssh.SshServerAction">
@@ -73,11 +71,6 @@
             </action>
         </command>
     </command-bundle>
-    
-    <bean id="sshClientFactory" class="org.apache.karaf.shell.ssh.SshClientFactory">
-        <argument ref="agentFactory" />
-        <argument value="$[user.home]/.sshkaraf/known_hosts"/>
-    </bean>
 
     <bean id="userAuthFactoriesFactory" class="org.apache.karaf.shell.ssh.UserAuthFactoriesFactory">
         <property name="authMethods" value="${authMethods}"/>