diff --git a/tutorial/4/cgroupns.md b/tutorial/4/cgroupns.md new file mode 100644 index 0000000..33567b1 --- /dev/null +++ b/tutorial/4/cgroupns.md @@ -0,0 +1,305 @@ +Le *namespace* `cgroup` {#cgroupns} +----------------------- + +### Introduction + +L'espace de noms `cgroup`, introduit dans Linux 4.6, permet de virtualiser la +vue d'un processus sur l'arborescence des *Control Groups*. Au sein d'un +*namespace* `cgroup`, la racine vue par le processus correspond en fait à un +sous-groupe de l'arborescence globale. + + +### Pourquoi virtualiser les *cgroups* ? + +Sans le *namespace* `cgroup`, un processus dans un conteneur pourrait : + +- Voir l'arborescence complète des *cgroups* du système hôte +- Obtenir des informations sur d'autres conteneurs +- Comprendre la structure organisationnelle du système + +Le *namespace* `cgroup` permet de : + +- Masquer l'arborescence globale des *cgroups* +- Faciliter la migration de conteneurs (l'arborescence reste cohérente) +- Renforcer l'isolation entre conteneurs + + +### Utilisation pratique + +#### Observer les *cgroups* sans isolation\ + +Commençons par observer notre position dans l'arborescence des *cgroups* : + +
+```bash +42sh$ cat /proc/self/cgroup +0::/user.slice/user-1000.slice/session-3.scope +``` +
+ +Cette ligne nous indique que nous nous trouvons dans le *cgroup* unifié +`/user.slice/user-1000.slice/session-3.scope`. + +Nous pouvons également explorer l'arborescence complète : + +
+```bash +42sh$ ls /sys/fs/cgroup/ +cgroup.controllers cpuset.cpus.effective memory.pressure +cgroup.max.depth cpuset.mems.effective sys-fs-fuse-connections.mount +cgroup.max.descendants dev-hugepages.mount sys-kernel-config.mount +cgroup.procs dev-mqueue.mount sys-kernel-debug.mount +cgroup.stat init.scope sys-kernel-tracing.mount +cgroup.subtree_control io.pressure system.slice +cgroup.threads memory.numa_stat user.slice +cpu.pressure memory.stat +``` +
+ + +#### S'isoler dans un nouveau *namespace* `cgroup`\ + +Créons maintenant un nouveau *namespace* `cgroup` : + +
+```bash +42sh$ unshare --cgroup /bin/bash + incgroupns$ cat /proc/self/cgroup + 0::/ +``` +
+ +Notre processus se voit maintenant à la racine de l'arborescence des *cgroups* ! +En réalité, il se trouve toujours au même endroit dans l'arborescence globale, +mais sa vue a été virtualisée. + +Si nous regardons le contenu de `/sys/fs/cgroup/` : + +
+```bash + incgroupns$ ls /sys/fs/cgroup/ + cgroup.controllers cgroup.threads io.pressure + cgroup.events cpu.pressure memory.numa_stat + cgroup.freeze cpu.stat memory.pressure + cgroup.max.depth cpuset.cpus memory.stat + cgroup.max.descendants cpuset.cpus.effective + cgroup.procs cpuset.mems + cgroup.stat cpuset.mems.effective + cgroup.subtree_control io.stat +``` +
+ +Nous voyons seulement notre sous-arbre, pas l'arborescence complète du système. + + +#### Créer des sous-*cgroups*\ + +Dans notre nouveau *namespace*, nous pouvons créer des sous-groupes : + +
+```bash + incgroupns$ mkdir /sys/fs/cgroup/mysubgroup + incgroupns$ ls /sys/fs/cgroup/ + cgroup.controllers cgroup.threads mysubgroup + cgroup.events cpu.pressure ... + ... +``` +
+ +Ces sous-groupes sont en réalité créés dans notre branche de l'arborescence +globale. Depuis l'extérieur du *namespace*, ils apparaîtraient sous un chemin +différent. + + +### Exemple avec Docker + +Docker utilise le *namespace* `cgroup` pour isoler la vue des *cgroups* dans +les conteneurs. Observons cela : + +
+```bash +42sh$ docker run --rm -it debian /bin/bash + indocker# cat /proc/self/cgroup + 0::/ + + indocker# ls /sys/fs/cgroup/ + cgroup.controllers cpu.max memory.events + cgroup.events cpu.pressure memory.high + cgroup.freeze cpu.stat memory.low + cgroup.kill cpu.weight memory.max + cgroup.max.depth io.max memory.min + cgroup.max.descendants io.pressure memory.numa_stat + cgroup.procs io.stat memory.oom.group + cgroup.stat io.weight memory.pressure + cgroup.subtree_control memory.current memory.stat + cgroup.threads memory.events.local memory.swap.current + cgroup.type ... +``` +
+ +Le conteneur se voit à la racine, bien qu'il soit en réalité placé dans un +sous-groupe dédié de l'arborescence globale. + + +### Vérifier la position réelle dans l'arborescence + +Depuis l'hôte, nous pouvons voir la véritable position d'un conteneur : + +
+```bash +42sh$ docker inspect mycontainer | grep -A5 CgroupParent + "CgroupParent": "", + "CgroupDriver": "systemd", + +42sh$ ps aux | grep containerd-shim +root 12345 /usr/bin/containerd-shim-runc-v2 ... + +42sh$ cat /proc/12345/cgroup +0::/system.slice/docker-abc123def456.scope +``` +
+ +Le processus du conteneur se trouve dans un sous-groupe bien spécifique, mais +grâce au *namespace* `cgroup`, il se perçoit à la racine. + + +### Combinaison avec le *namespace* `mount` + +Pour que l'isolation soit complète, il faut généralement combiner le *namespace* +`cgroup` avec le *namespace* `mount` afin de remonter `/sys/fs/cgroup` : + +
+```bash +42sh# unshare --cgroup --mount /bin/bash + incgroupns# mount -t cgroup2 none /sys/fs/cgroup + incgroupns# ls /sys/fs/cgroup/ + # Vue limitée à notre sous-arbre +``` +
+ +Sans remonter le *cgroup*, nous verrions toujours l'arborescence complète via +l'ancien point de montage. + + +### Cas d'usage + +#### Migration de conteneurs\ + +Lors de la migration d'un conteneur avec des outils comme CRIU (Checkpoint/Restore +In Userspace), le *namespace* `cgroup` garantit que l'arborescence des *cgroups* +reste cohérente du point de vue du processus, même si la structure de l'hôte +cible est différente. + + +#### Isolation de sécurité\ + +Sans *namespace* `cgroup`, un processus malveillant dans un conteneur pourrait : + +- Lire `/sys/fs/cgroup/` pour découvrir d'autres conteneurs +- Analyser les métriques d'autres processus +- Déduire des informations sur l'architecture du système + + +#### Délégation de contrôle\ + +Le *namespace* `cgroup` permet de donner à un utilisateur non-privilégié le +contrôle d'une sous-arborescence de *cgroups*, sans lui donner accès à +l'arborescence globale. + + +### Exemple en C + +Voici un exemple de création d'un *namespace* `cgroup` en C : + +
+```c +#define _GNU_SOURCE +#include +#include +#include +#include +#include + +static int child_func(void *arg) +{ + FILE *fp; + char line[256]; + + printf("=== Dans le namespace cgroup ===\n"); + + fp = fopen("/proc/self/cgroup", "r"); + if (fp == NULL) { + perror("fopen"); + return EXIT_FAILURE; + } + + while (fgets(line, sizeof(line), fp) != NULL) { + printf(" %s", line); + } + + fclose(fp); + + return 0; +} + +#define STACK_SIZE (1024 * 1024) +static char child_stack[STACK_SIZE]; + +int main(void) +{ + pid_t pid; + FILE *fp; + char line[256]; + + printf("=== Avant le namespace cgroup ===\n"); + + fp = fopen("/proc/self/cgroup", "r"); + if (fp != NULL) { + while (fgets(line, sizeof(line), fp) != NULL) { + printf(" %s", line); + } + fclose(fp); + } + + printf("\n"); + + pid = clone(child_func, child_stack + STACK_SIZE, + CLONE_NEWCGROUP | SIGCHLD, NULL); + if (pid == -1) { + perror("clone"); + exit(EXIT_FAILURE); + } + + waitpid(pid, NULL, 0); + + return 0; +} +``` +
+ +::::: {.code} + +Pour compiler et exécuter : + +
+```bash +42sh$ gcc -o cgroup_ns_demo cgroup_ns_demo.c +42sh$ ./cgroup_ns_demo +=== Avant le namespace cgroup === + 0::/user.slice/user-1000.slice/session-3.scope + +=== Dans le namespace cgroup === + 0::/ +``` +
+ +::::: + + +### Aller plus loin {-} + +Pour plus d'informations : + +- `cgroup_namespaces(7)` - page de manuel +- [Cgroup Namespace](https://lwn.net/Articles/621006/) : +- [Control Group v2](https://www.kernel.org/doc/html/latest/admin-guide/cgroup-v2.html) : Documentation du noyau sur cgroup v2 diff --git a/tutorial/4/howto.md b/tutorial/4/howto.md index f4e5dbf..2f6eb9b 100644 --- a/tutorial/4/howto.md +++ b/tutorial/4/howto.md @@ -121,6 +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. +::::: {.more} + +#### Compilation des exemples C {-} +\ + +Pour compiler les programmes C utilisant les namespaces, aucune bibliothèque +spéciale n'est requise au-delà de la bibliothèque C standard : + +
+```bash +42sh$ gcc -o mon_programme mon_programme.c +``` +
+ +Notez que certains appels système utilisés avec les namespaces nécessitent la +définition de `_GNU_SOURCE` au début du fichier source pour accéder aux +extensions GNU : + +
+```c +#define _GNU_SOURCE +#include +// ... +``` +
+ +::::: + ::::: {.question} #### Quel est le rôle du *flag* `SIGCHLD` ? {-} diff --git a/tutorial/4/ipcns.md b/tutorial/4/ipcns.md new file mode 100644 index 0000000..d5d8a62 --- /dev/null +++ b/tutorial/4/ipcns.md @@ -0,0 +1,514 @@ +Le *namespace* `IPC` {#ipc-ns} +------------------- + +### Introduction + +L'espace de noms `IPC`, introduit dans Linux 2.6.19, isole les objets de +communication inter-processus (IPC) System V et les files de messages POSIX. + +Les objets isolés incluent : + +- Les files de messages System V (`msgget`, `msgsnd`, `msgrcv`) +- Les sémaphores System V (`semget`, `semop`) +- Les segments de mémoire partagée System V (`shmget`, `shmat`) +- Les files de messages POSIX (`mq_open`, `mq_send`, `mq_receive`) + +Une fois dans un *namespace* IPC différent, un processus ne peut communiquer +qu'avec les processus du même *namespace* via ces mécanismes IPC. + + +### Pourquoi isoler les IPC ? + +Sans isolation IPC, des processus dans différents conteneurs pourraient : + +- Communiquer entre eux via des files de messages partagées +- Accéder à la mémoire partagée d'autres conteneurs +- Interférer avec les sémaphores d'autres applications + +L'isolation IPC garantit que chaque conteneur dispose de son propre ensemble +d'objets IPC, renforçant ainsi la sécurité et l'isolation. + + +### Utilisation pratique + +#### Observer les objets IPC du système\ + +La commande `ipcs` permet de lister tous les objets IPC du système : + +
+```bash +42sh$ ipcs + +------ Message Queues -------- +key msqid owner perms used-bytes messages + +------ Shared Memory Segments -------- +key shmid owner perms bytes nattch status +0x00000000 32768 alice 600 524288 2 dest + +------ Semaphore Arrays -------- +key semid owner perms nsems +0x12345678 0 bob 666 1 +``` +
+ +Cette commande nous montre tous les objets IPC visibles dans notre *namespace* +actuel. + + +#### Créer une file de messages System V\ + +Créons d'abord une file de messages dans notre *namespace* initial : + +
+```c +// ipc_create.c +#include +#include +#include +#include + +int main(void) +{ + key_t key; + int msgid; + + // Créer une clé unique + key = ftok("/tmp", 'A'); + if (key == -1) { + perror("ftok"); + exit(EXIT_FAILURE); + } + + // Créer une file de messages + msgid = msgget(key, 0666 | IPC_CREAT); + if (msgid == -1) { + perror("msgget"); + exit(EXIT_FAILURE); + } + + printf("File de messages créée avec l'ID : %d\n", msgid); + printf("Clé : 0x%08x\n", key); + + return 0; +} +``` +
+ +::::: {.code} + +Compilation et exécution : + +
+```bash +42sh$ gcc -o ipc_create ipc_create.c +42sh$ touch /tmp/ipc_key_file +42sh$ ./ipc_create +File de messages créée avec l'ID : 0 +Clé : 0x41000001 + +42sh$ ipcs -q +------ Message Queues -------- +key msqid owner perms used-bytes messages +0x41000001 0 alice 666 0 0 +``` +
+ +::::: + + +#### S'isoler dans un nouveau *namespace* IPC\ + +Maintenant, créons un nouveau *namespace* IPC : + +
+```bash +42sh$ unshare --ipc /bin/bash + inipcns$ ipcs -q + ------ Message Queues -------- + key msqid owner perms used-bytes messages +``` +
+ +Notre nouveau *namespace* ne contient aucun objet IPC ! La file de messages +créée précédemment n'est pas visible ici. + +Si nous créons une nouvelle file de messages dans ce *namespace* : + +
+```bash + inipcns$ ./ipc_create + File de messages créée avec l'ID : 0 + Clé : 0x41000001 + + inipcns$ ipcs -q + ------ Message Queues -------- + key msqid owner perms used-bytes messages + 0x41000001 0 alice 666 0 0 +``` +
+ +Cette nouvelle file a le même ID (0) et la même clé que celle créée dans le +*namespace* initial, mais il s'agit d'un objet complètement distinct. + + +#### Communication entre processus dans le même *namespace*\ + +Créons un exemple de communication par file de messages : + +
+```c +// ipc_send.c +#include +#include +#include +#include +#include + +struct message { + long mtype; + char mtext[100]; +}; + +int main(void) +{ + key_t key; + int msgid; + struct message msg; + + key = ftok("/tmp", 'A'); + msgid = msgget(key, 0666 | IPC_CREAT); + + msg.mtype = 1; + snprintf(msg.mtext, sizeof(msg.mtext), "Hello from PID %d", getpid()); + + if (msgsnd(msgid, &msg, sizeof(msg.mtext), 0) == -1) { + perror("msgsnd"); + exit(EXIT_FAILURE); + } + + printf("Message envoyé : %s\n", msg.mtext); + + return 0; +} +``` +
+ +
+```c +// ipc_receive.c +#include +#include +#include +#include + +struct message { + long mtype; + char mtext[100]; +}; + +int main(void) +{ + key_t key; + int msgid; + struct message msg; + + key = ftok("/tmp", 'A'); + msgid = msgget(key, 0666 | IPC_CREAT); + + if (msgrcv(msgid, &msg, sizeof(msg.mtext), 1, 0) == -1) { + perror("msgrcv"); + exit(EXIT_FAILURE); + } + + printf("Message reçu : %s\n", msg.mtext); + + return 0; +} +``` +
+ +::::: {.code} + +Compilation : + +
+```bash +42sh$ gcc -o ipc_send ipc_send.c +42sh$ gcc -o ipc_receive ipc_receive.c +``` +
+ +Test dans le même *namespace* : + +
+```bash +# Terminal 1 +42sh$ ./ipc_receive +# Attend un message... + +# Terminal 2 +42sh$ ./ipc_send +Message envoyé : Hello from PID 12345 + +# Terminal 1 reçoit : +Message reçu : Hello from PID 12345 +``` +
+ +::::: + + +#### Isolation entre *namespaces*\ + +Maintenant, testons avec deux *namespaces* IPC différents : + +
+```bash +# Terminal 1 - namespace IPC A +42sh$ unshare --ipc /bin/bash + ipcns-A$ ./ipc_receive + # Attend un message... + +# Terminal 2 - namespace IPC B (différent) +42sh$ unshare --ipc /bin/bash + ipcns-B$ ./ipc_send + Message envoyé : Hello from PID 23456 + +# Terminal 1 ne reçoit RIEN car les namespaces sont différents +``` +
+ +Les deux processus utilisent la même clé IPC, mais comme ils sont dans des +*namespaces* différents, ils ne peuvent pas communiquer. + + +### Exemple avec la mémoire partagée + +Voici un exemple utilisant la mémoire partagée System V : + +
+```c +// shm_demo.c +#include +#include +#include +#include +#include +#include + +int main(void) +{ + key_t key; + int shmid; + char *data; + + // Créer une clé + key = ftok("/tmp", 'S'); + + // Créer un segment de mémoire partagée de 1024 octets + shmid = shmget(key, 1024, 0644 | IPC_CREAT); + if (shmid == -1) { + perror("shmget"); + exit(EXIT_FAILURE); + } + + // Attacher le segment + data = shmat(shmid, NULL, 0); + if (data == (char *)(-1)) { + perror("shmat"); + exit(EXIT_FAILURE); + } + + printf("Segment de mémoire partagée attaché\n"); + printf("Écriture dans la mémoire partagée...\n"); + + sprintf(data, "Hello from PID %d in namespace", getpid()); + + printf("Contenu : %s\n", data); + printf("Appuyez sur Entrée pour détacher...\n"); + getchar(); + + // Détacher + shmdt(data); + + printf("Voulez-vous supprimer le segment ? (o/n) : "); + char choice = getchar(); + if (choice == 'o' || choice == 'O') { + shmctl(shmid, IPC_RMID, NULL); + printf("Segment supprimé\n"); + } + + return 0; +} +``` +
+ +::::: {.code} + +Compilation et test : + +
+```bash +42sh$ gcc -o shm_demo shm_demo.c + +# Dans le namespace initial +42sh$ ./shm_demo & +Segment de mémoire partagée attaché +Écriture dans la mémoire partagée... +Contenu : Hello from PID 12345 in namespace + +42sh$ ipcs -m +------ Shared Memory Segments -------- +key shmid owner perms bytes nattch status +0x53000001 0 alice 644 1024 1 + +# Dans un nouveau namespace IPC +42sh$ unshare --ipc /bin/bash + inipcns$ ipcs -m + ------ Shared Memory Segments -------- + key shmid owner perms bytes nattch status + + # Aucun segment visible ! +``` +
+ +::::: + + +### Files de messages POSIX + +Le *namespace* IPC isole également les files de messages POSIX (qui sont +différentes des files System V) : + +
+```c +// mqueue_demo.c +#include +#include +#include +#include +#include + +int main(void) +{ + mqd_t mq; + struct mq_attr attr; + char buffer[1024]; + + // Configurer les attributs de la file + attr.mq_flags = 0; + attr.mq_maxmsg = 10; + attr.mq_msgsize = 1024; + attr.mq_curmsgs = 0; + + // Créer ou ouvrir une file de messages POSIX + mq = mq_open("/myqueue", O_CREAT | O_RDWR, 0644, &attr); + if (mq == (mqd_t)-1) { + perror("mq_open"); + exit(EXIT_FAILURE); + } + + printf("File de messages POSIX créée : /myqueue\n"); + + // Envoyer un message + sprintf(buffer, "Hello from PID %d", getpid()); + if (mq_send(mq, buffer, strlen(buffer) + 1, 0) == -1) { + perror("mq_send"); + exit(EXIT_FAILURE); + } + + printf("Message envoyé : %s\n", buffer); + + // Recevoir le message + if (mq_receive(mq, buffer, 1024, NULL) == -1) { + perror("mq_receive"); + exit(EXIT_FAILURE); + } + + printf("Message reçu : %s\n", buffer); + + mq_close(mq); + mq_unlink("/myqueue"); + + return 0; +} +``` +
+ +::::: {.code} + +Pour compiler (nécessite la bibliothèque `librt`) : + +
+```bash +42sh$ gcc -o mqueue_demo mqueue_demo.c -lrt +42sh$ ./mqueue_demo +File de messages POSIX créée : /myqueue +Message envoyé : Hello from PID 12345 +Message reçu : Hello from PID 12345 +``` +
+ +Les files de messages POSIX peuvent être listées dans `/dev/mqueue/` : + +
+```bash +42sh$ ls -l /dev/mqueue/ +total 0 +-rw-r--r-- 1 alice alice 80 Nov 15 10:30 myqueue +``` +
+ +Dans un nouveau *namespace* IPC, ce répertoire sera vide. + +::::: + + +### Combinaison avec d'autres *namespaces* + +En pratique, le *namespace* IPC est souvent combiné avec d'autres *namespaces* +pour une isolation complète : + +
+```bash +42sh# unshare --ipc --pid --mount --fork --mount-proc /bin/bash + incontainer# # Conteneur isolé avec IPC, PID et mount namespaces +``` +
+ + +### Nettoyage des objets IPC + +Les objets IPC persistent généralement après la terminaison des processus qui +les ont créés. Pour les nettoyer : + +
+```bash +# Supprimer une file de messages +42sh$ ipcrm -q + +# Supprimer un segment de mémoire partagée +42sh$ ipcrm -m + +# Supprimer un sémaphore +42sh$ ipcrm -s + +# Ou supprimer par clé +42sh$ ipcrm -Q 0x41000001 +``` +
+ +Lorsqu'un *namespace* IPC est détruit (tous les processus terminés), tous ses +objets IPC sont automatiquement nettoyés. + + +### Aller plus loin {-} + +Pour plus d'informations : + +- `ipc_namespaces(7)` - page de manuel +- `ipcs(1)` - lister les objets IPC +- `ipcrm(1)` - supprimer les objets IPC +- `svipc(7)` - aperçu de System V IPC +- `mq_overview(7)` - aperçu des files de messages POSIX diff --git a/tutorial/4/mountns.md b/tutorial/4/mountns.md index 4fa7f09..acf7816 100644 --- a/tutorial/4/mountns.md +++ b/tutorial/4/mountns.md @@ -189,7 +189,7 @@ On considère préalablement que l'environnement est propice à la réalisation ``` 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# wget https://dl-cdn.alpinelinux.org/alpine/v3.20/releases/x86_64/alpine-minirootfs-3.20.3-x86_64.tar.gz 42sh# tar xpf alpine-minirootfs-*.tar.gz -C /mnt/newroot 42sh# cd / ``` diff --git a/tutorial/4/namespaces-recap.md b/tutorial/4/namespaces-recap.md new file mode 100644 index 0000000..6267dbb --- /dev/null +++ b/tutorial/4/namespaces-recap.md @@ -0,0 +1,532 @@ +\newpage + +Récapitulatif des *namespaces* +=============================== + +Nous venons d'explorer en détail les huit types d'espaces de noms disponibles +dans le noyau Linux. Avant de conclure cette partie, récapitulons les concepts +clés et les meilleures pratiques pour utiliser efficacement ces mécanismes +d'isolation. + + +## Tableau comparatif des *namespaces* + +Le tableau suivant résume les caractéristiques principales de chaque type +d'espace de noms : + +| *Namespace* | Flag `clone()` | Depuis | Isole | Privilèges requis* | +|-------------|----------------|--------|-------|-------------------| +| **mount** | `CLONE_NEWNS` | 2.4.19 | Points de montage | Oui | +| **UTS** | `CLONE_NEWUTS` | 2.6.19 | Hostname, domaine NIS | Oui | +| **IPC** | `CLONE_NEWIPC` | 2.6.19 | Objets IPC System V et POSIX | Oui | +| **PID** | `CLONE_NEWPID` | 2.6.24 | Arbre de processus | Oui | +| **network** | `CLONE_NEWNET` | 2.6.29 | Interfaces, routes, ports | Oui | +| **user** | `CLONE_NEWUSER` | 3.8 | UIDs, GIDs, capabilities | **Non** | +| **cgroup** | `CLONE_NEWCGROUP` | 4.6 | Vue arborescence cgroup | Oui | +| **time** | `CLONE_NEWTIME` | 5.6 | Horloges monotones | Oui | + +*\* Sauf si combiné avec un *namespace* `user`* + + +## Ce que chaque *namespace* vous permet de faire + +### `mount` -- Systèmes de fichiers isolés + +**Fonctionnalité :** Isole l'arborescence des points de montage. + +**Permet de :** +- Créer un système de fichiers racine différent (`pivot_root`, `switch_root`) +- Monter/démonter des partitions sans affecter l'hôte +- Implémenter un `chroot` sécurisé (impossible de s'échapper) +- Utiliser des systèmes de fichiers en couches (OverlayFS) + +**Cas d'usage typiques :** +- Conteneurs avec leur propre filesystem +- Environnements de build isolés +- Systèmes d'installation et de récupération + + +### `UTS` -- Identité système + +**Fonctionnalité :** Isole le nom d'hôte et le domaine NIS. + +**Permet de :** +- Donner un nom unique à chaque conteneur +- Éviter les conflits de nommage +- Faciliter l'identification des conteneurs + +**Cas d'usage typiques :** +- Conteneurs avec des noms de machine distincts +- Environnements multi-tenants +- Tests de configurations réseau + + +### `IPC` -- Communication inter-processus + +**Fonctionnalité :** Isole les objets IPC (files de messages, sémaphores, +mémoire partagée). + +**Permet de :** +- Empêcher la communication IPC entre conteneurs +- Isoler les applications utilisant IPC System V ou POSIX +- Protéger contre les fuites d'informations via IPC + +**Cas d'usage typiques :** +- Isolation de sécurité entre conteneurs +- Applications legacy utilisant IPC System V +- Environnements de test pour applications IPC + + +### `PID` -- Arbre de processus virtualisé + +**Fonctionnalité :** Isole l'arbre des processus avec renumérotation. + +**Permet de :** +- Avoir un processus `init` (PID 1) par conteneur +- Masquer les processus de l'hôte et des autres conteneurs +- Gérer le cycle de vie des processus indépendamment +- Récolter les processus orphelins proprement + +**Cas d'usage typiques :** +- Conteneurs avec gestion complète du cycle de vie +- Isolation de sécurité (masquage des processus) +- Environnements avec plusieurs niveaux d'`init` + +**Particularité :** Le processus doit forker après `unshare(CLONE_NEWPID)` +pour que l'isolation soit effective. + + +### `network` -- Pile réseau dédiée + +**Fonctionnalité :** Isole les interfaces réseau, routes, règles de pare-feu et +ports. + +**Permet de :** +- Donner une pile réseau complète à chaque conteneur +- Réutiliser les mêmes ports dans différents conteneurs +- Configurer des routes et pare-feu indépendants +- Créer des topologies réseau complexes (bridges, VLAN, MACVLAN) + +**Cas d'usage typiques :** +- Conteneurs avec leur propre IP +- Tests réseau isolés +- Micro-services avec ports identiques +- Simulations de topologies réseau + +**Note :** Nécessite généralement des interfaces virtuelles (`veth`, `macvlan`, +etc.) pour connecter les conteneurs. + + +### `user` -- Privilèges virtualisés + +**Fonctionnalité :** Isole les UIDs, GIDs et les capabilities. + +**Permet de :** +- Être root dans le conteneur sans être root sur l'hôte +- Lancer des conteneurs sans privilèges (rootless containers) +- Mapper des plages d'UIDs entre conteneur et hôte +- Déléguer des capacités limitées + +**Cas d'usage typiques :** +- Conteneurs rootless (podman, systemd-nspawn) +- Environnements multi-utilisateurs sécurisés +- Délégation contrôlée de privilèges + +**Particularité :** Seul *namespace* créable sans privilèges. Permet ensuite de +créer tous les autres types de *namespaces*. + +**Attention :** Historiquement source de vulnérabilités ; à utiliser avec +précaution. + + +### `cgroup` -- Vue filtrée des control groups + +**Fonctionnalité :** Virtualise la vue de l'arborescence des cgroups. + +**Permet de :** +- Masquer l'organisation des cgroups de l'hôte +- Faciliter la migration de conteneurs +- Empêcher la fuite d'informations système via `/sys/fs/cgroup` +- Déléguer la gestion de sous-arbres de cgroups + +**Cas d'usage typiques :** +- Renforcement de l'isolation de sécurité +- Migration de conteneurs (CRIU) +- Systèmes multi-tenants + +**Note :** Souvent combiné avec un *namespace* `mount` pour remonter +`/sys/fs/cgroup`. + + +### `time` -- Horloges virtualisées + +**Fonctionnalité :** Virtualise `CLOCK_MONOTONIC` et `CLOCK_BOOTTIME`. + +**Permet de :** +- Décaler les horloges monotones pour un conteneur +- Préserver l'état temporel lors de migrations +- Accélérer le temps pour les tests + +**Cas d'usage typiques :** +- Migration de conteneurs avec CRIU +- Tests d'applications sensibles au temps +- Simulations temporelles + +**Limitation :** Ne virtualise **pas** l'heure système (CLOCK_REALTIME). + +**Particularité :** Les offsets doivent être configurés avant de créer le +premier processus enfant. + + +## Combinaisons recommandées + +Les *namespaces* sont rarement utilisés seuls. Voici les combinaisons courantes +selon les besoins d'isolation : + + +### Isolation minimale (développement) + +Pour un environnement de développement simple avec un filesystem isolé : + +
+```bash +unshare --mount --pid --fork --mount-proc /bin/bash +``` +
+ +**Isolation :** Filesystem et processus +**Avantages :** Rapide, simple, suffisant pour tester des installations +**Limitations :** Partage le réseau, les utilisateurs, IPC avec l'hôte + + +### Conteneur standard (production light) + +Pour une isolation raisonnable sans réseau séparé : + +
+```bash +unshare --mount --uts --ipc --pid --fork --mount-proc /bin/bash +``` +
+ +**Isolation :** Filesystem, processus, IPC, hostname +**Avantages :** Bonne isolation, partage le réseau de l'hôte +**Cas d'usage :** Services système, environnements de build + + +### Conteneur avec réseau isolé + +Pour une isolation complète incluant le réseau : + +
+```bash +unshare --mount --uts --ipc --net --pid --fork --mount-proc /bin/bash +``` +
+ +**Isolation :** Filesystem, processus, IPC, hostname, réseau +**Avantages :** Isolation réseau complète +**Note :** Nécessite configuration réseau supplémentaire (veth, bridge, etc.) + + +### Conteneur rootless (sans privilèges) + +Pour lancer un conteneur en tant qu'utilisateur normal : + +
+```bash +unshare --user --map-root-user \ + --mount --uts --ipc --net --pid \ + --fork --mount-proc /bin/bash +``` +
+ +**Isolation :** Complète + virtualisation des privilèges +**Avantages :** Aucun privilège requis, sécurité renforcée +**Cas d'usage :** Environnements multi-utilisateurs, CI/CD non-privilégiée + + +### Conteneur type Docker (isolation maximale) + +Pour une isolation complète similaire à Docker : + +
+```bash +unshare --user --map-root-user \ + --mount --uts --ipc --net --pid --cgroup \ + --fork --mount-proc /bin/bash +``` +
+ +**Isolation :** Tous les namespaces (sauf `time`) +**Avantages :** Isolation maximale +**Note :** Équivalent approximatif de `docker run --rm -it` + + +## Ordre de création des *namespaces* + +L'ordre dans lequel les *namespaces* sont créés peut être important : + +1. **`user` en premier** : Si vous voulez créer des *namespaces* sans + privilèges, commencez toujours par le *namespace* `user`. + +2. **`mount` avant `pid`** : Pour pouvoir remonter `/proc` correctement avec + l'option `--mount-proc`. + +3. **`pid` avec `--fork`** : N'oubliez jamais l'option `--fork` avec le + *namespace* `pid`, sinon le processus actuel ne sera pas réellement isolé. + +4. **`time` et `cgroup` avant le fork** : Ces deux namespaces nécessitent des + configurations avant la création du premier processus enfant. + + +## Commandes essentielles + +Voici un récapitulatif des commandes les plus utiles pour travailler avec les +*namespaces* : + +### Création et entrée dans des *namespaces* + +
+```bash +# Créer de nouveaux namespaces et exécuter une commande +unshare [options] [commande] + +# Principales options : +# -m, --mount Namespace mount +# -u, --uts Namespace UTS +# -i, --ipc Namespace IPC +# -n, --net Namespace network +# -p, --pid Namespace PID +# -U, --user Namespace user +# -C, --cgroup Namespace cgroup +# -T, --time Namespace time +# -f, --fork Fork avant d'exécuter la commande (requis avec --pid) +# --mount-proc Monter /proc après création du namespace + +# Entrer dans les namespaces d'un processus existant +nsenter [options] [commande] +nsenter --target --all [commande] +``` +
+ +### Inspection des *namespaces* + +
+```bash +# Lister tous les namespaces du système +lsns + +# Lister les namespaces d'un processus spécifique +lsns -p + +# Voir les namespaces d'un processus +ls -l /proc//ns/ + +# Comparer les namespaces de deux processus +readlink /proc//ns/net +readlink /proc//ns/net +``` +
+ +### Gestion réseau (namespace network) + +
+```bash +# Créer un namespace network nommé +ip netns add + +# Lister les namespaces network +ip netns list + +# Exécuter une commande dans un namespace network +ip netns exec + +# Créer une paire veth +ip link add veth0 type veth peer name veth1 + +# Déplacer une interface dans un namespace +ip link set veth1 netns +``` +
+ + +## Appels système + +Pour les développeurs, voici les appels système principaux : + +
+```c +// Créer un nouveau processus dans de nouveaux namespaces +pid_t clone(int (*fn)(void *), void *stack, int flags, void *arg); +// flags: CLONE_NEWNS, CLONE_NEWUTS, CLONE_NEWIPC, CLONE_NEWPID, +// CLONE_NEWNET, CLONE_NEWUSER, CLONE_NEWCGROUP, CLONE_NEWTIME + +// Se dissocier de namespaces actuels +int unshare(int flags); + +// Rejoindre les namespaces d'un autre processus +int setns(int fd, int nstype); +// fd: file descriptor de /proc//ns/ +// nstype: 0 ou CLONE_NEW* pour vérification de type + +// Obtenir un file descriptor vers un processus (pour setns) +int pidfd_open(pid_t pid, unsigned int flags); +``` +
+ + +## Bonnes pratiques + +### Sécurité + +1. **Privilèges minimaux** : Utilisez le *namespace* `user` quand possible pour + éviter d'exécuter des conteneurs en tant que root. + +2. **Combinaison d'isolations** : Un seul namespace ne suffit généralement pas + pour une isolation sécurisée. Combinez plusieurs namespaces. + +3. **User namespace avec précaution** : Bien que pratique, le *namespace* + `user` a été source de nombreuses vulnérabilités. Assurez-vous d'utiliser un + noyau récent et correctement patché. + +4. **Nettoyage des ressources** : Les namespaces persistent tant qu'ils sont + référencés. Utilisez `umount` pour les bind mounts qui maintiennent des + namespaces actifs. + +5. **Capabilities** : Même dans un *namespace* `user`, les capabilities sont + limitées à l'intérieur du namespace. Ne comptez pas sur elles pour des + actions hors namespace. + + +### Performance + +1. **Coût minimal** : La création de namespaces a un coût négligeable en termes + de performance. N'hésitez pas à les utiliser. + +2. **Réseau** : Le type d'interface virtuelle utilisé (`veth`, `macvlan`, + `ipvlan`) peut avoir un impact significatif sur les performances réseau. + +3. **OverlayFS vs device-mapper** : Pour le stockage, OverlayFS est + généralement plus performant que les snapshots LVM (device-mapper). + + +### Débogage + +1. **`lsns` est votre ami** : Utilisez `lsns` pour comprendre rapidement + l'organisation des namespaces sur votre système. + +2. **Inspecter /proc** : Les fichiers dans `/proc//ns/` sont essentiels + pour comprendre dans quels namespaces se trouve un processus. + +3. **Bind mounts pour persistence** : Utilisez `mount --bind` sur les fichiers + de `/proc//ns/` pour maintenir un namespace actif même après la + terminaison du processus. + +4. **Logs et `strace`** : En cas de problème, `strace` peut vous montrer + exactement quels appels système échouent et pourquoi. + + +## Limitations et pièges courants + +### Namespace `PID` + +- **Oubli du `--fork`** : Sans fork, le processus actuel reste dans l'ancien + namespace PID. +- **Bash et PID 1** : Bash peut avoir des comportements inattendus en tant que + PID 1 (voir pidns.md pour les détails). +- **`/proc` doit être remonté** : Sinon `ps`, `top` montrent tous les processus + du système. + + +### Namespace `mount` + +- **Propagation des montages** : Attention aux politiques `shared`, `slave`, + `private`. Par défaut, utilisez `mount --make-rslave /` pour éviter les + fuites. +- **`pivot_root` vs `chroot`** : `pivot_root` est plus sûr mais plus complexe à + mettre en œuvre. + + +### Namespace `network` + +- **Pas d'interface par défaut** : Un nouveau namespace network ne contient + qu'une interface loopback désactivée. Il faut configurer le réseau + manuellement. +- **Complexité de la configuration** : Bridges, veth, routes, NAT... la + configuration réseau peut devenir complexe rapidement. + + +### Namespace `user` + +- **MappingUID/GID limité** : Sans privilèges, vous ne pouvez mapper qu'un + seul UID (le vôtre vers root). +- **Fichiers setuid** : Ne fonctionnent pas correctement dans un namespace + user. +- **Restrictions par distribution** : Certaines distributions désactivent le + namespace user par défaut (voir setup.md). + + +### Namespace `time` + +- **Configuration unique** : Les offsets ne peuvent être configurés qu'une fois, + avant le premier fork. +- **Pas d'heure réelle** : Ne virtualise pas `CLOCK_REALTIME`, seulement les + horloges monotones. + + +## Pour aller plus loin + +### Documentation officielle + +- Pages de manuel : `namespaces(7)`, `pid_namespaces(7)`, `user_namespaces(7)`, + `mount_namespaces(7)`, `network_namespaces(7)`, `ipc_namespaces(7)`, + `uts_namespaces(7)`, `cgroup_namespaces(7)`, `time_namespaces(7)` + +- Documentation du noyau : + + + +### Articles de référence + +- [Namespaces in operation (série LWN)](https://lwn.net/Articles/531114/) : + série d'articles exhaustive par Michael Kerrisk + + +- [Anatomy of a user namespaces vulnerability](https://lwn.net/Articles/543273/) : + pour comprendre les enjeux de sécurité + + + +### Projets utilisant les namespaces + +- **Docker/Podman/containerd** : Containerisation d'applications +- **systemd-nspawn** : Conteneurs légers pour systemd +- **LXC/LXD** : Conteneurs système complets +- **Flatpak/Snap** : Sandboxing d'applications +- **Chrome/Firefox** : Sandboxing de processus de rendu +- **bubblewrap** : Outil de sandboxing générique + + +## Conclusion + +Les *namespaces* Linux constituent une brique fondamentale de la virtualisation +légère moderne. Leur conception modulaire permet de créer des niveaux +d'isolation sur mesure, depuis un simple environnement de développement isolé +jusqu'à des conteneurs de production complets. + +La maîtrise des *namespaces* nécessite de comprendre : + +1. **Ce que chaque type isole** et dans quel contexte l'utiliser +2. **Comment les combiner** pour atteindre le niveau d'isolation souhaité +3. **Leurs interactions** et dépendances mutuelles +4. **Leurs limitations** et les cas particuliers à gérer + +Combinés avec les *cgroups* (pour la limitation de ressources) et les *security +modules* (AppArmor, SELinux pour les politiques de sécurité), les *namespaces* +permettent de construire des environnements d'exécution sûrs, isolés et +performants. + +Que vous développiez des outils de containerisation, construisiez des +plateformes cloud, ou cherchiez simplement à mieux isoler vos applications, les +*namespaces* sont un outil incontournable de l'écosystème Linux moderne. diff --git a/tutorial/4/networkns.md b/tutorial/4/networkns.md index 8fe702d..62065dd 100644 --- a/tutorial/4/networkns.md +++ b/tutorial/4/networkns.md @@ -82,7 +82,7 @@ virli 42sh# ip netns foo virli 42sh# ip netns exec foo ip link -1: lo: mut 65536 qdisc noop state DOWN mode DEFAULT group default +1: lo: mtu 65536 qdisc noop state DOWN mode DEFAULT group default link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 ``` @@ -99,7 +99,7 @@ des interfaces :
``` 42sh# ip netns exec virli ip link -1: lo: mut 65536 qdisc noop state DOWN mode DEFAULT group default +1: lo: mtu 65536 qdisc noop state DOWN mode DEFAULT group default link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 ```
@@ -175,11 +175,13 @@ struct nlmsghdr { ``` -Parmi les fonctionnalités de Netlink, nous allons utiliser le module NIS -(Network Interface Service)[^RFC3549NIS]. Il spécifie le format par lequel +Parmi les fonctionnalités de Netlink, nous allons utiliser RTNetlink +(Routing Netlink)[^RFC3549RTNETLINK]. Il spécifie le format par lequel doivent commencer les données liées à l'administration d'interfaces réseau. +Le module RTNetlink utilise la famille `NETLINK_ROUTE` pour communiquer avec +le noyau concernant les interfaces, routes et règles de routage. -[^RFC3549NIS]: Network Interface Service Module +[^RFC3549RTNETLINK]: RTNetlink - Linux Routing Socket
```c diff --git a/tutorial/4/timens.md b/tutorial/4/timens.md new file mode 100644 index 0000000..acf4167 --- /dev/null +++ b/tutorial/4/timens.md @@ -0,0 +1,263 @@ +Le *namespace* `time` {#time-ns} +--------------------- + +### Introduction + +L'espace de noms `time`, introduit dans Linux 5.6, permet de virtualiser les +horloges `CLOCK_MONOTONIC` et `CLOCK_BOOTTIME` pour des processus. Cela est +particulièrement utile pour la migration de conteneurs ou pour tester des +applications sensibles au temps. + +::::: {.warning} + +Contrairement à ce que son nom pourrait suggérer, le *namespace* `time` ne +permet **pas** de virtualiser l'heure système (l'horloge temps réel). Il ne +virtualise que les compteurs monotones. + +::::: + + +### Utilisation pratique + +Voyons comment utiliser le *namespace* `time` pour modifier le temps +d'exécution perçu par un processus. + + +#### Lire les horloges monotones\ + +Commençons par observer les valeurs actuelles de nos horloges : + +
+```bash +42sh$ cat /proc/uptime +123456.78 987654.32 +``` +
+ +Le premier nombre représente le temps depuis le démarrage du système (équivalent +à `CLOCK_BOOTTIME`), et le second le temps CPU cumulé en mode idle. + +On peut également utiliser un petit programme C pour afficher les horloges : + +
+```c +#include +#include + +int main(void) +{ + struct timespec ts; + + clock_gettime(CLOCK_MONOTONIC, &ts); + printf("CLOCK_MONOTONIC: %ld.%09ld\n", ts.tv_sec, ts.tv_nsec); + + clock_gettime(CLOCK_BOOTTIME, &ts); + printf("CLOCK_BOOTTIME: %ld.%09ld\n", ts.tv_sec, ts.tv_nsec); + + return 0; +} +``` +
+ +::::: {.code} + +Pour compiler ce programme : + +
+```bash +42sh$ gcc -o show_clocks show_clocks.c +``` +
+ +::::: + + +#### Créer un *namespace* `time` avec décalage\ + +Pour créer un nouveau *namespace* `time`, il faut d'abord le dissocier puis +configurer les décalages (offsets) avant de lancer un processus enfant. + +Le fichier `/proc//timens_offsets` permet de configurer les décalages pour +les horloges. Le format est le suivant : + +
+``` + +``` +
+ +Où `` peut être : +- `monotonic` pour `CLOCK_MONOTONIC` +- `boottime` pour `CLOCK_BOOTTIME` + +Voici un script shell pour créer un conteneur avec un temps décalé de 10 jours +dans le futur : + +
+```bash +42sh# unshare --time /bin/bash + intimens# echo "monotonic 864000 0" > /proc/self/timens_offsets + intimens# echo "boottime 864000 0" > /proc/self/timens_offsets + intimens# exec /bin/bash + intimens-child# ./show_clocks + CLOCK_MONOTONIC: 987654.123456789 + CLOCK_BOOTTIME: 987654.123456789 +``` +
+ +::::: {.warning} + +**Important :** Les décalages doivent être configurés **avant** de créer le +premier processus enfant. Une fois qu'un processus enfant existe dans le +*namespace*, les offsets ne peuvent plus être modifiés. C'est pourquoi nous +utilisons `exec` pour remplacer le shell actuel. + +::::: + + +#### Exemple en C\ + +Voici un exemple complet en C pour créer un *namespace* `time` avec un décalage : + +
+```c +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include + +static int child_func(void *arg) +{ + struct timespec ts; + + // Attendre que le parent configure les offsets + sleep(1); + + clock_gettime(CLOCK_MONOTONIC, &ts); + printf("Child - CLOCK_MONOTONIC: %ld.%09ld\n", + ts.tv_sec, ts.tv_nsec); + + clock_gettime(CLOCK_BOOTTIME, &ts); + printf("Child - CLOCK_BOOTTIME: %ld.%09ld\n", + ts.tv_sec, ts.tv_nsec); + + return 0; +} + +#define STACK_SIZE (1024 * 1024) +static char child_stack[STACK_SIZE]; + +int main(void) +{ + pid_t pid; + int fd; + char path[256]; + struct timespec ts; + + // Afficher les horloges du parent + clock_gettime(CLOCK_MONOTONIC, &ts); + printf("Parent - CLOCK_MONOTONIC: %ld.%09ld\n", + ts.tv_sec, ts.tv_nsec); + + // Se dissocier du namespace time + if (unshare(CLONE_NEWTIME) == -1) { + perror("unshare"); + exit(EXIT_FAILURE); + } + + // Configurer les offsets (décalage de 1000 secondes) + fd = open("/proc/self/timens_offsets", O_WRONLY); + if (fd == -1) { + perror("open timens_offsets"); + exit(EXIT_FAILURE); + } + + if (write(fd, "monotonic 1000 0\n", 17) != 17) { + perror("write monotonic offset"); + exit(EXIT_FAILURE); + } + + if (write(fd, "boottime 1000 0\n", 16) != 16) { + perror("write boottime offset"); + exit(EXIT_FAILURE); + } + + close(fd); + + // Créer un processus enfant qui verra les nouvelles horloges + pid = clone(child_func, child_stack + STACK_SIZE, + SIGCHLD, NULL); + if (pid == -1) { + perror("clone"); + exit(EXIT_FAILURE); + } + + waitpid(pid, NULL, 0); + + return 0; +} +``` +
+ +::::: {.code} + +Pour compiler : + +
+```bash +42sh$ gcc -o time_ns_demo time_ns_demo.c +42sh$ ./time_ns_demo +Parent - CLOCK_MONOTONIC: 123456.789012345 +Child - CLOCK_MONOTONIC: 124456.789012345 +Child - CLOCK_BOOTTIME: 124456.789012345 +``` +
+ +On voit bien le décalage de 1000 secondes entre le parent et l'enfant. + +::::: + + +### Cas d'usage pratiques + +#### Migration de conteneurs\ + +Lors de la migration d'un conteneur d'une machine à une autre, les processus +peuvent avoir des timers ou des alarmes basées sur `CLOCK_MONOTONIC`. Si on ne +virtualise pas ces horloges, tous ces timers seraient incorrects après la +migration. + +Le *namespace* `time` permet de restaurer l'état des horloges monotones lors +de la restauration d'un conteneur. + + +#### Tests et simulation\ + +Pour tester des applications qui utilisent des timeouts ou des mécanismes de +retry avec backoff exponentiel, il peut être utile de virtualiser le temps pour +accélérer les tests sans modifier le code de l'application. + + +### Limitations + +- Seules les horloges `CLOCK_MONOTONIC` et `CLOCK_BOOTTIME` peuvent être + virtualisées +- L'heure système (`CLOCK_REALTIME`) reste identique pour tous les processus +- Les offsets ne peuvent être configurés qu'une seule fois, avant la création + du premier processus enfant +- Les décalages ne peuvent pas être négatifs (on ne peut pas remonter dans le + temps) + + +### Aller plus loin {-} + +Pour plus d'informations sur le *namespace* `time`, consultez : + +- `time_namespaces(7)` - page de manuel +- [Time Namespace support](https://lwn.net/Articles/766089/) : +- [Checkpoint/Restore and Time Namespace](https://lwn.net/Articles/801652/) :