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.