# Today I tried setting up an lxc container on my Debian, that I can start # without root access. Here are the problems I had to solve... # First some config as root. # I want to use the lxc bridge: sudo -s MY_USER_NAME=neels MY_USER_ID=1000 cat >> /etc/lxc/default.conf <<END lxc.network.type = veth lxc.network.0.link = lxcbr0 lxc.network.0.flags = up lxc.network.0.hwaddr = 00:16:3e:xx:xx:xx END # (Got this from the archlinux wiki, but had to change lxc.net.* to lxc.network.*) # We also need to define IP addresses for lxcbr0. # You certainly already have a /etc/default/lxc-net file, can just edit that. cat >> /etc/default/lxc-net <<END USE_LXC_BRIDGE="true" LXC_BRIDGE="lxcbr0" LXC_ADDR="10.23.42.1" LXC_NETMASK="255.255.255.0" LXC_NETWORK="10.23.42.0/24" LXC_DHCP_RANGE="10.23.42.2,10.23.42.254" LXC_DHCP_MAX="253" END # I need to allow my user to use the lxc net, 2 is the amount of interfaces # allowed: echo $MY_USER_NAME veth lxcbr0 2 > /etc/lxc/lxc-usernet # To manage subusers (IIUC), there needs to be some quota allowance: cat > /etc/systemd/system/user-${MY_USER_ID}.slice <<END [Unit] Description=$MY_USER_ID user.slice [Slice] MemoryAccounting=true MemoryLimit=3000M END systemctl daemon-reload # And we have to enable this feature here: echo "kernel.unprivileged_userns_clone=1" > /etc/sysctl.d/80-lxc-userns.conf sysctl --system # It appears that some of these changes need an apparmor restart. systemctl restart apparmor # The remaining config is as your own user, exit the sudo. exit # And actually the user.slice stuff may need a re-login of your current user. # So log out completely and log back in now. # There needs to be config specific to the user in mkdir -p ~/.config/lxc # Normal users cannot write to /var/lib/, so configure a custom container # storage: MY_LXC_PATH=$HOME/lxc mkdir -p $MY_LXC_PATH echo lxc.lxcpath = $MY_LXC_PATH > ~/.config/lxc/lxc.conf # Make sure there is an /etc/subuid and /etc/subgid for your user, for me it # was already configured like this: cat /etc/subuid my_user_name:100000:65536 cat /etc/subgid my_user_name:100000:65536 # Tell lxc to use those: cat > ~/.config/lxc/default.conf <<END lxc.include = /etc/lxc/default.conf lxc.id_map = u 0 100000 65536 lxc.id_map = g 0 100000 65536 END # Finally we're in a position to create an lxc-container: lxc-create -n foo -t download -- -d debian -r buster -a amd64 lxc-start -n foo # If that worked, then this should give you a root prompt, where you can setup # a user and password (the password also important for sudo later). lxc-attach -n foo useradd -m -s /bin/bash user passwd user exit # From this point on you can login as the new user using lxc-console: lxc-console -n foo #login: user #Password: ******* #$ # Obviously "nothing" is installed, so that's not much use yet. # Exit again: as the lxc-console tells you, close it with CTRL-a q # Even if you go in with lxc-attach, there is no network. # Why: lxcbr0 needs NAT, and NAT needs forwarding and masquerading. # You may need to re-do these steps after each reboot: sudo sh -c "echo 1 > /proc/sys/net/ipv4/ip_forward" sudo iptables -t nat -A POSTROUTING -s 10.23.42.0/24 -o eth0 -j MASQUERADE # 10.23.42.0 matches the IP range configured in /etc/default/lxc-net. # eth0 should be the network device that gets internet uplink. # Now that the network is up, I want to install a bunch of stuff. I have a list # of packages in a file. Make it available in the lxc, by copying this file # from outside: cp ~/lxc_packages $HOME/lxc/foo/rootfs/tmp/ # And let's go back in as root with lxc-attach. lxc-attach -n foo apt-get update # Wait, still not working? Then likely /etc/resolv.conf is empty, no DNS. # (This sometimes happened to me, I'm not sure why. Normally it's fine.) # The typical fallback for these situations... echo nameserver 9.9.9.9 > /etc/resolv.conf # Once forwarding and DNS work out, install. apt-get update apt-get install --no-install-recommends $(cat /tmp/lxc_packages) # Among other things, I install: # dnsutils iptables less openssh-server openssh-client sudo # So add the user to the sudo group, and make sure there is a password. gpasswd -a user sudo # Now we could leave lxc-attach for good and only attach via # lxc-console, or even via ssh, and then do all admin via sudo. exit # To login via ssh, we need to know the IP address. lxc-ls --fancy #NAME STATE AUTOSTART GROUPS IPV4 IPV6 #foo RUNNING 0 - 10.23.42.175 - ssh user@10.23.42.175 exit # However, let's make it so that this container always gets the same IP # address. lxc-stop -n foo sudo -s echo dhcp-host=foo,10.23.42.123 >> /etc/lxc/dnsmasq.conf echo LXC_DHCP_CONFILE=/etc/lxc/dnsmasq.conf >> /etc/default/lxc-net systemctl restart lxc-net exit lxc-start -n foo lxc-ls --fancy #NAME STATE AUTOSTART GROUPS IPV4 IPV6 #foo RUNNING 0 - 10.23.42.123 - # Which enables things like cat >> ~/.ssh/config <<END Host foo Hostname 10.23.42.123 User user END ssh-copy-id foo ssh foo exit # If I remove the container, it would be gone completely and for good, # so I don't. #lxc-destroy -n foo # The big advantage of unprivileged containers is that all user- and group-ids # get mangled, so your host system is pretty safe. And one big drawback is that # all user- and group-ids get mangled. So even if your normal user can create, # destroy, start and stop the container, your user can *not* write to the # container's rootfs directly; why? Because your user ID is 1000, and even if # inside the container it is also 1000, outside it gets mangled to 101000 # instead. So you will not be able to easily create some bit of file system # where the user ID in the lxc container matches your outside user id. # But if I want to conveniently share files across the file systems, I can # (besides using scp) create a group matching the gid inside the container, and # mount in a dir belonging to that group. Check that group id: ls -al ~/lxc/foo/rootfs/home/ #drwxr-xr-x 4 101000 101000 4096 user/ # It will always turn out as 101000, because 100000 is the gid mangling offset # set up above, and debian starts to assign user/group IDs starting with 1000. # Outside, on the host system, create a name for that group and add yourself: sudo groupadd -g 101000 lxcuser sudo gpasswd -a $USER lxcuser # Set sticky group permissions for that group on a shared dir: mkdir ~/lxc/share sudo chgrp lxcuser ~/lxc/share sudo chmod g+rwxs ~/lxc/share sudo setfacl -d -m group:lxcuser:rwx ~/lxc/share # Mount this into the container. echo lxc.mount.entry = ~/lxc/share share none bind,create=dir 0 0 >> ~/lxc/foo/config # Restart to take effect. lxc-stop -n foo lxc-start -n foo # Let me demonstrate. touch ~/lxc/share/hello_container ls -al ~/lxc/share #-rw-rw-r-- 1 neels lxcuser 0 hello_container lxc-console -n foo #login: user touch /share/hello_host ls -al /share #-rw-rw-r-- 1 nobody user 0 hello_container #-rw-rw-r-- 1 user user 0 hello_host rm /share/hello_container # The lxc user was allowed to remove a file created by the outside user. # CTRL-a q (exit lxc-console) ls -al ~/lxc/share #-rw-rw-r-- 1 101000 lxcuser 0 hello_host rm ~/lxc/share/hello_host # The outside user was allowed to remove a file created by the lxc user. # That's all, enjoy! # Copyright 2018 neels@hofmeyr.de # License: CreativeCommons BY-NC-SA