KnastHorst

Auf meinem gute-Freunde-Shell-Server, auf dem auch diese edle Seite liegt, hatte ich bisher ein FreeBSD4.(5-11) installiert. In diesem wurden 14 sogenannte Jails gefahren. Das ist der FreeBSD-Ansatz, chroot soweit zu treiben, damit in virtuellen FreeBSD-Systemen root-Rechte vergeben zu koennen, ohne damit die Integritaet des Hostsystems zu beeintraechtigen.

Einige jails werden als shared service von $kumpel und mir betrieben, so ein mail-, ein www- und ein nameserver, andere in die Hand von Freunden gedrueckt. Da der Server wegen .. sagen wir, Rechenschwaechen .. des derzeitigen Hosters dort nicht mehr stehen bleiben kann und somit ein Umzug auf andere Hardware notwendig wird, werde ich die Gelegenheit nutzen, auf ein FreeBSD5 oder 6 zu wechseln.

Das bringt mehrere Vorteile mit sich: a) sind nuetzliche Tools zur Jailverwaltung, naemlich jls und jexec und 'kill -j' hinzugekommen, b) hat es das script /etc/rc.d/jail und macht jails laufen lassen zum Spass und c) sind da viele nette Features im Kernel, wie z.B. KSE, die das OS snappier[tm] machen sollten.

