summaryrefslogtreecommitdiff
path: root/blog/2005/KnastHorst.md
blob: af9e57146113ac0a089bc66dfec31caadbf34208 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
<!--
.. date: 2005/08/05 03:03
.. title: 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: <cite>"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."</cite> 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](http://lists.freebsd.org/pipermail/freebsd-bugs/2004-November/010320.html)) Mit so einem Setup kann man nicht prahlen gehen :(

Neulich entdeckte ich jedoch [die
Todoliste fuer die 6.0er Release](http://www.freebsd.org/releases/6.0R/todo.html), in der angedeutet wird, dass <cite>"Nullfs (and perhaps
other filesystems) use an absurdly small hash size that causes significant performance
penalties." </cite> 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 &amp;&amp; cd /usr/jails/newjail &amp;&amp; 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>&amp;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.