GNU/Linux >> LINUX-Kenntnisse >  >> Debian

Linux-Namespaces

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:  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
ifconfig -a zeigt jetzt auch das Hinzufügen von veth0 und veth1 an.

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:  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
ifconfig -a zeigt jetzt nur noch veth0 an, da sich veth1 im ns1-Namespace befindet.

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

IO Digital Sec
Linux-Berater


Debian
  1. Entmystifizierung von Namespaces und Containern in Linux

  2. Wie führt man Chroot mit Linux-Namespaces durch?

  3. Linux-mv-Befehl

  4. Linux-Du-Befehl

  5. Anzeigen/Manipulieren von Mount-Namespaces in Linux

Bei Befehl unter Linux

Df-Befehl unter Linux

mail-Befehl unter Linux

Manuelles Erstellen eines Linux-Containers mithilfe von Namespaces

Linux Perf-Befehl

Linux gegen Unix