You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@camel.apache.org by ac...@apache.org on 2017/08/22 06:55:28 UTC

[2/2] camel git commit: CAMEL-11611 - Add a knownHosts option to the camel-ssh component

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 <sa...@endless-webservices.de>
Authored: Mon Aug 14 13:25:30 2017 +0200
Committer: Andrea Cosentino <an...@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