diff --git a/slides/4-linux internals.odp b/slides/4-linux internals.odp index 8e6f339..6619969 100644 Binary files a/slides/4-linux internals.odp and b/slides/4-linux internals.odp differ diff --git a/tutorial/4/Makefile b/tutorial/4/Makefile index f8aac2b..09d17f0 100644 --- a/tutorial/4/Makefile +++ b/tutorial/4/Makefile @@ -1,6 +1,6 @@ include ../pandoc-opts.mk -SOURCES_TUTO = tutorial.md mount.md intro.md setup.md howto.md setns.md setns-samples.md cmpns.md lifetime.md namespaces_more.md networkns.md pidns.md mountns.md userns.md docker-exec.md rendu.md +SOURCES_TUTO = tutorial.md mount.md intro.md setup.md lsns.md howto.md setns.md setns-samples.md setns-pid.md cmpns.md lifetime.md namespaces_more.md ../new-page.md networkns.md ../new-page.md pidns.md ../new-page.md mountns.md ../new-page.md userns.md docker-exec.md rendu.md all: tutorial.pdf diff --git a/tutorial/4/cmpns.md b/tutorial/4/cmpns.md index b252193..c07aff9 100644 --- a/tutorial/4/cmpns.md +++ b/tutorial/4/cmpns.md @@ -5,20 +5,23 @@ Les *namespaces* d'un programme sont exposés sous forme de liens symboliques dans le répertoire `/proc//ns/`. -Deux programmes qui partagent un même *namespace* auront un lien vers la même -structure de données. +Deux programmes qui partagent un même *namespace* auront un lien vers le même +*inode*. -Écrivons un script ou un programme, `cmpns`, permettant de déterminer si deux -programmes s'exécutent dans les mêmes *namespaces*. On ignorera les -*namespace*s `*_for_children`, car ils ne font pas partie du cycle d'exécution -que l'on cherche à comparer. +Écrivons un script, `cmpns`, permettant de déterminer si deux programmes +s'exécutent dans les mêmes *namespaces*. On ignorera les *namespace*s +`*_for_children`, car ils ne font pas partie du cycle d'exécution que l'on +cherche à comparer. + +En shell, vous aurez besoin de `grep(1)` et de `readlink(1)`. #### Exemples {.unnumbered}
``` -42sh$ ./cmpns $(pgrep influxdb) $(pgrep init) +42sh$ docker run -d influxdb +42sh$ ./cmpns $(pgrep influxd) $(pgrep init) - cgroup: differ - ipc: differ - mnt: differ diff --git a/tutorial/4/howto.md b/tutorial/4/howto.md index 6d8b67c..f4e5dbf 100644 --- a/tutorial/4/howto.md +++ b/tutorial/4/howto.md @@ -1,28 +1,27 @@ -\newpage - Utiliser les *namespace*s ------------------------- ### S'isoler dans un nouveau *namespace* -Si l'on voit l'isolation procurée par les *namespace*s en parallèle avec les -machines virtuelles, on peut se dire qu'il suffit d'exécuter un appel système -pour arriver dans un conteneur bien isolé. Cependant, le choix fait par les -développeurs de Linux a été de laisser le choix des espaces de noms dont on veut -se dissocier. +Si l'on voit l'isolation procurée par les *namespace*s comme des machines +virtuelles, on peut se dire qu'il suffit d'exécuter un appel système pour +arriver dans un conteneur bien isolé. Cependant, le choix fait par les +développeurs de Linux a été de laisser le choix des espaces de noms dont on +veut se dissocier. L'intérêt principal de cette approche, exploitée bien après la mise en avant du concept, est que l'utilisation des *namespace*s ne se limite pas seulement à -des machines virtuelles légères. On retrouve ainsi dans Google Chrome de -nombreuses utilisations des *namespace*s dans le simple but d'accroître la -sécurité de leur navigateur. Ainsi, les threads de rendu n'ont pas accès au -réseau et sont cloisonnés de manière transparente pour l'utilisateur. +des machines virtuelles légères. On retrouve ainsi dans Google Chrome et +Firefox de nombreuses utilisations des *namespace*s dans le simple but +d'accroître la sécurité de leur navigateur. Ainsi, les *threads* de rendu n'ont +pas accès au réseau et sont cloisonnés de manière transparente pour +l'utilisateur. Nous allons voir dans cette partie plusieurs méthodes pour utiliser ces espaces de noms. -#### Dans son shell +#### Dans son shell\ De la même manière que l'on peut utiliser l'appel système `chroot(2)` depuis un shell via la commande `chroot(1)`, la commande `unshare(1)` permet de faire le @@ -55,16 +54,16 @@ Nous avons pu ici modifier le nom de la machine, sans que cela n'affecte notre machine hôte. -#### Les appels système +#### Les appels système\ L'appel système par excellence pour contrôler l'isolation d'un nouveau processus est `clone(2)`. -Ce *syscall*, propre à Linux, crée habituellement un nouveau processus (mais -aussi des threads) enfant de notre processus courant, comme `fork(2)` (qui lui -est un appel système POSIX) mais prend en plus de nombreux -paramètres. L'isolement ou non du processus se fait en fonction des *flags* qui -sont passés : +Ce *syscall*, propre à Linux, crée habituellement un nouveau processus enfant +de notre processus courant (mais il peut aussi créer des *threads*, on va voir +qu'il fait beaucoup de choses), comme `fork(2)` (qui lui est un appel système +POSIX). Mais il prend en plus de nombreux paramètres. L'isolement ou non du +processus se fait en fonction des *flags* qui sont passés. On retrouve donc : * `CLONE_NEWNS`, * `CLONE_NEWUTS`, @@ -75,12 +74,29 @@ sont passés : * `CLONE_NEWCGROUP`, * `CLONE_NEWTIME`. +::::: {.more} + +Le nom du *flag* `CLONE_NEWNS` est historique et assez peu explicite +contrairement aux autres : il désigne en fait l'espace de nom `mount`. + +Au départ, les *namespace*s ont étés pensés pour former un tout : une couche +d'isolation complète pour les processus. Mais lors des développements suivants, +il s'est avéré pratique de pouvoir choisir finement de quels aspects on +souhaitait se dissocier. + +C'est ainsi que pour chaque nouveau *namespace*, un nouveau *flag* est +introduit. + +::::: + On peut bien entendu cumuler un ou plusieurs de ces *flags*, et les combiner avec d'autres attendus par `clone(2)`. Ces mêmes *flags* sont utilisés lors des appels à `unshare(2)` ou `setns(2)`, que nous verrons plus tard. +::::: {.code} + Pour créer un nouveau processus qui sera à la fois dans un nouvel espace de noms réseau et dans un nouveau *namespace* `cgroup`, on écrirait un code C semblable à : @@ -105,21 +121,34 @@ pid_t pid = clone(do_execvp, // First function executed by child Dans cet exemple, le processus fils créé disposera d'un nouvel espace de noms pour les *CGroups* et disposera d'une nouvelle pile réseau. -Un exemple complet d'utilisation de `clone(2)` et du *namespace* `UTS` est -donné dans le `man` de l'appel système. +::::: {.question} + +#### Quel est le rôle du *flag* `SIGCHLD` ? {-} +\ + +Lorsque l'on crée un nouveau processus, on ajoute l'option `SIGCHLD` afin +d'être notifié par signal lorsque notre processus fil a terminé son +exécution. Cela permet d'être réveillé de notre `wait(2)`. + +::::: + +::::: + +L'appel système `clone(2)` va donc créer un nouveau processus, ou un nouveau +*thread*, mais parfois on souhaite juste isoler notre processus actuel. ##### `unshare`\ -L'appel système `clone(2)` va créer un nouveau processus, ou un nouveau -thread. Parfois, on souhaite faire entrer notre processus ou thread en cours -dans un nouvel espace de noms. Dans ce cas, on utilisera l'appel système +Lorsque l'on souhaite faire entrer notre processus courant ou notre *thread* +dans un nouvel espace de noms, on peut utiliser l'appel système `unshare(2)`. Celui-ci prend uniquement en argument une liste de *flags* des *namespace*s dont on souhaite se dissocier. ::::: {.warning} -Le comportement de `unshare(2)` (ou `unshare(3)`) avec les *namespace*s *PID* -et *Time* n'est pas celui que l'on peut attendre ! + +**Le comportement de `unshare(2)` (ou `unshare(3)`) avec les *namespace*s *PID* +et *Time* n'est pas celui que l'on peut attendre !**\ En effet, après avoir appelé `unshare`, le processus reste tout de même dans son *namespace* *Time* ou *PID* d'origine, seuls ses enfants (après un appel à @@ -135,4 +164,8 @@ peuvent être différents dans le dossier `/proc//ns`. On ne remarque pas cette bizarrerie avec `clone(2)`, car il crée déjà un nouveau processus, son PID et sa `CLOCK_MONOTONIC` sont directement à la bonne valeur dès l'exécution de la fonction `fn`. + ::::: + +Nous avons vu comment créer et nous dissocier d'un espace de nom. Maintenant +voyons comment en rejoindre un déjà existant. diff --git a/tutorial/4/intro.md b/tutorial/4/intro.md index fef7151..4e3ce96 100644 --- a/tutorial/4/intro.md +++ b/tutorial/4/intro.md @@ -10,17 +10,18 @@ notre machine. Ces fonctionnalités sont très utiles pour éviter les dénis de service, mais nos processus ne sont pas particulièrement isolés du reste du système. On aimerait maintenant que nos processus n'aient pas accès à l'ensemble des fichiers, ne -puissent pas interagir avec les autres processus, avoir sa propre pile +puissent pas interagir avec les autres processus, avoir leur propre pile réseau, ... Voyons maintenant les *namespaces* qui vont nous permettre de faire cela. -Initiation rapide ------------------ +Présentation des *namespaces* +----------------------------- Les espaces de noms du noyau, que l'on appelle *namespaces*, permettent de dupliquer certaines structures, habituellement considérées uniques pour le -noyau, dans le but de les isoler d'un groupe de processus à un autre. +noyau, dans le but qu'un groupe de processus soit isolé d'autres processus, sur +certains aspects de l'environnement dans lequel il s'exécute. On en dénombre huit (le dernier ayant été ajouté dans Linux 5.6) : `cgroup`, `IPC`, `network`, `mount`, `PID`, `time`, `user` et `UTS`. @@ -33,12 +34,13 @@ que AppArmor, SELinux, Yama, ...). [^NSDOC]: +Commençons par passer en revue rapidement les différents *namespaces*. #### L'espace de noms `mount` {.unnumbered #mount-ns} Depuis Linux 2.4.19. -Cet espace de noms isole la liste des points de montage. +Cet espace de noms dissocie la liste des points de montage. Chaque processus appartenant à un *namespace mount* différent peut monter, démonter et réorganiser à sa guise les points de montage, sans que cela n'ait @@ -47,6 +49,9 @@ donc pas nécessairement démontée après un appel à `umount(2)`, elle le sera lorsqu'elle aura effectivement été démontée de chaque *namespace mount* dans lequel elle était montée. +Il s'agit d'une version améliorée de nos bons vieux `chroot`, puisqu'il n'est +plus possible de s'en échapper en remontant l'arborescence. + #### L'espace de noms `UTS` {.unnumbered #uts-ns} @@ -136,7 +141,7 @@ Depuis Linux 5.6. Avec cet espace de noms, il n'est pas possible de virtualiser l'heure d'un de nos conteneurs (on peut seulement changer le fuseau horaire, puisqu'ils sont -gérés par la `libc`). Les horloges virtualisations avec ce *namespace* sont les +gérés par la `libc`). Les horloges virtualisées avec ce *namespace* sont les compteurs `CLOCK_MONOTONIC` et `CLOCK_BOOTTIME`. Lorsque l'on souhaite mesurer un écoulement de temps, la méthode naïve consiste diff --git a/tutorial/4/lifetime.md b/tutorial/4/lifetime.md index 5b595f5..0a89561 100644 --- a/tutorial/4/lifetime.md +++ b/tutorial/4/lifetime.md @@ -30,6 +30,7 @@ obtenir un descripteur de fichier valide vers le *namespace* (pour passer à ::::: {.question} #### Faire persister un *namespace* ? {-} +\ Il n'est pas possible de faire persister un espace de noms d'un reboot à l'autre.\ diff --git a/tutorial/4/lsns.md b/tutorial/4/lsns.md new file mode 100644 index 0000000..ec99913 --- /dev/null +++ b/tutorial/4/lsns.md @@ -0,0 +1,119 @@ +Explorons les *namespaces* +-------------------------- + +Maintenant que nous avons quelques notions de base sur les espaces de nom, +voyons quelles surprises nous réserve notre système... + + +### Voir les *namespace*s de notre système + +La première commande que l'on va utiliser est `lsns(8)`, afin d'afficher tous +les *namespace*s actuels de notre machine. + +
+```bash +42sh# lsns + NS TYPE NPROCS PID USER COMMAND +4026531834 time 238 1 root /sbin/init +4026531835 cgroup 238 1 root /sbin/init +4026531836 pid 239 1 root /sbin/init +4026531837 user 227 1 root /sbin/init +4026531838 uts 231 1 root /sbin/init +4026531839 ipc 228 1 root /sbin/init +4026531840 net 228 1 root /sbin/init +4026531841 mnt 223 1 root /sbin/init +4026532230 uts 1 227 root ├─/usr/lib/systemd/systemd-udevd +4026532483 mnt 4 366 root ├─/usr/lib/systemd/systemd-userdbd +4026532485 mnt 1 363 systemd-resolve ├─/usr/lib/systemd/systemd-resolved +4026532486 mnt 1 364 systemd-timesync ├─/usr/lib/systemd/systemd-timesyncd +4026532491 mnt 1 381 root ├─/usr/lib/systemd/systemd-logind +4026532569 mnt 1 428 systemd-network └─/usr/lib/systemd/systemd-networkd +4026531862 mnt 1 33 root kdevtmpfs +[...] +``` +
+ +Cette commande nous dévoile déjà de nombreuses choses : + +- Chaque processus se trouve dans un *namespace* de chaque type : le noyau n'a + pas de notion de « processus hôte » (sans *namespace*) et « processus + contenerisé » (dans un *namespace*). Le processus initial de la machine se + retrouve donc dans des espaces de nom, tout comme les processus d'un conteneur. + +- On aperçoit un genre de hiérarchie dans certain cas. + +- La première colonne nous renseigne sur l'identifiant du *namespace*. + +- `kdevtmpfs` : un *thread* du noyau, s'exécute dans un espace de nom `mnt` + dédié. + + +Vous verrez surement davantage de processus si vous exécutez cette commande sur +une machine que vous utilisez : chaque conteneur y sera bien entendu listé, +quelque soit la technologie sous-jacente (Docker, podman, CRI-O, snapd, ...), +mais chose plus étonnante, Chrome et Firefox tirent également parti des espaces +de nom pour de la défense en profondeur. + + +### Voir les *namespace*s d'un processus + +Chaque processus lancé est donc rattaché à une liste d'espaces de nom, y +compris s'il est issu du système de base (« l'hôte »). + +Nous pouvons dès lors consulter le dossier `/proc//ns/` de chaque +processus, pour consulter les différents espaces de nom de nos processus. + +Tous les processus ont la même liste de fichiers. Ils sont tous liés à un +espace de noms par *namespace* utilisable avec la version noyau dont on +dispose. D'une machine à l'autre, d'une version du noyau à l'autre, il est +normal d'avoir une liste de *namespace*s différente, mais d'un processus à +l'autre sur un même noyau, nous aurons les mêmes espaces de nom disponibles, +donc les mêmes fichiers. + +Ces fichiers sont en fait des liens symboliques un peu particuliers, car ils ne +pointent pas vers une destination "valide" : + +
+```bash +42sh$ ls -l /proc/self/ns +lrwxrwxrwx 1 nemunaire 1 oct. 23:42 cgroup -> 'cgroup:[4026531835]' +lrwxrwxrwx 1 nemunaire 1 oct. 23:42 ipc -> 'ipc:[4026531839]' +lrwxrwxrwx 1 nemunaire 1 oct. 23:42 mnt -> 'mnt:[4026531840]' +lrwxrwxrwx 1 nemunaire 1 oct. 23:42 net -> 'net:[4026532008]' +lrwxrwxrwx 1 nemunaire 1 oct. 23:42 pid -> 'pid:[4026531836]' +lrwxrwxrwx 1 nemunaire 1 oct. 23:42 pid_for_children -> 'pid:[4026531836]' +lrwxrwxrwx 1 nemunaire 1 oct. 23:42 time -> 'time:[4026531834]' +lrwxrwxrwx 1 nemunaire 1 oct. 23:42 time_for_children -> 'time:[4026531834]' +lrwxrwxrwx 1 nemunaire 1 oct. 23:42 user -> 'user:[4026531837]' +lrwxrwxrwx 1 nemunaire 1 oct. 23:42 uts -> 'uts:[4026531838]' +``` +
+ +Les liens référencent une structure du noyau résidant en mémoire. Les numéros +entre crochets sont les *inodes* du système de fichiers `nsfs`, que l'on a pu +voir dans la première colonne de `lsns(8)`. Ce sont les identifiants des +espaces de nom. + +Ces *inodes* seront les mêmes pour deux processus qui partagent le même espace +de noms : la structure pointée sera identique. Elle sera par contre différente +si l'espace de nom est différent, l'*inode* sera donc différent. + + +::::: {.question} + +##### `*_for_children` {-} + +Vous avez peut-être remarqué des fichiers `*_for_children` dans le dossier `ns` +de vos processus. Les espaces de noms *PID* et *Time*, lorsqu'on les change +pour un processus, ne s'appliquent pas directement au processus en cours +d'exécution, la dissociation de *namespace* ne pourra se faire que pour les +processus/threads fils. + +`pid_for_children` et `time_for_children` représentent donc les *namespace*s +qui seront attribués aux processus fils lancés par un `clone(2)` ou un +`fork(2)`. + +En attendant notre processus courant conserve ses espaces de nom `pid` et +`time`. + +::::: diff --git a/tutorial/4/mount.md b/tutorial/4/mount.md index 28b876c..b40d33d 100644 --- a/tutorial/4/mount.md +++ b/tutorial/4/mount.md @@ -187,7 +187,9 @@ mount -t tmpfs none /mnt/test-shared/foo ```
-Le point de montage apparaît bien sous `/mnt/test-slave/foo`. Par contre : +Le point de montage apparaît bien sous `/mnt/test-slave/foo`. + +Par contre :
```bash @@ -288,10 +290,15 @@ mount --move /dev /newroot/dev ```
+::::: {.question} + +#### Quand a-t-on besoin de déplacer un point de montage ? {-} +\ Cette possibilité s'emploie notamment lorsque l'on souhaite changer la racine de notre système de fichiers : par exemple pour passer de l'*initramfs* au -système démarré, de notre système hôte au système d'un conteneur, ... +système démarré, ou encore de notre système hôte au système d'un conteneur, ... +::::: ## Aller plus loin {-} diff --git a/tutorial/4/mountns.md b/tutorial/4/mountns.md index d70b44f..4fa7f09 100644 --- a/tutorial/4/mountns.md +++ b/tutorial/4/mountns.md @@ -1,5 +1,3 @@ -\newpage - Le *namespace* `mount` ---------------------- @@ -20,7 +18,7 @@ montage, récursivement, dès que l'on est entré dans notre nouvel espace de noms.
-```bash +``` mount --make-rslave / ```
@@ -45,7 +43,7 @@ montage virtuels. Le changement de racine sera donc effectif uniquement dans cet espace de noms. -#### L'environnement +#### L'environnement\ Pour pouvoir changer de racine, il est nécessaire que la nouvelle racine soit la racine d'un point de montage, comme l'explique `pivot_root(2)`. En effet, il @@ -55,7 +53,7 @@ ne se trouvait pas à la racine d'une partition au moment du basculement. Si vous n'avez pas de partition à disposition, vous pouvez utiliser un `tmpfs` :
-```bash +``` 42sh# mkdir /mnt/newroot 42sh# mount -t tmpfs none /mnt/newroot ``` @@ -75,7 +73,7 @@ Voici les grandes étapes du changement de racine : 3. `pivot_root` ! -#### S'isoler +#### S'isoler\ Notre but étant de démonter toutes les partitions superflues, nous allons devoir nous isoler sur : @@ -89,48 +87,54 @@ devoir nous isoler sur : Isolons-nous :
-```bash +``` 42sh# unshare -p -m -f --mount-proc ```
+::::: {.warning} -#### Dissocier la propagation des démontages - -Attention ! avant de pouvoir commencer à démonter les partitions, il faut +Avant de pouvoir commencer à démonter les partitions, il faut s'assurer que les démontages ne se propagent pas via une politique de *shared mount*. +::::: + + +#### Dissocier la propagation des démontages\ + Commençons donc par étiqueter tous nos points de montage (de ce *namespace*), comme esclaves :
-```bash +``` 42sh# mount --make-rslave / ```
-#### Démonter tout ! +#### Démonter tout !\ À vous maintenant de démonter vos points d'attache. Il ne devrait vous rester après cette étape que : `/`, `/dev`, `/sys`, `/proc`, `/run` et leurs fils. -#### Switch ! +#### Switch !\ À ce stade, dans votre console, vous avez plusieurs solutions : utiliser `switch_root(8)` ou `pivot_root(8)`. La première abstrait plus de choses que la seconde. -##### `switch_root`\ +##### `switch_root` {-} +\ Cette commande s'occupe de déplacer les partitions restantes pour vous, et lance la première commande (*init*) de votre choix. -##### `pivot_root`\ +##### `pivot_root` {-} +\ Cette commande, plus proche du fonctionnement de l'appel système `pivot_root(2)`, requiert de notre part que nous ayons préalablement déplacé @@ -147,9 +151,58 @@ Pour lancer la première commande dans la nouvelle racine, on passe généraleme par :
-```bash +``` 42sh# exec chroot / command ```
+#### Erreurs courantes {-} +\ + +Voici une liste des erreurs les plus courantes que vous allez sans doute rencontrer : + +`EINVAL` +: - La nouvelle racine n'est pas un point de montage. +: - Le chemin où placer l'ancienne racine n'est pas sur la nouvelle racine. +: - Le point de montage de la nouvelle ou l'ancienne racine utilise le type de propagation *shared*. + +`EBUSY` +: La nouvelle racine ou le chemin où mettre l'ancienne racine se trouve sur la racine actuelle. + +`ENOTDIR` +: La nouvelle racine ou le chemin où mettre l'ancienne racine ne sont pas des dossiers. + +`EPERM` +: Le processus appelant ne dispose pas de la capability `CAP_SYS_ADMIN`. + +Vous pouvez retrouver toutes les erreurs dans le manuel de `pivot_root(2)`. + +#### Assemblage {-} +\ + +Vous devriez maintenant pouvoir réaliser un script `myswitch_root.sh` qui +enchaîne toutes les étapes précédentes. + +On considère préalablement que l'environnement est propice à la réalisation de ce script : + +
+``` +42sh# mkdir -p /mnt/newroot +42sh# mount -t tmpfs none /mnt/newroot +42sh# wget https://dl-cdn.alpinelinux.org/alpine/v3.14/releases/x86_64/alpine-minirootfs-3.14.8-x86_64.tar.gz +42sh# tar xpf alpine-minirootfs-*.tar.gz -C /mnt/newroot +42sh# cd / +``` +
+ +Puis on s'attend à le lancer ainsi : + +
+``` +42sh# ~/myswitch_root.sh /mnt/newroot /bin/bash + innewns# _ +``` +
+ + ::::: diff --git a/tutorial/4/networkns.md b/tutorial/4/networkns.md index 3060a1a..aec2103 100644 --- a/tutorial/4/networkns.md +++ b/tutorial/4/networkns.md @@ -1,8 +1,9 @@ -\newpage - Le *namespace* `network` {#net-ns} ------------------------ +Voyons maintenant plus en détail les différents espaces de nom, leurs +caractéristiques et leurs usages ; en commençant par le *namespace* `network`. + ### Introduction L'espace de noms `network`, comme son nom l'indique permet de virtualiser tout @@ -14,16 +15,25 @@ environnement qui n'a plus qu'une interface de *loopback* :
``` -42sh# unshare -n ip a +42sh# unshare --net ip a 1: lo: mtu 65536 qdisc noop state DOWN group default qlen 1 link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 ```
+::::: {.warning} + Bien que portant le même nom que l'interface de *loopback* de notre environnement principal, il s'agit bien de deux interfaces isolées l'une de l'autre. +::::: + +Afin d'amener du réseau à notre nouvel espace de nom, il va falloir lui +attribuer des interface. En fait, nous allons pouvoir déplacer nos interfaces +réseaux, dans le *namespace* vers lequel elle doit être accessible. Une +interface donnée ne peut se trouver que dans un seul *namespace* à la fois. + Qui dit nouvelle pile réseau, dit également que les ports qui sont assignés dans l'espace principal, ne le sont plus dans le conteneur : il est donc possible de lancer un serveur web sans qu'il n'entre en conflit avec celui d'un @@ -38,17 +48,51 @@ La suite d'outils `iproute2` propose une interface simplifiée pour utiliser le Nous pouvons tout d'abord créer un nouvel espace de noms :
-```bash +``` 42sh# ip netns add virli ```
+::::: {.code} + La technique utilisée ici pour avoir des *namespaces* nommés est la même que celle que nous avons vue dans [la première partie sur les *namespaces*](#ns-lifetime) : via un `mount --bind` dans le dossier `/var/run/netns/`. Cela permet de faire persister le namespace malgré le fait que plus aucun processus ne s'y exécute. +Nous pouvons créer artificiellement des entrées pour `ip netns` avec les +quelques commandes suivantes : + +
+``` +# On affiche la liste des netns déjà créés +42sh# ip netns +virli + +# On crée un fichier pour servir de réceptacle au bind mount +42sh# touch /var/run/netns/foo + +# On crée un nouveau namespace net, puis on bind tout de suite le +# fichier de namespace vers le fichier que l'on vient de créer +42sh# unshare --net \ + mount --bind /proc/self/ns/net /var/run/netns/foo + +# Testons si cela a bien marché +42sh# ip netns +foo virli +42sh# ip netns exec foo ip link +1: lo: mut 65536 qdisc noop state DOWN mode DEFAULT group default + link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 +``` +
+ +Les fichiers utilisés par `ip netns` ne sont donc rien de plus que des +*bind-mount*. Ce qui explique qu'ils soient persistant même sans processus +s'exécutant à l'intérieur. + +::::: + Maintenant que notre *namespace* est créé, nous pouvons regarder s'il contient des interfaces : @@ -67,7 +111,7 @@ D'ailleurs, cette interface est rapportée comme étant désactivée, activons-l via la commande :
-```bash +``` 42sh# ip netns exec virli ip link set dev lo up ```
@@ -105,6 +149,171 @@ entre d'un côté sort de l'autre et inversement. La commande précédente a don créé deux interfaces `veth0` et `veth1` : les paquets envoyés sur `veth0` sont donc reçus par `veth1` et les paquets envoyés à `veth1` sont reçus par `veth0`. +::::: {.code} + +Pour réaliser ces étapes dans un langage de programmation, nous allons passer +par Netlink. Il s'agit d'une interface de communication entre le noyau et +l'espace utilisateur. Il faut commencer par créer une `socket` pour avoir accès +à l'API Netlink, nous pourrons ensuite envoyer des requêtes, comme celle nous +permettant de créer notre interface `veth`. + +Un message Netlink a une structure, alignée sur 4 octets, contenant un en-tête +et des données. Le format de l'en-tête est décrit dans le RFC +3549[^RFC3549] : + +[^RFC3549]: + +
+```c +struct nlmsghdr { + uint32_t nlmsg_len; // Length of message including header + uint16_t nlmsg_type; // Type of message content + uint16_t nlmsg_flags; // Additional flags + uint32_t nlmsg_seq; // Sequence number + uint32_t nlmsg_pid; // Sender port ID +}; +``` +
+ +Parmi les fonctionnalités de Netlink, nous allons utiliser le module NIS +(Network Interface Service)[^REF3549NIS]. Il spécifie le format par lequel +doivent commencer les données liées à l'administration d'interfaces réseau. + +[^RFC3549NIS]: Network Interface Service Module + +
+```c +struct ifinfomsg { + uint8_t ifi_family; // AF_UNSPEC + // uint8_t Reserved + uint16_t ifi_type; // Device type + int32_t ifi_index; // Interface index + uint32_t ifi_flags; // Device flags + uint32_t ifi_change; // change mask +}; +``` +
+ +Le module NIS a besoin que les données soient transmises sous forme d'*attributs +Netlink*. Ces attributs fournissent un moyen de segmenter la charge utile en +sous-sections. Un attribut a une taille et un type, en plus de sa charge utile. + +
+```c +struct rtattr { + uint16_t rta_len; // Length of option + uint16_t rta_type; // Type of option + // Data follows +}; +``` +
+ +Le *payload* du message Netlink sera donc transmis comme une liste d'attributs (où +chaque attribut peut à son tour avoir des attributs imbriqués). + +
+```c +struct rtattr { + unsigned short rta_len; + unsigned short rta_type; +}; +``` +
+ +En se basant sur du code d'`ip link`[^IPLINK], on peut reconstituer la +communication suivante : + +[^IPLINK]: + +
+```c +#define MAX_PAYLOAD 1024 + +struct nlreq { + struct nlmsghdr hdr; // Netlink message header + struct ifinfomsg msg; // First data filled with NIS module info + char buf[MAX_PAYLOAD]; // Remaining payload +}; + +int +create_veth(char *ifname, char *peername) +{ + // Create the netlink socket + int sock_fd = socket(PF_NETLINK, SOCK_RAW | SOCK_CLOEXEC, NETLINK_ROUTE); + if (sock_fd < 0) + return -1; + + uint16_t flags = + NLM_F_REQUEST // We build a request message + | NLM_F_CREATE // Create a device if it doesn't exist + | NLM_F_EXCL // Do nothing if it already exists + | NLM_F_ACK; // Except an ack as reply or an error + + // Initialise request message + struct nl_req req = { + .hdr.nlmsg_len = NLMSG_LENGTH(sizeof(struct ifinfomsg)), + .hdr.nlmsg_flags = flags, + .hdr.nlmsg_type = RTM_NEWLINK, + .msg.ifi_family = PF_NETLINK, + }; + struct nlmsghdr *hdr = &req.hdr; + int maxlen = sizeof(req); + + // Attribute r0 with veth info + addattr_l(hdr, maxlen, IFLA_IFNAME, ifname, strlen(ifname) + 1); + + // Attribute r1 nested within r1, containing iface info + struct rtattr *linfo = + addattr_nest(n, maxlen, IFLA_LINKINFO); + // Specify the device type is veth + addattr_l(hdr, maxlen, IFLA_INFO_KIND, "veth", 5); + + // r2: another nested attribute + struct rtattr *linfodata = + addattr_nest(hdr, maxlen, IFLA_INFO_DATA); + + // r3: nested attribute, contains the peer name + struct rtattr *peerinfo = + addattr_nest(n, maxlen, VETH_INFO_PEER); + n->nlmsg_len += sizeof(struct ifinfomsg); + addattr_l(n, maxlen, IFLA_IFNAME, peername, strlen(peername) + 1); + addattr_nest_end(hdr, peerinfo); + + addattr_nest_end(hdr, linfodata); + addattr_nest_end(hdr, linfo); + + // Send the message + sendmsg(sock_fd, &req, 0); +} +``` +
+ +Maintenant que l'on a notre interface virtuelle, nous pouvons envoyer un second +message pour la déplacer dans un nouveau *namespace*[^IPSETNS] : + +[^IPSETNS]: + +
+```c + // Initialise request message + struct nl_req req = { + .hdr.nlmsg_len = NLMSG_LENGTH(sizeof(struct ifinfomsg)), + .hdr.nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK, + .hdr.nlmsg_type = RTM_NEWLINK, + .msg.ifi_family = PF_NETLINK, + }; + + addattr_l(&req.hdr, sizeof(req), IFLA_NET_NS_FD, &netns, 4); + addattr_l(&req.hdr, sizeof(req), IFLA_IFNAME, + ifname, strlen(ifname) + 1); + + sendmsg(sock_fd, &req, 0); +``` +
+ + +::::: + Dans cette configuration, ces deux interfaces ne sont pas très utiles, mais si l'on place l'une des deux extrémités dans un autre *namespace* `network`, il devient alors possible de réaliser un échange de paquets entre les deux. @@ -112,7 +321,7 @@ devient alors possible de réaliser un échange de paquets entre les deux. Pour déplacer `veth1` dans notre *namespace* `virli` :
-```bash +``` 42sh# ip link set veth1 netns virli ```
@@ -120,7 +329,7 @@ Pour déplacer `veth1` dans notre *namespace* `virli` : Il ne reste maintenant plus qu'à assigner une IP à chacune des interfaces :
-```bash +``` 42sh# ip netns exec virli ip a add 10.10.10.42/24 dev veth1 42sh# ip a add 10.10.10.41/24 dev veth0 ``` @@ -150,8 +359,9 @@ couches du noyau. Utiliser les interfaces *veth* est plutôt simple et disponibl partout, mais c'est loin d'être la technique la plus rapide ou la moins gourmande. +Voyons ensemble les autres possibilités à notre disposition. -#### VLAN +#### VLAN \ Il est possible d'attribuer juste une interface de VLAN, si l'on a un switch supportant la technologie [802.1q](https://fr.wikipedia.org/wiki/IEEE_802.1Q) @@ -165,8 +375,11 @@ derrière notre machine. ```
+On attribuera alors à chaque conteneur une interface de VLAN différente. Cela +peut donner lieu à une configuration de switch(s) assez complexe. -#### MACVLAN + +#### MACVLAN \ @@ -176,11 +389,11 @@ MACVLAN. S'il est activé dans votre noyau, vous allez avoir le choix entre l'un des quatre modes : *private*, VEPA, *bridge* ou *passthru*. Quel que soit le mode choisi, les paquets en provenance d'autres machines et à -destination d'un MAC seront délivrés à l'interface possédant la MAC. Les +destination d'une MAC seront délivrés à l'interface possédant ladite MAC. Les différences entre les modes se trouvent au niveau de la communication entre les interfaces. -##### VEPA +##### VEPA \ Dans ce mode, tous les paquets sortants sont directement envoyés sur l'interface Ethernet de sortie, sans qu'aucun routage préalable n'ait été @@ -198,7 +411,7 @@ Pour construire une nouvelle interface de ce type :
-##### *Private* +##### *Private* \ À la différence du mode *VEPA*, si un paquet émis par un conteneur à destination d'un autre conteneur est réfléchi par un switch, le paquet ne sera @@ -214,12 +427,12 @@ conteneur de la même machine. -##### *Bridge* +##### *Bridge* \ -À l'inverse des modes *VEPA* et *private*, les paquets sont routés selon leur -adresse MAC : si jamais une adresse MAC est connue, le paquet est délivré à -l'interface MACVLAN correspondante ; dans le cas contraire, le paquet est -envoyé sur l'interface de sortie. +En mode *Bridge*, les paquets sont routés selon leur adresse MAC : si jamais +une adresse MAC est connue, le paquet est délivré à l'interface MACVLAN +correspondante ; dans le cas contraire, le paquet est envoyé sur l'interface de +sortie. Pour construire une nouvelle interface de ce type : @@ -230,6 +443,28 @@ Pour construire une nouvelle interface de ce type : +##### *passthru* \ + +Enfin, le mode *passthru* permet de récupérer le contrôle sur tout ce qu'il +reste du périphérique initial (notamment pour lui changer sa MAC propre, ou +pour activer le mode de promiscuité). + +L'intérêt est surtout de pouvoir donner cette interface à un conteneur ou une +machine virtuelle, sans lui donner un accès complet à l'interface physique (et +notamment aux autres MACVLAN). + +On construit l'interface en mode *passthru* de cette façon : + +
+``` +42sh# ip link add link eth0 mac3 type macvlan mode passthru +``` +
+ +Une seule interface MACVLAN peut être en mode *passthru* par interface +physique. + + ### Aller plus loin {-} Pour approfondir les différentes techniques de routage, je vous diff --git a/tutorial/4/pidns.md b/tutorial/4/pidns.md index 31386f4..418b992 100644 --- a/tutorial/4/pidns.md +++ b/tutorial/4/pidns.md @@ -1,5 +1,3 @@ -\newpage - Le *namespace* `PID` {#pid-ns} -------------------- @@ -23,13 +21,92 @@ trouve. Première étape s'isoler :
-```bash +``` 42sh# unshare --pid --fork /bin/bash + inpidns# echo $$ + 1 ```
-Nous utilisons ici l'option `-f`, pour que le passage dans le nouvel espace de -noms des PID soit effectif (cf. [Introduction](#pid-ns-intro)). +::::: {.question} + +#### Qu'est-ce qu'il se passe sans l'option `--fork` ? {-} +\ + +Nous utilisons ici l'option `--fork`, pour que le passage dans le nouvel espace +de noms des PID soit effectif (cf. [Introduction](#pid-ns-intro)). Si on +l'omet, voici ce qu'il se passe : + +
+``` +42sh# unshare --pid /bin/sh + inpidns# echo $$ + 23456 +``` +
+ +Ce n'est apparemment pas ce que l'on souhaitait : on est toujours dans l'ancien +*namespace*, puisqu'on a juste `unshare(2)`, sans `fork(2)`. Si l'on va plus +loin et que l'on `fork` en demandant à `sh` d'exécuter un nouveau processus : + +
+``` +42sh# unshare --pid /bin/sh + inpidns# echo $$ + 34567 + inpidns# sh + inpidns# echo $$ + 1 +``` +
+ +C'est d'ailleurs le bon moment pour regarder le contenu de notre fichier +`pid_for_children` pour le processus `34567` : + +
+``` +42sh# ls -l /proc/34567/ns/pid* +lrwxrwxrwx 1 nemunaire 1 oct. 23:42 pid -> 'pid:[4026531836]' +lrwxrwxrwx 1 nemunaire 1 oct. 23:42 pid_for_children -> 'pid:[4026532993]' +``` +
+ +On remarque bien que l'on se trouve dans un entre-deux : `pid` pointe toujours +sur l'*inode* de l'arbre de PID initial, tandis que `pid_for_children` est prêt +à transmettre le nouvel *inode* aux nouveaux processus. +\ + +#### Pourquoi ça ne fonctionne pas du tout avec `bash` ? {-} +\ + +Si on utilise `bash` à la place de `sh` dans les exemples précédents, toujours +sans l'option `--fork`, on obtient une erreur plutôt étrange : + +
+``` +42sh# unshare --pid /bin/bash + inpidns# echo $$ + 65432 + inpidns# sh + -bash: fork: Cannot allocate memory +``` +
+ +Il se trouve que `bash` commence par `fork(2)` lui-même afin de réaliser un +certain nombre d'opérations. Notre PID 1, le premier PID de notre conteneur, a +donc été alloué à un processus d'initialisation de `bash`, qui s'est terminé +depuis.\ + +Le comportement du noyau, lorsque le PID 1 se termine, est de lancer un *kernel +panic* (car c'est un processus indispensable, notamment de part son rôle de +parent pour tous les processus orphelin). Au sein d'un *namespace* `PID` qui +n'est pas le *namespace* racine, le noyau appelle la fonction +`disable_pid_allocation` qui retire le *flag* `PIDNS_HASH_ADDING` de l'espace +de nom. Le fait de ne pas avoir `PIDNS_HASH_ADDING` fait retourner `ENOMEM` à +la fonction `alloc_pid` appelée par `fork(2)` et `clone(2)`. On n'a donc pas +d'autre choix que de quitter ce *namespace* pour en recréer un nouveau. + +::::: Un coup d'œil à `top` ou `ps aux` devrait nous montrer que l'on est maintenant le seul processus ... pourtant, il n'en est rien, ces deux commandes continuent @@ -45,12 +122,12 @@ notre système initial. Pour s'en sortir, il est nécessaire de s'isoler dans un *namespace* `mount` séparé. -#### Double isolation : ajout du *namespace* `mount` +### Double isolation : ajout du *namespace* `mount` Voici la nouvelle ligne de commande que l'on va utiliser :
-```bash +``` 42sh# unshare --pid --mount --fork --mount-proc /bin/bash ```
@@ -90,11 +167,11 @@ de noms. [^PR_SET_CHILD_SUBREAPER]: en réalité, ce comportement est lié à la propriété `PR_SET_CHILD_SUBREAPER`, qui peut être définie pour n'importe quel processus - de l'arborescence. Le processus au PID 1 hérite forcément de cette propriété\ ; + de l'arborescence. Le processus au PID 1 hérite forcément de cette propriété ; il va donc récupérer tous les orphelins, si aucun de leurs parents n'a la propriété définie. -Lorsque l'on lance un processus via `nsenter(1)` ou `setns(2)`, cela crée un +Lorsqu'on lance un processus via `nsenter(1)` ou `setns(2)`, cela crée un processus qui n'est sans doute pas un fils direct du processus d'`init` de notre conteneur. Malgré tout, même s'il est affiché comme n'étant pas un fils à l'extérieur du conteneur, les propriétés d'`init` sont biens appliquées à diff --git a/tutorial/4/rendu.md b/tutorial/4/rendu.md index 1f8f270..ca1c02f 100644 --- a/tutorial/4/rendu.md +++ b/tutorial/4/rendu.md @@ -1,51 +1,65 @@ \newpage -Projet et rendu -=============== +Rendu +===== -Est attendu d'ici le TP suivant : +Est attendu d'ici le cours suivant : -- le rendu des exercice de ce TP ; -- vos réponses à [l'évaluation du cours](https://virli.nemunai.re/quiz/15). - -Pour les GISTRE (et en bonus pour les SRS), [un -projet](https://virli.nemunai.re/project-2.pdf) est à rendre pour le 13 -novembre. Consultez les modalités de rendu sur le sujet directement. - -## Modalités de rendu - -En tant que personnes sensibilisées à la sécurité des échanges électroniques, -vous devrez m'envoyer vos rendus signés avec votre clef PGP. - -Un service automatique s'occupe de réceptionner vos rendus, de faire des -vérifications élémentaires et de vous envoyer un accusé de réception (ou de -rejet). - -Ce service écoute sur l'adresse . C'est donc à cette adresse -et exclusivement à celle-ci que vous devez envoyer vos rendus. Tout rendu -envoyé à une autre adresse et/ou non signé et/ou reçu après la correction ne -sera pas pris en compte. - -Afin d'orienter correctement votre rendu, ajoutez une balise `[TP4]` au sujet -de votre courriel. N'hésitez pas à indiquer dans le corps du message votre -ressenti et vos difficultés ou bien alors écrivez votre meilleure histoire -drôle si vous n'avez rien à dire. +- vos réponses à l'évaluation du cours, +- [SRS] tous les exercices de ce TP, +- [GISTRE] l'avancement des paliers du [projet final](https://virli.nemunai.re/project-gistre.pdf). -Tarball +Arborescence attendue (SRS) ------- -Tous les exercices de ce TP sont à placer dans une tarball (pas d'archive ZIP, -RAR, ...). +Tous les fichiers identifiés comme étant à rendre sont à placer dans un dépôt +Git privé, que vous partagerez avec [votre +professeur](https://gitlab.cri.epita.fr/nemunaire/). -Voici une arborescence type (adaptez les extensions et les éventuels -fichiers supplémentaires associés au langage que vous aurez choisi -pour chaque exercice) : +Voici une arborescence type (vous pourriez avoir des fichiers supplémentaires) :
``` -login_x-TP4/cmpns.sh -login_x-TP4/mydocker_exec.sh -login_x-TP4/myswitch_root.sh +./cmpns.sh +./mydocker_exec.sh +./myswitch_root.sh ```
+ +Votre rendu sera pris en compte en faisant un [tag **signé par votre clef +PGP**](https://lessons.nemunai.re/keys). Consultez les détails du rendu (nom du +tag, ...) sur la page dédiée au projet sur la plateforme de rendu. + +::::: {.question} + +Si vous utilisez un seul dépôt pour tous vos rendus, vous **DEVRIEZ** +créer une branche distincte pour chaque rendu : + +
+``` +42sh$ git checkout --orphan renduX +42sh$ git reset +42sh$ rm -r * +42sh$ # Créer l'arborescence de rendu ici +``` +
+ +Pour retrouver ensuite vos rendus des travaux précédents : + +
+``` +42sh$ git checkout renduY +-- ou -- +42sh$ git checkout master +... +``` +
+ +Chaque branche est complètement indépendante l'une de l'autre. Vous +pouvez avoir les exercices du TP1 sur `master`, les exercices du TP4 +sur `rendu4`, ... ce qui vous permet d'avoir une arborescence +correspondant à ce qui est demandé, sans pour autant perdre votre +travail (ou le rendre plus difficile d'accès). + +:::: diff --git a/tutorial/4/setns-pid.md b/tutorial/4/setns-pid.md new file mode 100644 index 0000000..dce2216 --- /dev/null +++ b/tutorial/4/setns-pid.md @@ -0,0 +1,25 @@ +\ + +D'une manière similaire à notre dernier exemple, depuis Linux 5.8, `setns(2)` +peut utiliser comme premier argument, un *file descriptor* pointant un +processus (`pidfd`). + +Eh oui, outre l'obtention d'un *file descriptor* sur un lien symbolique étrange +d'une structure du noyau, il est possible d'en obtenir un sur un processus : + +- soit en réalisant un `open(2)` d'un dossier `/proc/` ; +- soit en utilisant l'appel système `pidfd_open(2)`, en précisant l'identifiant + du processus dont on souhaite obtenir le *file descriptor* ; +- soit en retour d'un `clone(2)` avec l'option `CLONE_PIDFD`. + +
+```bash +int fd = pidfd_open(42, 0); +setns(fd, CLONE_NEWUSER | CLONE_NEWNET | CLONE_NEWUTS); +``` +
+ +À travers cet exemple, on cherche à récupérer un *file descriptor* pour le +processus 42. On le passe ensuite à `setns(2)` en précisant que l'on ne +souhaite rejoindre que les espaces de noms Utilisateurs, Réseau et UTS (nom de +la machine). diff --git a/tutorial/4/setns-samples.md b/tutorial/4/setns-samples.md index ff31e6a..0152800 100644 --- a/tutorial/4/setns-samples.md +++ b/tutorial/4/setns-samples.md @@ -1,5 +1,5 @@ -#### Exemple C - +#### Exemple C {-} +\ Voici un exemple de code C utilisant `setns(2)` :
@@ -35,10 +35,58 @@ main(int argc, char *argv[]) ```
+Ce programme prend au minimum deux arguments : +- le chemin d'un fichier d'espace de nom que l'on souhaite rejoindre (le chemin + vers le lien symbolique donc) ; +- le programme (et ses arguments) que l'on souhaite souhaite exécuter une fois + que l'on a rejoint l'espace de noms ciblé. -#### Exemple shell +Dans un premier temps, on ouvre le fichier passé en paramètre afin d'obtenir un +*file descriptor* de la structure du noyau. -Dans un shell, on utilisera la commande `nsenter(1)` : +On passe ensuite ce *file descriptor* en argument de l'appel système +`setns(2)`. Et enfin on exécute la commande données dans les derniers +paramètres. + +::::: {.question} + +#### Qu'attend le deuxième argument de `setns(2)` ? {-} +\ + +Il s'agit d'une contrainte sur le type d'espace de nom que l'on souhaite +rejoindre, dans le cas où on ne souhaite pas rejoindre n'importe quel espace de +nom. + +Une fois encore, on utilisera les mêmes options `CLONE_NEW*` lorsque l'on +attendra un type particulier d'espace de nom, ou 0 pour autoriser tous les +types. + +::::: + + +::::: {.question} + +#### Peut-on connaître le type de *namespace* à partir du *file descriptor* ? {-} +\ + +Il est possible de récupérer le type d'espace de nom en passant notre *file +descriptor* à `ioctl(2)`, avec le *flag* `NS_GET_NSTYPE` : + +
+```c +nstype = ioctl(fd, NS_GET_NSTYPE); +if (nstype & CLONE_NEWUTS != 0) { + ... // This is a file descriptor to an UTS namespace +} +``` +
+ +::::: + + +#### Exemple shell {-} +\ +Dans un shell, nous utiliserons la commande `nsenter(1)` :
```bash @@ -55,3 +103,25 @@ chaton jument ```
+ +Avec `nsenter(1)`, il est possible de cibler un processus particulier, sans +aller nécessairement faire référence aux fichiers de `/proc`, en utilisant +l'option `--target` : + +
+```bash +42sh# unshare --uts /bin/bash + inutsns# echo $$ + 42 + inutsns# hostname jument + # Keep this shell active to perform nexts steps, in another shell. + +42sh# hostname +chaton +42sh# nsenter --target 42 --uts /bin/bash + inutsns# hostname + jument +``` +
+ +Les options supplémentaires `--uts`, `--mount`, `--ipc`, `--pid`, ... ne prennent alors pas d'argument, mais désignent les espaces de noms du processus ciblé que l'on souhaite rejoindre. diff --git a/tutorial/4/setns.md b/tutorial/4/setns.md index 566863b..9e75fdd 100644 --- a/tutorial/4/setns.md +++ b/tutorial/4/setns.md @@ -1,71 +1,15 @@ -\newpage - ### Rejoindre un *namespace* Rejoindre un espace de noms se fait en utilisant l'appel système `setns(2)`, ou la commande `nsenter(1)`. Il est nécessaire de donner en argument respectivement un *file descriptor* ou le chemin vers le fichier, lien -symbolique, représentant l'espace de nom. +symbolique, représentant l'espace de nom (dans `/proc//ns/...`). - -#### Voir les *namespace*s d'un processus - -Chaque processus lancé est rattaché à une liste d'espaces de nom, y compris -s'il est issu du système de base (« l'hôte »). - -Nous pouvons dès lors consulter le dossier `/proc//ns/` de chaque -processus, pour consulter les différents espaces de nom de nos processus. - -Tous les processus auront la même liste de fichiers, car ils sont liés à un -espace de noms par *namespace* utilisable avec le noyau. D'une machine à -l'autre, d'une version du noyau à l'autre, il est normal d'avoir une liste de -*namespace*s différents, mais d'un processus à l'autre sur le même noyau, nous -aurons les mêmes. - -Ces fichiers sont en fait des liens symboliques un peu particuliers, car ils ne -pointent pas vers une destination valide : - -
-```bash -42sh$ ls -l /proc/self/ns -lrwxrwxrwx 1 nemunaire 1 oct. 23:42 cgroup -> 'cgroup:[4026531835]' -lrwxrwxrwx 1 nemunaire 1 oct. 23:42 ipc -> 'ipc:[4026531839]' -lrwxrwxrwx 1 nemunaire 1 oct. 23:42 mnt -> 'mnt:[4026531840]' -lrwxrwxrwx 1 nemunaire 1 oct. 23:42 net -> 'net:[4026532008]' -lrwxrwxrwx 1 nemunaire 1 oct. 23:42 pid -> 'pid:[4026531836]' -lrwxrwxrwx 1 nemunaire 1 oct. 23:42 pid_for_children -> 'pid:[4026531836]' -lrwxrwxrwx 1 nemunaire 1 oct. 23:42 time -> 'time:[4026531834]' -lrwxrwxrwx 1 nemunaire 1 oct. 23:42 time_for_children -> 'time:[4026531834]' -lrwxrwxrwx 1 nemunaire 1 oct. 23:42 user -> 'user:[4026531837]' -lrwxrwxrwx 1 nemunaire 1 oct. 23:42 uts -> 'uts:[4026531838]' - -``` -
- -Les liens référencent une structure du noyau résidant en mémoire. Les numéros -entre crochets seront les mêmes pour deux processus qui partagent le même -espace de noms. La structure pointée sera différente si l'espace de nom est -différent, donc le numéro sera différent. - -On ne peut pas afficher tel quel les structures, mais on peut l'ouvrir avec -`open(2)` pour obtenir un *file descriptor* que l'on pourra passer à -`setns(2)`. +Une particularité de ces fichiers, que l'on ne peut pas afficher (leurs liens +ne pointent pas sur des fichiers que l'on peut atteindre), c'est que l'on peut +les ouvrir avec `open(2)` pour obtenir un *file descriptor* que l'on pourra +passer à `setns(2)`. Pour les commandes *shell*, il convient de donner en argument le chemin vers le -lien symbolique : la commande se chargera d'`open(2)` le fichier. - - -::::: {.question} - -##### `*_for_children` {-} - -Vous avez peut-être remarqué des fichiers `*_for_children` dans le dossier `ns` -de vos processus. Nous verrons par la suite que les espaces de noms *PID* et -*Time*, une fois créés, ne s'appliquent pas directement au processus en cours -d'exécution, la dissociation de *namespace* ne peut se faire que pour les -processus/threads fils. - -`pid_for_children` et `time_for_children` représentent donc les *namespace*s -qui seront attribués aux fils lancés après un `unshare(2)` réussi. - -::::: +lien symbolique : la commande se chargera d'`open(2)` le fichier pour obtenir +le *file descriptor* nécessaire. diff --git a/tutorial/4/tutorial.md b/tutorial/4/tutorial.md index f6cf426..478a477 100644 --- a/tutorial/4/tutorial.md +++ b/tutorial/4/tutorial.md @@ -3,7 +3,7 @@ title: Virtualisation légère -- TP n^o^ 4 subtitle: Linux Internals partie 2 author: Pierre-Olivier *nemunaire* [Mercier]{.smallcaps} institute: EPITA -date: Jeudi 7 octobre 2021 +date: Mercredi 9 novembre 2022 abstract: | Le but de ce second TP sur les mécanismes internes du noyau va nous permettre d'utiliser les commandes et les appels système relatifs @@ -12,15 +12,8 @@ abstract: | \vspace{1em} - Tous les exercices de ce TP sont à rendre à au - plus tard le mercredi 4 novembre 2021 à 23 h 42. Consultez la dernière - section de chaque partie pour plus d'informations sur les éléments à - rendre. Et n'oubliez pas de répondre aux [questions de - cours](https://virli.nemunai.re/quiz/13). - - En tant que personnes sensibilisées à la sécurité des échanges électroniques, - vous devrez m'envoyer vos rendus signés avec votre clef PGP. Pensez à - [me](https://keys.openpgp.org/search?q=nemunaire%40nemunai.re) faire signer - votre clef et n'hésitez pas à [faire signer la - vôtre](https://www.meetup.com/fr/Paris-certification-de-cles-PGP-et-CAcert/). + Les exercices de ce cours sont à rendre au plus tard le mardi 15 + novembre 2022 à 23 h 42. Consultez les sections matérialisées par un + bandeau jaunes et un engrenage pour plus d'informations sur les + éléments à rendre. ... diff --git a/tutorial/4/userns.md b/tutorial/4/userns.md index dc16ec8..13f4d77 100644 --- a/tutorial/4/userns.md +++ b/tutorial/4/userns.md @@ -1,5 +1,3 @@ -\newpage - Le *namespace* `user` {#user-ns} --------------------- @@ -23,8 +21,8 @@ utilisateur, on obtient les privilèges requis pour créer tous les autres types de *namespaces*. Grâce à cette technique, il est possible de lancer des conteneurs en tant que -simple utilisateur ; le projet [Singularity](https://sylabs.io/) repose -en grande partie sur cela. +simple utilisateur ; les projets [`podman`](https://podman.io/) et +[Singularity](https://sylabs.io/), ... reposent en grande partie sur cela. ### Correspondance des utilisateurs et des groupes @@ -34,7 +32,7 @@ garder dans le nouvel espace, que les utilisateurs et les groupes utiles au processus, en les renumérotant au passage si besoin. -#### L'utilisateur -2 : *nobody* +#### L'utilisateur -2 : *nobody*\ Lorsque l'on arrive dans un nouvel espace, aucun utilisateur ni groupe n'est défini. Dans cette situation, tous les identifiants d'utilisateur et de groupe, @@ -45,13 +43,14 @@ l'utilisateur *nobody* et au groupe *nogroup*. non-modification d'un paramètre passé en argument d'une fonction. -#### `uid_map` et `gid_map` +#### `uid_map` et `gid_map`\ Pour établir la correspondance, une fois que l'on a créé le nouveau *namespace*, ces deux fichiers, accessibles dans `/proc/self/`, peuvent être écrits une fois. -##### `uid_map`\ +##### `uid_map` {-} +\ Sur chaque ligne, on doit indiquer : @@ -89,13 +88,21 @@ l'utilisateur root, dans le conteneur équivaut à l'utilisateur 1000 hors de l'espace de noms. -##### `gid_map`\ +##### `gid_map` {-} +\ Le principe est identique pour ce fichier, mais agit sur les correspondances des groupes au lieu des utilisateurs. +Il y a cependant un subtilité car il faut écrire la chaîne `deny` dans le +fichier `setgroups` avant de modifier `gid_map`. -### Utilisation de l'espace de noms + +### Utilisation basique de l'espace de noms + +Avec `unshare(1)`, voici comment, en tant que simple utilisateur, se dissocier +de plusieurs *namespace*s en gardant un environnement cohérent, dans lequel on +devient le super-utilisateur :
```bash