Pozadí
Počínaje jádrem 2.6.24 Linux podporuje 6 různých typů jmenných prostorů. Jmenné prostory jsou užitečné při vytváření procesů, které jsou více izolované od zbytku systému, aniž by bylo nutné používat plnou nízkoúrovňovou virtualizační technologii.
- CLONE_NEWIPC:Jmenné prostory IPC:SystemV IPC a fronty zpráv POSIX lze izolovat.
- CLONE_NEWPID:Jmenné prostory PID:PID jsou izolované, což znamená, že virtuální PID uvnitř jmenného prostoru může být v konfliktu s PID mimo jmenný prostor. PID uvnitř jmenného prostoru budou mapovány na jiné PID mimo jmenný prostor. První PID uvnitř jmenného prostoru bude '1', která mimo jmenný prostor je přiřazena init
- CLONE_NEWNET:Síťové jmenné prostory:Sítě (/proc/net, IP, rozhraní a trasy) jsou izolované. Služby lze provozovat na stejných portech v rámci jmenných prostorů a lze vytvořit „duplicitní“ virtuální rozhraní.
- CLONE_NEWNS:Připojit jmenné prostory. Máme schopnost izolovat přípojné body tak, jak se jeví procesům. Pomocí mount jmenných prostorů můžeme dosáhnout podobné funkce jako chroot(), avšak s lepším zabezpečením.
- CLONE_NEWUTS:jmenné prostory UTS. Primárním účelem tohoto jmenného prostoru je izolovat název hostitele a název NIS.
- CLONE_NEWUSER:Uživatelské jmenné prostory. Zde se ID uživatelů a skupin liší uvnitř a vně jmenných prostorů a lze je duplikovat.
Podívejme se nejprve na strukturu programu v jazyce C, který je nutný k demonstraci jmenných prostorů procesů. Následující bylo testováno na Debianu 6 a 7. Nejprve musíme alokovat stránku paměti v zásobníku a nastavit ukazatel na konec této stránky paměti. K alokaci paměti zásobníku používáme alloca spíše než malloc, který by alokoval paměť na haldě.
void *mem = alloca(sysconf(_SC_PAGESIZE)) + sysconf(_SC_PAGESIZE);
Dále použijeme clone k vytvoření podřízeného procesu, předáme umístění našeho podřízeného zásobníku 'mem' a také požadované příznaky pro specifikaci nového jmenného prostoru. Jako funkci, která se má provést v podřízeném prostoru, specifikujeme 'callee':
mypid = clone(callee, mem, SIGCHLD | CLONE_NEWIPC | CLONE_NEWPID | CLONE_NEWNS | CLONE_FILES, NULL);
Po zavolání clone pak čekáme na dokončení podřízeného procesu, než ukončíme nadřazený. Pokud ne, bude tok spouštění rodiče pokračovat a okamžitě se ukončí, čímž se vyčistí i podřízené:
while (waitpid(mypid, &r, 0) < 0 && errno == EINTR) { continue; }
Nakonec se vrátíme do shellu s výstupním kódem potomka:
if (WIFEXITED(r)) { return WEXITSTATUS(r); } return EXIT_FAILURE;
Nyní se podívejme na funkci volaného:
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; }
Zde připojíme souborový systém /proc a poté nastavíme uid (ID uživatele) a gid (ID skupiny) na hodnotu 'u' před vytvořením shellu /bin/bash. LXC je virtualizační nástroj na úrovni operačního systému využívající cgroups a jmenné prostory pro izolaci zdrojů. Pojďme to dát dohromady, nastavíme 'u' na 65534, což je uživatel "nobody" a skupina "nogroup" v Debianu:
#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; }
Spuštění kódu vytvoří následující:
[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$
Všimněte si, že UID a GID jsou nastaveny na hodnotu none a nogroup. Konkrétně si všimněte, že úplný výstup ps zobrazuje pouze dva běžící procesy a že jejich PID jsou 1 a 5. Nyní přejděme k používání ip netns pro práci se síťovými jmennými prostory. Nejprve si ověřte, že aktuálně neexistují žádné jmenné prostory:
[email protected]:~# ip netns list Object "netns" is unknown, try "ip help".
V tomto případě buď ip potřebuje upgrade, nebo kernel. Za předpokladu, že máte jádro novější než 2.6.24, je to s největší pravděpodobností ip. Po upgradu by seznam ip netns standardně neměl vracet nic. Pojďme přidat nový jmenný prostor nazvaný 'ns1':
[email protected]:~# ip netns add ns1 [email protected]:~# ip netns list ns1
Nejprve si uveďme seznam aktuálních rozhraní:
[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
Nyní vytvořte nové virtuální rozhraní a přidejte jej do našeho nového jmenného prostoru. Virtuální rozhraní jsou vytvářena ve dvojicích a jsou vzájemně propojena – představte si virtuální křížený kabel:
[email protected]:~# ip link add veth0 type veth peer name veth1 [email protected]:~# ip link list 1: lo:ifconfig -a nyní také zobrazí přidání obou veth0 a veth1.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
Skvělé, nyní přiřadit naše nová rozhraní do jmenného prostoru. Všimněte si, že ip netns exec se používá k provádění příkazů v rámci jmenného prostoru:
[email protected]:~# ip link set veth1 netns ns1 [email protected]:~# ip netns exec ns1 ip link list 1: lo:ifconfig -a nyní zobrazí pouze veth0, protože veth1 je ve jmenném prostoru ns1.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
Pokud bychom chtěli odstranit veth0/veth1:
ip netns exec ns1 ip link del veth1
Nyní můžeme přiřadit IP adresu 192.168.5.5/24 veth0 na našem hostiteli:
ifconfig veth0 192.168.5.5/24
A přiřadit veth1 192.168.5.10/24 v rámci ns1:
ip netns exec ns1 ifconfig veth1 192.168.5.10/24 up
Chcete-li spustit seznam ip addr na našem hostiteli i v našem jmenném prostoru:
[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
Zobrazení směrovacích tabulek uvnitř a vně jmenného prostoru:
[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
A konečně, k propojení našich fyzických a virtuálních rozhraní budeme potřebovat most. Pojďme přemostit eth0 a veth0 na hostiteli a pak pomocí DHCP získat IP v rámci jmenného prostoru ns1:
[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 má přiděleno IP 192.168.3.122/24. Nyní k jmennému prostoru:
[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
Vynikající! veth1 bylo přiděleno 192.168.3.248/24
Odkazy
IO Digital Sec
Linux konzultant