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}"/>