Menu

Unprivileged LXC container on Oracle Linux 7

23 octobre 2018 - Informatique
Unprivileged LXC container on Oracle Linux 7

For my torrents needs, I’ve been using the software Transmission in an Oracle Linux 7 privileged LXC container for a while. Now, I hear you say: « Why Oracle Linux? Just use CentOS. » Well, I would if they provided, like Oracle, security infos for updates (« yum list-security »). So, getting back to my « torrenting », since this service is exposed on Internet and privileged containers are not a security panacea, I’ve been searching recently for a better setup. Googling around the net, I’ve found many articles about running unprivileged LXC containers on Ubuntu or Debian Linux but none on Red Hat-like distributions, only articles about privileged containers. The reason for that may be the kernel version on RHEL 7. Indeed, 3.10 is too old for running those (see pre-requirements section of this article). Nonetheless, since I run Oracle Linux and its 4.1.12 kernel on one of my physical hosts, I told myself I should give it a try.

I’ve hit the wall pretty fast with an OL 7 container with some nasty errors on container startup.

Failed to read AF_UNIX datagram queue length, ignoring: No such file or directory
Failed to install release agent, ignoring: No such file or directory
Failed to create root cgroup hierarchy: No such file or directory
Failed to allocate manager object: No such file or directory
[!!!!!!] Failed to allocate manager object, freezing.

It seemed to be related to Systemd and I couldn’t find a fix after searching for a while… So I thought I should try with an Oracle Linux 6 container instead which doesn’t use Systemd and since that OS will be supported until beginning of 2021, it seemed like a good workaround solution for the time being.

So, let’s do this. Please note that I use the 1.1.5 lxc package from Oracle, not the 1.0.X one from EPEL. The main difference between the two versions is that EPEL’s one makes /var/lib/lxc the root of containers while on the Oracle’s one it’s /container.

First, we add these lines to /etc/lxc/default.conf which tells LXC to map uid/gid 0 in container to uid/gid 100000 on the host and go on until uid/gid 65536 of the container.

lxc.id_map = u 0 100000 65536
lxc.id_map = g 0 100000 65536

Next, let’s create the container which will be named shady. Usual provided templates cannot be used for unprivileged containers so we have to choose the download option.

[root@oldy ~]# lxc-create -n shady -t download
...
Distribution: oracle
Release: 6
Architecture: amd64
...

Now that the container is created, let’s add some parameters to end of the file /container/shady/config .

...
lxc.network.ipv4 = 10.193.173.4/24
lxc.network.ipv4.gateway = 10.193.173.1
lxc.network.name = eth0
lxc.network.mtu = 1500
lxc.autodev = 1
lxc.kmsg = 0
lxc.cap.drop = sys_resource net_admin net_raw
lxc.mount = /container/shady/fstab
lxc.start.auto = 1

10.193.173.1 is the ip of lxcbr0. Please adjust accordingly. Also, I’ve added one « lxc.mount » parameter since our container will have to bind mount one directory from the physical host in which stuff downloaded with Transmission will go. Here is /container/shady/fstab :

/srv/data /container/shady/rootfs/srv/data none rw,bind 0 0

Before starting the container, let’s look at the permissions inside it:

[root@oldy ~]# ls -l /container/shady/rootfs/
total 82
dr-xr-xr-x. 2 100000 100000 84 Oct 4 07:52 bin
dr-xr-xr-x. 3 100000 100000 3 Oct 4 07:52 boot
drwxr-xr-x. 4 100000 100000 6 Oct 4 07:52 dev
...

Good, everything owned by root inside the container is seen as uid and gid 100000 on the physical host.

Then, we create the directory referenced by the container’s fstab and start it.

[root@oldy ~]# mkdir -p /container/shady/rootfs/srv/data
[root@oldy ~]# chown 100000:100000 /container/shady/rootfs/srv/data
[root@oldy ~]# lxc-start -n shady

Now, let’s go into the container and install Transmission. We could take the EPEL version but the one available for RHEL 6 and derivatives is a bit old so we’ll install from another repo. That repo is linked on the official site of Transmission as the method to install the software on CentOS so I think we can trust it.

[root@oldy ~]# lxc-attach -n shady
[root@shady ~]# yum install http://geekery.altervista.org/geekery/el6/x86_64/geekery-release-6-1.noarch.rpm
...
[root@shady ~]# yum install transmission
...
[root@shady ~]# id -a transmission
uid=499(transmission) gid=499(transmission) groups=499(transmission)

So, when Transmission will write stuff in /srv/data, it will be seen as uid/gid 100499 on the physical host. We could create a transmission user on the host with that uid just to remind us which application created the files.

[root@oldy ~]# groupadd -g 100499 transmission
[root@oldy ~]# useradd -u 100499 -g 100499 transmission

Here’s some of my Transmission configuration (/var/lib/transmission/settings.json):

"dht-enabled": false,
"download-dir": "/srv/data",
...
"message-level": 1
...
"pex-enabled": false
...
"rpc-authentication-required": true,
"rpc-bind-address": "10.193.173.4"
...
"rpc-password": "mypassword",
"rpc-port": 9091
...
"rpc-username": "myuser",
...
"rpc-whitelist-enabled": false
...
"umask": 2,
...

The value « 2 » for « umask » means mode 664 for created files and 775 for created directories. I have to set it like that to be friendly with users who will access the files from the host and who will be in the transmission group.

We can now activate the service:

[root@shady ~]# chkconfig transmission-daemon on
[root@shady ~]# service transmission-daemon start

The only thing remaining is to add some NAT rules on the physical host to reach the container from outside. I use iptables instead of Firewalld so I’ll have to modify /etc/sysconfig/iptables.

-A PREROUTING -i eth0 -p tcp --dport 9091 -j DNAT --to-destination 10.193.173.4:9091
-A PREROUTING -i eth0 -p tcp --dport 51413 -j DNAT --to-destination 10.193.173.4:51413
-A PREROUTING -i eth0 -p udp --dport 51413 -j DNAT --to-destination 10.193.173.4:51413

It is important to also add port forwarding for port 51413 in our home router (towards oldy.local in this case) in order to be in active mode for maximizing connections to peers.

Finally, after reloading iptables rules, let’s verify that it’s working:

So, here we have it: Even if an attacker could break into the container under the transmission user because of a security bug in the software and elevate privileges to root and, then, break out of the container into the host, he would still have no privilege whatsoever.

Please note that some things won’t work inside our unprivileged container. For example:

[root@shady ~]# df -h
df: cannot read table of mounted file systems
[root@shady ~]#

Also, trying to change permissions on files with uid/gid under 100000 will fail, that is files created from outside the container:

[root@shady ~]# chown root:root /srv/data/some_file_created_from_the_physical_host
chown: changing ownership of `/srv/data/some_file_created_from_the_physical_host': Operation not permitted
[root@shady ~]#

That’s it for today. I hope my english wasn’t too bad for this first article written in Shakespeare’s language.

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *