Jailkit chroots with SFTP and interactive SSH logins


Linux has privileged users and non-privileged users. Privileged users (like root) have a user id less than 1000 and typically have super abilities like being able to listen on low number ports (like the port 80 and 443 for web servers).

Privilege separation is a good thing. It is recommended when running websites that the website files are owned and executed as an unprivileged user. This way they can only access files that the linux file permissions allow them.

However, there are many files on Linux systems that are world readable. And if things are not setup correctly there may be files that you would not want a particular user accessing. (e.g. /etc/passwd is readable – though not editable – by a non privileged user, and you may not wish that particular user to be able to see who else has an account on the server).

One way to restrict access to files is for the user to be setup in a chroot (aka jail). Then they can only access files under a particular directory you nominate.

It is a good approach to giving access to a user, but also putting limits on files that they can see, as well as some limits on the files they can access.

Configuring a jailkit

You can install jailkit from the developers instruction. Or on a Debian distro you can run:

apt-get install jailkit

Next add a user. For this post let us assume that user is called chrootuser. Let us assume the chroot we are creating will be anchored at /var/chroot/chrootuser

# create the jail chroot directory
mkdir -p /var/chroot/chrootuser
# fill the jail with copies of the files from the main file system, as described in the /etc/jailkit/jk_init.ini config file
jk_init  -j /var/chroot/chrootuser  --configfile=/etc/jailkit/jk_init.ini  rsync sftp jk_lsh ssh ping editors extendedshell scp netutils netbasics logbasics uidbasics terminfo

Now setup this user to be jailed:

jk_jailuser -n --shell=/bin/bash --jail="/var/chroot/chrootuser" chrootuser

This will give the jailed user an interactive shell (so they can log in). And they will be able to use the programs you enabled with the jk_init command.

In /etc/passwd you will now have an entry like:

chrootuser:x:1001:1001:,,,:/var/chroot/chrootuser/./home/chrootuser:/usr/sbin/jk_chrootsh

The /./ is a marker jailkit uses to help ensure that the path specified is a chroot directory (and if you modify that, expect things not to work).

Inside the jail you’ll have another passwd file at /var/chroot/chrootuser/etc/passwd  like:

root:x:0:0:root:/root:/bin/bash
chrootuser:x:1001:1001:,,,:/home/chrootuser:/bin/bash

The jk_chrootsh shell on the main /etc/passwd will setup the chroot/jail for the user. Then hand it over to the shell specified in /var/chroot/chrootuser/etc/passwd. In our case it will be bash, an interactive shell.

More limits with a limited shell

Another common use case is to use jk_lsh, which is a limited shell. Which let’s you tightly control what commands can be run. jk_lsh is designed to not be used interactively (e.g. you would not get a prompt). You typically pass the commands in via ssh command you run. This can be handy if you only wish the user to run particular commands, e.g. triggering a particular script or running a particular command, or copying files.

Using a bind mount to add directories to the chroot

The jail/chroot currently does not have the chrootuser’s home directory in it. We could copy those files across. But it would be better to keep the home directory in sync with the home directory inside the jail. The jailkit/chroot code is smart enough to prevent symlinks working like you may wish. Instead we can do a ‘bind mount’.

# put a directory where we need it 
mkdir -p /var/chroot/chrootuser/home/chrootuser
# bind the user's home directory to that location
mount --bind /home/chrootuser /var/chroot/chrootuser/home/chrootuser 
findmnt --real /var/chroot/chrootuser/home/chrootuser 
#TARGET                                 SOURCE                       FSTYPE OPTIONS
#/var/chroot/chrootuser/home/chrootuser /dev/xvda1[/home/chrootuser] ext4   rw,relatime

The bind mount technique can be used to add other things into the chroot that otherwise exist outside the chroot/jail. For example, you could include a website directory owned by your jailed user.

SSH access

You can now ssh to the server as that user. The chrooted/jailed user can only see files under their chroot/jail directory. When the jailed user logs in they have no visibility of directories outside their chroot directory.

ssh chrootuser@localhost
chrootuser@travel:~$ ls -la /home/chrootuser/
total 20
drwxr-xr-x 2 chrootuser chrootuser 4096 Dec  1 02:12 .
drwxr-xr-x 3 root       root       4096 Dec  1 02:34 ..
-rw-r--r-- 1 chrootuser chrootuser  220 Dec  1 02:12 .bash_logout
-rw-r--r-- 1 chrootuser chrootuser 3526 Dec  1 02:12 .bashrc
-rw-r--r-- 1 chrootuser chrootuser  807 Dec  1 02:12 .profile
chrootuser@travel:~$ ls /
bin  dev  etc  home  lib  lib64  usr  var

With your ssh access you can also use commands like scp (and rsync) to copy files around.

# echo foo > foo.log
# scp foo.log chrootuser@localhost:
foo.log                                                                                                                                                                                                  100%    4     8.3KB/s   00:00    
# ssh chrootuser@localhost cat /home/chrootuser/foo.log
foo

SFTP access

You may also wish to have sftp access for this user (also restricted to the files in the jail/chroot).

You may be familiar with a setup like this in /etc/ssh/sshd_config:

Subsystem sftp internal-sftp
Match User chrootuser
  X11Forwarding no
  AllowTcpForwarding no
  ChrootDirectory /var/chroot/chrootuser
  ForceCommand internal-sftp

When you are using jailkit/chroots a couple of changes are required. First, the new/default/all-improved internal-sftp Subsystem will not work, you need to use Subsystem sftp /usr/lib/openssh/sftp-server. Secondly, you would not use a ChrootDirectory or ForceCommand (instead, you would let jk_chrootsh handle the chroot/jail).

Subsystem sftp /usr/lib/openssh/sftp-server
Match User chrootuser
  X11Forwarding no
  AllowTcpForwarding no
  #ChrootDirectory /var/sftp
  #ForceCommand internal-sftp

You can now also sftp with that user and only see the files inside your chroot.

# sftp chrootuser@localhost
chrootuser@localhost's password: 
Connected to localhost.
sftp> ls /
/bin    /dev    /etc    /home   /lib    /lib64  /usr    /var    
sftp> ls -a /home/chrootuser/
/home/chrootuser/.               /home/chrootuser/..              /home/chrootuser/.bash_history   /home/chrootuser/.bash_logout    /home/chrootuser/.bashrc         /home/chrootuser/.profile 

Troubleshooting with jk_socketd

Troubleshooting a chroot/jail can be quite a challenge. One of the issues is accessing messages triggered by the jailed user. For example, trying to find a hint about why logins are failing, or why you are not able to run the commands you expect, or see the files you expect.

In our earlier jk_init command we included logbasics which is defined as follows in /etc/jailkit/jk_init.ini

[logbasics]
comment = timezone information and log sockets
paths = /etc/localtime
need_logsocket = 1
# Solaris does not need logsocket
# but needs 
# devices = /dev/log, /dev/conslog

The ‘needs_logsocket’ lets you run the jk_socketd command which will launch a background process can intercept logging from inside the jail and place it in your main logs (outside the jail). For example:

# jk_socketd
# grep jk_ /var/log/*
/var/log/auth.log:Dec  1 02:41:44 travel jk_chrootsh[136065]: now entering jail /var/chroot/chrootuser for user chrootuser (1001) with arguments 
/var/log/auth.log:Dec  1 02:48:07 travel jk_chrootsh[136454]: now entering jail /var/chroot/chrootuser for user chrootuser (1001) with arguments -c /usr/lib/openssh/sftp-server

You now have a non-privileged user, with an interactive SSH shell, and sftp, that is restricted to accessing only files under the directory you allow.