Scrappy SFTP is a multi-user SFTP server that emails transfer logs. Each SFTP user has read-write access to their own private directory in a chroot jail. A special sftpadmin
user has access to all users' directories and can upload files to any of them, which can then be downloaded (or removed) by the individual users.
The implementation is based on a blog post I wrote a while back about setting up a chrooted SFTP server on Ubuntu, but I've added a few improvements since then and wrapped it up in a nice, portable Docker image.
If you just want to try it out, start the container like this:
docker run -it --rm --name sftp -p 2022:22 rhasselbaum/scrappy-sftp
The container runs an OpenSSH server and a file transfer mailer daemon. The SSH/SFTP server is exposed as port 2022 on host. Naturally, you can change this to whatever you like including the SSH default port 22 if it isn't already in use on the host.
You can't do much with the server, though, until you create a user. In a separate terminal, run the following:
docker exec -it sftp addsftpuser testuser
Follow the prompts to set a password. You can leave the other attributes blank if you want. Once it's done, you can try uploading a file. Make sure you have the sftp
client installed on your host. Then, in the same terminal, run:
echo "Hello world" >foo.txt
sftp -P 2022 testuser@localhost
And at the SFTP prompt:
sftp> put foo.txt
sftp> quit
Congratulations, you uploaded your first file! If the container had been configured to email transfer logs, an email would have been sent out at this point. We'll set that up later. For now, let's connect as the sftpadmin
user to retrieve the uploaded file. Run:
sftp -P 2022 sftpadmin@localhost
The default password for the sftpadmin
user is sftpadmin
. When logged in as this user, we can browse all users' directories, as well as upload, download, and change files and subdirectories.
sftp> ls
dev testuser
sftp> cd testuser/data
sftp> ls
foo.txt
sftp> get foo.txt
Fetching /testuser/data/foo.txt to foo.txt
/testuser/data/foo.txt 100% 12 0.0KB/s 00:00
sftp> rm foo.txt
Removing /testuser/data/foo.txt
sftp> quit
Neat! But we should really change the sftpadmin
user's password, which we can do with:
docker exec -it sftp passwd sftpadmin
The same command can be used to change any other user's password. All of the users are regular local Linux users, so they can be managed through the standard utilities executed via docker exec
. Alternatively, you can use public key authentication by placing a .ssh/authorized_keys
file with appropriate permissions in a user's home directory. When you're done experimenting, you can hit CTRL-C in the terminal that started the Scrappy SFTP container to shut it down and clear out the test data.
If you just want a small server and you're not concerned about backups or emailed transfer logs, you can start up a permanent container by adjusting the Docker parameters to run Scrappy SFTP in the background:
docker run -d --name sftp -p 2022:22 rhasselbaum/scrappy-sftp
Then you can use standard Docker commands like docker ps
and docker logs
to monitor the server and see real-time file transfers. But if you want to learn how to store files on the host file system, perform backups, or email transfer logs, read on.
In this section, we'll look at how to build a durable SFTP container that supports backups and emailed transfer logs.
Scrappy SFTP enables you to store the data it maintains on the host file system, which makes it easy to backup or move the container to a different host. This data includes:
- The SFTP directory structure and files
- The user database and SSH host keys
All user directories are stored in the /sftp-root
container volume, and the user database and SSH host keys are replicated to the /creds
volume. To use directories on the host for both, specify their locations using the -v
argument of docker run
. For example:
docker run -d --name sftp -p 2022:22 \
-v /host/path/to/sftp-root:/sftp-root \
-v /host/path/to/credentials:/creds \
rhasselbaum/scrappy-sftp
Scrappy SFTP will create subdirectories in the /sftp-root
volume as users are added through the addsftpuser
command shown earlier. It also creates socket files and hard links to log child process activity, so make sure the file system on the host allows these special file types.
The user database and SSH host keys are stored in their normal locations within the container's /etc
directory. But as changes are made, they are periodically copied into the /creds
volume for backup. When the container starts up, any backup files contained in the /creds
volume will be imported back into the container's /etc
directory, making it easy to move the container to a different host transparently.
Note: It may take up to 10 seconds after a change is made to the user database for the change to propagate to the /creds
volume. Do not shut down the container until this process completes or you may lose your change. You can run docker logs
to see when credentials have been exported to the volume.
You can export a fresh user database and set of SSH keys without starting the SFTP server using this command:
docker run --rm -v /host/path/to/credentials:/creds \
rhasselbaum/scrappy-sftp exportcreds
The /host/path/to/credentials
must be empty for this command to work correctly. Otherwise, the container will attempt to import what's there and you will end up with identical files in the export.
The ownership of the SFTP files and directories in /sftp-root
is based on user and group IDs stored in the user database, so the contents of the /sftp-root
and /creds
volumes must be kept in sync. Also, since the /creds
directory contains sensitive data such as password hashes, it is recommended that only root have access to it:
chmod 700 /host/path/to/credentials
We now have a working container that supports backups. Cool! Now let's look at transfer logs.
You can use the standard docker logs
command to view user activity including file transfers. For example, this command will show file transfers in real-time for a container called sftp
:
docker logs -f sftp
Scrappy SFTP can also send transfer logs via email using the built-in SSMTP mail forwarding agent. To get this working, you need to create an ssmtp.conf
file containing your mail server connection details and account credentials. The man page describes the file syntax in detail and the Arch Linux wiki has an example for Gmail.
Once you've got the file created, you can enable email in the container with a couple of additional options in docker run
.
docker run -d --name sftp -p 2022:22 \
-v /host/path/to/ssmtp.conf:/etc/ssmtp/ssmtp.conf \
-e [email protected] \
-e 'MAIL_FROM=SFTP Daemon <[email protected]>' \
rhasselbaum/scrappy-sftp
The MAIL_TO
variable lets you to specify the email recipient for transfer logs, and by doing so, enables email functionality in the container. A MAIL_FROM
value can be given to set the FROM header on generated emails. It can take a simple email address or the more expressive "Display Name <[email protected]>" form. Note that some mail systems (e.g. Gmail) will use the authenticated user's address regardless of what appears in the FROM header, so this variable is optional.
A transfer log is sent out after each session closes. You can use docker logs
to troubleshoot email problems. The output of SSMTP's sendmail
command is included there.
The following command creates a container that combines all of the features above:
docker run -d --name sftp -p 2022:22 \
-v /host/path/to/sftp-root:/sftp-root \
-v /host/path/to/credentials:/creds \
-v /host/path/to/ssmtp.conf:/etc/ssmtp/ssmtp.conf \
-e [email protected] \
-e 'MAIL_FROM=SFTP Daemon <[email protected]>' \
rhasselbaum/scrappy-sftp
These options support external storage for files and the user database, as well as emailed transfer logs.
Scrappy SFTP works well for a population of a few dozen to a couple hundred semi-trusted users. It partitions user data directories with chroot and sets appropriate file permissions, but it is not well-suited for completely untrusted users or high traffic volumes. Here are some limitations I'd like to address in the future:
- Per user limits on number of sessions, transfers, and storage capacity
- Retry mechanism for failed email attempts
Want to help? Pull requests welcome!