CAMEL-11611 - Add a knownHosts option to the camel-ssh component

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

Branch: refs/heads/master
Commit: be592e50dcfa89e0de1ea6638264ece10b99a595
Parents: 183d642
Author: sdirbach <sascha.dirb...@endless-webservices.de>
Authored: Mon Aug 14 13:25:30 2017 +0200
Committer: Andrea Cosentino <anco...@gmail.com>
Committed: Tue Aug 22 08:55:06 2017 +0200

----------------------------------------------------------------------
 .../camel-ssh/src/main/docs/ssh-component.adoc  |   4 +-
 .../ssh/ResourceBasedSSHKeyVerifier.java        | 211 +++++++++++++++++++
 .../camel/component/ssh/SSHPublicKeyHolder.java | 165 +++++++++++++++
 .../camel/component/ssh/SshConfiguration.java   |  92 ++++++--
 .../apache/camel/component/ssh/SshConsumer.java |  20 +-
 .../apache/camel/component/ssh/SshEndpoint.java |  23 +-
 .../apache/camel/component/ssh/SshProducer.java |  19 +-
 .../ssh/SshComponentKnownHostTest.java          | 124 +++++++++++
 .../src/test/resources/known_hosts_invalid      |   0
 .../src/test/resources/known_hosts_valid        |   1 +
 10 files changed, 620 insertions(+), 39 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/camel/blob/be592e50/components/camel-ssh/src/main/docs/ssh-component.adoc
----------------------------------------------------------------------
diff --git a/components/camel-ssh/src/main/docs/ssh-component.adoc 
b/components/camel-ssh/src/main/docs/ssh-component.adoc
index 4b28cda..db1064e 100644
--- a/components/camel-ssh/src/main/docs/ssh-component.adoc
+++ b/components/camel-ssh/src/main/docs/ssh-component.adoc
@@ -71,11 +71,13 @@ with the following path and query parameters:
 | **port** | Sets the port number for the remote SSH server. | 22 | int
 |=======================================================================
 
-#### Query Parameters (26 parameters):
+#### Query Parameters (28 parameters):
 
 [width="100%",cols="2,5,^1,2",options="header"]
 |=======================================================================
 | Name | Description | Default | Type
+| **failOnUnknownHost** (common) | Specifies whether a connection to an 
unknown host should fail or not. This value is only checked when the property 
knownHosts is set. | false | boolean
+| **knownHostsResource** (common) | Sets the resource path for a known_hosts 
file |  | String
 | **timeout** (common) | Sets the timeout in milliseconds to wait in 
establishing the remote SSH server connection. Defaults to 30000 milliseconds. 
| 30000 | long
 | **bridgeErrorHandler** (consumer) | Allows for bridging the consumer to the 
Camel routing Error Handler which mean any exceptions occurred while the 
consumer is trying to pickup incoming messages or the likes will now be 
processed as a message and handled by the routing Error Handler. By default the 
consumer will use the org.apache.camel.spi.ExceptionHandler to deal with 
exceptions that will be logged at WARN or ERROR level and ignored. | false | 
boolean
 | **pollCommand** (consumer) | Sets the command string to send to the remote 
