The traditional way to restrict SFTP access is to use a chroot (with sshd_config's ChrootDirectory keyword). The main issue with this is it needs to be set up by root, however it also suffers from a lack of flexibility, as matching in sshd_config can't be done per key. It's nicer to be able to configure things as a user, where different keys can access different things, rather than root, particularly on shared systems or where the SSH server configuration isn't under the user's full control for some reason.
My first use case for this is using SFTP on my phone, with a passwordless (for convenience) key, but without giving it access to everything on the remote system. Obviously it's a trade-off like most things in security, but the idea is this somewhat limits the files which my phone can see and touch.
First, simply generate a dedicated SSH key as normal:
ssh-keygen -f ~/.ssh/id_ed25519_sftp-only -t ed25519 -N ""
Configure it with no password, then get the public key line you'd normally add to ~/.ssh/authorized_keys
.
With some edits to the normal line you'd add, it is easy to limit what the SSH key can do, for example we can make an SFTP only key:
restrict,command="/lib/sftp-server" ssh-ed25519....
(Assuming a recent system with a merged /usr directory, adjust the path as needed.)
However, this key will through SFTP have access to everything the user has access to on the remote system.
There are various tools which allow restricting or otherwise adjusting what commands can do, using user namespaces underneath. Some examples are Boxxy, Try and bubblewrap.
Bubblewrap is the cloest to what we need and it can be used to implement most of this, however it can't mark the root directory as read-only after it has mounted in the directories you want to be able to access. This isn't a huge problem, but has the usability nightmare that someone may upload something to a tmpfs directory that won't actually store the resulting file anywhere persistent. I don't want to lose files!
Instead I implemented most of what Bubblewrap does with the unshare
command.
The rough steps are fairly simple:
- Create a user namespace and mount namespace, with root capabilities
- Bind mount the relevant system files
- Bind mount the user's files
- Create a user namespace inside that, mapped to the user's normal user ID
I have packaged this up as a single script, for ease of installation. Rather than putting the sftp-server command as the forced command for the key, you put the wrapper script, which sets up the enviornment and then runs the SFTP server.
To use this, copy sftp-ssh-limit
to your ~/bin directory:
mkdir -p ~/bin
curl -Lo ~/bin/sftp-ssh-limit https://raw.github.com/dgl/sftp-ssh-limit/refs/heads/main/sftp-ssh-limit
chmod 755 ~/bin/sftp-ssh-limit
Then, like above set up an SSH key in ~/.ssh/authorized_keys
with the
following options (edit the line to add these before the key) where
$HOME/Media
, etc. are the directories to allow access to:
restrict,command="$HOME/bin/sftp-ssh-limit $HOME/Media $HOME/Pictures" ssh-ed25519...rest-of-key-all-on-one-line
The code is available at https://github.com/dgl/sftp-ssh-limit
Is this secure?
Yes; I think overall this can be used to improve security. This is using the technology behind containers without creating a full container where the system differs, therefore keeping the host updated keeps this up-to-date.
The sftp server itself configures a seccomp sandbox, so while the code doesn't do this, it does benefit from the existing security SSH has. With some changes this wrapper can also be used for other non SFTP access, but that should be carefully considered, SFTP expects to run in limited security contexts like this and therefore implements some of the security boundary itself.
rsync is a pretty obvious extension and I believe that should be quite easy, although it needs some thought.
Unprivileged user namespaces
This makes use of unprivileged user namespaces in order to allow the user to create a user namespace, that restricts the SFTP connection. There are some downsides of user namespaces as they open up more kernel attack surface. However the fact they can add security without needing more components running as root shouldn't be forgotten.
Ubuntu has gone as far as disabling them in recent versions.
This is a little annoying as it breaks scripts using unshare and there isn't an easy way to just enable that. I turn them back on my systems.
Misconfiguration
Currently this won't complain if you directly give write access to /home/user
,
but if you do, the sftp client can rewrite files in ~/.ssh
(or add something to
shell startup files and so on). Do not map the home directory itself, or
~/.config
, ~/bin
or other such directories. (Read-only access to $HOME
should be slightly more safe, part of this is how much you trust yourself and
what your threat model is.)
Alternatives
When using a tool like this it is always worth considering alternatives.
Not using unshare
As mentioned, bubblewrap can be used to implement this, the main downside is it cannot currently make the directories read-only.
Landlock
Using landlock could avoid the need for user namespaces, therefore removing the security concerns around those. One downside of landlock is it forbids access rather than giving a different view of the filesystem. This means some SFTP tools will break as they can't give a directory listing of the home directory, whereas using bind mounts means we can construct a view of the home directory that contains only what we want.
On OpenBSD it should be possible to use unveil() for some of this too. I've not looked at that. Those system calls don't work across process boundaries though, so it would likely need an SSH patch to make it work (note that SSH already does use pledge, just not unveil), rather than being possible to implement with an external wrapper.
Currently I'm using the user namespace approach because it works on older systems too and has some upsides.
Run ssh inside a container and only mount in the directories needed
You can probably trust the container implementation more, but it also introduces the need for a container image and managing more moving parts, such as another SSH daemon running somewhere.
Configure SSH to do this...
As mentioned at the start the downside of that is you need root to set up
ChrootDirectory
.
However one interesting thing about sftp is the protocol is spoken over SSH usually, but it isn't tied to SSH. It would be possible to easily plug in another implementation of sftp-server, maybe one which implements restrictions itself.
Standard disclaimer: This if configured correctly should add to your security but is only effective as part of a balanced security diet.