From b256312b2195a75b1ed46b04908441de024424d1 Mon Sep 17 00:00:00 2001 From: Pawel Date: Wed, 22 Feb 2017 17:18:38 +0000 Subject: [PATCH] Added possibility configure Sftp connection using private key #197. - Added SftpIdentity case class to allow configuring private/public key, - Added option to configure known_hosts file and test to check its usage. - Added spec that should fail password based authentication and revert to private key one, - Added docs paragraph to describe this option. --- docs/src/main/paradox/ftp.md | 2 + .../stream/alpakka/ftp/impl/FtpLike.scala | 1 - .../alpakka/ftp/impl/FtpSourceFactory.scala | 2 +- .../alpakka/ftp/impl/SftpOperations.scala | 12 +- .../scala/akka/stream/alpakka/ftp/model.scala | 117 +++++++++++++++--- .../alpakka/ftp/KeyFileSftpSourceTest.java | 53 ++++++++ .../alpakka/ftp/RawKeySftpSourceTest.java | 54 ++++++++ .../stream/alpakka/ftp/SftpStageTest.java | 12 +- .../stream/alpakka/ftp/SftpSupportImpl.java | 3 +- .../ftp/StrictHostCheckingSftpSourceTest.java | 54 ++++++++ ftp/src/test/resources/client.pem | 15 +++ ftp/src/test/resources/hostkey.pem | Bin 886 -> 1202 bytes ftp/src/test/resources/known_hosts | 1 + .../stream/alpakka/ftp/BaseSftpSpec.scala | 6 +- .../alpakka/ftp/CommonFtpStageSpec.scala | 40 ++++++ 15 files changed, 345 insertions(+), 27 deletions(-) create mode 100644 ftp/src/test/java/akka/stream/alpakka/ftp/KeyFileSftpSourceTest.java create mode 100644 ftp/src/test/java/akka/stream/alpakka/ftp/RawKeySftpSourceTest.java create mode 100644 ftp/src/test/java/akka/stream/alpakka/ftp/StrictHostCheckingSftpSourceTest.java create mode 100644 ftp/src/test/resources/client.pem create mode 100644 ftp/src/test/resources/known_hosts diff --git a/docs/src/main/paradox/ftp.md b/docs/src/main/paradox/ftp.md index a942265c73..89e30f4980 100644 --- a/docs/src/main/paradox/ftp.md +++ b/docs/src/main/paradox/ftp.md @@ -51,6 +51,8 @@ respectively. For non-anonymous connection, please provide an instance of @scaladoc[NonAnonFtpCredentials](akka.stream.alpakka.ftp.FtpCredentials$$NonAnonFtpCredentials) instead. +For connection using a private key, please provide an instance of @scaladoc[SftpIdentity](akka.stream.alpakka.ftp.SftpIdentity) to @scaladoc[SftpSettings](akka.stream.alpakka.ftp.RemoteFileSettings$$SftpSettings). + ### Traversing a remote FTP folder recursively In order to traverse a remote folder recursively, you need to use the `ls` method in the FTP API: diff --git a/ftp/src/main/scala/akka/stream/alpakka/ftp/impl/FtpLike.scala b/ftp/src/main/scala/akka/stream/alpakka/ftp/impl/FtpLike.scala index 1d819aa868..1dda5e4106 100644 --- a/ftp/src/main/scala/akka/stream/alpakka/ftp/impl/FtpLike.scala +++ b/ftp/src/main/scala/akka/stream/alpakka/ftp/impl/FtpLike.scala @@ -4,7 +4,6 @@ package akka.stream.alpakka.ftp package impl -import akka.stream.alpakka.ftp.RemoteFileSettings.SftpSettings import com.jcraft.jsch.JSch import org.apache.commons.net.ftp.FTPClient diff --git a/ftp/src/main/scala/akka/stream/alpakka/ftp/impl/FtpSourceFactory.scala b/ftp/src/main/scala/akka/stream/alpakka/ftp/impl/FtpSourceFactory.scala index dffedc1dec..bf5c334d8e 100644 --- a/ftp/src/main/scala/akka/stream/alpakka/ftp/impl/FtpSourceFactory.scala +++ b/ftp/src/main/scala/akka/stream/alpakka/ftp/impl/FtpSourceFactory.scala @@ -4,7 +4,7 @@ package akka.stream.alpakka.ftp.impl import akka.stream.alpakka.ftp.FtpCredentials.{AnonFtpCredentials, NonAnonFtpCredentials} -import akka.stream.alpakka.ftp.{FtpFileSettings, RemoteFileSettings} +import akka.stream.alpakka.ftp.{FtpFileSettings, RemoteFileSettings, SftpSettings} import akka.stream.alpakka.ftp.RemoteFileSettings._ import com.jcraft.jsch.JSch import org.apache.commons.net.ftp.FTPClient diff --git a/ftp/src/main/scala/akka/stream/alpakka/ftp/impl/SftpOperations.scala b/ftp/src/main/scala/akka/stream/alpakka/ftp/impl/SftpOperations.scala index 94d0aa0381..337a9731f1 100644 --- a/ftp/src/main/scala/akka/stream/alpakka/ftp/impl/SftpOperations.scala +++ b/ftp/src/main/scala/akka/stream/alpakka/ftp/impl/SftpOperations.scala @@ -4,7 +4,6 @@ package akka.stream.alpakka.ftp package impl -import akka.stream.alpakka.ftp.RemoteFileSettings.SftpSettings import com.jcraft.jsch.{ChannelSftp, JSch} import scala.collection.immutable @@ -12,13 +11,23 @@ import scala.util.Try import scala.collection.JavaConverters._ import java.io.{InputStream, OutputStream} import java.nio.file.Paths +import scala.collection.JavaConversions._ import java.nio.file.attribute.PosixFilePermissions private[ftp] trait SftpOperations { _: FtpLike[JSch, SftpSettings] => type Handler = ChannelSftp + private def configureIdentity(sftpIdentity: SftpIdentity)(implicit ftpClient: JSch) = sftpIdentity match { + case identity: RawKeySftpIdentity => + ftpClient.addIdentity(identity.name, identity.privateKey, identity.publicKey.orNull, identity.password.orNull) + case identity: KeyFileSftpIdentity => + ftpClient.addIdentity(identity.privateKey, identity.publicKey.orNull, identity.password.orNull) + } + def connect(connectionSettings: SftpSettings)(implicit ftpClient: JSch): Try[Handler] = Try { + connectionSettings.sftpIdentity.foreach(configureIdentity) + connectionSettings.knownHosts.foreach(ftpClient.setKnownHosts) val session = ftpClient.getSession( connectionSettings.credentials.username, connectionSettings.host.getHostAddress, @@ -27,6 +36,7 @@ private[ftp] trait SftpOperations { _: FtpLike[JSch, SftpSettings] => session.setPassword(connectionSettings.credentials.password) val config = new java.util.Properties config.setProperty("StrictHostKeyChecking", if (connectionSettings.strictHostKeyChecking) "yes" else "no") + config.putAll(connectionSettings.options) session.setConfig(config) session.connect() val channel = session.openChannel("sftp").asInstanceOf[ChannelSftp] diff --git a/ftp/src/main/scala/akka/stream/alpakka/ftp/model.scala b/ftp/src/main/scala/akka/stream/alpakka/ftp/model.scala index 85cfb5cd5f..8889e1e815 100644 --- a/ftp/src/main/scala/akka/stream/alpakka/ftp/model.scala +++ b/ftp/src/main/scala/akka/stream/alpakka/ftp/model.scala @@ -93,20 +93,52 @@ object RemoteFileSettings { passiveMode: Boolean = false ) extends FtpFileSettings - /** - * SFTP settings - * - * @param host host - * @param port port - * @param credentials credentials (username and password) - * @param strictHostKeyChecking sets whether to use strict host key checking. - */ - final case class SftpSettings( - host: InetAddress, - port: Int = DefaultSftpPort, - credentials: FtpCredentials = AnonFtpCredentials, - strictHostKeyChecking: Boolean = true - ) extends RemoteFileSettings +} + +/** + * + * @param host host + * @param port port + * @param credentials credentials (username and password) + * @param strictHostKeyChecking sets whether to use strict host key checking. + * @param knownHosts known hosts file to be used when connecting + * @param sftpIdentity private/public key config to use when connecting + * @param options additional options for ssh connection + */ +final case class SftpSettings( + host: InetAddress, + port: Int = RemoteFileSettings.DefaultSftpPort, + credentials: FtpCredentials = AnonFtpCredentials, + strictHostKeyChecking: Boolean = true, + knownHosts: Option[String] = None, + sftpIdentity: Option[SftpIdentity] = None, + options: Map[String, String] = Map.empty +) extends RemoteFileSettings { + def withPort(port: Int) = copy(port = port) + + def withCredentials(credentials: FtpCredentials) = copy(credentials = credentials) + + def withStrictHostKeyChecking(strictHostKeyChecking: Boolean) = copy(strictHostKeyChecking = strictHostKeyChecking) + + def withKnownHosts(knownHosts: String) = copy(knownHosts = Some(knownHosts)) + + def withSftpIdentity(sftpIdentity: SftpIdentity) = copy(sftpIdentity = Some(sftpIdentity)) + + def withOptions(option: (String, String), options: (String, String)*) = + copy(options = (option +: options).toMap) + + @annotation.varargs + def withOptions(option: akka.japi.Pair[String, String], options: akka.japi.Pair[String, String]*) = + copy(options = (option +: options).map(_.toScala).toMap) + +} + +object SftpSettings { + def create(host: InetAddress) = SftpSettings(host) + + def createEmptyIdentity(): Option[SftpIdentity] = None + + def createEmptyKnownHosts(): Option[String] = None } /** @@ -137,3 +169,60 @@ object FtpCredentials { */ final case class NonAnonFtpCredentials(username: String, password: String) extends FtpCredentials } + +object SftpIdentity { + + /** Java API */ + def createRawSftpIdentity(name: String, privateKey: Array[Byte]): RawKeySftpIdentity = + RawKeySftpIdentity(name, privateKey) + + def createFileSftpIdentity(privateKey: String): KeyFileSftpIdentity = + KeyFileSftpIdentity(privateKey) +} + +sealed abstract class SftpIdentity { + type KeyType + val privateKey: KeyType + val publicKey: Option[KeyType] + val password: Option[Array[Byte]] +} + +/** + * SFTP identity for authenticating using private/public key value + * + * @param name name of identity + * @param privateKey private key value to use when connecting + * @param password password to use to decrypt private key + * @param publicKey public key value to use when connecting + */ +final case class RawKeySftpIdentity(name: String, + privateKey: Array[Byte], + password: Option[Array[Byte]] = None, + publicKey: Option[Array[Byte]] = None) + extends SftpIdentity { + + override type KeyType = Array[Byte] + + def withPassword(password: Array[Byte]) = copy(password = Some(password)) + + def withPublicKey(publicKey: KeyType) = copy(publicKey = Some(publicKey)) +} + +/** + * SFTP identity for authenticating using private/public key file + * + * @param privateKey private key file to use when connecting + * @param password password to use to decrypt private key file + * @param publicKey public key file to use when connecting + */ +final case class KeyFileSftpIdentity(privateKey: String, + password: Option[Array[Byte]] = None, + publicKey: Option[String] = None) + extends SftpIdentity { + + override type KeyType = String + + def withPassword(password: Array[Byte]) = copy(password = Some(password)) + + def withPublicKey(publicKey: String) = copy(publicKey = Some(publicKey)) +} diff --git a/ftp/src/test/java/akka/stream/alpakka/ftp/KeyFileSftpSourceTest.java b/ftp/src/test/java/akka/stream/alpakka/ftp/KeyFileSftpSourceTest.java new file mode 100644 index 0000000000..fcaed25a69 --- /dev/null +++ b/ftp/src/test/java/akka/stream/alpakka/ftp/KeyFileSftpSourceTest.java @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2016-2017 Lightbend Inc. + */ +package akka.stream.alpakka.ftp; + +import akka.NotUsed; +import akka.stream.IOResult; +import akka.stream.alpakka.ftp.javadsl.Sftp; +import akka.stream.javadsl.Sink; +import akka.stream.javadsl.Source; +import akka.util.ByteString; +import org.junit.Test; + +import java.net.InetAddress; +import java.util.concurrent.CompletionStage; + +public class KeyFileSftpSourceTest extends SftpSupportImpl implements CommonFtpStageTest { + + @Test + public void listFiles() throws Exception { + CommonFtpStageTest.super.listFiles(); + } + + @Test + public void fromPath() throws Exception { + CommonFtpStageTest.super.fromPath(); + } + + public Source getBrowserSource(String basePath) throws Exception { + return Sftp.ls(basePath, settings()); + } + + public Source> getIOSource(String path) throws Exception { + return Sftp.fromPath(path, settings()); + } + + public Sink> getIOSink(String path) throws Exception { + return Sftp.toPath(path, settings()); + } + + private SftpSettings settings() throws Exception { + //#create-settings + final SftpSettings settings = SftpSettings.create( + InetAddress.getByName("localhost")) + .withPort(getPort()) + .withCredentials(new FtpCredentials.NonAnonFtpCredentials("different user and password", "will fail password auth")) + .withStrictHostKeyChecking(false) // strictHostKeyChecking + .withSftpIdentity(SftpIdentity.createFileSftpIdentity("ftp/src/test/resources/client.pem") + ); + //#create-settings + return settings; + } +} diff --git a/ftp/src/test/java/akka/stream/alpakka/ftp/RawKeySftpSourceTest.java b/ftp/src/test/java/akka/stream/alpakka/ftp/RawKeySftpSourceTest.java new file mode 100644 index 0000000000..b470f31aff --- /dev/null +++ b/ftp/src/test/java/akka/stream/alpakka/ftp/RawKeySftpSourceTest.java @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2016-2017 Lightbend Inc. + */ +package akka.stream.alpakka.ftp; + +import akka.NotUsed; +import akka.stream.IOResult; +import akka.stream.alpakka.ftp.javadsl.Sftp; +import akka.stream.javadsl.Sink; +import akka.stream.javadsl.Source; +import akka.util.ByteString; +import org.junit.Test; + +import java.net.InetAddress; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.concurrent.CompletionStage; + +public class RawKeySftpSourceTest extends SftpSupportImpl implements CommonFtpStageTest { + + @Test + public void listFiles() throws Exception { + CommonFtpStageTest.super.listFiles(); + } + + @Test + public void fromPath() throws Exception { + CommonFtpStageTest.super.fromPath(); + } + + public Source getBrowserSource(String basePath) throws Exception { + return Sftp.ls(basePath, settings()); + } + + public Source> getIOSource(String path) throws Exception { + return Sftp.fromPath(path, settings()); + } + + public Sink> getIOSink(String path) throws Exception { + return Sftp.toPath(path, settings()); + } + + private SftpSettings settings() throws Exception { + //#create-settings + final SftpSettings settings = SftpSettings.create(InetAddress.getByName("localhost")) + .withPort(getPort()) + .withCredentials(new FtpCredentials.NonAnonFtpCredentials("different user and password", "will fail password auth")) + .withStrictHostKeyChecking(false) // strictHostKeyChecking + .withSftpIdentity(SftpIdentity.createRawSftpIdentity("id", Files.readAllBytes(Paths.get("ftp/src/test/resources/client.pem"))) + ); + //#create-settings + return settings; + } +} diff --git a/ftp/src/test/java/akka/stream/alpakka/ftp/SftpStageTest.java b/ftp/src/test/java/akka/stream/alpakka/ftp/SftpStageTest.java index a39b3e8be6..023f754a16 100644 --- a/ftp/src/test/java/akka/stream/alpakka/ftp/SftpStageTest.java +++ b/ftp/src/test/java/akka/stream/alpakka/ftp/SftpStageTest.java @@ -5,7 +5,6 @@ import akka.NotUsed; import akka.stream.IOResult; -import akka.stream.alpakka.ftp.RemoteFileSettings.SftpSettings; import akka.stream.alpakka.ftp.javadsl.Sftp; import akka.stream.javadsl.Sink; import akka.stream.javadsl.Source; @@ -46,12 +45,11 @@ public Sink> getIOSink(String path) throws private SftpSettings settings() throws Exception { //#create-settings - final SftpSettings settings = new SftpSettings( - InetAddress.getByName("localhost"), - getPort(), - FtpCredentials.createAnonCredentials(), - false // strictHostKeyChecking - ); + final SftpSettings settings = SftpSettings.create( + InetAddress.getByName("localhost")) + .withPort(getPort()) + .withCredentials(FtpCredentials.createAnonCredentials()) + .withStrictHostKeyChecking(false); //#create-settings return settings; } diff --git a/ftp/src/test/java/akka/stream/alpakka/ftp/SftpSupportImpl.java b/ftp/src/test/java/akka/stream/alpakka/ftp/SftpSupportImpl.java index 6e58303cb8..9c9ab32a12 100644 --- a/ftp/src/test/java/akka/stream/alpakka/ftp/SftpSupportImpl.java +++ b/ftp/src/test/java/akka/stream/alpakka/ftp/SftpSupportImpl.java @@ -18,6 +18,7 @@ import org.apache.sshd.server.subsystem.sftp.SftpSubsystemFactory; import org.junit.After; import org.junit.Before; + import java.io.File; import java.nio.file.Files; import java.nio.file.Path; @@ -31,7 +32,7 @@ abstract class SftpSupportImpl extends FtpBaseSupport { SftpSupportImpl() { keyPairProviderFile = - new File(getClass().getClassLoader().getResource("hostkey.pem").getFile()); + new File("ftp/src/test/resources/hostkey.pem"); } @Before diff --git a/ftp/src/test/java/akka/stream/alpakka/ftp/StrictHostCheckingSftpSourceTest.java b/ftp/src/test/java/akka/stream/alpakka/ftp/StrictHostCheckingSftpSourceTest.java new file mode 100644 index 0000000000..1815992c64 --- /dev/null +++ b/ftp/src/test/java/akka/stream/alpakka/ftp/StrictHostCheckingSftpSourceTest.java @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2016-2017 Lightbend Inc. + */ +package akka.stream.alpakka.ftp; + +import akka.NotUsed; +import akka.japi.Pair; +import akka.stream.IOResult; +import akka.stream.alpakka.ftp.javadsl.Sftp; +import akka.stream.javadsl.Sink; +import akka.stream.javadsl.Source; +import akka.util.ByteString; +import org.junit.Test; + +import java.net.InetAddress; +import java.util.concurrent.CompletionStage; + +public class StrictHostCheckingSftpSourceTest extends SftpSupportImpl implements CommonFtpStageTest { + + @Test + public void listFiles() throws Exception { + CommonFtpStageTest.super.listFiles(); + } + + @Test + public void fromPath() throws Exception { + CommonFtpStageTest.super.fromPath(); + } + + public Source getBrowserSource(String basePath) throws Exception { + return Sftp.ls(basePath, settings()); + } + + public Source> getIOSource(String path) throws Exception { + return Sftp.fromPath(path, settings()); + } + + public Sink> getIOSink(String path) throws Exception { + return Sftp.toPath(path, settings()); + } + + private SftpSettings settings() throws Exception { + //#create-settings + final SftpSettings settings = SftpSettings.create(InetAddress.getByName("localhost")) + .withPort(getPort()) + .withCredentials(new FtpCredentials.NonAnonFtpCredentials("different user and password", "will fail password auth")) + .withStrictHostKeyChecking(true) // strictHostKeyChecking + .withKnownHosts("ftp/src/test/resources/known_hosts") + .withSftpIdentity(SftpIdentity.createFileSftpIdentity("ftp/src/test/resources/client.pem")) + .withOptions(new Pair("HostKeyAlgorithms", "+ssh-dss")); + //#create-settings + return settings; + } +} diff --git a/ftp/src/test/resources/client.pem b/ftp/src/test/resources/client.pem new file mode 100644 index 0000000000..9b215f45bb --- /dev/null +++ b/ftp/src/test/resources/client.pem @@ -0,0 +1,15 @@ +-----BEGIN RSA PRIVATE KEY----- +MIICXAIBAAKBgQDdfIWeSV4o68dRrKSzFd/Bk51E65UTmmSrmW0O1ohtzi6HzsDP +jXgCtlTt3FqTcfFfI92IlTr4JWqC9UK1QT1ZTeng0MkPQmv68hDANHbt5CpETZHj +W5q4OOgWhVvj5IyOC2NZHtKlJBkdsMAa15ouOOJLzBvAvbqOR/yUROsEiQIDAQAB +AoGBANG3JDW6NoP8rF/zXoeLgLCj+tfVUPSczhGFVrQkAk4mWfyRkhN0WlwHFOec +K89MpkV1ij/XPVzU4MNbQ2yod1KiDylzvweYv+EaEhASCmYNs6LS03punml42SL9 +97tOmWfVJXxlQoLiY6jHPU97vTc65k8gL+gmmrpchsW0aqmZAkEA/c8zfmKvY37T +cxcLLwzwsqqH7g2KZGTf9aRmx2ebdW+QKviJJhbdluDgl1TNNFj5vCLznFDRHiqJ +wq0wkZ39cwJBAN9l5v3kdXj21UrurNPdlV0n2GZBt2vblooQC37XHF97r2zM7Ou+ +Lg6MyfJClyguhWL9dxnGbf3btQ0l3KDstxMCQCRaiEqjAfIjWVATzeNIXDWLHXso +b1kf5cA+cwY+vdKdTy4IeUR+Y/DXdvPWDqpf0C11aCVMohdLCn5a5ikFUycCQDhV +K/BuAallJNfmY7JxN87r00fF3ojWMJnT/fIYMFFrkQrwifXQWTDWE76BSDibsosJ +u1TGksnm8zrDh2UVC/0CQFrHTiSl/3DHvWAbOJawGKg46cnlDcAhSyV8Frs8/dlP +7YGG3eqkw++lsghqmFO6mRUTKsBmiiB2wgLGhL5pyYY= +-----END RSA PRIVATE KEY----- \ No newline at end of file diff --git a/ftp/src/test/resources/hostkey.pem b/ftp/src/test/resources/hostkey.pem index 9b215f45bb459280fa18694843aab325b7309b57..752ef1b15b5e683084466993823f58406dd652d4 100644 GIT binary patch literal 1202 zcmZ4UmVvdnh(RG?oMW#pDH2tf?XNz6;v4=yRn%uBb9W?)avOU_S8O(|huigxm0U`xv{ z%1taOWT<0c^VUR|11<+|gdsISzl0bKqFfh9WJC+uK3|RA7{0AFH$O9$@ zhKd3P2F3t`CPp77Mg}0I!^W=F=5fw~g%QY+XKHL@_*);`C|fF|8Fb%kj^3Lm&%X(7 z53+gh!f;s5zF}LSTBd8PgHqt{NZa0sU;B@K-DU9Y^z6`_>y6e8vL^YT(h|03&kR_) zWd8gYEY$)^hx~W1y#8-aTa@zNY3tF$uw!L?H&*~Vn z9pO7Gr>x)o%j#9r&u|}ZTlS{(z{v2vT~{LgpB1tTG&XGU6f0DI;iDlYy!||Q#r5336it&p{!Xf5RIxs@jYl zr0%r1hU!q&6hXLdm>L^0iY`^wjX=;mWzAtm`r_nwNRyhzNHnTnaq+XA9lI;epBg3J~M>1j);G( zU1q5+uJS77@BcN{fgA-IYvW#4slD#}Ed1du%WUCutKuUK#k2qai>cakwXuYOHA2tS Sz_Jb$nY^HM6YAvS=?nmfS?c2e literal 886 zcmZvbx3Z&H07P@XVsh#gPPkx#i}&6t6CmtG2qO@D{l49JJMF3tPWAcu`Hdi}y7MOp z!5=Ts6A;ONOgaCz_(5!47c-y>0GLAKOI59>&oH@73|9q<8D2_-2?I^Y3>`<~7%pRq zyTsIPSI21ADtSnsHR1-NjZ)^Qtd&~lNgYPZ%}8gFk4?glh_q2YxBI(?<&4h+9RI3dh1U-FR*RU>O2YWHQ0d~fLyULxf zOTY&L0wz@ej!GF)$~cq9EedvJ)6;4!u}8N$iM_DAT2)CFe*nX6OzSfkS_e+TPLI-hMFz`S{^yNS7sHR zH~kq0XpTu)wR@>FziV71h=-WiQ(IZ$i+<4Epu}e>`cj)9CMJBq146^8$DB334#zGy zUMtkpr?&Bmuv*V;MOg-aIl4OY(@08XmJvd!E7oZ>kPi{5!}=PzYmr(qPDBhBePz+2 zE`%5*X?qF^*LWPN+1!}&1*H3_BQ2kk)~qW}N^ diff --git a/ftp/src/test/resources/known_hosts b/ftp/src/test/resources/known_hosts new file mode 100644 index 0000000000..42acf49a93 --- /dev/null +++ b/ftp/src/test/resources/known_hosts @@ -0,0 +1 @@ +localhost,127.0.0.1 ssh-dss AAAAB3NzaC1kc3MAAACBAP1/U4EddRIpUt9KnC7s5Of2EbdSPO9EAMMeP4C2USZpRV1AIlH7WT2NWPq/xfW6MPbLm1Vs14E7gB00b/JmYLdrmVClpJ+f6AR7ECLCT7up1/63xhv4O1fnxqimFQ8E+4P208UewwI1VBNaFpEy9nXzrith1yrv8iIDGZ3RSAHHAAAAFQCXYFCPFSMLzLKSuYKi64QL8Fgc9QAAAIEA9+GghdabPd7LvKtcNrhXuXmUr7v6OuqC+VdMCz0HgmdRWVeOutRZT+ZxBxCBgLRJFnEj6EwoFhO3zwkyjMim4TwWeotUfI0o4KOuHiuzpnWRbqN/C/ohNWLx+2J6ASQ7zKTxvqhRkImog9/hWuWfBpKLZl6Ae1UlZAFMO/7PSSoAAACAaXMjzze1neOLNYQoCdacSFoHDVrzAwq9Td7kiNu6CZFyyPgjtJSAj/hUz91kuCtOB2u00bzcNJXp1XEkj585OgE2HKRaTAceX481G+JMmBMqxBfxfaY5Jxck6mT9/6w7UQhwKK1e6Xom64nzE/DtBJsTzqpfWTEXa//+XHq81YE= \ No newline at end of file diff --git a/ftp/src/test/scala/akka/stream/alpakka/ftp/BaseSftpSpec.scala b/ftp/src/test/scala/akka/stream/alpakka/ftp/BaseSftpSpec.scala index 3919430464..043c77040e 100644 --- a/ftp/src/test/scala/akka/stream/alpakka/ftp/BaseSftpSpec.scala +++ b/ftp/src/test/scala/akka/stream/alpakka/ftp/BaseSftpSpec.scala @@ -4,7 +4,6 @@ package akka.stream.alpakka.ftp import akka.NotUsed -import akka.stream.alpakka.ftp.RemoteFileSettings.SftpSettings import akka.stream.alpakka.ftp.FtpCredentials.AnonFtpCredentials import akka.stream.alpakka.ftp.scaladsl.Sftp import akka.stream.IOResult @@ -21,7 +20,10 @@ trait BaseSftpSpec extends SftpSupportImpl with BaseSpec { InetAddress.getByName("localhost"), getPort, AnonFtpCredentials, - strictHostKeyChecking = false + strictHostKeyChecking = false, + knownHosts = None, + sftpIdentity = None, + options = Map.empty ) //#create-settings diff --git a/ftp/src/test/scala/akka/stream/alpakka/ftp/CommonFtpStageSpec.scala b/ftp/src/test/scala/akka/stream/alpakka/ftp/CommonFtpStageSpec.scala index 67d3f0a15e..20894ac91b 100644 --- a/ftp/src/test/scala/akka/stream/alpakka/ftp/CommonFtpStageSpec.scala +++ b/ftp/src/test/scala/akka/stream/alpakka/ftp/CommonFtpStageSpec.scala @@ -3,7 +3,13 @@ */ package akka.stream.alpakka.ftp +import java.io.File +import java.net.InetAddress +import java.nio.file.{Files, Paths} + import akka.stream.IOResult +import akka.stream.alpakka.ftp.FtpCredentials.NonAnonFtpCredentials +import akka.stream.scaladsl.{Keep, Sink} import akka.stream.scaladsl.{Keep, Sink, Source} import akka.stream.testkit.scaladsl.TestSink import akka.util.ByteString @@ -19,6 +25,40 @@ final class FtpsStageSpec extends BaseFtpsSpec with CommonFtpStageSpec { setUseImplicit(false) } +final class RawKeySftpSourceSpec extends BaseSftpSpec with CommonFtpStageSpec { + override val settings = SftpSettings( + InetAddress.getByName("localhost"), + getPort, + NonAnonFtpCredentials("different user and password", "will fail password auth"), + strictHostKeyChecking = false, + knownHosts = None, + Some(RawKeySftpIdentity("id", Files.readAllBytes(Paths.get("ftp/src/test/resources/client.pem")))) + ) +} + +final class KeyFileSftpSourceSpec extends BaseSftpSpec with CommonFtpStageSpec { + override val settings = SftpSettings( + InetAddress.getByName("localhost"), + getPort, + NonAnonFtpCredentials("different user and password", "will fail password auth"), + strictHostKeyChecking = false, + knownHosts = None, + Some(KeyFileSftpIdentity("ftp/src/test/resources/client.pem")) + ) +} + +final class StrictHostCheckingSftpSourceSpec extends BaseSftpSpec with CommonFtpStageSpec { + override val settings = SftpSettings( + InetAddress.getByName("localhost"), + getPort, + NonAnonFtpCredentials("different user and password", "will fail password auth"), + strictHostKeyChecking = true, + knownHosts = Some(new File("ftp/src/test/resources/known_hosts").getAbsolutePath), + Some(KeyFileSftpIdentity("ftp/src/test/resources/client.pem", None, None)), + options = Map("HostKeyAlgorithms" -> "+ssh-dss") + ) +} + trait CommonFtpStageSpec extends BaseSpec { implicit val system = getSystem