SSH server during every poll cycle. Only works with camel-ssh component being 
used as a consumer i.e. from(ssh://...) You may need to end your command with a 
newline and that must be URL encoded 0A |  | String

http://git-wip-us.apache.org/repos/asf/camel/blob/be592e50/components/camel-ssh/src/main/java/org/apache/camel/component/ssh/ResourceBasedSSHKeyVerifier.java
----------------------------------------------------------------------
diff --git 
a/components/camel-ssh/src/main/java/org/apache/camel/component/ssh/ResourceBasedSSHKeyVerifier.java
 
b/components/camel-ssh/src/main/java/org/apache/camel/component/ssh/ResourceBasedSSHKeyVerifier.java
new file mode 100644
index 0000000..63a1317
--- /dev/null
+++ 
b/components/camel-ssh/src/main/java/org/apache/camel/component/ssh/ResourceBasedSSHKeyVerifier.java
@@ -0,0 +1,211 @@
+/**
+ * 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.camel.component.ssh;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.math.BigInteger;
+import java.net.InetSocketAddress;
+import java.net.SocketAddress;
+import java.nio.charset.Charset;
+import java.security.NoSuchAlgorithmException;
+import java.security.PublicKey;
+import java.security.spec.InvalidKeySpecException;
+import java.util.Base64;
+import java.util.LinkedList;
+import java.util.List;
+
+import org.apache.camel.CamelContext;
+import org.apache.camel.util.ResourceHelper;
+import org.apache.sshd.ClientSession;
+import org.apache.sshd.client.ServerKeyVerifier;
+import org.bouncycastle.util.Arrays;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * ServerKeyVerifier that takes a camel resource as input file to validate the 
server key against.
+ *
+ */
+public class ResourceBasedSSHKeyVerifier implements ServerKeyVerifier {
+    protected final Logger log = LoggerFactory.getLogger(getClass());
+
+    private CamelContext camelContext;
+    private boolean failOnUnknownHost;
+    private String knownHostsResource;
+
+    public ResourceBasedSSHKeyVerifier(CamelContext camelContext, String 
knownHostsResource) {
+        this.camelContext = camelContext;
+        this.knownHostsResource = knownHostsResource;
+        this.failOnUnknownHost = false;
+    }
+    
+    public ResourceBasedSSHKeyVerifier(CamelContext camelContext, String 
knownHostsResource,
+            boolean failOnUnknownHost) {
+        this.camelContext = camelContext;
+        this.knownHostsResource = knownHostsResource;
+        this.failOnUnknownHost = failOnUnknownHost;
+    }
+
+    @Override
+    public boolean verifyServerKey(ClientSession sshClientSession, 
SocketAddress remoteAddress, PublicKey serverKey) {
+        log.debug("Trying to find known_hosts file %s", knownHostsResource);
+        InputStream knownHostsInputStream = null;
+        try {
+            knownHostsInputStream = 
ResourceHelper.resolveMandatoryResourceAsInputStream(camelContext,
+                    knownHostsResource);
+            List<String> possibleTokens = 
getKnownHostsFileTokensForSocketAddress(remoteAddress);
+            log.debug("Trying to mach PublicKey against provided known_hosts 
file");
+            PublicKey matchingKey = 
findKeyForServerToken(knownHostsInputStream, possibleTokens);
+            if (matchingKey != null) {
+                log.debug("Found PublicKey match for server");
+                boolean match = Arrays.areEqual(matchingKey.getEncoded(), 
serverKey.getEncoded());
+                return match;
+            }
+        } catch (IOException ioException) {
+            log.debug(String.format("Could not find known_hosts file %s", 
knownHostsResource), ioException);
+        } finally {
+            if (knownHostsInputStream != null) {
+                try {
+                    knownHostsInputStream.close();
+                } catch (IOException e) {
+                    // Ignore
+                }
+            }
+        }
+        if (failOnUnknownHost) {
+            log.warn("Could not find matching key for client session, 
connection will fail due to configuration");
+            return false;
+        } else {
+            log.warn(
+                    "Could not find matching key for client session, 
connection will continue anyway due to configuration");
+            return true;
+        }
+    }
+
+    private PublicKey findKeyForServerToken(InputStream knownHostsInputStream, 
List<String> possibleTokens)
+            throws IOException {
+        List<String> knowHostsLines = 
readInputStreamToStringList(knownHostsInputStream);
+
+        for (String s : knowHostsLines) {
+            String[] parts = s.split(" ");
+            if (parts.length != 3) {
+                log.warn("Found malformed entry in known_hosts file");
+                continue;
+            }
+            String entry = parts[0];
+            String key = parts[2];
+            for (String serverToken : possibleTokens) {
+                if (entry.contains(serverToken)) {
+                    try {
+                        return loadKey(key);
+                    } catch (NoSuchAlgorithmException | 
InvalidKeySpecException e) {
+                        log.warn(String.format("Could not load key for server 
token %s", entry), e);
+                    }
+                }
+            }
+        }
+        return null;
+    }
+
+    private List<String> readInputStreamToStringList(InputStream 
knownHostsInputStream) throws IOException {
+        List<String> returnList = new LinkedList<>();
+        String line;
+        BufferedReader bufferedReader = new BufferedReader(
+                new InputStreamReader(knownHostsInputStream, 
Charset.forName("UTF-8")));
+        while ((line = bufferedReader.readLine()) != null) {
+            returnList.add(line);
+        }
+        return returnList;
+    }
+
+    private List<String> getKnownHostsFileTokensForSocketAddress(SocketAddress 
remoteAddress) {
+        List<String> returnList = new LinkedList<>();
+        if (remoteAddress instanceof InetSocketAddress) {
+            InetSocketAddress inetSocketAddress = (InetSocketAddress) 
remoteAddress;
+
+            String hostName = inetSocketAddress.getHostName();
+            String ipAddress = inetSocketAddress.getAddress().getHostAddress();
+            String remotePort = String.valueOf(inetSocketAddress.getPort());
+
+            returnList.add(hostName);
+            returnList.add("[" + hostName + "]:" + remotePort);
+            returnList.add(ipAddress);
+            returnList.add("[" + ipAddress + "]:" + remotePort);
+        }
+
+        return returnList;
+    }
+
+    /*
+     * Decode the public key string, which is a base64 encoded string that 
consists
+     * of multiple parts: 1. public key type (ssh-rsa, ssh-dss, ...) 2. binary 
key
+     * data (May consists of multiple parts)
+     * 
+     * Each part is composed by two sub-parts 1. Length of the part (4 bytes) 
2.
+     * Binary part (length as defined by 1.)
+     * 
+     * Uses SSHPublicKeyHolder to construct the actual PublicKey Object
+     * 
+     * Note: Currently only supports RSA and DSA Public keys as required by
+     * https://tools.ietf.org/html/rfc4253#section-6.6
+     * 
+     */
+    private PublicKey loadKey(String key) throws NoSuchAlgorithmException, 
InvalidKeySpecException {
+        SSHPublicKeyHolder sshPublicKeyHolder = new SSHPublicKeyHolder();
+
+        byte[] keyByteArray = Base64.getDecoder().decode(key);
+        int keyByteArrayCursor = 0;
+
+        byte[] tmpData = new byte[4];
+        int tmpCursor = 0;
+
+        boolean getLengthMode = true;
+        while (keyByteArrayCursor < keyByteArray.length) {
+            if (getLengthMode) {
+                if (tmpCursor < 4) {
+                    tmpData[tmpCursor] = keyByteArray[keyByteArrayCursor];
+                    tmpCursor++;
+                    keyByteArrayCursor++;
+                    continue;
+                } else {
+                    tmpCursor = 0;
+                    getLengthMode = false;
+                    tmpData = new byte[byteArrayToInt(tmpData)];
+                }
+            }
+            tmpData[tmpCursor] = keyByteArray[keyByteArrayCursor];
+            tmpCursor++;
+            keyByteArrayCursor++;
+            if (tmpCursor == tmpData.length) {
+                sshPublicKeyHolder.push(tmpData);
+                getLengthMode = true;
+                tmpData = new byte[4];
+                tmpCursor = 0;
+            }
+        }
+
+        return sshPublicKeyHolder.toPublicKey();
+    }
+
+    private int byteArrayToInt(byte[] tmpData) {
+        return new BigInteger(tmpData).intValue();
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/camel/blob/be592e50/components/camel-ssh/src/main/java/org/apache/camel/component/ssh/SSHPublicKeyHolder.java
----------------------------------------------------------------------
diff --git 
a/components/camel-ssh/src/main/java/org/apache/camel/component/ssh/SSHPublicKeyHolder.java
 
b/components/camel-ssh/src/main/java/org/apache/camel/component/ssh/SSHPublicKeyHolder.java
new file mode 100644
index 0000000..83b2f76
--- /dev/null
+++ 
b/components/camel-ssh/src/main/java/org/apache/camel/component/ssh/SSHPublicKeyHolder.java
@@ -0,0 +1,165 @@
+/**
+ * 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.camel.component.ssh;
+
+import java.math.BigInteger;
+import java.nio.charset.Charset;
+import java.security.KeyFactory;
+import java.security.NoSuchAlgorithmException;
+import java.security.PublicKey;
+import java.security.spec.DSAPublicKeySpec;
+import java.security.spec.InvalidKeySpecException;
+import java.security.spec.RSAPublicKeySpec;
+
+public class SSHPublicKeyHolder {
+    private static final String SSH_RSA = "ssh-rsa";
+    private static final String SSH_DSS = "ssh-dss";
+    private static final String SSH_ECDSA = "ecdsa-sha2-nistp256";
+    private static final String SSH_ED25519 = "ssh-ed25519";
+
+    private String keyType;
+
+    /* RSA key parts */
+    private BigInteger e;
+    private BigInteger m;
+
+    /* DSA key parts */
+    private BigInteger p;
+    private BigInteger q;
+    private BigInteger g;
+    private BigInteger y;
+
+    public String getKeyType() {
+        return keyType;
+    }
+
+    public void setKeyType(String keyType) {
+        this.keyType = keyType;
+    }
+
+    public BigInteger getE() {
+        return e;
+    }
+
+    public void setE(BigInteger e) {
+        this.e = e;
+    }
+
+    public BigInteger getM() {
+        return m;
+    }
+
+    public void setM(BigInteger m) {
+        this.m = m;
+    }
+
+    public BigInteger getG() {
+        return g;
+    }
+
+    public void setG(BigInteger g) {
+        this.g = g;
+    }
+
+    public BigInteger getP() {
+        return p;
+    }
+
+    public void setP(BigInteger p) {
+        this.p = p;
+    }
+
+    public BigInteger getQ() {
+        return q;
+    }
+
+    public void setQ(BigInteger q) {
+        this.q = q;
+    }
+
+    public BigInteger getY() {
+        return y;
+    }
+
+    public void setY(BigInteger y) {
+        this.y = y;
+    }
+
+    public void push(byte[] keyPart) {
+        if (keyType == null) {
+            this.keyType = new String(keyPart, Charset.forName("UTF-8"));
+            return;
+        }
+
+        if (SSH_RSA.equals(keyType)) {
+            if (e == null) {
+                this.e = new BigInteger(keyPart);
+                return;
+            }
+
+            if (m == null) {
+                this.m = new BigInteger(keyPart);
+                return;
+            }
+        }
+
+        if (SSH_DSS.equals(keyType)) {
+            if (p == null) {
+                this.p = new BigInteger(keyPart);
+            }
+
+            if (q == null) {
+                this.q = new BigInteger(keyPart);
+            }
+
+            if (g == null) {
+                this.g = new BigInteger(keyPart);
+            }
+
+            if (y == null) {
+                this.y = new BigInteger(keyPart);
+            }
+        }
+
+    }
+
+    public PublicKey toPublicKey() throws NoSuchAlgorithmException, 
InvalidKeySpecException {
+        PublicKey returnValue = null;
+
+        if (SSH_RSA.equals(keyType)) {
+            RSAPublicKeySpec dsaPublicKeySpec = new RSAPublicKeySpec(m, e);
+            KeyFactory factory = KeyFactory.getInstance("RSA");
+            returnValue = factory.generatePublic(dsaPublicKeySpec);
+        }
+
+        if (SSH_DSS.equals(keyType)) {
+            DSAPublicKeySpec dsaPublicKeySpec = new DSAPublicKeySpec(y, p, q, 
g);
+            KeyFactory factory = KeyFactory.getInstance("DSA");
+            returnValue = factory.generatePublic(dsaPublicKeySpec);
+        }
+
+        if (SSH_ED25519.equals(keyType)) {
+            throw new UnsupportedOperationException();
+        }
+
+        if (SSH_ECDSA.equals(keyType)) {
+            throw new UnsupportedOperationException();
+        }
+
+        return returnValue;
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/camel/blob/be592e50/components/camel-ssh/src/main/java/org/apache/camel/component/ssh/SshConfiguration.java
----------------------------------------------------------------------
diff --git 
a/components/camel-ssh/src/main/java/org/apache/camel/component/ssh/SshConfiguration.java
 
b/components/camel-ssh/src/main/java/org/apache/camel/component/ssh/SshConfiguration.java
index 11f2df9..d6f3189 100644
--- 
a/components/camel-ssh/src/main/java/org/apache/camel/component/ssh/SshConfiguration.java
+++ 
b/components/camel-ssh/src/main/java/org/apache/camel/component/ssh/SshConfiguration.java
@@ -31,7 +31,8 @@ import org.apache.sshd.common.keyprovider.KeyPairProvider;
 public class SshConfiguration implements Cloneable {
     public static final int DEFAULT_SSH_PORT = 22;
 
-    @UriPath @Metadata(required = "true")
+    @UriPath
+    @Metadata(required = "true")
     private String host;
     @UriPath(defaultValue = "" + DEFAULT_SSH_PORT)
     private int port = DEFAULT_SSH_PORT;
@@ -49,6 +50,10 @@ public class SshConfiguration implements Cloneable {
     private String certResource;
     @UriParam(defaultValue = "30000")
     private long timeout = 30000;
+    @UriParam()
+    private String knownHostsResource;
+    @UriParam(defaultValue = "false")
+    private boolean failOnUnknownHost;
 
     public SshConfiguration() {
     }
@@ -99,7 +104,8 @@ public class SshConfiguration implements Cloneable {
     /**
      * Sets the username to use in logging into the remote SSH server.
      *
-     * @param username String representing login username.
+     * @param username
+     *            String representing login username.
      */
     public void setUsername(String username) {
         this.username = username;
@@ -112,7 +118,8 @@ public class SshConfiguration implements Cloneable {
     /**
      * Sets the hostname of the remote SSH server.
      *
-     * @param host String representing hostname of SSH server.
+     * @param host
+     *            String representing hostname of SSH server.
      */
     public void setHost(String host) {
         this.host = host;
@@ -125,7 +132,8 @@ public class SshConfiguration implements Cloneable {
     /**
      * Sets the port number for the remote SSH server.
      *
-     * @param port int representing port number on remote host. Defaults to 22.
+     * @param port
+     *            int representing port number on remote host. Defaults to 22.
      */
     public void setPort(int port) {
         this.port = port;
@@ -136,10 +144,11 @@ public class SshConfiguration implements Cloneable {
     }
 
     /**
-     * Sets the password to use in connecting to remote SSH server.
-     * Requires keyPairProvider to be set to null.
+     * Sets the password to use in connecting to remote SSH server. Requires
+     * keyPairProvider to be set to null.
      *
-     * @param password String representing password for username at remote 
host.
+     * @param password
+     *            String representing password for username at remote host.
      */
     public void setPassword(String password) {
         this.password = password;
@@ -150,11 +159,13 @@ public class SshConfiguration implements Cloneable {
     }
 
     /**
-     * Sets the command string to send to the remote SSH server during every 
poll cycle.
-     * Only works with camel-ssh component being used as a consumer, i.e. 
from("ssh://...")
-     * You may need to end your command with a newline, and that must be URL 
encoded %0A
+     * Sets the command string to send to the remote SSH server during every 
poll
+     * cycle. Only works with camel-ssh component being used as a consumer, 
i.e.
+     * from("ssh://...") You may need to end your command with a newline, and 
that
+     * must be URL encoded %0A
      *
-     * @param pollCommand String representing the command to send.
+     * @param pollCommand
+     *            String representing the command to send.
      */
     public void setPollCommand(String pollCommand) {
         this.pollCommand = pollCommand;
@@ -165,10 +176,13 @@ public class SshConfiguration implements Cloneable {
     }
 
     /**
-     * Sets the KeyPairProvider reference to use when connecting using 
Certificates to the remote SSH Server.
+     * Sets the KeyPairProvider reference to use when connecting using 
Certificates
+     * to the remote SSH Server.
      *
-     * @param keyPairProvider KeyPairProvider reference to use in 
authenticating. If set to 'null',
-     *                        then will attempt to connect using 
username/password settings.
+     * @param keyPairProvider
+     *            KeyPairProvider reference to use in authenticating. If set to
+     *            'null', then will attempt to connect using username/password
+     *            settings.
      *
      * @see KeyPairProvider
      */
@@ -182,9 +196,11 @@ public class SshConfiguration implements Cloneable {
 
     /**
      * Sets the key type to pass to the KeyPairProvider as part of 
authentication.
-     * KeyPairProvider.loadKey(...) will be passed this value. Defaults to 
"ssh-rsa".
+     * KeyPairProvider.loadKey(...) will be passed this value. Defaults to
+     * "ssh-rsa".
      *
-     * @param keyType String defining the type of KeyPair to use for 
authentication.
+     * @param keyType
+     *            String defining the type of KeyPair to use for 
authentication.
      *
      * @see KeyPairProvider
      */
@@ -197,10 +213,11 @@ public class SshConfiguration implements Cloneable {
     }
 
     /**
-     * Sets the timeout in milliseconds to wait in establishing the remote SSH 
server connection.
-     * Defaults to 30000 milliseconds.
+     * Sets the timeout in milliseconds to wait in establishing the remote SSH
+     * server connection. Defaults to 30000 milliseconds.
      *
-     * @param timeout long milliseconds to wait.
+     * @param timeout
+     *            long milliseconds to wait.
      */
     public void setTimeout(long timeout) {
         this.timeout = timeout;
@@ -227,12 +244,43 @@ public class SshConfiguration implements Cloneable {
     }
 
     /**
-     * Sets the resource path of the certificate to use for Authentication.
-     * Will use {@link ResourceHelperKeyPairProvider} to resolve file based 
certificate, and depends on keyType setting.
+     * Sets the resource path of the certificate to use for Authentication. 
Will use
+     * {@link ResourceHelperKeyPairProvider} to resolve file based 
certificate, and
+     * depends on keyType setting.
      *
-     * @param certResource String file, classpath, or http url for the 
certificate
+     * @param certResource
+     *            String file, classpath, or http url for the certificate
      */
     public void setCertResource(String certResource) {
         this.certResource = certResource;
     }
+
+    public String getKnownHostsResource() {
+        return knownHostsResource;
+    }
+
+    /**
+     * Sets the resource path for a known_hosts file
+     *
+     * @param knownHosts
+     *            String file, classpath, or http url for the certificate
+     */
+    public void setKnownHostsResource(String knownHostsResource) {
+        this.knownHostsResource = knownHostsResource;
+    }
+
+    public boolean isFailOnUnknownHost() {
+        return failOnUnknownHost;
+    }
+
+    /**
+     * Specifies whether a connection to an unknown host should fail or not. 
This
+     * value is only checked when the property knownHosts is set.
+     *
+     * @param boolean
+     *            boolean flag, whether a connection to an unknown host should 
fail
+     */
+    public void setFailOnUnknownHost(boolean failOnUnknownHost) {
+        this.failOnUnknownHost = failOnUnknownHost;
+    }
 }

http://git-wip-us.apache.org/repos/asf/camel/blob/be592e50/components/camel-ssh/src/main/java/org/apache/camel/component/ssh/SshConsumer.java
----------------------------------------------------------------------
diff --git 
a/components/camel-ssh/src/main/java/org/apache/camel/component/ssh/SshConsumer.java
 
b/components/camel-ssh/src/main/java/org/apache/camel/component/ssh/SshConsumer.java
index 5558fca..45432cb 100644
--- 
a/components/camel-ssh/src/main/java/org/apache/camel/component/ssh/SshConsumer.java
+++ 
b/components/camel-ssh/src/main/java/org/apache/camel/component/ssh/SshConsumer.java
@@ -23,26 +23,26 @@ import org.apache.sshd.client.SshClient;
 
 public class SshConsumer extends ScheduledPollConsumer {
     private final SshEndpoint endpoint;
-    
+
     private SshClient client;
 
     public SshConsumer(SshEndpoint endpoint, Processor processor) {
         super(endpoint, processor);
         this.endpoint = endpoint;
     }
-    
+
     @Override
     protected void doStart() throws Exception {
         client = SshClient.setUpDefaultClient();
         client.start();
-        
+
         super.doStart();
     }
 
     @Override
     protected void doStop() throws Exception {
         super.doStop();
-        
+
         if (client != null) {
             client.stop();
             client = null;
@@ -54,12 +54,18 @@ public class SshConsumer extends ScheduledPollConsumer {
         if (!isRunAllowed()) {
             return 0;
         }
-        
+
         String command = endpoint.getPollCommand();
         Exchange exchange = endpoint.createExchange();
-        
+
+        String knownHostResource = endpoint.getKnownHostsResource();
+        if (knownHostResource != null) {
+            client.setServerKeyVerifier(new 
ResourceBasedSSHKeyVerifier(exchange.getContext(), knownHostResource,
+                    endpoint.isFailOnUnknownHost()));
+        }
+
         SshResult result = 
SshHelper.sendExecCommand(exchange.getIn().getHeaders(), command, endpoint, 
client);
-        
+
         exchange.getIn().setBody(result.getStdout());
         exchange.getIn().setHeader(SshResult.EXIT_VALUE, 
result.getExitValue());
         exchange.getIn().setHeader(SshResult.STDERR, result.getStderr());

http://git-wip-us.apache.org/repos/asf/camel/blob/be592e50/components/camel-ssh/src/main/java/org/apache/camel/component/ssh/SshEndpoint.java
----------------------------------------------------------------------
diff --git 
a/components/camel-ssh/src/main/java/org/apache/camel/component/ssh/SshEndpoint.java
 
b/components/camel-ssh/src/main/java/org/apache/camel/component/ssh/SshEndpoint.java
index f20d5f5..50d6b00 100644
--- 
a/components/camel-ssh/src/main/java/org/apache/camel/component/ssh/SshEndpoint.java
+++ 
b/components/camel-ssh/src/main/java/org/apache/camel/component/ssh/SshEndpoint.java
@@ -27,10 +27,10 @@ import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 /**
- * The ssh component enables access to SSH servers such that you can send an 
SSH command, and process the response.
+ * The ssh component enables access to SSH servers such that you can send an 
SSH
+ * command, and process the response.
  */
-@UriEndpoint(firstVersion = "2.10.0", scheme = "ssh", title = "SSH", syntax = 
"ssh:host:port", alternativeSyntax = "ssh:username:password@host:port",
-        consumerClass = SshConsumer.class, label = "file")
+@UriEndpoint(firstVersion = "2.10.0", scheme = "ssh", title = "SSH", syntax = 
"ssh:host:port", alternativeSyntax = "ssh:username:password@host:port", 
consumerClass = SshConsumer.class, label = "file")
 public class SshEndpoint extends ScheduledPollEndpoint {
     protected final Logger log = LoggerFactory.getLogger(getClass());
 
@@ -162,4 +162,21 @@ public class SshEndpoint extends ScheduledPollEndpoint {
     public void setCertResource(String certResource) {
         getConfiguration().setCertResource(certResource);
     }
+
+    public String getKnownHostsResource() {
+        return getConfiguration().getKnownHostsResource();
+    }
+
+    public void setKnownHostsResource(String knownHostsResource) {
+        getConfiguration().setKnownHostsResource(knownHostsResource);
+    }
+
+    public boolean isFailOnUnknownHost() {
+        return getConfiguration().isFailOnUnknownHost();
+    }
+
+    public void setFailOnUnknownHost(boolean failOnUnknownHost) {
+        getConfiguration().setFailOnUnknownHost(failOnUnknownHost);
+    }
+
 }

http://git-wip-us.apache.org/repos/asf/camel/blob/be592e50/components/camel-ssh/src/main/java/org/apache/camel/component/ssh/SshProducer.java
----------------------------------------------------------------------
diff --git 
a/components/camel-ssh/src/main/java/org/apache/camel/component/ssh/SshProducer.java
 
b/components/camel-ssh/src/main/java/org/apache/camel/component/ssh/SshProducer.java
index f2b82f8..434349e 100644
--- 
a/components/camel-ssh/src/main/java/org/apache/camel/component/ssh/SshProducer.java
+++ 
b/components/camel-ssh/src/main/java/org/apache/camel/component/ssh/SshProducer.java
@@ -16,6 +16,7 @@
  */
 package org.apache.camel.component.ssh;
 
+import java.io.InputStream;
 import java.util.Map;
 
 import org.apache.camel.CamelExchangeException;
@@ -23,29 +24,30 @@ import org.apache.camel.Exchange;
 import org.apache.camel.Message;
 import org.apache.camel.impl.DefaultProducer;
 import org.apache.sshd.client.SshClient;
+import org.apache.camel.util.ResourceHelper;
 
 public class SshProducer extends DefaultProducer {
     private SshEndpoint endpoint;
-    
+
     private SshClient client;
 
     public SshProducer(SshEndpoint endpoint) {
         super(endpoint);
         this.endpoint = endpoint;
     }
-    
+
     @Override
     protected void doStart() throws Exception {
         client = SshClient.setUpDefaultClient();
         client.start();
-        
+
         super.doStart();
     }
 
     @Override
     protected void doStop() throws Exception {
         super.doStop();
-        
+
         if (client != null) {
             client.stop();
             client = null;
@@ -56,10 +58,15 @@ public class SshProducer extends DefaultProducer {
     public void process(Exchange exchange) throws Exception {
         final Message in = exchange.getIn();
         String command = in.getMandatoryBody(String.class);
-        
+
         final Map<String, Object> headers = exchange.getIn().getHeaders();
 
         try {
+            String knownHostResource = endpoint.getKnownHostsResource();
+            if (knownHostResource != null) {
+                client.setServerKeyVerifier(new 
ResourceBasedSSHKeyVerifier(exchange.getContext(), knownHostResource,
+                        endpoint.isFailOnUnknownHost()));
+            }
             SshResult result = SshHelper.sendExecCommand(headers, command, 
endpoint, client);
             exchange.getOut().setBody(result.getStdout());
             exchange.getOut().setHeader(SshResult.EXIT_VALUE, 
result.getExitValue());
@@ -72,4 +79,4 @@ public class SshProducer extends DefaultProducer {
         exchange.getOut().getHeaders().putAll(in.getHeaders());
         exchange.getOut().setAttachments(in.getAttachments());
     }
-}
\ No newline at end of file
+}

http://git-wip-us.apache.org/repos/asf/camel/blob/be592e50/components/camel-ssh/src/test/java/org/apache/camel/component/ssh/SshComponentKnownHostTest.java
----------------------------------------------------------------------
diff --git 
a/components/camel-ssh/src/test/java/org/apache/camel/component/ssh/SshComponentKnownHostTest.java
 
b/components/camel-ssh/src/test/java/org/apache/camel/component/ssh/SshComponentKnownHostTest.java
new file mode 100644
index 0000000..7465b07
--- /dev/null
+++ 
b/components/camel-ssh/src/test/java/org/apache/camel/component/ssh/SshComponentKnownHostTest.java
@@ -0,0 +1,124 @@
+/**
+ * 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.camel.component.ssh;
+
+import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.component.mock.MockEndpoint;
+import org.junit.Test;
+
+public class SshComponentKnownHostTest extends SshComponentTestSupport {
+
+    @Test
+    public void testProducerWithValidFile() throws Exception {
+        final String msg = "test\n";
+
+        MockEndpoint mock = getMockEndpoint("mock:password");
+        mock.expectedMinimumMessageCount(1);
+        mock.expectedBodiesReceived(msg);
+        mock.expectedHeaderReceived(SshResult.EXIT_VALUE, 0);
+        mock.expectedHeaderReceived(SshResult.STDERR, "Error:test\n");
+
+        template.sendBody("direct:ssh", msg);
+
+        assertMockEndpointsSatisfied();
+    }
+
+    @Test
+    public void testProducerWithInvalidFile() throws Exception {
+        final String msg = "test\n";
+
+        MockEndpoint mock = getMockEndpoint("mock:password");
+        mock.expectedMessageCount(0);
+
+        template.sendBody("direct:sshInvalid", msg);
+
+        assertMockEndpointsSatisfied();
+    }
+
+    @Test
+    public void testProducerWithInvalidFileWarnOnly() throws Exception {
+        final String msg = "test\n";
+
+        MockEndpoint mock = getMockEndpoint("mock:password");
+        mock.expectedMinimumMessageCount(1);
+        mock.expectedBodiesReceived(msg);
+        mock.expectedHeaderReceived(SshResult.EXIT_VALUE, 0);
+        mock.expectedHeaderReceived(SshResult.STDERR, "Error:test\n");
+
+        template.sendBody("direct:sshInvalidWarnOnly", msg);
+
+        assertMockEndpointsSatisfied();
+    }
+    
+    @Test
+    public void testPollingConsumerWithValidKnownHostFile() throws Exception {
+        MockEndpoint mock = getMockEndpoint("mock:result");
+        mock.expectedMinimumMessageCount(1);
+        mock.expectedBodiesReceived("test\r");
+        mock.expectedHeaderReceived(SshResult.EXIT_VALUE, 0);
+        mock.expectedHeaderReceived(SshResult.STDERR, "Error:test\r");
+        assertMockEndpointsSatisfied();
+    }
+    
+    @Test
+    public void testPollingConsumerWithInvalidKnownHostFile() throws Exception 
{
+        MockEndpoint mock = getMockEndpoint("mock:resultInvalid");
+        mock.expectedMessageCount(0);
+        assertMockEndpointsSatisfied();
+    }
+    
+    @Test
+    public void testPollingConsumerWithInvalidKnownHostFileWarnOnly() throws 
Exception {
+        MockEndpoint mock = getMockEndpoint("mock:resultInvalidWarnOnly");
+        mock.expectedMinimumMessageCount(1);
+        mock.expectedBodiesReceived("test\r");
+        mock.expectedHeaderReceived(SshResult.EXIT_VALUE, 0);
+        mock.expectedHeaderReceived(SshResult.STDERR, "Error:test\r");
+        assertMockEndpointsSatisfied();
+    }
+
+    @Override
+    protected RouteBuilder createRouteBuilder() throws Exception {
+        return new RouteBuilder() {
+            @Override
+            public void configure() {
+                onException(Exception.class).handled(true).to("mock:error");
+
+                from("ssh://smx:smx@localhost:" + port + 
"?useFixedDelay=true&delay=40000&pollCommand=test%0D&knownHostsResource=classpath:known_hosts_valid&failOnUnknownHost=true")
+                    .to("mock:result");
+                
+                from("ssh://smx:smx@localhost:" + port + 
"?useFixedDelay=true&delay=40000&pollCommand=test%0D&knownHostsResource=classpath:known_hosts_invalid&failOnUnknownHost=true")
+                    .to("mock:resultInvalid");
+
+                from("ssh://smx:smx@localhost:" + port + 
"?useFixedDelay=true&delay=40000&pollCommand=test%0D&knownHostsResource=classpath:known_hosts_invalid")
+                    .to("mock:resultInvalidWarnOnly");
+
+                from("direct:ssh")
+                        .to("ssh://smx:smx@localhost:" + port
+                                + 
"?timeout=3000&knownHostsResource=classpath:known_hosts_valid&failOnUnknownHost=true")
+                        .to("mock:password");
+
+                from("direct:sshInvalid").to("ssh://smx:smx@localhost:" + port
+                        + 
"?timeout=3000&knownHostsResource=classpath:known_hosts_invalid&failOnUnknownHost=true")
+                        .to("mock:password");
+
+                
from("direct:sshInvalidWarnOnly").to("ssh://smx:smx@localhost:" + port
+                        + 
"?timeout=3000&knownHostsResource=classpath:known_hosts_invalid").to("mock:password");
+            }
+        };
+    }
+}

http://git-wip-us.apache.org/repos/asf/camel/blob/be592e50/components/camel-ssh/src/test/resources/known_hosts_invalid
----------------------------------------------------------------------
diff --git a/components/camel-ssh/src/test/resources/known_hosts_invalid 
b/components/camel-ssh/src/test/resources/known_hosts_invalid
new file mode 100644
index 0000000..e69de29

http://git-wip-us.apache.org/repos/asf/camel/blob/be592e50/components/camel-ssh/src/test/resources/known_hosts_valid
----------------------------------------------------------------------
diff --git a/components/camel-ssh/src/test/resources/known_hosts_valid 
b/components/camel-ssh/src/test/resources/known_hosts_valid
new file mode 100644
index 0000000..e7ae1da
--- /dev/null
+++ b/components/camel-ssh/src/test/resources/known_hosts_valid
@@ -0,0 +1 @@
+127.0.0.1 ssh-rsa 
AAAAB3NzaC1yc2EAAAADAQABAAAAgQDdfIWeSV4o68dRrKSzFd/Bk51E65UTmmSrmW0O1ohtzi6HzsDPjXgCtlTt3FqTcfFfI92IlTr4JWqC9UK1QT1ZTeng0MkPQmv68hDANHbt5CpETZHjW5q4OOgWhVvj5IyOC2NZHtKlJBkdsMAa15ouOOJLzBvAvbqOR/yUROsEiQ==
\ No newline at end of file

Reply via email to