LXC sur Debian squeeze

Ce billet va probablement décevoir une partie de mes lecteurs, mais j’ai remplacé la FreeBSD de mon serveur dédié kimsufi par une Debian squeeze. Plusieurs facteurs m’y ont poussés:

  • J’ai moins de temps à consacrer à la maintenance du serveur. J’aurais pu me monter une infrastructure avec les excellents poudriere et pkgng, mais ça demande, plus pour très longtemps, la maintenance d’un repository.
  • J’adore FreeBSD + ZFS, mais ZFS sur un kimsufi avec 2G de ram c’est pas top.
  • Je suis plus habitué à lvm2 qu’au management des disque avec UFS.
  • Je voulais m’aguerrir à l’administration Linux.
  • Je voulais tester lxc, le sujet de ce billet.

Maintenant je regrette mon choix pour ces raisons:

  • Je déteste les services qui se lancent tout seuls à l’installation.
  • Manque de documentation et de cohérence du système.
  • Les fichiers de config sont dispersés partout, j’aimais mon fichier /etc/rc.conf qui marchait et sans magie.
  • Debian impose un layout de configuration pour certains softs, c’est la galère pour migrer une config existante.
  • La syntaxe iptables/iproute2 est bien moins simple et lisible que celle de pf.
  • backports.debian.org se permettent d’upgrader de dovecot 1.2 à dovecot 2.0 et j’ai passé 2h en speed à migrer ma config. Alors que sur FreeBSD on a un port dovecot et un port dovecot2.
  • Mauvaise intégration de lxc. Les jails font partie intégrante de FreeBSD, ce n’est pas le cas de lxc sur Debian.
  • Compliqué et buggé VS simple et stable au final :)

Mais venons en au sujet, lxc (Linux containers) est une fonctionnalité de virtualisation légère (similaire aux jails sur FreeBSD) qui permet dans mon cas de faire tourner plusieurs Debian dans ma Debian. Lxc lui même n’est qu’une interface aux cgroups qu’offrent les noyaux Linux récents, c’est la solution qui a été choisie dans Linux pour isoler les processus par groupes, c’est pour ça que l’on entend souvent que lxc va a terme remplacer vserver et openvz qui offrent les mêmes fonctionnalités mais avec un patch kernel compliqué à maintenir et avec beaucoup plus de code userland.

Je ne vais pas expliquer comment créer et lancer un container puisqu’internet fourmille d’informations à ce sujet. Par contre je vais expliquer comment j’ai mis en place certaines précautions de sécurité car ce sont des informations que je n’ai pas trouvées de manière complètes sur internet.

Installation

Pour profiter des dernières fonctionnalités de lxc (lxc.network.ipv4.gateway et CAP_SYSLOG du kernel), il faut utiliser un kernel récent (>= 2.6.37) et un lxc récent (=> 0.8). Je me suis compilé un kernel 3.2.19 avec le patch grsec, je ne vais pas détailler la manip, mais si vous avez un kimsufi je donne ma config kernel.

Pour installer un lxc récent, j’ai utilisé celui de testing (wheezy):

$ echo "deb http://debian.mirrors.ovh.net/debian/ wheezy main contrib non-free" >> /etc/apt/sources.list
$ cat << EOF >> /etc/apt/preferences
Package: *
Pin: release a=stable
Pin-Priority: 700
Package: *
Pin: release a=testing
Pin-Priority: 650
EOF
$ apt-get -t wheezy install lxc

kernel grsec

Un peu de configuration des sysctls est nécessaire pour que les containers se lancent si on utilise un kernel grsec, puisque vos containers seronts chrootés et que certaines opérations sont faites dans le chroot par lxc (ou par des applications comme le chroot). Mais n’ayez pas peur, toutes ces opérations, une fois le container démarré, seront filtrées par le système de capabilities(7) de linux.

$ cat /etc/sysctl.d/grsecurity.conf
kernel.grsecurity.chroot_deny_mount = 0
kernel.grsecurity.chroot_deny_chroot = 0
kernel.grsecurity.chroot_deny_chmod = 0
kernel.grsecurity.chroot_deny_mknod = 0
kernel.grsecurity.chroot_caps = 0
kernel.grsecurity.chroot_findtask = 0

config lxc

Je vous conseille fortement de mettre vos containers dans des volumes lvm pour éviter une éventuelle attaque par “disk full”.

Tous mes containers sont dans un bridge tun/tap (on peut aussi faire avec des interfaces dummy, mais je ne l’ai jamais fait). Voyez plutôt ce morceau de mon /etc/network/interfaces:

auto lxc0
iface lxc0 inet manual
		tunctl_user root
		up ip link set lxc0 up
		down ip link set lxc0 down

