Install an SFTP server on OpenShift

Jostein Leira
Compendium
Published in
8 min readNov 11, 2020

Background

At work, we needed an SFTP server for testing our software. I volunteered to install one in our OpenShift test environment, but ran into lots of problems underway and used way too much time on this project. I’m new to OpenShift and really underestimated this task.

We use OpenShift/okd version 3.11. As our source, I found https://github.com/atmoz/sftp which is also on Docker Hub with over 100 million downloads.

The GitHub repository contains several branches, and I chose to use the alpine-3.7 branch since it’s size is only 16 MB.

I soon ran into problems trying to modify the atmoz/sftp project to be able to run as a non-root user. It did not go well, and I abandoned this attempt.

Another problem was line feeds in config files CRLF (Windows) vs LF (Linux).

I hope this installation log may be helpful to you.

Security considerations

Do you trust the source? The SFTP server must run as root, which is against the OpenShift recommendations. We only use this server internally, not exposed to the internet. Made a fork (copy) of the GitHub source project and use that.

Installation overview

The installation consists of the following:

  1. Container source from docker hub or GitHub.
  2. Config map for the users:
    /etc/sftp/users.conf
  3. Config map for ssh keys and config:
    /etc/ssh/ssh_host_ed25519_key
    /etc/ssh/ssh_host_ed25519_key.pub
    /etc/ssh/ssh_host_rsa_key
    /etc/ssh/ssh_host_rsa_key.pub
    /etc/ssh/sshd_config
  4. NodePort allowing external traffic to port 30022 -> internal port 22
  5. A Persistent Volume (PV) storage for the user's upload directory:
    /home/user/upload

Project

Log in to your OpenShift server and either create a new project/namespace for the SFTP service or select an existing one.

either create a new project:

oc new-project int-sftp --display-name="Internal sftp server"

or select an existing project:

oc project int-sftp

Throughout this installation, it is assumed that you are logged in to the same project/namespace.

New application

Create a new application:

From docker hub:

oc new-app atmoz/sftp:alpine-3.7

or from a GitHub repository:

oc new-app https://github.com/atmoz/sftp#alpine-3.7

Take a look at the pod’s log. Expect error message:

mkdir: can't create directory '/var/run/sftp': Permission denied

Allow running as root

Will change the policy for the default user in your current project/namespace (int-sftp) to run as any user ID (root):

oc adm policy add-scc-to-user anyuid -z default
scc “anyuid” added to: [“system:serviceaccount:int-sftp:default”]

To check which users have the ability to run as root:

oc edit scc anyuidusers:- system:serviceaccount:int-sftp:default

To see that the “run as root” setting had the desired effect, scale down and up again the sftp deployment.

See that you get the expected error message:

[entrypoint] FATAL: No users provided!

SFTP users config map

Create a new config map with the name sftp-etc-sftp and key users.conf and content:

foo:123:1001:100:upload
bar:abc:1002:100:upload
baz:xyz:1003:100:upload

The format of the user config file is username:password:UID:GUI:directory.

I am on Windows and get CRLF at the end of each line, but you must remove the extra CR characters. Remove by Edit YAML:

Remove the ‘\r’ characters otherwise, it will be part of the upload directory name and cause problems.

Mount the sftp config map

Mount the sftp-etc-sftp config map to the file system path /etc/sftp by going to your Deployments > sftp > Configuration > Add config files:

Source: sftp-etc-sftp
Mount path: /etc/sftp

Hit the Add button.

Test the config map

To test the new config map, ensure that your pod is redeployed and scaled to one. You may not need to do anything if automatic redeploy on config changes are turned on.

Startup log:

[entrypoint] Parsing user data: "foo:123:1001:100:upload"
Creating mailbox file: No such file or directory
[entrypoint] Creating directory: /home/foo/upload
[entrypoint] Parsing user data: "bar:abc:1002:100:upload"
Creating mailbox file: No such file or directory
[entrypoint] Creating directory: /home/bar/upload
[entrypoint] Parsing user data: "baz:xyz:1003:100:upload"
Creating mailbox file: No such file or directory
[entrypoint] Creating directory: /home/baz/upload
Generating public/private ed25519 key pair.
Your identification has been saved in /etc/ssh/ssh_host_ed25519_key.
Your public key has been saved in /etc/ssh/ssh_host_ed25519_key.pub.
The key fingerprint is:
SHA256:qNWATmxbbYyHinTx5n10eIml7UG5K2vana9XHcqr3rY root@sftp-3-kwg2c
The key's randomart image is:
+--[ED25519 256]--+
| . o. |
| . + = B.. |
| . * B = = *. |
| . * * B . +.. . |
| . + + S . o.. o|
| o .. .o o|
| . o .. |
| .o..+. |
| .ooo*Eo |
+----[SHA256]-----+
Generating public/private rsa key pair.
Your identification has been saved in /etc/ssh/ssh_host_rsa_key.
Your public key has been saved in /etc/ssh/ssh_host_rsa_key.pub.
The key fingerprint is:
SHA256:aTIlNkPOyXYDXiZ4Yixz2poRwYrmQJ4DuCIRqxljuxU root@sftp-3-kwg2c
The key's randomart image is:
+---[RSA 4096]----+
|oo.o .+ o |
|+o= **.* |
|O=.E o% + |
|XO+ oo * o |
|O..= o S |
| .= + |
| . |
| |
| |
+----[SHA256]-----+
[entrypoint] Executing sshd
Server listening on 0.0.0.0 port 22.
Server listening on :: port 22.

