Contexte
À partir du noyau 2.6.24, Linux prend en charge 6 types différents d'espaces de noms. Les espaces de noms sont utiles pour créer des processus plus isolés du reste du système, sans avoir besoin d'utiliser une technologie de virtualisation complète de bas niveau.
- CLONE_NEWIPC :espaces de noms IPC :les files d'attente de messages SystemV IPC et POSIX peuvent être isolées.
- CLONE_NEWPID :espaces de noms PID :les PID sont isolés, ce qui signifie qu'un PID virtuel à l'intérieur de l'espace de noms peut entrer en conflit avec un PID en dehors de l'espace de noms. Les PID à l'intérieur de l'espace de noms seront mappés à d'autres PID en dehors de l'espace de noms. Le premier PID à l'intérieur de l'espace de noms sera '1' qui en dehors de l'espace de noms est assigné à init
- CLONE_NEWNET :espaces de noms réseau :les réseaux (/proc/net, IP, interfaces et routes) sont isolés. Les services peuvent être exécutés sur les mêmes ports dans les espaces de noms et des interfaces virtuelles "dupliquées" peuvent être créées.
- CLONE_NEWNS :monter les espaces de noms. Nous avons la possibilité d'isoler les points de montage tels qu'ils apparaissent aux processus. En utilisant les espaces de noms de montage, nous pouvons obtenir des fonctionnalités similaires à chroot() mais avec une sécurité améliorée.
- CLONE_NEWUTS :espaces de noms UTS. L'objectif principal de cet espace de noms est d'isoler le nom d'hôte et le nom NIS.
- CLONE_NEWUSER :espaces de noms d'utilisateurs. Ici, les ID d'utilisateur et de groupe sont différents à l'intérieur et à l'extérieur des espaces de noms et peuvent être dupliqués.
Examinons d'abord la structure d'un programme C, nécessaire pour démontrer les espaces de noms de processus. Ce qui suit a été testé sur Debian 6 et 7. Tout d'abord, nous devons allouer une page de mémoire sur la pile et définir un pointeur vers la fin de cette page de mémoire. Nous utilisons alloca pour allouer de la mémoire dans la pile plutôt que malloc qui allouerait de la mémoire sur le tas.
void *mem = alloca(sysconf(_SC_PAGESIZE)) + sysconf(_SC_PAGESIZE);
Ensuite, nous utilisons clone pour créer un processus enfant, en transmettant l'emplacement de notre pile enfant 'mem', ainsi que les drapeaux requis pour spécifier un nouvel espace de noms. Nous spécifions 'callee' comme fonction à exécuter dans l'espace enfant :
mypid = clone(callee, mem, SIGCHLD | CLONE_NEWIPC | CLONE_NEWPID | CLONE_NEWNS | CLONE_FILES, NULL);
Après avoir appelé clone, nous attendons la fin du processus enfant avant de terminer le parent. Si ce n'est pas le cas, le flux d'exécution parent continuera et se terminera immédiatement après, effaçant l'enfant avec :
while (waitpid(mypid, &r, 0) < 0 && errno == EINTR) { continue; }
Enfin, nous reviendrons au shell avec le code de sortie de l'enfant :
if (WIFEXITED(r)) { return WEXITSTATUS(r); } return EXIT_FAILURE;
Maintenant, regardons la fonction appelée :
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; }
Ici, nous montons un système de fichiers /proc, puis définissons l'uid (ID utilisateur) et le gid (ID de groupe) sur la valeur 'u' avant de générer le shell /bin/bash. LXC est un outil de virtualisation au niveau du système d'exploitation utilisant des cgroups et des espaces de noms pour l'isolation des ressources. Mettons tout cela ensemble, en définissant 'u' sur 65534 qui est l'utilisateur "nobody" et le groupe "nogroup" sur Debian :
#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; }
Pour exécuter le code produit ce qui suit :
[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$
Notez que l'UID et le GID sont définis sur personne et nogroup. Notez spécifiquement que la sortie complète de ps ne montre que deux processus en cours d'exécution et que leurs PID sont 1 et 5 respectivement. Passons maintenant à l'utilisation de ip netns pour travailler avec les espaces de noms réseau. Tout d'abord, confirmons qu'aucun espace de noms n'existe actuellement :
[email protected]:~# ip netns list Object "netns" is unknown, try "ip help".
Dans ce cas, soit ip a besoin d'une mise à jour, soit le noyau en a besoin. En supposant que vous ayez un noyau plus récent que 2.6.24, il s'agit très probablement d'ip. Après la mise à niveau, la liste ip netns ne devrait rien renvoyer par défaut. Ajoutons un nouvel espace de noms appelé 'ns1' :
[email protected]:~# ip netns add ns1 [email protected]:~# ip netns list ns1
Commençons par lister les interfaces actuelles :
[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
Maintenant, pour créer une nouvelle interface virtuelle et l'ajouter à notre nouvel espace de noms. Les interfaces virtuelles sont créées par paires et sont liées les unes aux autres - imaginez un câble croisé virtuel :
[email protected]:~# ip link add veth0 type veth peer name veth1 [email protected]:~# ip link list 1: lo:ifconfig -a affichera également l'ajout de veth0 et 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
Super, maintenant pour assigner nos nouvelles interfaces à l'espace de noms. Notez que ip netns exec est utilisé pour exécuter des commandes dans l'espace de noms :
[email protected]:~# ip link set veth1 netns ns1 [email protected]:~# ip netns exec ns1 ip link list 1: lo:ifconfig -a n'affichera désormais que veth0, car veth1 se trouve dans l'espace de noms 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
Devrions-nous supprimer veth0/veth1 :
ip netns exec ns1 ip link del veth1
Nous pouvons maintenant attribuer l'adresse IP 192.168.5.5/24 à veth0 sur notre hébergeur :
ifconfig veth0 192.168.5.5/24
Et assignez veth1 192.168.5.10/24 dans ns1 :
ip netns exec ns1 ifconfig veth1 192.168.5.10/24 up
Pour exécuter la liste d'adresses IP sur notre hôte et dans notre espace de noms :
[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
Pour afficher les tables de routage à l'intérieur et à l'extérieur de l'espace de noms :
[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
Enfin, pour connecter nos interfaces physiques et virtuelles, nous aurons besoin d'un pont. Relions eth0 et veth0 sur l'hôte, puis utilisons DHCP pour obtenir une adresse IP dans l'espace de noms 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 s'est vu attribuer l'adresse IP 192.168.3.122/24. Passons maintenant à l'espace de noms :
[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
Excellent! veth1 a été attribué 192.168.3.248/24
Liens
IO Digital Sec
Consultant Linux