Over at Denny, I discovered that OpenSSH, since version 4.8, supports chrooted SFTP operation. This is, of course, a feature that many of us have been waiting for, so I immediately gave it a try, using a somewhat adventurous manually compiled OpenSSH on CentOS 5. (Update, February 27, 2009: See Packaging OpenSSH on CentOS for a more coherent installation method.) I also had a little help from the Debian Administration Blog.
In order to enable chrooted SFTP for some users, we’ll first create a separate group for users that will get the chroot treatment. I named this group chrooted, for no obvious reason. This group will be assigned as a supplementary group for chroot users.
The common application for this will be virtual WWW hosting, so I started with the assumption that a website called www.example.com will be accessed by SFTP. The directory /vhost/www.example.com will therefore serve as the user’s home directory. In order to make chrooting work along with key-based authentication, I found that it neccessary to make the user name and the name of his home directory identical, so the user was named www.example.com as well (login shell: /bin/false), along with a similar user private group www.example.com. This looks as if it may have a tendency to get awkward, but it really only is a first test. I’ll have to invest a bit more thought before this goes into production.
The directory /vhost/www.example.com was created and populated like this:
drwxr-xr-x 5 root root 4096 Apr 5 22:01 .
drwxr-xr-x 3 root root 4096 Apr 5 21:22 ..
drwxrwxr-x 2 www.example.com www.example.com 4096 Apr 6 08:45 htdocs
drwxr-xr-x 2 root root 4096 Apr 5 21:22 logs
drwxr-xr-x 2 www.example.com www.example.com 4096 Apr 5 22:02 .ssh
With the user created and his directory populated, we’ll now edit sshd_config as follows:
#Replace the OpenSSH sftp-server backend with its internal SFTP engine:
#Subsystem sftp /opt/openssh/libexec/sftp-server
Subsystem sftp internal-sftp
# Configure special treatment of members of the group chrooted:
Match group chrooted
# chroot members into this directory
# %u gets substituted with the user name:
ChrootDirectory /vhost/%u
X11Forwarding no
AllowTcpForwarding no
# Force the internal SFTP engine upon them:
ForceCommand internal-sftp
I actually had a quite hard time figuring out the proper constellation of user name, user home directory and ChrootDirectory. ChrootDirectory applies after the user has been authenticated. Before that, his home directory from /etc/passwd still applies. In order to enable the user to maintain his SSH key and to enable sshd to find the key, both environments must be congruent. However, the chroot destination must not be owned by the user for security reasons; the user’s home directory therefore belongs to root. Tricky, isn’t it? I must admit, though, that this would have been a lot more intuitive if I hadn’t strayed away from /home on the very first test. Doh!
Here’s a sample session with the user “www.example.com”, authenticated by public key:
$ sftp www.example.com@192.168.1.24
Connecting to 192.168.1.24...
sftp> ls -la
drwxr-xr-x 5 0 0 4096 Apr 5 20:01 .
drwxr-xr-x 5 0 0 4096 Apr 5 20:01 ..
drwxr-xr-x 2 59984 59984 4096 Apr 5 20:02 .ssh
drwxrwxr-x 2 59984 59984 4096 Apr 6 07:32 htdocs
drwxr-xr-x 2 0 0 4096 Apr 5 19:22 logs
sftp> pwd
Remote working directory: /
sftp> cd ..
sftp> ls -la
drwxr-xr-x 5 0 0 4096 Apr 5 20:01 .
drwxr-xr-x 5 0 0 4096 Apr 5 20:01 ..
drwxr-xr-x 2 59984 59984 4096 Apr 5 20:02 .ssh
drwxrwxr-x 2 59984 59984 4096 Apr 6 07:32 htdocs
drwxr-xr-x 2 0 0 4096 Apr 5 19:22 logs
sftp> pwd
Remote working directory: /
sftp> ls -la .ssh
drwxr-xr-x 2 59984 59984 4096 Apr 5 20:02 .
drwxr-xr-x 5 0 0 4096 Apr 5 20:01 ..
-r-------- 1 59984 59984 601 Apr 5 20:02 authorized_keys
sftp> bye
Pay attention to the UIDs, 0 and 59984: The SFTP subsystem, running under chroot, doesn't have access to /etc/passwd from the user's environment.
I am convinced that this is the most important update to OpenSSH for at least the past five years. This has the opportunity to entirely eradicate authenticated FTP from the internet, just as it has already happened with Telnet.
Thanks a lot to the OpenSSH developers for making this happen!