The SFTP server is now up and running!

What is still missing is ensuring that the server ssh keys (generated during startup) and files uploaded by the users are permanently stored. Now they will be gone in case of a pod restart. In addition, we can not access the SFTP server from the outside of our pod.

SSH config map

Without persistent ssh keys, the end-users will get login errors after a pod restart due to changed host keys.

We must ensure that the file access permissions to the ssh keys are restricted to only be readable by the root user.

Create a new config map with the name sftp-etc-ssh and add the following keys:

ssh_host_ed25519_key
ssh_host_ed25519_key.pub
ssh_host_rsa_key
ssh_host_rsa_key.pub
sshd_config

The content/value of these files could be lifted from the running pod via Pods > sftp-N-AAAA > Terminal and the cat-commands:

cat /etc/ssh/ssh_host_ed25519_key
cat /etc/ssh/ssh_host_ed25519_key.pub
cat /etc/ssh/ssh_host_rsa_key
cat /etc/ssh/ssh_host_rsa_key.pub
cat /etc/ssh/sshd_config

Or you can generate new keys with the following two commands. Use empty passphrases. Then cat their content:

ssh-keygen -t ed25519 -f ssh_host_ed25519_key < /dev/null
ssh-keygen -t rsa -b 4096 -f ssh_host_rsa_key < /dev/null

The sshd_config content is:

Protocol 2
HostKey /etc/ssh/ssh_host_ed25519_key
HostKey /etc/ssh/ssh_host_rsa_key
# Faster connection
# See: https://github.com/atmoz/sftp/issues/11
UseDNS no
# Limited access
PermitRootLogin no
X11Forwarding no
AllowTcpForwarding no
# Force sftp and chroot jail
Subsystem sftp internal-sftp
ForceCommand internal-sftp
ChrootDirectory %h

You should end up with something like this:

Mount ssh config map

Map the sftp-etc-ssh config map to the file system path /etc/ssh by going to your Deployments > sftp > Configuration > Add Config Files:

Source: sftp-etc-ssh
Mount path: /etc/ssh

Ensure that your pod is restarted (redeployed or scaled down to 0 and up to 1 again).

Next, you should get an error message like this:

We need to restrict the permissions for the ssh key files. Go to Deployments > sftp > Edit YAML:

Change to defaultMode: 384 setting for sftp-stc-ssh:

spec:
template:
spec:
volumes:
- configMap:
defaultMode: 384
name: sftp-etc-ssh

384 decimal = 600 octal = user +rw
(See http://permissions-calculator.org/)

Test the SSH config map

Ensure the pod is restarted. The new startup log should now look something like this:

[entrypoint] Parsing user data: “foo:123:1001:100:upload”
Creating mailbox file: No such file or directory
[entrypoint] Creating directory: /home/foo/upload
[entrypoint] Parsing user data: “bar:abc:1002:100:upload”
Creating mailbox file: No such file or directory
[entrypoint] Creating directory: /home/bar/upload
[entrypoint] Parsing user data: “baz:xyz:1003:100:upload”
Creating mailbox file: No such file or directory
[entrypoint] Creating directory: /home/baz/upload
[entrypoint] Executing sshd
Server listening on 0.0.0.0 port 22.
Server listening on :: port 22.

Expose service port

To be able to access the SFTP server from an external machine we need to create a NodePort. A default is already automatically created, but we need to make some changes. Go to Services > sftp > Edit YAML:

Do two modifications:

  1. Change type from ClusterIP to NodePort:
  2. Add ‘nodePort: 30022’ to the port definition section

You should end up with something like this:

spec:
ports:
- name: 22-tcp
nodePort: 30022
port: 22
protocol: TCP
targetPort: 22
type: NodePort

Test the exposed port

It should now be possible to access the OpenShift node server’s external address with SFTP.

sftp -P 30022 bar@myopenshiftserver.com
bar@myopenshiftserver’s password:
Connected to bar@myopenshiftserver.com.
sftp>
sftp> ls
upload
sftp> cd upload
sftp> lls

Persistent volume

We need to make a Persistent Volume (PV) claim to permanently store files uploaded by the users. Until now all uploaded files would disappear after a pod restart.

For now, I only create a PV claim for one user (mounted on: /home/bar/upload). You need to repeat this procedure for each user.

Go to Storage > Create Storage:

Name: sftp-bar-storage
Access Mode: Shared Access (RWX)
Size: 10 GiB

Hit the Create button.

Then go to Deployments > sftp > Add Storage:

Select:
Storage: sftp-bar-storage
Mount path: /home/bar/upload

Press the Add button.

Ensure pod restart. Upload a file to the directory /home/bar/upload and restart the pod again. Confirm that the file is still there.

Conclusion

This is a complicated installation, and I especially struggled with CRLF characters sneaking into my config and script files, giving strange error messages. I hope this document is helpful.

Future work

Use secrets instead of config map.

Encrypt users’ passwords.

A single Persistent Volume (PV) for all users.

Remove showing password during startup. Is a security problem if a centralized logging system is used.

--

--

Jostein Leira
Compendium

Python software developer and Google Cloud Certified Professional Cloud Architect