Monochrome vector graphic of a cat's nose and furry ears. The ears look similar to those of a desert fox.
Tags: Selfhosting , Security
Last change:
Written by: traumweh

SFTP, but with jailtime #

Let’s talk about what problem I tried to solve. Essentially, I wanted to give my devices access to a fairly large set of files. But I didn’t want to use Syncthing, partially because I seem to attract sync-conflicts, but more importantly because some of the devices don’t even have enough free storage space. Of course some might ask: why don’t you just host a Nextcloud instance? Simple: I neither like Nextcloud, nor do I need any of its Groupware, Sharing, and other magic features. I just need a place to put my files to retrieve them at a later point.

So I did the only logical thing: I hosted a Samba server. I looked at SFTP. The server is already accessible via its OpenSSH server using authorized FIDO-based SSH keys, limited to my Wireguard VPN. So I created a nas user and mounted some HDD-based storage to its home directory and done.

Some more constraints #

Except… I neither need, nor want a full secure-shell for this user. It should only be able to access the files on the designated drive; i.e. no access (not even read-access) to /tmp, /etc or similar. On top of that, I was wandering if it was possible to prevent access to the user’s authorized_keys file. That is, can I prevent someone with access to the nas user from changing who can access it?

Turns out: yes! And it is surprisingly simple, too. An OpenSSH server by default hosts an internal SFTP server, so no need to anything in this regard. To verify that your’s has SFTP enabled too, simply verify that your /etc/ssh/sshd_config or a file in /etc/ssh/sshd_config.d/ contains the line:

Subsystem sftp /usr/libexec/openssh/sftp-server

If it doesn’t, you can either add it to your /etc/ssh/sshd_config, or add it to the file that we’ll craete in a moment. Read on to understand the difference, and what benefits the latter has.

To start, let’s create a new subconfiguration /etc/ssh/sshd_config.d/20-sftp.conf and, for those who don’t know, talk about what this file-path and -name even are:

Maybe you stumbled across something.d directories are that are littered throughout /etc and elsewhere in Linux before and wandered what they were. Think of them as extensions to the regular configuration files like /etc/ssh/sshd_config. The regular files are often provided by the distributions package manager and can get overwritten by package updates. Files in something.d on the other hand, are (normally) garanteed to persist updates. On top of that, if they specify options which are already specified in the regular file something, then the something.d-file overrides the values in the regular one.

You might have notices, that we prefixed the file with 20-; this is a priority. Most software loads the files in something.d in the order specified by these priorities (low to high), excepting values from 0 (or 1) to 100. It depends on the application whether or not a priority can be specified more than once. Everything after the seperating hyphen (-) is (normally) up to you; use something self-documenting.

To the jail you go! #

I stumbled across an OpenSSH option to redefine, what a connected user sees as root (to be read as /). The ChrootDirectory option does just that. It changes the root directory a user sees (let’s call that directory the “chroot”). I also found another option, ForceCommand, which forces a user to execute a specific command, including internal commands provides by the OpenSSH server. But just these options alone don’t really help us. After all, we don’t want all users to follow these rules.

Its like the OpenSSH developers had thought about this. Because there is also the Match directive, which allows one to apply a set of options only to a set of connections. So let’s look at a minimal example and go over it:

Match user nas
 ChrootDirectory %h
 ForceCommand internal-sftp

In the first line, we apply the configuration options only to the connections of the user nas. We could also match users of a specific group, which could e.g. be used to allow multiple users to access the drive and use either file-ownership, permissions or other configuration options to further limit specific users.

The %h in the second line specifies the user’s home directory as the chroot. One could also use %u to build a path like /path/to/drive/%u based on the user name (obviosuly not useful with the specified Match line above).

Lastly, we can limit the user to only use SFTP using OpenSSH’s internal SFTP command. This results in connection resets for all other types of connections like regular ssh or scp connections.

We can also specify many more options to override rules not matches to a specific user. To give some examples of this, the following snippet prevents users from accessing graphical X11-sessions or tunnel TCP ports. It also requires them to use asymmetric keys instead of passwords for authentication:

Match user nas
 ChrootDirectory %h
 ForceCommand internal-sftp
 X11Forwarding no
 AllowTcpForwarding no
 PasswordAuthentication no
 PubkeyAuthentication yes

Some happy little caveats #

The ChrootDirectory option requires that the chroot (but not its contents) must be owned by the root user. This means, that it should contain at least one more directory writable by the user(s), or otherwise this whole endeavour would have been useless. In my case, I have the two directories volatile and persistent, the latter being included in daily backups.

A more welcome caveat is the way, public keys are authorized. Because the home directory gets overwritten to be the /, the user’s regular ~/.ssh/authorized_keys path doesn’t work anymore. We could of course just change the chroot to a different directory, but either way, the authorized_keys file wouldn’t be accessible anymore.

But this is fine. After all, we don’t want connected users to change who has access, anyway. Instead, we can simply add the following line to the top of our subconfiguration:

AuthorizedKeysFile /etc/ssh/authorized_keys/%u .ssh/authorized_keys

Match user nas
 ChrootDirectory %h
 ForceCommand internal-sftp
 X11Forwarding no
 AllowTcpForwarding no
 PasswordAuthentication no
 PubkeyAuthentication yes

Now we can simply use the file /etc/ssh/authorized_keys/nas to authorize ssh public keys. Its syntax is equivalent to that of regular authorized_keys files.

Conclusion #

While this seems like an unnecessarily comlex way to achieve a simple file share, it is still a lot less complex, and way simpler to update than hosting a Nextcloud instance or dealing with sync-conflicts. And to my very specific use-case, this is a good enough solution.