I had a colleague who was having issues with public key authentication with SFTP, so I wanted to be able to set this situation up on my minishift/crc environment in order to figure out how to do this.

In effect, this meant:

  1. Creating an FTP container
  2. Creating new keys
  3. Configuring that container to use key authentication
  4. Testing out that configuration
  5. Creating a new Camel/Fuse application to talk to the FTP server
  6. Configuring the Camel/Fuse application to talk to the FTP server

Create the SFTP container

Thankfully there is already an SFTP container available on dockerhub. We will be able to use this to run an SFTP service with ssh key authentication: https://hub.docker.com/r/atmoz/sftp/

First we need to import the image into Openshift from dockerhub so we can use it:

oc new-project sftp
oc import-image sftp --from=atmoz/sftp:latest -n openshift --confirm
view raw import-image.sh hosted with ❤ by GitHub

Now let’s create the container in a new openshift project:

[user@localhost ~]$ oc new-app --image=openshift/sftp
Flag --image has been deprecated, use --image-stream instead
--> Found image 6345f82 (9 months old) in image stream "openshift/sftp" under tag "latest" for "openshift/sftp"
* This image will be deployed in deployment config "sftp"
* Port 22/tcp will be load balanced by service "sftp"
* Other containers can access this service through the hostname "sftp"
* WARNING: Image "openshift/sftp:latest" runs as the 'root' user which may not be permitted by your cluster administrator
--> Creating resources ...
imagestreamtag.image.openshift.io "sftp:latest" created
deploymentconfig.apps.openshift.io "sftp" created
service "sftp" created
--> Success
Application is not exposed. You can expose services to the outside world by executing one or more of the commands below:
'oc expose svc/sftp'
Run 'oc status' to view your app.

Create the authentication keys

Please note the naming, and that we have chosen to write the file to a local location rather than /home/user/.ssh/id_rsa. The public key will be added into the SFTP container, and clients will use the private key in order to authenticate. I also have an empty passphrase for this, and ensure the keytype is rsa, and that it is output as a PEM.

[user@localhost temp]$ ssh-keygen -t rsa -m PEM
Generating public/private rsa key pair.
Enter file in which to save the key (/home/user/.ssh/id_rsa): id_rsa
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in id_rsa.
Your public key has been saved in id_rsa.pub.
The key fingerprint is:
SHA256:OFMPj7OkWNDVLuefdaoFz1gk9CorjabvB73wWTKfwiw user@localhost.localdomain
The key's randomart image is:
+---[RSA 3072]----+
| .. . |
| . . .. . |
| . . o. . o |
| . o.=o + |
| = S=+ o . |
| o =o==o.*. .|
| . . =*oO.++o |
| oE.O =.. |
| .ooo ... |
+----[SHA256]-----+
view raw create-keys.sh hosted with ❤ by GitHub

Configure the SFTP container

Create a user foo in users.conf, and configure the container to use key authentication

foo::1001:100
view raw users.conf hosted with ❤ by GitHub

Add the users.conf file as a volume for the container to consume

oc create secret generic sftp-users --from-file=users.conf
oc set volume dc/sftp --add --mount-path=/etc/sftp --secret-name=sftp-users --read-only=true

And add the public key as a volume/file for the container to consume

oc create secret generic sftp-ssh-key --from-file=id_rsa.pub
oc set volume dc/sftp --add --mount-path=/home/foo/.ssh/keys --secret-name=sftp-ssh-key --read-only=true

The SFTP container should now be running and configured for key auth.

Test the SFTP Container

We can test connectivity to the SFTP pod by port-forwarding:

oc port-forward pod/$(oc get pods -l app=sftp -o jsonpath='{.items[0].metadata.name}') 22222:22
view raw port-forward.sh hosted with ❤ by GitHub

By providing the private key, we should be able to connect to the FTP server

[user@localhost host]$ sftp -P 22222 -i id_rsa foo@0.0.0.0
The authenticity of host '[0.0.0.0]:22222 ([0.0.0.0]:22222)' can't be established.
ED25519 key fingerprint is SHA256:emLMHxpfdIc92uhnJY1e0WI+JCPgMFKKQADn73w9muM.
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Warning: Permanently added '[0.0.0.0]:22222' (ED25519) to the list of known hosts.
foo@0.0.0.0's password:
Connected to foo@0.0.0.0.
sftp>
view raw sftp-test.sh hosted with ❤ by GitHub

Create a new Camel application

[user@localhost host]$ oc new-app fuse7-java-openshift:1.2~https://github.com/welshstew/camel-sftp-example.git --allow-missing-images --strategy=source
--> Found image 62f2af0 (5 months old) in image stream "openshift/fuse7-java-openshift" under tag "1.2" for "fuse7-java-openshift:1.2"
Fuse for OpenShift
------------------
Build and run Spring Boot-based integration applications
Tags: builder, java, fuse
* A source build using source code from https://github.com/welshstew/camel-sftp-example.git will be created
* The resulting image will be pushed to image stream tag "camel-sftp-example:latest"
* Use 'oc start-build' to trigger a new build
* This image will be deployed in deployment config "camel-sftp-example"
* Ports 8778/tcp, 9779/tcp will be load balanced by service "camel-sftp-example"
* Other containers can access this service through the hostname "camel-sftp-example"
--> Creating resources ...
imagestream.image.openshift.io "camel-sftp-example" created
buildconfig.build.openshift.io "camel-sftp-example" created
deploymentconfig.apps.openshift.io "camel-sftp-example" created
service "camel-sftp-example" created
--> Success
Build scheduled, use 'oc logs -f bc/camel-sftp-example' to track its progress.
Application is not exposed. You can expose services to the outside world by executing one or more of the commands below:
'oc expose svc/camel-sftp-example'
Run 'oc status' to view your app.

