FreeBSD Jail Infrastruktur
From RootForum Community » Wiki
Contents |
Einleitung
Jails benötigen normalerweise keine gesonderte Infrastruktur. Wenn jedoch viele Jails auf einem System parallel betrieben werden sollen, führt dies zu erheblichen Redundanzen, da ein Großteil des Jail-Innenlebens bei allen Jails identisch sein wird (eben alle Bestandteile des Basissystems, die in den einzelnen Jails benötigt werden, wie etwa die libc). Dabei spielt der so verschwendete Speicherplatz zwar bei den heutigen Festplattengrößen kaum mehr eine Rolle, aber unter Eleganz- und Effizienzgesichtspunkten ist es trotzdem häßlich, diese Redundanz zu erzeugen (man denke nur an einen Update- oder gar Upgrade-Prozess, bei dem 50 Jails einzeln angefasst werden müssten).
Dass es auch anders geht, zeigt das entsprechende Kapitel im FreeBSD-Handbuch[1]. Die dort beschriebene Vorgehensweise hat auch noch einen weiteren Vorteil: Neue Jails (z. B. für einen neuen Dienst) sind binnen weniger Minuten erstellt und in Betrieb genommen. Erst durch diese wartungsarme Infrastruktur wird es möglich, z. B. jede Webanwendung in ein separates Jail zu stecken und so die Auswirkungen eines erfolgreichen Angriffs auf eine der Anwendungen zu minimieren.Wie bereits angedeutet, lehne ich mich in diesem Artikel eng an Kapitel 15.6 des FreeBSD-Handbuchs an. Ich beschreibe hier hauptsächlich die Anpassung an das in FreeBSD_manuelle_Installation_(de) und FreeBSD_System_einrichten_(de) beschriebene Setup.
ZFS Datasets erstellen
Da unser System über einen riesigen, bis jetzt kaum genutzten Zpool verfügt, werden dort alle Daten zu den Jails abgelegt. Dazu benötigen wir drei Datasets:
- ein Dataset soll die Template-Dateien für die Erstellung neuer Jails aufnehmen
- ein Dataset soll die Jail-spezifischen Daten (veränderliche Teile wie Konfigurationsdateien,
/usr/localsowie die Nutzdaten) aufnehmen - ein Dataset soll als Mountpunkt für den gesamten Jail-Baum herhalten
Da ZFS Datasets immer in einer Hierarchie von Datasets unterhalb des Pools angelegt werden müssen, wird noch ein viertes Dataset benötigt, sozusagen das Jail-Superdataset:
zfs create pool/jail zfs create pool/jail/jro zfs create pool/jail/jrw zfs create pool/jail/template
Templates erstellen
Vorlage für den gemeinsamen Teil
Hier wird keine Vorlage im eigentlichen Sinne erstellt, sondern eine Struktur, die später in jedem Jail per nullfs-Mount[2] eingebunden wird und das Rückgrat der gesamten Infrastruktur darstellt:
mkdir /pool/jail/template/mroot cd /usr/src make installworld DESTDIR=/pool/jail/template/mroot make clean mkdir /pool/jail/template/mroot/usr/ports cpdup /usr/src /pool/jail/template/mroot/usr/src
Vorlage für den individuellen Teil
Nun wird tatsächlich eine Vorlage erstellt, die später für jedes Jail einmal dupliziert werden muss:
mkdir /pool/jail/template/skel mkdir /pool/jail/template/skel/home mkdir /pool/jail/template/skel/usr-X11R6 mkdir /pool/jail/template/skel/distfiles cd /pool/jail/template/mroot mv etc /pool/jail/template/skel/ mv usr/local /pool/jail/template/skel/usr-local mv tmp /pool/jail/template/skel/ mv var /pool/jail/template/skel/ mv root /pool/jail/template/skel/ mergemaster -t /pool/jail/template/skel/var/tmp/temproot -D /pool/jail/template/skel -i cd /pool/jail/template/skel rm -R bin boot lib libexec mnt proc rescue sbin sys usr dev
Gemeinsamen und individuellen Teil verknüpfen
Damit die Zusammenarbeit zwischen dem statischen und dem individuellen Teil später reibungslos funktioniert, werden noch eine Reihe von Symlinks benötigt:
cd /pool/jail/template/mroot mkdir local ln -s local/etc etc ln -s local/home home ln -s local/root root ln -s ../local/usr-local usr/local ln -s ../local/usr-X11R6 usr/X11R6 ln -s ../../local/distfiles usr/ports/distfiles ln -s local/tmp tmp ln -s local/var var
Jail-Vorlage konfigurieren
Zu guter letzt werden noch einige Konfigurationsdateien im Jail angepasst und ein zufälliges Passwort für root gesetzt.
cp /etc/resolv.conf /pool/jail/template/skel/etc/ cp /etc/csh.cshrc /pool/jail/template/skel/etc/ cp /etc/make.conf /pool/jail/template/skel/etc/ cp /etc/login.conf /pool/jail/template/skel/etc/ cap_mkdb /pool/jail/template/skel/etc/login.conf cat > /pool/jail/template/skel/etc/rc.conf << EOF # ----- security settings ----- syslogd_flags="-ss" clear_tmp_enable="YES" # ----- miscellaneous ----- keymap="german.iso" # ----- no sendmail inside jails ----- sendmail_enable="NONE" sendmail_submit_enable="NO" sendmail_outbound_enable="NO" sendmail_msp_queue_enable="NO" EOF pw -V /pool/jail/template/skel/etc/ usermod root -L unicode pwgen -s 24 -N 1 | pw -V /pool/jail/template/skel/etc/ usermod root -h 0 cat > /pool/jail/template/skel/root/.cshrc << EOF # $FreeBSD: src/etc/root/dot.cshrc,v 1.30.8.1 2009/04/15 03:14:26 kensmith Exp $ # # .cshrc - csh resource script, read at beginning of execution by each shell # # see also csh(1), environ(7). # # A righteous umask umask 22 set path = (/sbin /bin /usr/sbin /usr/bin /usr/games /usr/local/sbin /usr/local/bin $HOME/bin) setenv EDITOR vim setenv PAGER most setenv BLOCKSIZE K if ($?prompt) then # An interactive shell -- set some stuff up set prompt = "%{\033[1;47;34m%}%m%{\033[0m%} %{\033[36m%}%~ #%{\033[0m%} " set filec set history = 100 set savehist = 100 set mail = (/var/mail/$USER) if ( $?tcsh ) then bindkey "^W" backward-delete-word bindkey -k up history-search-backward bindkey -k down history-search-forward bindkey ^[[3~ delete-char endif endif EOF
Netzwerk-Interfaces bereitstellen
Um ein Jail vernünftig nutzen zu können, benötigt es eine IP-Adresse. Die IP eines Jails muss eindeutig sein; d. h. eine IP kann nicht zwischen verschiedenen Jails geteilt werden (man kann sich alle Jails auf einem System wie Rechner vorstellen, die alle gemeinsam am selben Backbone hängen). FreeBSD bietet wie Linux auch die Möglichkeit, Alias-IPs für die vorhandenen Netzwerk-Schnittstellen zu vergeben. Allerdings ist deren Zahl sehr stark limitiert; deshalb bevorzuge ich es, virtuelle Loopback-Interfaces zu erzeugen, die dann jeweils mit mehreren Alias-Adressen belegt werden können.
Dazu bedarf es nur der folgenden Einträge in die Datei /etc/rc.conf:
# ----- Additional Jail Network ----- cloned_interfaces="lo1 lo2 lo3 lo4 lo5 lo6 lo7 lo8" ifconfig_lo1="inet 127.0.10.1 netmask 0xffffff00" ifconfig_lo2="inet 127.0.20.1 netmask 0xffffff00" ifconfig_lo3="inet 127.0.30.1 netmask 0xffffff00" ifconfig_lo4="inet 127.0.40.1 netmask 0xffffff00" ifconfig_lo5="inet 127.0.50.1 netmask 0xffffff00" ifconfig_lo6="inet 127.0.60.1 netmask 0xffffff00" ifconfig_lo7="inet 127.0.70.1 netmask 0xffffff00" ifconfig_lo8="inet 127.0.80.1 netmask 0xffffff00" ifconfig_lo1_alias0="inet 127.0.10.10 netmask 0xffffff00" ifconfig_lo1_alias1="inet 127.0.10.11 netmask 0xffffff00" ifconfig_lo1_alias2="inet 127.0.10.12 netmask 0xffffff00" ifconfig_lo1_alias3="inet 127.0.10.13 netmask 0xffffff00" ifconfig_lo1_alias4="inet 127.0.10.14 netmask 0xffffff00" ifconfig_lo1_alias5="inet 127.0.10.15 netmask 0xffffff00" ifconfig_lo1_alias6="inet 127.0.10.16 netmask 0xffffff00" ifconfig_lo1_alias7="inet 127.0.10.17 netmask 0xffffff00" ifconfig_lo1_alias8="inet 127.0.10.18 netmask 0xffffff00" ifconfig_lo1_alias9="inet 127.0.10.19 netmask 0xffffff00" ifconfig_lo2_alias0="inet 127.0.20.10 netmask 0xffffff00" ifconfig_lo2_alias1="inet 127.0.20.11 netmask 0xffffff00" ifconfig_lo2_alias2="inet 127.0.20.12 netmask 0xffffff00" ifconfig_lo2_alias3="inet 127.0.20.13 netmask 0xffffff00" ifconfig_lo2_alias4="inet 127.0.20.14 netmask 0xffffff00" ifconfig_lo2_alias5="inet 127.0.20.15 netmask 0xffffff00" ifconfig_lo2_alias6="inet 127.0.20.16 netmask 0xffffff00" ifconfig_lo2_alias7="inet 127.0.20.17 netmask 0xffffff00"
Die Liste der Aliase ließe sich für jedes der geklonten Interfaces entsprechend fortführen; je nachdem, wie viele virtuelle Interfaces tatsächlich benötigt werden.
Hinweis: Die neu eingetragenen Interfaces werden erst beim nächsten Neustart aktiviert.
Ein Jail erstellen
Nachdem nun alle Vorarbeit geleistet ist, stellt die Erstellung eines neuen Jails keine Herausforderung mehr dar. Als Beispiel soll ein leeres Jail erstellt werden, von dem aus per masquerade auf das Internet zugegriffen werden kann. Außerdem soll das Jail Zugriff auf den zentralen Ports-Tree erhalten.
pf_enable="YES" und gateway_enable="YES" in der Datei /etc/rc.conf eingetragen sein, damit Networking für die Jails funktionieren kann!
Infrastruktur erstellen
Zunächst wird für das Jail ein ZFS Dataset benötigt; dieses soll die „beweglichen“ Daten des Jails aufnehmen:
zfs create pool/jail/jrw/beispiel-jail cpdup /pool/jail/template/skel /pool/jail/jrw/beispiel-jail
Außerdem muss ein Verzeichnis erstellt werden, in dem das Jail aufgebaut wird:
mkdir /pool/jail/jro/beispiel-jail
Jail-Struktur mounten
Damit die Jail-Infrastruktur künfitg automatisch bereit steht, gehen wir gleich den Weg über die fstab. Ergänzen Sie dort die folgenden Einträge:
/pool/jail/template/mroot /pool/jail/jro/beispiel-jail nullfs ro,late 0 0 /pool/ports /pool/jail/jro/beispiel-jail/usr/ports nullfs ro,late 0 0 /pool/jail/jrw/beispiel-jail /pool/jail/jro/beispiel-jail/local nullfs rw,late 0 0
Die Option late stellt sicher, dass diese Einträge der fstab erst gemounted werden, nachdem durch den inti-Vorgang die Einträge aus der /etc/rc.conf abgearbeitet wurden und somit die ZFS Datasets verfügbar sind. Um die Einträge ohne Neustart zu aktivieren, verwenden Sie einfach den mount-Befehl:
mount -a -l
Jail-Konfiguration bekannt machen und Jail starten
Dies geschieht wieder in der Konfigurationsdatei /etc/rc.conf. Ergänzen Sie folgende Einträge:
# ----- Jail setup ----- jail_enable="YES" jail_set_hostname_allow="NO" jail_sysvipc_allow="NO" jail_list="beispiel" jail_beispiel_hostname="beispieljail.meinedomain.local" jail_beispiel_ip="127.0.10.10" jail_beispiel_rootdir="/pool/jail/jro/beispiel-jail" jail_beispiel_devfs_enable="YES"
Hinweis: Bindestriche im Jail-Namen (hier: beispiel) funktionieren nicht; nur Buchstaben und Ziffern sind erlaubt.
Nun können Sie das Jail bereits starten:
/etc/rc.d/jail start beispiel
Mit dem Befehl jls[3] können Sie überprüfen, ob Ihr Jail gestartet wurde. Wenn Sie sich nun in das Jail begeben möchten, können Sie dies ebenfalls tun, indem Sie im Jail-Kontext eine Shell starten. Mit ps sehen Sie innerhalb des Jails viel weniger Prozesse als im Hauptsystem. Mit exit können Sie das Jail wieder verlassen:
jexec 1 tcsh ps aux exit
NAT aktivieren
Das Jail läuft und kann genutzt werden; auch der Portstree ist innerhalb des Jails vorhanden und nutzbar. Fehlt also nur noch der Netzwerk-Zugang. Eine IP-Adresse hat unser Jail schon; es muss also nur noch eine entsprechende NAT-Regel für pf erstellt werden. Tragen Sie dazu folgendes in die Datei /etc/pf.conf ein:
if_ext = "em0" if_loop = "lo0" vif_01 = "lo1" master_ip = "12.34.56.78" beispiel_ip = "127.0.10.10" nat pass on $if_ext from $beispiel_ip to any -> $master_ip
Anschließend müssen die neuen Regeln geladen werden:
/etc/rc.d/pf reload
Um die Netzwerkverbindung zu testen, können Sie nun aus dem Jail heraus versuchen, mit telnet auf einen entfernten Rechner zuzugreifen oder mit fetch per HTTP eine Datei von einem Webserver herunterladen. ping ist übrigens kein probates Mittel für einen Netzwerk-Test in Jails, da ping sogenannte „Raw Sockets“ verwendet, was innerhalb von Jails aus Sicherheitsgründen per Default deaktiviert ist (und ich kann Ihnen nur raten, bei dieser Einstellung zu bleiben).
Bemerkung zur Sicherheit von Jails
Im oben vorgestellten Setup wurde SysV IPC bewusst unterbunden (jail_sysvipc_allow="NO" bzw. sysctl security.jail.sysvipc_allowed=0). Hintergrund: Ist die Interprozess-Kommunikation erlaubt, können auch Prozesse innerhalb eines Jails auf die Shared Memory Segmente aller anderen Jails sowie des Host-Systems schreibend zugreifen (sichtbar sind sie sonst auch, können aber nicht genutzt werden). Dies muss nicht notwendigerweise zu einem Ausbruch führen; jedoch wächst die Wahrscheinlichkeit, dass auf diesem Wege andere Jails oder gar das Hostsystem kompromittiert werden können.
Wermutstropfen: PostgreSQL als prominenter Datenbankvertreter benötigt SysV IPC, und selbiges lässt sich leider nicht selektiv für einzelne Jails, sondern nur global aktivieren. Wie groß das dadurch entstehende Risiko für das Gesamtsystem ist, hängt stark von den Prozessen innerhalb der anderen Jails ab. Werden stark sicherheitsgefährdende Applikationen betrieben (IRC-Dienste, dynamische Webanwendungen, PHP, Blobs, …), ist es eventuell eine Überlegung wert, entweder PostgreSQL direkt im Hostsystem zu betreiben und so die kritischen Prozesse weiter sicher jailen zu können – oder ggf. auf PostgreSQL zugunsten von MySQL verzichten, da letzteres nicht auf SysV IPC zurückgreift und sich somit in Bezug auf Jails unproblematischer verhält.
Eine interessante Diskussion zu diessem Thema findet sich bei bsdforen.de
Mögliche Alternativen
Wer Jails nicht im Massenbetrieb einsetzt und nur einen kritischen Dienst einsperren möchte, wird ggf. mit FreeBSD_minimalistisches_Jail_(de) glücklich.