auto brlxc0
iface brlxc0 inet static
		address 192.168.42.1
		netmask 255.255.255.0
		bridge_ports lxc0
		bridge_fd 0
		bridge_maxwait 0
		bridge_stp off

Et voilà un exemple commenté de config lxc à peu près sécure:

# Je n'utilise pas les ttys pour accéder aux différents containers
# j'utilise plutôt ssh. Mais j'en ai quand même laissé un, pourquoi ? :p
lxc.tty = 1
lxc.pts = 1024
lxc.rootfs = /var/lib/lxc/ussh/rootfs
lxc.utsname = ussh
# Ici c'est important, on spécifie les devices auquel on a accès
# dans le container.
lxc.cgroup.devices.deny = a
# /dev/null and zero
lxc.cgroup.devices.allow = c 1:3 rwm
lxc.cgroup.devices.allow = c 1:5 rwm
# consoles
lxc.cgroup.devices.allow = c 5:1 rwm
lxc.cgroup.devices.allow = c 5:0 rwm
lxc.cgroup.devices.allow = c 4:0 rwm
lxc.cgroup.devices.allow = c 4:1 rwm
# /dev/{,u}random
lxc.cgroup.devices.allow = c 1:9 rwm
lxc.cgroup.devices.allow = c 1:8 rwm
lxc.cgroup.devices.allow = c 136:* rwm
lxc.cgroup.devices.allow = c 5:2 rwm
# rtc
lxc.cgroup.devices.allow = c 254:0 rwm
# mounts point
lxc.mount.entry=proc /var/lib/lxc/ussh/rootfs/proc proc nodev,noexec,nosuid 0 0
lxc.mount.entry=sysfs /var/lib/lxc/ussh/rootfs/sys sysfs defaults  0 0
lxc.network.type = veth
lxc.network.flags = up
lxc.network.link = brlxc0
lxc.network.hwaddr = 00:FF:C0:A8:2A:16
lxc.network.ipv4 = 192.168.42.22/24
# Permet de donner un nom compréhensible à l'interface vue du système hôte.
lxc.network.veth.pair = lxc-ussh
# Ceci n'est valide qu'avec lxc >= 0.8 et ça nous permet de drop la capabilitie net_admin (cf plus bas)
lxc.network.ipv4.gateway = 192.168.42.1
# drop capabilities
# man 7 capabilities
# Le but est d'en droper le maximum jusqu'à ce que le container ne fonctionne plus :)
# Je ne peut pas vous les décrire toutes, ni vous expliquer comment la sécurité du système hôte est mise à mal
# si on ne les interdit pas, mais certains noms parlent d'eux mêmes.
# net_admin interdit de changer la config réseau depuis l'intérieur du container.
# syslog permet d'interdire l'accès aux messages kernel depuis le container (dmesg)
lxc.cap.drop = audit_control audit_write fsetid ipc_lock ipc_owner lease linux_immutable mac_admin mac_override mac_admin mknod setfcap setpcap sys_admin sys_boot sys_module sys_nice sys_pacct sys_ptrace sys_rawio sys_resource sys_time sys_tty_config net_admin syslog

firewall

Maintenant il faut isoler du réseau nos containers, faire les redirection pour l’accès aux service depuis l’exterieur et filtrer ce qui sort, voilà un morceau de mes rules iptables pour mon serveur de mail (la syntaxe est celle de iptables-save).

*nat
# NAT Entrée
-A PREROUTING -i eth0 -d 178.33.42.27 -p tcp -m multiport --dports smtp,smtps,imaps,pop3s -j DNAT --to 192.168.42.25
# NAT Sortie (ce sont les FORWARD qui filtrent en amont)
-A POSTROUTING -s 192.168.42.0/24 -o eth0 -j SNAT --to-source 178.33.42.27
COMMIT
*filter
# Tous les containers ont accès  serveur DNS en sortie uniquement sur le port prévu à cet usage
-A FORWARD -i brlxc0 -s 192.168.42.0/24 -d 213.186.33.99 -p udp --dport domain -j ACCEPT
# Filtrer les communications entre deux containers
# L'accès au postgresql du container psql (192.168.42.91) par le container de mail (192.168.42.25)
-A FORWARD -i brlxc0 -s 192.168.42.25 -d 192.168.42.91 -p tcp --dport postgresql -j ACCEPT
# Mon serveur de mail contacte d'autres serveur de mails sur le port smtp
# (mail sortant)
-A FORWARD -s 192.168.42.25 -p tcp --dport smtp -j ACCEPT
COMMIT

Conclusion

J’espère que ce billet n’est pas imbuvable (en le relisant, il l’est !). Si vous voulez en savoir plus sur ma config, j’ai codé un petit script de création de mes containers Debian, mes scripts sont versionés sous forme de dépot git.