Bisher musste ich alle jails muehsam von einem selbstgehackten Scripteset kontrollieren lassen. Um nicht fuer jedes jail ein komplettes Betriebssystem auf der Platte herumliegen zu haben (kostet viel Platz), wird die Welt aufgeteilt in ein zentral gewartetes basejail (/bin /sbin /usr/bin /usr/include /usr/lib /usr/libexec /usr/sbin /usr/src /usr/share /usr/ports /usr/src) und die zweite, vom User anpassbare Haelfte, das "newjail" (kostet wenig Platz, um die 8MB). Ersteres wird nun readonly in jedes Jail (nach /usr/jails/*/basejail) gemounted. Softlinks lassen dann z.B. /usr/bin nach /basejail/usr/bin zeigen. (man jail zeigt die Schritte, die man braucht, sich ein basejail zu basteln, ein lokales cvs-repository hilft). Soweit, so gut.

Nun gab es dieses kleine Problem: fuer das simple loopback-Mounten eines Verzeichnisses hat sich FreeBSD mount_nullfs ausgedacht. Doch ein kleiner Blick in die man-page macht einem den Mut, den man braucht, seinen Mailserver darauf aufzubauen: "THIS FILE SYSTEM TYPE IS NOT YET FULLY SUPPORTED (READ: IT DOESN'T WORK) AND USING IT MAY, IN FACT, DESTROY DATA ON YOUR SYSTEM. USE AT YOUR OWN RISK. BEWARE OF DOG. SLIPPERY WHEN WET." Meine ersten Versuche vor ein paar Jahren ergaben genau dies: crashes und komische Effekte im Filesystem. Spaetere Experimente von Freunden erbrachten zwar keine Crashes mehr, dafuer aber ploetzliche hohe CPU-Load. Aus diesem Grund werkelt nun zur Zeit auf dem Server noch ein nfs-server, der fuer localhost das basejail exportiert und lauter mount_nfs, die es wieder mounten. (Dazu muss man erstmal portmap patchen) Mit so einem Setup kann man nicht prahlen gehen :(

Neulich entdeckte ich jedoch die Todoliste fuer die 6.0er Release, in der angedeutet wird, dass "Nullfs (and perhaps other filesystems) use an absurdly small hash size that causes significant performance penalties." Der Source (/usr/src/sys/fs/nullfs/null_subr.c) verriet mir auch #define NNULLNODECACHE 16. Also, wenn die zu kleinen Hashs deren einziges Problem sind... Ich habe aus der 16 eine 65536 gemacht, neuen Kernel gebaut und habe nun das basejail endlich per nullfs gemounted. (Zur Zeit laufen 12 Jails auf einer Testinstallation, die dann auf den neuen Server uebernommen wird.)

Die Features des jail-scripts aus der /etc/rc.d sind zwar grossartig, aber echt unglaublich wirklich voll total mies dokumentiert. Die jails, die das System starten soll, traegt man space-separiert in der /etc/rc.conf in jail_list="JAILNAME1 JAILNAME2..." ein. Dann macht man noch jail_enable="YES" an und beim Startup werden alle jails hochgefahren. Die Parameter dafuer traegt man in Variablen wie zum Beispiel jail_JAILNAME_ip="10.1.1.200" ein (nicht vergessen, der Netzwerkkarte auch die aliase fuer alle IPs zu geben). Punkte sind in den Variablen ungern gesehn, bei mir heisst das dann immer erdgeist_org, also spaeter auch jail_erdgeist_org_option_enable="YES".

Dann ist cool, dass jedes jail eine eigene fstab mitbekommt. Wer jail_JAILNAME_mount_enable="YES" anhat, kann beim jail Starten /etc/fstab.JAILNAME mounten lassen. Bei mir steht da naetuerlich "/usr/jails/basejail /usr/jails/JAILNAME/basejail nullfs ro 0 0". Fertig ist der Lack. Wer jail_JAILNAME_devfs_enable="YES" (immer gern genommen mit jail_JAILNAME_devfs_ruleset="devfsrules_jail", wegen der Sicherheit, wissenschon) anhat, findet auch gleich ein /dev im jail gemounted vor, aehnlich verhaelt es sich mit jail_JAILNAME_procfs_enable="YES" und jail_JAILNAME_fdescfs_enable="YES".

Das Verwalten der jails ist nun simpel, eigentlich haette das gleich vom rc.d-script mit erledigt werden koennen: Man legt sich ein /etc/jails/ oder so an, in das man die config-Bloecke fuer jeweils ein jail zusammenfasst, also z.B. /etc/jails/erdgeist_org und schreibt in seine /etc/rc.conf jail_list=ls /etc/jails/`, und included danach. /etc/jails/*`.

Beim Erzeugen eines neuen jails kopiert man aus dem "newjail" mittels mkdir /usr/jails/$JAILNAME && cd /usr/jails/newjail && find * | cpio -p -d -v /usr/jails/JAILNAME das Skelett. /etc/resolv.conf, /etc/rc.conf, /etc/passwd und /home/admin/.ssh/authorized_keys im newail gleich zu bevoelkern macht sich auch immer gut, sonst vergisst man das. (Auch wichtig fuer den sshd: nicht vergessen, dass man im Hostsystem alle Services nur auf die IP des Hostsystems binden lassen sollte, sonst kann man das im Jail nicht mehr.) Danach erzeugt man (beispielsweise aus einer template-config) das File, was nach /etc/jails/JAILNAME soll. Ausserdem legt man die /etc/fstab.JAILNAME an. Done.

Da wir /usr/ports mit dem basejail readonly gemounted haben (dadurch reicht es, einmal jede Nacht ein cvsup auf die ports im Hostsystem zu machen), muessen wir den jails sagen, dass sie die ports nicht in /usr/ports/X/Y/work sondern irgendwo anders bauen sollen, wo man schreiben darf, die distfiles koennen natuerlich auch nicht nach /usr/ports/distfiles. Das macht man in der /etc/make.conf. Bei mir steht da WRKDIRPREFIX=/var/ports und DISTDIR=/var/ports/distfiles. Das schreibt man am besten gleich ins newjail.

Diese Vorgehensweise steht eigentlich auch dem Hostsystem gut. Man kann naemlich spielen alle work-directories und distfiles an einem zentralen Punkt loeschen: rm -rf /var/ports/* statt rekursivem make distclean oder rm -rf */*/work/ in /usr/ports. Eigentlich koennte man die distfiles zwischen den jails noch in einem unionfs teilen, aber erstens liest sich die Doku zu mount_unionfs NOCH entmutigender, als die von mount_nullfs und zweitens muesste fuer den Fall, dass korrupte distfiles rumliegen, immer ein hostsystem-Admin putzen kommen.

Nun zum wirklich Betrieb der jails: Einzelne jails kann man mit sudo sh /etc/rc.d/jail start JAILNAME anstossen, die jail_list wird genommen, wenn man keinen jail-Namen angibt. Alle laufenden jails, inklusive ihrer jail-id kann man sich mit jls angucken. Nachtraeglich tasks an ein jail haengen geht mit sudo jexec jail-id cmd, wobei man da meist /bin/csh nimmt. Mit sudo jexec jail-id ps auxw kann man sich dann die laufenden Programme im jail angucken. Beim Traversieren der jail-Verzeichnisse (bei mir in /usr/jails) aus dem Hostsystem sollte man DRINGEND auf softlinks aufpassen. Gerne verpeilt werden /usr/jails/JAILNAME/home -> /usr/home, was einem Aerger mit den Homeverzeichnissen im Hostsystem einbringen kann und natuerlich /usr/jails/JAILNAME/usr/bin -> /basejail/usr/bin. Jail anhalten geht mit sudo sh /etc/rc.d/jail stop JAILNAME.

Zu guter letzt noch ein paar Fallstricke: im jail geht ping nicht. Das liegt daran, dass man keine raw sockets aufmachen darf, da man in diese natuerlich jede IP als Source IP eintragen koennte und damit das Sicherheitskonzept der jails umginge. ping ist aber sehr nuetzlich. Das script /usr/local/bin/jailping bestehend aus finger $*@HOSTSYSTEM gepaart mit einem fingerd, der read input; (ping ${input%^M} 2>&1) fuer Verbindungen aus den jails erlaubt, schafft Abhilfe. Am besten noch alias ping='/usr/local/bin/jailping'. Done.

Hostname im jail aendern verbieten mit jail_set_hostname_allow="NO" in der /etc/rc.conf. Ein Sicherheitsfeature, was gern abgeschaltet wird (auch in meinem jail-Server, wegen der Datenbanken, die es brauchen), ist sysvipc. jail_sysvipc_allow und postgresql geht.