Let's briefly touch on security with LXC. Starting with LXC version 1.0, support for unprivileged containers was introduced, allowing for unprivileged users to run containers. The main security concern running LXC containers as root is that UID 0 inside the container is the same as UID 0 on the host; thus, breaking out of a container will grant you root privileges on the server.
In Chapter 1, Introduction to Linux Containers, we talked in detail about the user namespace and how it allows for a process inside the user namespace to have a different user and group ID than that of the default namespace. In the context of LXC, this allows for a process to run as root inside the container, while having the unprivileged ID on the host. To take advantage of this, we can create a mapping per container that will use a defined set of UIDs and GIDs between the host and the LXC container.
Let's look at an example of setting up and running a LXC container as an unprivileged user.
Start by updating your Ubuntu system and installing LXC:
root@ubuntu:~# apt-get update&& apt-get upgrade root@ubuntu:~# reboot root@ubuntu:~# apt-get install lxc
Next, create a new user and assign it a password:
root@ubuntu:~# useradd -s /bin/bash -c 'LXC user' -m lxc_user root@ubuntu:~# passwd lxc_user Enter new UNIX password: Retype new UNIX password: passwd: password updated successfully root@ubuntu:~#
Make a note of what the range of UIDs and GIDs on the system is for the new user we created:
root@ubuntu:~# cat /etc/sub{gid,uid} | grep lxc_user lxc_user:231072:65536 lxc_user:231072:65536 root@ubuntu:~#
Note the name of the Linux bridge that was created:
root@ubuntu:~# brctl show bridge name bridge id STP enabled interfaces lxcbr0 8000.000000000000 no root@ubuntu:~#
Specify how many virtual interfaces can be added to the bridge for a user, in this example, 50
:
root@ubuntu:~# cat /etc/lxc/lxc-usernet # USERNAME TYPE BRIDGE COUNT lxc_user veth lxcbr0 50 root@ubuntu:~#
Next, as the lxc_user
, create the directory structure and config files as follows:
root@ubuntu:~# su - lxc_user lxc_user@ubuntu:~$ pwd /home/lxc_user lxc_user@ubuntu:~$ mkdir -p .config/lxc lxc_user@ubuntu:~$ cp /etc/lxc/default.conf .config/lxc/ lxc_user@ubuntu:~$ cat .config/lxc/default.conf lxc.network.type = veth lxc.network.link = lxcbr0 lxc.network.flags = up lxc.network.hwaddr = 00:16:3e:xx:xx:xx lxc.id_map = u 0 231072 65536 lxc.id_map = g 0 231072 65536 lxc_user@ubuntu:~$
The preceding id_map
options will map the range of virtual IDs for the lxc_user
.
We can now create the container as usual:
lxc_user@ubuntu:~$ lxc-create --name user_lxc --template download ... Distribution: ubuntu Release: xenial Architecture: amd64 Downloading the image index Downloading the rootfs Downloading the metadata The image cache is now ready Unpacking the rootfs ... lxc_user@ubuntu:~$
The container is in the stopped state; to start it, run the following command:
lxc_user@ubuntu:~$ lxc-ls -f NAME STATE AUTOSTART GROUPS IPV4 IPV6 user_lxc STOPPED 0 - - - lxc_user@ubuntu:~$ lxc-start --name user_lxc lxc_user@ubuntu:~$ lxc-ls -f NAME STATE AUTOSTART GROUPS IPV4 IPV6 user_lxc RUNNING 0 - - - lxc_user@ubuntu:~$
Notice that the processes in the container are owned by the lxc_user
instead of root:
lxc_user@ubuntu:~$ ps axfwwu lxc_user 4291 0.0 0.0 24448 1584 ? Ss 19:13 0:00 /usr/lib/x86_64-linux-gnu/lxc/lxc-monitord /home/lxc_user/.local/share/lxc 5 lxc_user 4293 0.0 0.0 32892 3148 ? Ss 19:13 0:00 [lxc monitor] /home/lxc_user/.local/share/lxc user_lxc 231072 4304 0.5 0.0 37316 5356 ? Ss 19:13 0:00 \_ /sbin/init 231072 4446 0.1 0.0 35276 4056 ? Ss 19:13 0:00 \_ /lib/systemd/systemd-journald 231176 4500 0.0 0.0 182664 3084 ? Ssl 19:13 0:00 \_ /usr/sbin/rsyslogd -n 231072 4502 0.0 0.0 28980 3044 ? Ss 19:13 0:00 \_ /usr/sbin/cron -f 231072 4521 0.0 0.0 4508 1764 ? S 19:13 0:00 \_ /bin/sh /etc/init.d/ondemand background 231072 4531 0.0 0.0 7288 816 ? S 19:13 0:00 | \_ sleep 60 231072 4568 0.0 0.0 15996 856 ? Ss 19:13 0:00 \_ /sbin/dhclient -1 -v -pf /run/dhclient.eth0.pid -lf /var/lib/dhcp/dhclient.eth0.leases -I -df /var/lib/dhcp/dhclient6.eth0.leases eth0 231072 4591 0.0 0.0 15756 2228 pts/1 Ss+ 19:13 0:00 \_ /sbin/agetty --noclear --keep-baud pts/1 115200 38400 9600 vt220 231072 4592 0.0 0.0 15756 2232 pts/2 Ss+ 19:13 0:00 \_ /sbin/agetty --noclear --keep-baud pts/2 115200 38400 9600 vt220 231072 4593 0.0 0.0 15756 2224 pts/0 Ss+ 19:13 0:00 \_ /sbin/agetty --noclear --keep-baud pts/0 115200 38400 9600 vt220 231072 4594 0.0 0.0 15756 2228 pts/2 Ss+ 19:13 0:00 \_ /sbin/agetty --noclear --keep-baud console 115200 38400 9600 vt220 231072 4595 0.0 0.0 15756 2232 ? Ss+ 19:13 0:00 \_ /sbin/agetty --noclear --keep-baud pts/3 115200 38400 9600 vt220 lxc_user@ubuntu:~$
The root filesystem and config file location is different when running the container as a non root user:
lxc_user@ubuntu:~$ ls -la .local/share/lxc/user_lxc/ total 16 drwxrwx--- 3 231072 lxc_user 4096 Dec 15 19:13 . drwxr-xr-x 3 lxc_user lxc_user 4096 Dec 15 19:13 .. -rw-rw-r-- 1 lxc_user lxc_user 845 Dec 15 19:13 config drwxr-xr-x 21 231072 231072 4096 Dec 15 03:59 rootfs -rw-rw-r-- 1 lxc_user lxc_user 0 Dec 15 19:13 user_lxc.log lxc_user@ubuntu:~$ cat .local/share/lxc/user_lxc/config | grep -vi "#" | sed '/^$/d' lxc.include = /usr/share/lxc/config/ubuntu.common.conf lxc.include = /usr/share/lxc/config/ubuntu.userns.conf lxc.arch = x86_64 lxc.id_map = u 0 231072 65536 lxc.id_map = g 0 231072 65536 lxc.rootfs = /home/lxc_user/.local/share/lxc/user_lxc/rootfs lxc.rootfs.backend = dir lxc.utsname = user_lxc lxc.network.type = veth lxc.network.link = lxcbr0 lxc.network.flags = up lxc.network.hwaddr = 00:16:3e:a7:f2:97 lxc_user@ubuntu:~$