logo_header

icon_bubbles Forum

icon_bubbles Wiki

icon_bubbles Planet

RootForum Community » Wiki

FreeBSD Jail Infrastruktur

From RootForum Community » Wiki

Jump to:navigation, search

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).

Schema für den Aufbau eines Hosting-Systems mit Jails
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/local sowie 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.

Wie in FreeBSD_manuelle_Installation_(de)#.2Fetc.2Frc.conf_erstellen beschrieben müssen 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.