openSUSE Leap als Container-Host
Datum: 23. August 2020
Ziel ist es, ein stabiles Host-System für Linux-Container auf Basis von openSUSE Leap einzurichten, das als Testumgebung für verschiedene Netzwerk- und Client/Server-Szenarien verwendet werden kann.
Linux-Distribution: openSUSE Leap 15.2
Linux-Bridge einrichten
Um transparentes Arbeiten zu ermöglichen, ist eine native Linux-Bridge (= Software-Switch) ratsam. Die Container befinden sich dadurch im selben phyischen Netzsegment wie der Host und nicht hinter NAT. Unter openSUSE ist solch eine Bridge schnell eingerichtet.
Als root direkt an der Maschine eingeloggt in der Konsole ‚yast‘ aufrufen:
„System“ / „Network Settings“ in die Netzwerkkonfiguration gehen
Im Menü „Overview:
Mit F3 ein neues Device hinzufügn
Mit Alt + B „Bridge“ auswählen, mitF10 übernehmen
Mit Alt + Y „Dynamic Address“ auswählen (oder gerne auch fest vergeben)
Mit Alt + R in „Bridged Deviges“ gehen
Mit Alt + I sowie danach Leertaste „eth0“ auswählen, F10 drücken
Mit F10 alles übernehmen, schlißlich mit F9 beenden
LXD installieren
Mit den folgenden Zeilen installieren wir „Lex Dee“, die von Ubuntu entwickelte Hypervisorlösung für LXC. Dabei wird dem Nutzer ‚tux‘ die sekundäre Gruppe ‚lxd‘ zugewiesen, damit die Arbeit später nicht als ‚root‘ durchgeführt werden muss:
zypper update
zypper in lxd lxd-bash-completion
usermod -aG lxd tux
systemctl start lxd
systemctl start lxcfs
systemctl enable lxd
systemctl enable lxcfs
Bei der nun folgenden Initialisierung alle Vorgaben einfach mit „ENTER“ übernehmen:
testhost:~ # lxd init
Would you like to use LXD clustering? (yes/no) [default=no]:
Do you want to configure a new storage pool? (yes/no) [default=yes]:
Name of the new storage pool [default=default]:
Name of the storage backend to use (btrfs, dir, lvm) [default=btrfs]:
Would you like to create a new btrfs subvolume under /var/lib/lxd? (yes/no) [default=yes]:
Would you like to connect to a MAAS server? (yes/no) [default=no]:
Would you like to create a new local network bridge? (yes/no) [default=yes]:
What should the new bridge be called? [default=lxdbr0]:
What IPv4 address should be used? (CIDR subnet notation, “auto” or “none”) [default=auto]:
What IPv6 address should be used? (CIDR subnet notation, “auto” or “none”) [default=auto]:
Would you like LXD to be available over the network? (yes/no) [default=no]:
Would you like stale cached images to be updated automatically? (yes/no) [default=yes]
Would you like a YAML "lxd init" preseed to be printed? (yes/no) [default=no]:
testhost:~ #
testhost:~ # exit
tux@testhost:~> logout
Nach dem aus- und wieder einloggen von ‚tux‘ (falls mehrfach eingeloggt: überall ausloggen!) kann er sich die Liste der aktuell vorhandenen Container, Profile und Netzwerke ausgeben lassen:
tux@testhost:~> lxc list
To start your first instance, try: lxc launch ubuntu:18.04
+------+-------+------+------+------+-----------+
| NAME | STATE | IPV4 | IPV6 | TYPE | SNAPSHOTS |
+------+-------+------+------+------+-----------+
tux@testhost:~>
tux@testhost:~> lxc profile list
+---------+---------+
| NAME | USED BY |
+---------+---------+
| default | 0 |
+---------+---------+
tux@testhost:~>
tux@testhost:~> lxc network list
+--------+----------+---------+---------------+---------------------------+-------------+---------+
| NAME | TYPE | MANAGED | IPV4 | IPV6 | DESCRIPTION | USED BY |
+--------+----------+---------+---------------+---------------------------+-------------+---------+
| br0 | bridge | NO | | | | 0 |
+--------+----------+---------+---------------+---------------------------+-------------+---------+
| eth0 | physical | NO | | | | 0 |
+--------+----------+---------+---------------+---------------------------+-------------+---------+
| lxdbr0 | bridge | YES | 10.31.99.1/24 | fd42:fc0c:b479:f27d::1/64 | | 1 |
+--------+----------+---------+---------------+---------------------------+-------------+---------+
tux@testhost:~>
Dies ist ein guter Ausgangspunkt für einfache Szenarien mit Client-/Serverarchitekturen, für geroutete Netzwerke wie hier unter https://pemmann.de/cc/Doc/Testumgebung/Bridged/testnetz-mit-Netzwerkbruecke.html skizziert, benötigen wir aber mehr…
LXD-Networking für Testumgebung
Alle Kommandozeilen bitte als einfacher Nutzer ‚tux‘ ausführen. Aus Gründen der Übersichtlichkeit sind hier nur die Kommandos ohne ihren Output aufgelistet.
Profil für die Host-Brücke erstellen
Um die Konfigurationsarbeit zu erleichtern, bieten sich Profile an. Sie lassen sich einem Container zuweisen, der dann gleich mehrere Voreinstellungen erhält.
Das neu zu erzeugende Profil „extbridge“ wird aus dem bei lxd init
erstellten Standardprofil „default“ generiert (Zeile 1), wobei das ursprüngliche Device „eth0“ entfernt (Zeile 2) und mit „br0“ als übergeordnetes Device wieder hinzugefügt wird (Zeile 3):
lxc profile copy default extbridge
lxc profile device remove extbridge eth0
lxc profile device add extbridge eth0 nic nictype=bridged parent=br0
Profil für eine interne Gast-Brücke erstellen
Mit dem Initialisieren von LXD ist automatisch eine erste Bridge „lxdbr0“ erstellt und dem Profil „default“ zugeordnet worden. Sie ist für Situationen gedacht, in denen ein Container direkt mit dem Host und via NAT mit dem Internet kommunizieren soll. Für unsere Testumgebung ist dies aber nicht ganz ausreichend.
Wir erzeugen deshalb eine weitere LXD-Bridge „lxdbr1“ (Zeile 1), die wie oben vom Standardprofil Gebrauch gemacht (Zeile 2), wobei das ursprüngliche Device „eth0“ entfernt (Zeile 3) und mit „lxdbr1“ als übergeordnetes Device wieder hinzugefügt wird (Zeile 4):
lxc network create lxdbr1 ipv4.nat=false ipv6.nat=false ipv4.address=none ipv6.dhcp=false dns.mode=none
lxc profile copy default intbridge
lxc profile device remove intbridge eth0
lxc profile device add intbridge eth0 nic nictype=bridged parent=lxdbr1
Container „alp-router“ mit 2 NICs erstellen (eth0 via lxdbr0, eth1 via lxdbr1)
Der erste Container soll ein Alpine Linux namens „alp-router“ werden, er bekommt beim Erstellen das Profil „extbridge“ zugewiesen (Zeile 1) und danach eine zweite NIC spendiert, die als übergeordnetes Device die interne Bridge „lxdbr1“ verwendet (Zeile 2):
lxc launch -p extbridge images:alpine/3.9 alp-router
lxc config device add alp-router eth1 nic nictype=bridged parent=lxdbr1 name=eth1
Container „alp-server“ mit einer NIC erstellen (eth0 via lxdbr1)
Der zweite Container soll ebenfalls ein Alpine Linux werden, „alp-server“ heißen und beim Erstellen das Profil „intbridge“ zugewiesen bekommen:
lxc launch -p intbridge images:alpine/3.9 alp-server
Zur Kontrolle
Neben lxc list
und lxc network list
ist auch lxc profile list
interessant, weil anhand der Spalte „USED BY“ gut zu erkennen ist, dass die beiden neuen Profile nun von jeweils einer Instanz benutzt werden:
tux@vhost ~$ lxc profile list
+-----------+---------+
| NAME | USED BY |
+-----------+---------+
| default | 0 |
+-----------+---------+
| extbridge | 1 |
+-----------+---------+
| intbridge | 1 |
+-----------+---------+
tux@vhost ~$
Aufschlussreicher sind dann die Details, hier speziell für den Router, wobei wir zuerst das Profil „extbridge“ betrachten sollten:
tux@vhost ~$ lxc profile show extbridge
config: {}
description: ""
devices:
eth0:
nictype: bridged
parent: br0
type: nic
root:
path: /
pool: default
type: disk
name: extbridge
used_by:
- /1.0/instances/alp-router
tux@vhost ~$
Und nun betrachten wir unseren „alp-router“ genauer, der mit eth0 via extbridge an der nativen Linux-Brücke br0 hängt und zum zweiten mit eth1 direkt mit der internen Brücke lxdbr1 verbunden ist:
tux@vhost ~$ lxc config show alp-router | grep -A2 profile
profiles:
- extbridge
stateful: false
tux@vhost ~$
tux@vhost ~$ lxc config show alp-router | grep -A5 devices
devices:
eth1:
name: eth1
nictype: bridged
parent: lxdbr1
type: nic
tux@vhost ~$
Manuelle IP-Konfiguration der Container, erste Tests
Warnung
Wie dort beschrieben, müssen wir noch den Promiscous-Modus in VirtualBox bezüglich „Adapter 1“ aktivieren, damit die von den Containern stammenden Datenpakete die Netzwerkbrücke passieren können.
Container mit mehr Rechten ausstatten
Containervirtualisierung bietet naturgemäß keine vollständige Abgrenzung der einzelnen Prozessumgebungen (gemeinsam genutzer Host-Kernel). Daher müssen die einzelnen Instanzen vom Host abgeschottet werden. Für reine Anwendungsvirtualisierung stellt das kein Problem dar - allerdings für komplexere Aufgabenstellungen, die tief ins Betriebssystem hineinreichen.
Voll privilegieren
Da die LXD-Container per Default nichtprivilegiert sind, d.h. mit eingeschränkten User-Prozessen laufen, gelingt z.B. samba-tool domain provision
oder mount 10.2.2.22:/srv/nfs /mnt
nicht („Operation not permitted“). Abhilfe bringt folgendes:
lxc config set alp-server security.privileged true
lxc config show alp-server | grep privi
lxc restart alp-server
AppArmor-Ausnahmen
Soll ein Container außerdem als NFS-Server auftreten, behindert AppArmor das Starten von ‚nfsd‘, weil dieser Daemon als ‚nfs-kernel-server‘ im System Space auf dem Host selber ausgeführt werden muss! Was übrigens für ‚mountd‘ nicht gilt; er kann innerhalb des Containers laufen.
Einen ähnlichen Fall haben wir bei OpenVPN, wenn das automatische Starten via ‚systemctl‘ erwünscht ist (Distribution: Debian 10; ‚openvpn –config …‘ stellt dagegen kein Problem dar).
Da temporäre Stoppen der Sicherheitseinrichtung ist mittels aa-teardown
leicht möglich, das Starten der NFS-Daemons ist damit kein Problem mehr. Die Reaktivierung von AppArmor gelingt dann am besten mit reboot
, alternativ kann systemctl restart apparmor
oder auch apparmor_parser -r /etc/apparmor.d/* 2>/dev/null
verwendet werden, wobei dann noch die verschiedenen Daemons und Container neu gestartet werden müssen, um die entsprechenden Profile und Prozesse in den enforce-Modus zu bringen. Dauerhaft kann AppArmor deaktiviert werden, indem in der Datei /etc/default/grub die Default-Cmdline auf diesen Wert gesetzt wird: GRUB_CMDLINE_LINUX_DEFAULT="apparmor=0"
. Danach nicht vergessen, update-grub
auszuführen.
Besser als das generelle „Teardown“ ist allerdings, das Sicherheitsystem aktiviert zu lassen und dem betreffenden Container alles zu erlauben. Im folgenden Beispiel gibt es zwei Container namens ‚deb1‘ und ‚deb2‘, von denen der zweite volle Rechte erhält:
my@bash $ lxc config set deb2 raw.lxc 'lxc.apparmor.profile=unconfined'
my@bash $
my@bash $ lxc config show deb2 | grep app
raw.lxc: lxc.apparmor.profile=unconfined
my@bash $
my@bash $ lxc restart deb2
Mit aa-status | grep -i deb
können wir dann als root kontrollieren, ob das tatsächlich greift, bezüglich ‚deb1‘ sollten mehrere Zeilen zum Vorschein kommen, für ‚deb2‘ dagegen nur eine einzige.
Weitere Probleme
Eine weitere Einschränkung erlebt man im Zusammenhang mit einer iptables-Firewall, die in einem sogar voll privilegierten Container läuft: das Loggen von blockierten Paketen mit einer Zeile wie
iptables -A INPUT -m limit --limit 5/m --limit-burst 7 -j LOG --log-level info --log-prefix="iptables: "
zeigt keinerlei Wirkung!
LÖSUNG: Laut forum.proxmox.com kann als Log-Target ‚NFLOG‘ anstelle von ‚LOG‘ verwendet werden. Siehe dazu auch hier.
Neue Linux-Container
Es lassen sich problemlos weitere Containerabbilder aus dem Netz laden und an den Start bringen. Einen Gesamtüberblick kann man sich mittels
lxc image list images: | less
verschaffen. Zur Erinnerung: mit einem Slash lässt sich bei less
eine Suche durchführen. So findet sich z.B. auch Debian Buster, was sich dann so in die Umgebung einbinden lässt:
lxc launch -p extbridge images:debian/buster deb10srv
Hyperlinks
http://www.panticz.de/lxd (LXD Sheet Cheat)
https://thomas-leister.de/en/lxd-use-public-interface/ (guter Einstieg, -> br0; mit Grafiken)
https://docs.trentsonlinedocs.xyz/lxd_container_home_server_networking_for_dummies/
https://www.dannykorpan.de/2018/03/05/lxc-container-with-bridged-ip/
https://lists.linuxcontainers.org/pipermail/lxc-users/2016-March/011282.html
https://discuss.linuxcontainers.org/t/how-to-add-a-network-interface-in-lxc/437 (Name angepasst: eth0 -> eth1)
https://linuxcontainers.org/lxd/docs/master/networks (Hauptdoku, Unterseite zu mögl. Eigenschaften von networks)
https://askubuntu.com/questions/980156/how-can-i-create-a-secondary-lxd-bridge
https://blog.simos.info/a-network-isolated-container-in-lxd/
https://blog.simos.info/how-to-add-both-a-private-and-public-network-to-lxd-using-cloud-init/
https://discuss.linuxcontainers.org/t/error-the-default-profile-cannot-be-deleted/3972
Speziell zu Fragen der Sicherheit:
https://github.com/lxc/lxd/issues/3754 („networks to be isolated“, -> iptables)
https://reboare.github.io/lxd/lxd-escape.html (Privilege Escalation via lxd)
https://shenaniganslabs.io/2019/05/21/LXD-LPE.html (Hijacked UNIX Socket Credentials)