Hintergrund
Ab Kernel 2.6.24 unterstützt Linux 6 verschiedene Arten von Namespaces. Namespaces sind nützlich, um Prozesse zu erstellen, die stärker vom Rest des Systems isoliert sind, ohne dass eine vollständige Low-Level-Virtualisierungstechnologie verwendet werden muss.
- CLONE_NEWIPC:IPC Namespaces:SystemV IPC und POSIX Message Queues können isoliert werden.
- CLONE_NEWPID:PID-Namensräume:PIDs sind isoliert, was bedeutet, dass eine virtuelle PID innerhalb des Namensraums mit einer PID außerhalb des Namensraums in Konflikt geraten kann. PIDs innerhalb des Namensraums werden anderen PIDs außerhalb des Namensraums zugeordnet. Die erste PID innerhalb des Namensraums ist '1', die außerhalb des Namensraums init zugewiesen wird
- CLONE_NEWNET:Netzwerk-Namespaces:Netzwerke (/proc/net, IPs, Schnittstellen und Routen) sind isoliert. Dienste können auf denselben Ports innerhalb von Namespaces ausgeführt werden, und "duplizierte" virtuelle Schnittstellen können erstellt werden.
- CLONE_NEWNS:Namespaces mounten. Wir haben die Möglichkeit, Einhängepunkte so zu isolieren, wie sie für Prozesse erscheinen. Durch die Verwendung von Mount-Namespaces können wir eine ähnliche Funktionalität wie chroot() erreichen, jedoch mit verbesserter Sicherheit.
- CLONE_NEWUTS:UTS-Namespaces. Der Hauptzweck dieses Namensraums besteht darin, den Hostnamen und den NIS-Namen zu isolieren.
- CLONE_NEWUSER:Benutzernamensräume. Hier sind Benutzer- und Gruppen-IDs innerhalb und außerhalb von Namespaces unterschiedlich und können dupliziert werden.
Schauen wir uns zunächst die Struktur eines C-Programms an, das zur Demonstration von Prozessnamensräumen erforderlich ist. Folgendes wurde auf Debian 6 und 7 getestet. Zuerst müssen wir eine Speicherseite auf dem Stapel zuweisen und einen Zeiger auf das Ende dieser Speicherseite setzen. Wir verwenden alloca, um Stack-Speicher zuzuweisen, und nicht malloc, das Speicher auf dem Heap zuweisen würde.
void *mem = alloca(sysconf(_SC_PAGESIZE)) + sysconf(_SC_PAGESIZE);
Als Nächstes verwenden wir clone, um einen untergeordneten Prozess zu erstellen, wobei wir den Speicherort unseres untergeordneten Stacks „mem“ sowie die erforderlichen Flags zur Angabe eines neuen Namespace übergeben. Wir spezifizieren „callee“ als die Funktion, die innerhalb des untergeordneten Bereichs ausgeführt werden soll:
mypid = clone(callee, mem, SIGCHLD | CLONE_NEWIPC | CLONE_NEWPID | CLONE_NEWNS | CLONE_FILES, NULL);
Nachdem wir clone aufgerufen haben, warten wir, bis der untergeordnete Prozess beendet ist, bevor wir den übergeordneten Prozess beenden. Wenn nicht, wird der übergeordnete Ausführungsablauf fortgesetzt und unmittelbar danach beendet, wobei das untergeordnete Element damit gelöscht wird:
while (waitpid(mypid, &r, 0) < 0 && errno == EINTR) { continue; }
Zuletzt kehren wir mit dem Exit-Code des Kindes zur Shell zurück:
if (WIFEXITED(r)) { return WEXITSTATUS(r); } return EXIT_FAILURE;
Sehen wir uns nun die callee-Funktion an:
static int callee() { int ret; mount("proc", "/proc", "proc", 0, ""); setgid(u); setgroups(0, NULL); setuid(u); ret = execl("/bin/bash", "/bin/bash", NULL); return ret; }
Hier mounten wir ein /proc-Dateisystem und setzen dann die uid (Benutzer-ID) und gid (Gruppen-ID) auf den Wert „u“, bevor wir die /bin/bash-Shell erzeugen. LXC ist ein Virtualisierungstool auf Betriebssystemebene, das Cgroups und Namespaces zur Ressourcenisolation verwendet. Fassen wir alles zusammen, indem wir 'u' auf 65534 setzen, was dem Benutzer "nobody" und der Gruppe "nogroup" auf Debian entspricht:
#define _GNU_SOURCE #include <unistd.h> #include <stdio.h> #include <stdlib.h> #include <sys/types.h> #include <sys/wait.h> #include <sys/mount.h> #include <grp.h> #include <alloca.h> #include <errno.h> #include <sched.h> static int callee(); const int u = 65534; int main(int argc, char *argv[]) { int r; pid_t mypid; void *mem = alloca(sysconf(_SC_PAGESIZE)) + sysconf(_SC_PAGESIZE); mypid = clone(callee, mem, SIGCHLD | CLONE_NEWIPC | CLONE_NEWPID | CLONE_NEWNS | CLONE_FILES, NULL); while (waitpid(mypid, &r, 0) < 0 && errno == EINTR) { continue; } if (WIFEXITED(r)) { return WEXITSTATUS(r); } return EXIT_FAILURE; } static int callee() { int ret; mount("proc", "/proc", "proc", 0, ""); setgid(u); setgroups(0, NULL); setuid(u); ret = execl("/bin/bash", "/bin/bash", NULL); return ret; }
Das Ausführen des Codes erzeugt Folgendes:
[email protected]:~/pen/tmp# gcc -O -o ns.c -Wall -Werror -ansi -c89 ns.c [email protected]:~/pen/tmp# ./ns [email protected]:~/pen/tmp$ id uid=65534(nobody) gid=65534(nogroup) [email protected]:~/pen/tmp$ ps auxw USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND nobody 1 0.0 0.0 4620 1816 pts/1 S 21:21 0:00 /bin/bash nobody 5 0.0 0.0 2784 1064 pts/1 R+ 21:21 0:00 ps auxw [email protected]:~/pen/tmp$
Beachten Sie, dass die UID und GID auf die von none und nogroup gesetzt sind. Beachten Sie insbesondere, dass die vollständige ps-Ausgabe nur zwei laufende Prozesse anzeigt und dass ihre PIDs 1 bzw. 5 sind. Fahren wir nun mit der Verwendung von ip netns fort, um mit Netzwerk-Namespaces zu arbeiten. Lassen Sie uns zunächst bestätigen, dass derzeit keine Namespaces existieren:
[email protected]:~# ip netns list Object "netns" is unknown, try "ip help".
In diesem Fall muss entweder ip oder der Kernel aktualisiert werden. Angenommen, Sie haben einen Kernel, der neuer als 2.6.24 ist, ist es höchstwahrscheinlich ip. Nach dem Upgrade sollte ip netns list standardmäßig nichts zurückgeben. Lassen Sie uns einen neuen Namensraum mit dem Namen „ns1“ hinzufügen:
[email protected]:~# ip netns add ns1 [email protected]:~# ip netns list ns1
Lassen Sie uns zuerst die aktuellen Schnittstellen auflisten:
[email protected]:~# ip link list 1: lo:mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 2: eth0: mtu 1500 qdisc pfifo_fast state UNKNOWN mode DEFAULT qlen 1000 link/ether 00:0c:29:65:25:9e brd ff:ff:ff:ff:ff:ff
Erstellen Sie nun eine neue virtuelle Schnittstelle und fügen Sie sie unserem neuen Namespace hinzu. Virtuelle Schnittstellen werden paarweise angelegt und miteinander verknüpft – stellen Sie sich ein virtuelles Crossover-Kabel vor:
[email protected]:~# ip link add veth0 type veth peer name veth1 [email protected]:~# ip link list 1: lo:ifconfig -a zeigt jetzt auch das Hinzufügen von veth0 und veth1 an.mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 2: eth0: mtu 1500 qdisc pfifo_fast state UNKNOWN mode DEFAULT qlen 1000 link/ether 00:0c:29:65:25:9e brd ff:ff:ff:ff:ff:ff 3: veth1: mtu 1500 qdisc noop state DOWN mode DEFAULT qlen 1000 link/ether d2:e9:52:18:19:ab brd ff:ff:ff:ff:ff:ff 4: veth0: mtu 1500 qdisc noop state DOWN mode DEFAULT qlen 1000 link/ether f2:f7:5e:e2:22:ac brd ff:ff:ff:ff:ff:ff
Toll, jetzt ordnen wir unsere neuen Interfaces dem Namensraum zu. Beachten Sie, dass ip netns exec verwendet wird, um Befehle innerhalb des Namensraums auszuführen:
[email protected]:~# ip link set veth1 netns ns1 [email protected]:~# ip netns exec ns1 ip link list 1: lo:ifconfig -a zeigt jetzt nur noch veth0 an, da sich veth1 im ns1-Namespace befindet.mtu 65536 qdisc noop state DOWN mode DEFAULT link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 3: veth1: mtu 1500 qdisc noop state DOWN mode DEFAULT qlen 1000 link/ether d2:e9:52:18:19:ab brd ff:ff:ff:ff:ff:ff
Sollen wir veth0/veth1 löschen:
ip netns exec ns1 ip link del veth1
Wir können jetzt veth0 auf unserem Host die IP-Adresse 192.168.5.5/24 zuweisen:
ifconfig veth0 192.168.5.5/24
Und weisen Sie veth1 192.168.5.10/24 innerhalb von ns1 zu:
ip netns exec ns1 ifconfig veth1 192.168.5.10/24 up
So führen Sie ip addr list sowohl auf unserem Host als auch in unserem Namespace aus:
[email protected]:~# ip addr list 1: lo:mtu 65536 qdisc noqueue state UNKNOWN link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 inet 127.0.0.1/8 scope host lo inet6 ::1/128 scope host valid_lft forever preferred_lft forever 2: eth0: mtu 1500 qdisc pfifo_fast state UNKNOWN qlen 1000 link/ether 00:0c:29:65:25:9e brd ff:ff:ff:ff:ff:ff inet 192.168.3.122/24 brd 192.168.3.255 scope global eth0 inet6 fe80::20c:29ff:fe65:259e/64 scope link valid_lft forever preferred_lft forever 6: veth0: mtu 1500 qdisc pfifo_fast state UP qlen 1000 link/ether 86:b2:c7:bd:c9:11 brd ff:ff:ff:ff:ff:ff inet 192.168.5.5/24 brd 192.168.5.255 scope global veth0 inet6 fe80::84b2:c7ff:febd:c911/64 scope link valid_lft forever preferred_lft forever [email protected]:~# ip netns exec ns1 ip addr list 1: lo: mtu 65536 qdisc noop state DOWN link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 5: veth1: mtu 1500 qdisc pfifo_fast state UP qlen 1000 link/ether 12:bd:b6:76:a6:eb brd ff:ff:ff:ff:ff:ff inet 192.168.5.10/24 brd 192.168.5.255 scope global veth1 inet6 fe80::10bd:b6ff:fe76:a6eb/64 scope link valid_lft forever preferred_lft forever
So zeigen Sie Routing-Tabellen innerhalb und außerhalb des Namespace an:
[email protected]:~# ip route list default via 192.168.3.1 dev eth0 proto static 192.168.3.0/24 dev eth0 proto kernel scope link src 192.168.3.122 192.168.5.0/24 dev veth0 proto kernel scope link src 192.168.5.5 [email protected]:~# ip netns exec ns1 ip route list 192.168.5.0/24 dev veth1 proto kernel scope link src 192.168.5.10
Schließlich benötigen wir eine Brücke, um unsere physischen und virtuellen Schnittstellen zu verbinden. Lassen Sie uns eth0 und veth0 auf dem Host überbrücken und dann DHCP verwenden, um eine IP innerhalb des ns1-Namespace zu erhalten:
[email protected]:~# brctl addbr br0 [email protected]:~# brctl addif br0 eth0 [email protected]:~# brctl addif br0 veth0 [email protected]:~# ifconfig eth0 0.0.0.0 [email protected]:~# ifconfig veth0 0.0.0.0 [email protected]:~# dhclient br0 [email protected]:~# ip addr list br0 7: br0:mtu 1500 qdisc noqueue state UP link/ether 00:0c:29:65:25:9e brd ff:ff:ff:ff:ff:ff inet 192.168.3.122/24 brd 192.168.3.255 scope global br0 inet6 fe80::20c:29ff:fe65:259e/64 scope link valid_lft forever preferred_lft forever
br0 wurde die IP 192.168.3.122/24 zugewiesen. Nun zum Namensraum:
[email protected]:~# ip netns exec ns1 dhclient veth1 [email protected]:~# ip netns exec ns1 ip addr list 1: lo:mtu 65536 qdisc noop state DOWN link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 5: veth1: mtu 1500 qdisc pfifo_fast state UP qlen 1000 link/ether 12:bd:b6:76:a6:eb brd ff:ff:ff:ff:ff:ff inet 192.168.3.248/24 brd 192.168.3.255 scope global veth1 inet6 fe80::10bd:b6ff:fe76:a6eb/64 scope link valid_lft forever preferred_lft forever
Exzellent! veth1 wurde 192.168.3.248/24 zugewiesen
Links
IO Digital Sec
Linux-Berater