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:
- Creating an FTP container
- Creating new keys
- Configuring that container to use key authentication
- Testing out that configuration
- Creating a new Camel/Fuse application to talk to the FTP server
- 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 |
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]-----+ |
Configure the SFTP container
Create a user foo
in users.conf
, and configure the container to use key authentication
foo::1001:100 |
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 |
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> |
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");