Until recently, I’ve been restricting users to SFTP (or SCP) by setting their shell to scponly. But then Debian removed scponly from their distribution. It turns out there’s another, better, way to do it using a Match block in sshd_config, and some directives that strictly limit what matching users can do.
But first, a word about SFTP:
"Compared to the SCP protocol, which allows only file transfers, the SFTP protocol allows for a range of operations on remote files – it is more like a remote file system protocol. An SFTP client's extra capabilities compared to an SCP client include resuming interrupted transfers, directory listings, and remote file removal.” — Wikipedia
Apparently the extra overhead makes SFTP a little slower than SCP for file transfers, but SCP is a power user program. Any user with a GUI will use SFTP, since the GUI will need directory listings at a minimum. And if you mount an SSHFS filesystem, SFTP is the underlying protocol.
Using the Internal SFTP Server
Traditionally, sshd’s SFTP server was implemented by a separate program called sftp-server, which was defined in sshd_config using the Subsystem directive.
Since OpenSSH 4.8 (we’re now at 6.x), sftp-server has been linked in sshd itself. This means that sshd can respond directly to SFTP requests, without needing to run another command as the user. This closes some potential security and operational gaps (like motd breaking sftp, or the need for a wrapper shell like scponly to set restrictions). It also means that sftp sessions can occur inside a chroot, without having to make any binaries available inside that jail.
So the first edit you may need to make to your sshd_config is to change the sftp Subsystem to internal-sftp:
#Subsystem sftp /usr/lib/sftp-server
Subsystem sftp internal-sftp
For the purist, this step isn’t technically necessary. You can still use the external sftp-server command and restrict users to that, but why would you want to? It’s the same thing, and internal is easier (and, I believe, safer).
Restricting Users to SFTP Only
Now you can use a Match block in sshd_config to restrict users who belong to a particular group to using only the internal-sftp server when they connect via ssh.
It’s important to note that Match blocks go at the end of sshd_config, because there is (apparently? really?) no EndMatch directive. The Match applies to all following lines until the next Match or the end of the file. So tack this on to the end of your sshd_config:
# sftponly users
Match group sftponly
You can see that this does a few things. It prevents the ssh connection from using TCP forwarding, so users can’t use your server to proxy websites or carry out forwarded attacks on other hosts. For good measure, it prevents X11 forwarding (if you have to ask). And most importantly, it forces the internal-sftp server to be the only “command” run by ssh.
ForceCommand overrides an command specified on the ssh client command line, and it also overrides any command specified in the user’s authorized_keys file. They are locked down, barring any exploits in sshd itself.
Keeping Users in a Chroot Jail
Sometimes, restricting users to SFTP is enough. But if you don’t need to allow access to the rest of the filesystem, why would you? You usually only want users to be able to access files within their home directory, so use a chroot to keep them there. Otherwise they can go wandering all over your server, looking at configuration files and /etc/passwd and /dev and /proc…
The ChrootDirectory directive :
Specifies the pathname of a directory to chroot(2) to after
authentication. All components of the pathname must be root-
owned directories that are not writable by any other user or
group. After the chroot, sshd(8) changes the working directory
to the user's home directory.
Since the pathname must be owned by root, this may prevent some admins from chrooting to the user’s home directory, which is classically (if dangerously?) owned by the user, and definitely writable by the user.
One recommendation is to create one or more directories inside of the user’s home directory to use for file transfer. This is the safest approach, as it prevents the sftponly user from creating new top-level directories and files. It makes their unix home directory a sort of demilitarized zone controlled by root, where the user can write to existing files (with permission) but cannot create new files there.
# sftponly users, chrooted
Match group sftponly
Same Match directive as before, but with ChrootDirectory added. The %u in the path expands to the user’s username. You could also use %h for the home directory, instead of /home/%u, in case home directory location varies by user.
At first I was upset that Debian had ditched scponly in 7.x (Wheezy). But using sshd_config to restrict file transfer users to internal-sftp and chroot them to their home directories is a much better and safer solution. It requires a few extra steps when creating a new file transfer user, such as:
usermod -a -G sftponly username
chown root:root /home/username
chown username /home/username/files
.. but those are easily scripted, and simple enough to perform by hand if you only add new users like this occasionally.