Generate these application.properties:

logging.config=classpath:logback.xml
# the options from org.apache.camel.spring.boot.CamelConfigurationProperties can be configured here
camel.springboot.name=MyCamel
# lets listen on all ports to ensure we can be invoked from the pod IP
server.address=0.0.0.0
management.address=0.0.0.0
# lets use a different management port in case you need to listen to HTTP requests on 8080
management.port=8081
# disable all management enpoints except health
endpoints.enabled = false
endpoints.health.enabled = true
sftp.address=sftp:22
sftp.username=foo
sftp.password=
sftp.privatekey=/etc/ssh/id_rsa

Create a configmap and secret for the application.properties and private key respectively.

oc create configmap camel-config --from-file=application.properties
oc create secret generic sftp-private-key --from-file=id_rsa

Mount the configmap and secret as volumes to the camel application

oc set volume dc/camel-sftp-example --add --mount-path=/etc/ssh/ --secret-name=sftp-private-key
oc set volume dc/camel-sftp-example --add --mount-path=/etc/config/ --configmap-name=camel-config
oc set env dc/camel-sftp-example SPRING_CONFIG_LOCATION=/etc/config/application.properties

Getting the latest camel pod log should now reveal that the application is able to connect and poll the SFTP site.

11:49:16.578 [Camel (MyCamel) thread #1 - sftp://sftp:22] TRACE o.a.c.c.file.remote.SftpConsumer - Polling directory:
11:49:16.578 [Camel (MyCamel) thread #1 - sftp://sftp:22] TRACE o.a.c.c.file.remote.SftpOperations - listFiles(.)
11:49:16.586 [Camel (MyCamel) thread #1 - sftp://sftp:22] TRACE o.a.c.c.file.remote.SftpConsumer - Found 3 in directory:
11:49:16.586 [Camel (MyCamel) thread #1 - sftp://sftp:22] TRACE o.a.c.c.file.remote.SftpConsumer - SftpFile[fileName=., longName=drwxr-xr-x 3 0 0 18 Oct 7 11:40 ., dir=true]
11:49:16.586 [Camel (MyCamel) thread #1 - sftp://sftp:22] TRACE o.a.c.c.file.remote.SftpConsumer - SftpFile[fileName=.., longName=drwxr-xr-x 3 0 0 18 Oct 7 11:40 .., dir=true]
11:49:16.586 [Camel (MyCamel) thread #1 - sftp://sftp:22] TRACE o.a.c.c.file.remote.SftpConsumer - SftpFile[fileName=.ssh, longName=drwxr-xr-x 3 0 0 41 Oct 7 11:40 .ssh, dir=true]
11:49:16.586 [Camel (MyCamel) thread #1 - sftp://sftp:22] TRACE o.a.c.c.file.remote.SftpOperations - changeCurrentDirectory(/)
11:49:16.586 [Camel (MyCamel) thread #1 - sftp://sftp:22] TRACE o.a.c.c.file.remote.SftpOperations - Compacted path: / -> / using separator: /
11:49:16.586 [Camel (MyCamel) thread #1 - sftp://sftp:22] TRACE o.a.c.c.file.remote.SftpOperations - getCurrentDirectory()
11:49:16.586 [Camel (MyCamel) thread #1 - sftp://sftp:22] TRACE o.a.c.c.file.remote.SftpOperations - Current dir: /
11:49:16.586 [Camel (MyCamel) thread #1 - sftp://sftp:22] TRACE o.a.c.c.file.remote.SftpOperations - getCurrentDirectory()
11:49:16.586 [Camel (MyCamel) thread #1 - sftp://sftp:22] TRACE o.a.c.c.file.remote.SftpOperations - Current dir: /
11:49:16.586 [Camel (MyCamel) thread #1 - sftp://sftp:22] DEBUG o.a.c.c.file.remote.SftpConsumer - Took 0.009 seconds to poll:
11:49:16.586 [Camel (MyCamel) thread #1 - sftp://sftp:22] TRACE o.a.c.c.file.remote.SftpConsumer - postPollCheck on sftp://foo@sftp:22
11:49:16.586 [Camel (MyCamel) thread #1 - sftp://sftp:22] TRACE o.a.c.c.file.remote.SftpConsumer - postPollCheck disconnect from: sftp://sftp:22?binary=true&disconnect=true&passiveMode=true&password=xxxxxx&preferredAuthentications=publickey&privateKeyFile=%2Fetc%2Fssh%2Fid_rsa&useUserKnownHostsFile=false&username=foo
11:49:16.586 [Camel (MyCamel) thread #1 - sftp://sftp:22] DEBUG o.a.c.c.file.remote.SftpConsumer - Disconnecting from: sftp://foo@sftp:22

It is worth noting that the camel route ignores the host keys useUserKnownHostsFile=false. And the main camel route is: from("sftp://?preferredAuthentications=publickey&username=&password=&privateKeyFile=&passiveMode=true&disconnect=true&binary=true&useUserKnownHostsFile=false").to("log:hello");


codergists