tuto4: done

This commit is contained in:
nemunaire 2017-11-09 01:30:41 +01:00
parent 886197bfe8
commit a476cd8b6d
14 changed files with 643 additions and 331 deletions

View File

@ -1,4 +1,6 @@
SOURCES = tutorial.md mount.md namespaces.md networkns.md pidns.md mountns.md userns.md project-intro.md project-body.md project-rendu.md sondage.md
SOURCES_TUTO = tutorial.md setup.md cmpns.md docker-exec.md mountns.md rendu.md
SOURCES_LESSON = lesson.md mount.md namespaces.md networkns.md pidns.md userns.md
PANDOCOPTS = --latex-engine=xelatex \
--standalone \
--normalize \
@ -13,10 +15,13 @@ PANDOCOPTS = --latex-engine=xelatex \
--include-in-header=../header.tex
all: tutorial.pdf
all: lesson.pdf tutorial.pdf
tutorial.pdf: ${SOURCES}
lesson.pdf: ${SOURCES_LESSON}
pandoc ${PANDOCOPTS} -o $@ $+
tutorial.pdf: ${SOURCES_TUTO}
pandoc ${PANDOCOPTS} -o $@ $+
clean::
rm tutorial.pdf
rm lesson.pdf tutorial.pdf

60
tutorial/4/cmpns.md Normal file
View File

@ -0,0 +1,60 @@
\newpage
Comparaison de *namespace*
==========================
Les *namespaces* d'un programme sont exposés sous forme de liens symboliques
dans le répertoire `/proc/<PID>/ns/`.
Deux programmes qui partagent un même *namespace* auront un lien vers la même
structure de données.
Écrivons un script ou un programme, `cmpns`, permettant de déterminer si deux
programmes s'exécutent dans les mêmes *namespaces*.
Exemples {.unnumbered}
--------
<div lang="en-US">
```sh
42sh$ ./cmpns $(pgrep influxdb) $(pgrep init)
- cgroup: differ
- ipc: differ
- mnt: differ
- net: differ
- pid: differ
- user: same
- uts: same
```
</div>
<div lang="en-US">
```sh
42sh$ ./cmpns $(pgrep init) self
- cgroup: same
- ipc: same
- mnt: same
- net: same
- pid: same
- user: same
- uts: same
```
</div>
Ici, `self` fait référence au processus actuellement exécuté.
Et pourquoi pas :
<div lang="en-US">
```sh
42sh$ unshare -m ./cmpns $$ self
- cgroup: same
- ipc: same
- mnt: differ
- net: same
- pid: same
- user: same
- uts: same
```
</div>

49
tutorial/4/docker-exec.md Normal file
View File

@ -0,0 +1,49 @@
\newpage
`docker exec`
=============
Après voir lu la partie concernant les *namespaces*, vous avez dû comprendre
qu'un `docker exec`, n'était donc rien de plus qu'un `nsenter(1)`.
Réécrivons, en quelques lignes, la commande `docker exec` !
Pour savoir si vous avez réussi, comparez les sorties des commandes :
- `ip address` ;
- `hostname` ;
- `mount` ;
- `pa -aux` ;
- ...
Tests {.unnumbered}
-----
<div lang="en-US">
```shell
42sh$ docker run --name mywebserver -d -p 80:80 nginx
d63ceae863956f8312aca60b7a57fbcc1fdf679ae4c90c5d9455405005d4980a
42sh$ docker container inspect --format '{{ .State.Pid }}' mywebserver
234269
42sh# ./mydocker_exec mywebserver ip address
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
13: eth0@if14: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
link/ether 02:42:ac:11:00:02 brd ff:ff:ff:ff:ff:ff link-netnsid 0
inet 172.17.0.1/16 scope global eth0
valid_lft forever preferred_lft forever
42sh# hostname
koala.zoo.paris
42sh# ./mydocker_exec mywebserver hostname
d63ceae86395
42sh# ./mydocker_exec mywebserver mount
42sh# ./mydocker_exec mywebserver ps aux
...
```
</div>

14
tutorial/4/lesson.md Normal file
View File

@ -0,0 +1,14 @@
---
title: Virtualisation légère -- Linux Internals partie 2
subtitle: Support de cours
author: Pierre-Olivier *nemunaire* Mercier
institute: EPITA
date: Jeudi 2 novembre 2017
...
Le but de cette seconde partie sur les mécanismes internes du noyau va nous
permettre d'utiliser les commandes et les appels systèmes relatifs aux espaces
de noms du noyau Linux ainsi que d'appréhender la complexité des sytèmes de
fichiers.
\tableofcontents

View File

@ -1,6 +1,6 @@
\newpage
Des particularités de `mount`
Des particularités de `mount` {#mount}
=============================
## Les points de montage
@ -14,7 +14,7 @@ précédent.
Mais avez-vous déjà essayé de monter la même partition d'un disque physique à
deux endroits différents de votre arborescence ?
Si pour plein de raisons on pourrait se dire que cela ne devrait pas être
Si pour plein de raisons on pouvait se dire que cela ne devrait pas être
autorisé, ce problème s'avère être à la base de beaucoup de fonctionnalités
intéressantes. Le noyau va finalement décorréler les notions de montage,
d'accès et d'accroches dans l'arborescence : et par exemple, une partition ne
@ -22,26 +22,62 @@ sera plus forcément démontée après un appel à `umount(2)`, mais le sera
seulement lorsque cette partition n'aura plus d'accroches dans aucune
arborescence.
Durant cette partie du TP, vous allez avoir besoin de la commande
`findmnt(1)`. Commençons par la lancer maintenant pour se familiariser avec les
différents points d'accroche actuellement montés.
La commande `findmnt(1)`, des
[`util-linux`](https://www.kernel.org/pub/linux/utils/util-linux/) nous permet
d'avoir une vision arborescente des points de montage en cours d'utilisation.
<div lang="en-US">
```
TARGET SOURCE FSTYPE OPTIONS
/ /dev/sda1 ext4 rw,relatime,data=ordered
├─/proc proc proc rw,nosuid,nodev,noexec,relatime
├─/sys sysfs sysfs rw,nosuid,nodev,noexec,relatime
│ ├─/sys/kernel/security securityfs securityfs rw,nosuid,nodev,noexec,relatime
│ ├─/sys/firmware/efi/efivars efivarfs efivarfs ro,relatime
│ └─/sys/fs/cgroup cgroup_root tmpfs rw,nosuid,nodev,noexec,relatime,size=10240k,mode=755
│ ├─/sys/fs/cgroup/unified none cgroup2 rw,nosuid,nodev,noexec,relatime
│ ├─/sys/fs/cgroup/cpuset cpuset cgroup rw,nosuid,nodev,noexec,relatime,cpuset
│ ├─/sys/fs/cgroup/cpu cpu cgroup rw,nosuid,nodev,noexec,relatime,cpu
│ ├─/sys/fs/cgroup/cpuacct cpuacct cgroup rw,nosuid,nodev,noexec,relatime,cpuacct
│ ├─/sys/fs/cgroup/blkio blkio cgroup rw,nosuid,nodev,noexec,relatime,blkio
│ ├─/sys/fs/cgroup/memory memory cgroup rw,nosuid,nodev,noexec,relatime,memory
│ ├─/sys/fs/cgroup/devices devices cgroup rw,nosuid,nodev,noexec,relatime,devices
│ ├─/sys/fs/cgroup/freezer freezer cgroup rw,nosuid,nodev,noexec,relatime,freezer
│ ├─/sys/fs/cgroup/net_cls net_cls cgroup rw,nosuid,nodev,noexec,relatime,net_cls
│ ├─/sys/fs/cgroup/perf_event perf_event cgroup rw,nosuid,nodev,noexec,relatime,perf_event
│ ├─/sys/fs/cgroup/net_prio net_prio cgroup rw,nosuid,nodev,noexec,relatime,net_prio
│ └─/sys/fs/cgroup/pids pids cgroup rw,nosuid,nodev,noexec,relatime,pids
├─/dev devtmpfs devtmpfs rw,nosuid,size=10240k,nr_inodes=486250,mode=755
│ ├─/dev/pts devpts devpts rw,nosuid,noexec,relatime,gid=5,mode=620,ptmxmode=000
│ ├─/dev/shm tmpfs tmpfs rw
│ └─/dev/mqueue mqueue mqueue rw,nosuid,nodev,noexec,relatime
├─/home /dev/sda3 ext4 rw,nosuid,nodev,relatime,data=ordered
├─/run tmpfs tmpfs rw,nosuid,nodev,noexec,mode=755
└─/tmp tmpfs tmpfs rw,nosuid,nodev,noexec,relatime
```
</div>
## `bind`
Lorsque l'on souhaite monter à un deuxième endroit (ou plus) une partition, on
utilise le *bind mount* : `mount --bind olddir newdir`.
utilise le *bind mount* :
Lorsque l'on utilise des `chroot`s sur un système complet (par exemple lorsqu'on
l'installe ou qu'on le répare via un live CD), il est nécessaire de dupliquer
les points de montage de `/dev`, `/proc` et `/sys`.
<div lang="en-US">
```
mount --bind olddir newdir
```
</div>
Reprenons l'environnement que nous avons créé au précédent TP. Sans monter ces
partitions, vous ne serez pas en mesure d'utiliser le système dans son
intégralité : vous ne pourrez pas monter les partitions indiquées par le
`/etc/fstab`, vous ne pourrez pas utiliser `top` ou `ps`, ...
Lorsque l'on souhaite `chroot` dans un système complet (par exemple lorsqu'on
l'installe ou qu'on le répare via un *live CD*), il est nécessaire de dupliquer
certains points de montage, tels que `/dev`, `/proc` et `/sys`.
Pour que tout cela fonctionne, vous avez besoin au préalable d'exécuter les
Sans monter ces partitions, vous ne serez pas en mesure d'utiliser le système
dans son intégralité : vous ne pourrez pas monter les partitions indiquées par
le `/etc/fstab`, vous ne pourrez pas utiliser `top` ou `ps`, `sysctl` ne pourra
pas accorder les paramètres du noyau, ...
Pour que tout cela fonctionne, nous aurons besoin, au préalable, d'exécuter les
commandes suivantes :
<div lang="en-US">
@ -53,12 +89,12 @@ commandes suivantes :
```
</div>
En se `chroot`ant à nouveau dans cette nouvelle racine, nous voyons que tous
nos outils fonctionnent comme prévu.
En se `chroot`ant à nouveau dans cette nouvelle racine, tous nos outils
fonctionneront comme prévu.
Tous ? ... en fait non. Si l'on jette un œil à `findmnt(1)`, nous constatons
par exemple que `/sys/fs/cgroup` dans notre nouvelle racine est vide, alors que
celui de notre machine hôte contient bien les répertoires de nos CGroups.
celui de notre machine hôte contient bien les répertoires de nos *cgroups*.
`--bind` va se contenter d'attacher le système de fichiers (ou au moins une
partie de celui-ci) à un autre endroit, sans se préoccuper des points de
@ -79,31 +115,28 @@ correct de lancer :
## Les montages parfumés
On distingue 4 variétés de répercution des montages pour un sous-arbre :
On distingue quatre variétés de répercution des montages pour un sous-arbre :
partagé, esclave, privé et non-attachable.
Il s'agit d'agir sur la manière dont seront propagées les nouvelles accroches
au sein d'un système de fichiers attaché à plusieurs endroit.
Chacun va agir sur la manière dont seront propagées les nouvelles accroches au
sein d'un système de fichiers attaché à plusieurs endroits.
### partagé -- *shared mount*
Une nouvelle accroche sera propagée parmi tous les systèmes de fichiers de ce
partage (on parle de *peer group*).
Essayons de voir à quoi cela correspond avec l'exemple suivant :
Dans une montage partagé, une nouvelle accroche sera propagée parmi tous les
systèmes de fichiers de ce partage (on parle de *peer group*).
<div lang="en-US">
```shell
# Création de nos répertoires de travail
cd /mnt
mkdir test-shared
# Création de nos répertoires de travail
mkdir /mnt/test-shared
# On s'assure que le dossier que l'on va utiliser pour nos tests utilise bien la politique shared
mount --make-shared /tmp
# On s'assure que le dossier que l'on va utiliser pour nos tests utilise bien la politique shared
mount --make-shared /tmp
# Duplication de l'accroche, sans s'occuper des éventuels sous-accroches
mount --bind /tmp /mnt/test-shared
# Duplication de l'accroche, sans s'occuper des éventuels sous-accroches
mount --bind /tmp /mnt/test-shared
```
</div>
@ -112,32 +145,31 @@ Si l'on attache un nouveau point de montage dans `/tmp` ou dans
<div lang="en-US">
```shell
mkdir /mnt/test-shared/toto
mount -t tmpfs none /mnt/test-shared/toto
mkdir /mnt/test-shared/toto
mount -t tmpfs none /mnt/test-shared/toto
```
</div>
Un coup de `findmnt` nous montre l'existence de deux nouveaux points de
montages. À `/mnt/test-shared/toto`, mais également à `/tmp/toto`.
montage. À `/mnt/test-shared/toto`, mais également à `/tmp/toto`.
### esclave -- *slave mount*
De la même manière que lorsque la propagation est partagée, cette politique
propagera, mais seulement dans un sens. Le point de montage déclaré comme
esclave ne propagera pas ses nouveaux points de montage.
esclave ne propagera pas ses nouveaux points de montage à son *maître*.
<div lang="en-US">
```shell
# Suite de l'exemple précédent
cd /mnt
mkdir test-slave
# Suite de l'exemple précédent
cd /mnt/test-slave
# Duplication de l'accroche, sans s'occuper des éventuels sous-accroches
mount --bind /mnt/test-shared /mnt/test-slave
# Duplication de l'accroche, sans s'occuper des éventuels sous-accroches
mount --bind /mnt/test-shared /mnt/test-slave
# On rend notre dossier esclave
mount --make-slave /mnt/test-slave
# On rend notre dossier esclave
mount --make-slave /mnt/test-slave
```
</div>
@ -145,8 +177,8 @@ Si l'on effectue un montage dans `/mnt/test-shared` :
<div lang="en-US">
```shell
mkdir /mnt/test-shared/foo
mount -t tmpfs none /mnt/test-shared/foo
mkdir /mnt/test-shared/foo
mount -t tmpfs none /mnt/test-shared/foo
```
</div>
@ -154,8 +186,8 @@ Le point de montage apparaît bien sous `/mnt/test-slave/foo`. Par contre :
<div lang="en-US">
```shell
mkdir /mnt/test-slave/bar
mount -t tmpfs none /mnt/test-slave/bar
mkdir /mnt/test-slave/bar
mount -t tmpfs none /mnt/test-slave/bar
```
</div>
@ -164,14 +196,15 @@ Le nouveau point de montage n'est pas propagé dans `/mnt/test-shared/bar`.
### privé -- *private mount*
C'est le mode le plus simple : ici les points de montage ne sont pas propagés.
C'est le mode le plus simple : ici les points de montage ne sont tout
simplement pas propagés.
Pour forcer un point d'accroche a ne pas propager et à ne pas recevoir de
Pour forcer un point d'accroche à ne pas propager et à ne pas recevoir de
propagation, on utilise l'option suivante :
<div lang="en-US">
```shell
mount --make-private mountpoint
mount --make-private mountpoint
```
</div>
@ -182,7 +215,7 @@ Ce mode interdira tout tentative d'attache à un autre endroit.
<div lang="en-US">
```shell
mount --make-unbindable /mnt/test-slave
mount --make-unbindable /mnt/test-slave
```
</div>
@ -190,24 +223,24 @@ Il ne sera pas possible de faire :
<div lang="en-US">
```shell
mkdir /mnt/test-unbindable
mount --bind /mnt/test-slave /mnt/test-unbindable
mkdir /mnt/test-unbindable
mount --bind /mnt/test-slave /mnt/test-unbindable
```
</div>
### Parfums récursifs
Les options que nous venons de voir s'applique sur un point de montage. Il
Les options que nous venons de voir s'appliquent sur un point de montage. Il
existe les mêmes options pour les appliquer en cascade sur les points d'attache
contenu dans le sous-arbre :
contenus dans leur sous-arbre :
<div lang="en-US">
```
mount --make-rshared mountpoint
mount --make-rslave mountpoint
mount --make-rprivate mountpoint
mount --make-runbindable mountpoint
mount --make-rshared mountpoint
mount --make-rslave mountpoint
mount --make-rprivate mountpoint
mount --make-runbindable mountpoint
```
</div>
@ -215,9 +248,10 @@ mount --make-runbindable mountpoint
## `bind` de dossiers et de fichiers
Il n'est pas nécessaire que le point d'accroche que l'on cherche à dupliquer
pointe sur un point de montage. Il peut parfaitement pointer sur un dossier, et
même sur un simple fichier, à la manière d'un *hardlink*, mais que l'on
pourrait faire entre plusieurs partition et qui ne persisterait pas au
pointe sur un point de montage (c'est-à-dire, dans la plupart des cas : une
partition ou un système de fichiers virtuel). Il peut parfaitement pointer sur
un dossier, et même sur un simple fichier, à la manière d'un *hardlink*, mais
que l'on pourrait faire entre plusieurs partitions et qui ne persisterait pas au
redémarrage.
Nous verrons dans la partie *namespace* réseau, une utilisation d'attache sur
@ -226,17 +260,17 @@ un fichier.
## Déplacer un point de montage
À tout moment, il est possible réorganiser les points de montage, en les
À tout moment, il est possible de réorganiser les points de montage, en les
déplaçant. Comme cela se fait sans démonter de partition, il est possible de le
faire même si un fichier est en cours d'utilisation. Il faut cependant veiller
à ce que les programmes suceptibles d'aller chercher un fichier à l'ancien
emplacement soit prévenu du changement.
à ce que les programmes susceptibles d'aller chercher un fichier à l'ancien
emplacement soient prévenu du changement.
On utilise pour cela l'option `--move` de `mount(8)` :
<div lang="en-US">
```shell
mount --move olddir newdir
mount --move olddir newdir
```
</div>
@ -244,18 +278,19 @@ Par exemple :
<div lang="en-US">
```shell
mount --move /dev /newroot/dev
mount --move /dev /newroot/dev
```
</div>
Il est courant de faire appel à cette option lorsque l'on souhaite changer la
racine de notre système de fichier : passer de l'initramfs au système au
booter, de notre système hôte au système d'un conteneur, ...
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, ...
## Aller plus loin
Voici quelques articles qui valent de détour :
Voici quelques articles qui valent le détour, en lien avec les points de
montage :
* [Shared subtree](https://lwn.net/Articles/159077) et la
[documentation du noyau associée](https://kernel.org/doc/Documentation/filesystems/sharedsubtree.txt) ;

View File

@ -3,10 +3,8 @@
Le *namespace* `mount`
======================
## Introduction
Le *namespace* `mount`, comme nous l'avons vu au chapitre précédent, permet
d'isoler la vision du système de fichiers qu'à un processus et ses fils.
L'espace de noms `mount` permet d'isoler la vision du système de fichiers
qu'ont un processus et ses fils.
Peut-être que l'on peut trouver avec ça, un moyen de faire un `chroot` plus sûr ?
@ -42,8 +40,8 @@ Si vous n'avez pas de partition à disposition, vous pouvez utiliser un `tmpfs`
<div lang="en-US">
```shell
mkdir /mnt/newroot
mount -t tmpfs none /mnt/newroot
42sh# mkdir /mnt/newroot
42sh# mount -t tmpfs none /mnt/newroot
```
</div>
@ -76,7 +74,7 @@ Isolons-nous :
<div lang="en-US">
```shell
unshare -p -m -f --mount-proc
42sh# unshare -p -m -f --mount-proc
```
</div>
@ -92,7 +90,7 @@ comme esclave :
<div lang="en-US">
```shell
mount --make-rslave /
42sh# mount --make-rslave /
```
</div>
@ -119,12 +117,13 @@ la première commande (*init*) de votre choix.
#### `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ées
`pivot_root(2)`, requiert de notre part que nous ayons préalablement déplacé
les partitions systèmes à leur place dans la nouvelle racine.
L'appel de la commande, qui prend en argument le chemin de la nouvelle racine
et le chemin dans la nouvelle racine où placer l'ancienne, va donc intervertir
les deux racines.
L'appel de la commande sert à intervertir les deux racines ; elle prend en argument :
* le chemin de la nouvelle racine,
* le chemin dans la nouvelle racine où placer l'ancienne.
Une fois le pivot effectué, on peut démonter l'ancienne racine.
@ -133,6 +132,6 @@ par :
<div lang="en-US">
```shell
exec chroot / command
42sh# exec chroot / command
```
</div>

View File

@ -1,53 +1,77 @@
\newpage
Les *namespaces*
================
Les espaces de noms -- *namespaces* {#namespaces}
===================================
## Introduction
Les espaces de noms du noyau, les *namespaces*, permettent de dupliquer
certaines structures du noyau, dans le but de les isoler d'un groupe de
processus à un autre.
Les espaces de noms du noyau, les *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.
On en dénombre 7 depuis Linux 4.6 : `CGroup`, `IPC`, `network`, `mount`, `PID`,
`user` et `UTS`.
On en dénombre sept depuis Linux 4.6 : `cgroup`, `IPC`, `network`,
`mount`, `PID`, `user` et `UTS`.
La notion d'espace de noms est relativement nouvelle et a été intégrée
progressivement au sein du noyau Linux. Aussi, toutes les structures
ne sont pas encore *containerisables* :
[le document fondateur](https://www.kernel.org/doc/ols/2006/ols2006v1-pages-101-112.pdf)
parle ainsi d'isoler les périphériques, ou encore l'horloge. Pour ce
dernier,
[un patch a même déjà été proposé](https://lwn.net/Articles/179825/).
### `mount` *namespaces*
### L'espace de noms `mount` {#mount-ns}
Depuis Linux 2.4.19.
Isole la liste des points de montage.
Cet espace de noms isole la liste des points de montage.
Chaque processus d'un *namespace* différent peut monter, démonter et
réorganiser à sa guise les points de montage. Une partition ne sera donc pas
nécessairement démonté après un appel à `umount(2)`, elle le sera lorsqu'elle
Chaque processus appartenant à un *namespace* différent peut monter, démonter
et réorganiser à sa guise les points de montage, sans que cela n'ait d'impact
sur les processus hors de cet espace de noms. Une partition ne sera 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* dans lequel elle était
montée.
Attention il convient cependant de prendre garde aux types de liaison existant
entre vos points de montage (voir la partie sur
[les particularités des points de montage](#mount)), car les montages et
démontages pourraient alors être répercutés dans l'espace de noms parent.
### `UTS` *namespaces*
Une manière rapide pour s'assurer que nos modifications ne sortiront pas de
notre *namespace* est d'appliquer le type esclave à l'ensemble de nos points de
montage, récursivement, dès que l'on est entré dans notre nouvel espace de
noms.
```shell
mount --make-rslave /
```
### L'espace de noms `UTS` {#uts-ns}
Depuis Linux 2.6.19.
Isole le nom de machine et son domaine NIS.
Cet espace de noms isole le nom de machine et son domaine NIS.
### `IPC` *namespaces*
### L'espace de noms `IPC` {#ipc-ns}
Depuis Linux 2.6.19.
Isole les objets IPC et les files de messages POSIX.
Cet espace de noms isole les objets IPC et les files de messages POSIX.
Une fois le *namespace* attaché à un processus, il ne peut alors plus parler
qu'avec les autres processus de son *namespace*.
qu'avec les autres processus de son espace de noms (lorsque ceux-ci passent par
l'API IPC du noyau).
### `PID` *namespaces*
### L'espace de noms `PID`
Depuis Linux 2.6.24.
Isole la liste des processus et virtualise leurs numéros.
Cet espace de noms isole la liste des processus et virtualise leurs numéros.
Une fois dans un espace, le processus ne voit que le sous-arbre de processus
également attachés à son espace. Il s'agit d'un sous-ensemble de l'arbre global
@ -55,22 +79,22 @@ de PID : les processus de tous les PID *namespaces* apparaissent donc dans
l'arbre initial.
Pour chaque nouvel espace de noms de processus, une nouvelle numérotation est
initié ; ainsi, le premier processus de cet espace porte le numéro 1 et aura
les mêmes propriétés que le processus `init` usuel ; entre autres, si un
initiée. Ainsi, le premier processus de cet espace porte le numéro 1 et aura
les mêmes propriétés que le processus `init` usuel ; entre autre, si un
processus est rendu orphelin dans ce *namespace*, il devient un fils de ce
processus, et non un fils de l'`init` de l'arbre global.
### `network` *namespaces*
### L'espace de nom `network`
Depuis Linux 2.6.29.
Fourni une isolation pour toutes les ressources associées aux réseaux : les
interfaces, les piles protocolaires IPv4 et IPv6, les tables de routage,
pare-feu, ports numérotés, etc.
Cet espace de noms fournit une isolation pour toutes les ressources associées
aux réseaux : les interfaces, les piles protocolaires IPv4 et IPv6, les tables
de routage, règles pare-feu, ports numérotés, etc.
Une interface réseau (`eth0`, `wlan0`, ...) ne peut se trouver que dans un seul
*namespace* à la fois, mais il est possible de les déplacer.
espace de noms à la fois. Il est par contre possible de les déplacer.
Lorsque le *namespace* est libéré (généralement lorsque le dernier processus
attaché à cet espace de noms se termine), les interfaces qui le composent sont
@ -78,30 +102,32 @@ ramenées dans l'espace initial (et non pas dans l'espace parent, en cas
d'imbrication).
### `user` *namespaces*
### L'espace de noms `user`
Depuis Linux 3.8.
Isole la liste des utilisateurs, des groupes, leurs identifiants, les
capabilities, la racine et le trousseau de clefs du noyau.
Cet espace de noms isole la liste des utilisateurs, des groupes, leurs
identifiants, les *capabilities*, la racine et le trousseau de clefs du noyau.
La principale caractéristique est que les identifiants d'utilisateur et de
groupe pour un processus peuvent être différent entre l'intérieur et
l'extérieur du conteneur. Il est alors possible, alors que l'on est un simple
utilisateur à l'extérieur du *namespace*, d'avoir l'UID 0 dans le conteneur.
groupe pour un processus peuvent être différents entre l'intérieur et
l'extérieur de l'espace de noms. Il est donc possible, alors que l'on est un
simple utilisateur à l'extérieur du *namespace*, d'avoir l'UID 0 dans le
conteneur.
### `CGroup` *namespaces*
### L'espace de noms `cgroup` {#cgroup-ns}
Depuis Linux 4.6.
Isole la vue de la racine des *Control Group* en la plaçant sur un
sous-groupe de l'arborescence.
Cet espace de noms filtre l'arborescence des *Control Group* en changeant la
racine de l'arborescence des cgroups. Au sein d'un *namespace*, la racine vue
correspond en fait à un sous-groupe de l'arborescence globale.
Ainsi, un processus dans un `CGroup` *namespace* ne peut pas voir le contenu
des sous-groupes parents (pouvant laisser fuiter des informations sur le reste
du système). Cela peut également permettre de faciliter la migration de
processus (d'un système à un autre) : l'arborescences des cgroups n'a alors
processus (d'un système à un autre) : l'arborescence des cgroups n'a alors
plus d'importance car le processus ne voit que son groupe.
@ -112,38 +138,34 @@ plus d'importance car le processus ne voit que son groupe.
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
nécessaire pour appeler l'appel système `unshare(2)`, puis, tout comme
nécessaire pour lancer l'appel système `unshare(2)`, puis, tout comme
`chroot(1)`, exécuter le programme passé en paramètre.
En fonction des options qui lui sont passées, `unshare(1)` va créer le/les
nouveaux *namespaces* et placer le processus dedans.
Par exemple, nous pouvons modifier sans crainte le nom de notre machine, si
nous sommes passé dans un autre *namespace* `UTS` :
nous sommes passés dans un autre *namespace* `UTS` :
<div lang="en-US">
```shell
42sh# hostname --fqdn
koala.zoo.paris
42sh# sudo unshare -u /bin/bash
bash# hostname --fqdn
koala.zoo.paris
bash# hostname lynx.zoo.paris
bash# hostname --fqdn
lynx.zoo.paris
bash# exit
42sh# hostname --fqdn
koala.zoo.paris
42sh# hostname --fqdn
koala.zoo.paris
42sh# sudo unshare -u /bin/bash
bash# hostname --fqdn
koala.zoo.paris
bash# hostname lynx.zoo.paris
bash# hostname --fqdn
lynx.zoo.paris
bash# exit
42sh# hostname --fqdn
koala.zoo.paris
```
</div>
Nous avons pu ici modifier le nom de machine, sans que cela n'affecte notre
Nous avons pu ici modifier le nom de la machine, sans que cela n'affecte notre
machine hôte.
Essayons maintenant avec d'autres options de notre programme pour voir les
effets produits : par exemple, comparons un `ip address` à l'extérieur et à
l'intérieur d'un `unshare -n`.
### Les appels systèmes
@ -166,8 +188,9 @@ avec d'autres `flags` attendu par la fonction.
Les mêmes `flags` sont utilisés lors des appels à `unshare(2)` ou `setns(2)`.
Pour créer un nouveau processus qui sera à la fois dans un nouvel *namespace*
réseau et dans un nouveau *namespace* CGroup, on écrirait un code similaire à :
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
similaire à :
<div lang="en-US">
```c
@ -188,67 +211,10 @@ pid_t pid = clone(do_execvp,
Le premier argument est un pointeur sur fonction. Il s'agit de la fonction qui
sera appelée par le nouveau processus.
## Comparaison de *namespace*
Les *namespaces* d'un programme sont exposés sous forme de liens symboliques
dans le répertoire `/proc/<PID>/ns/`.
Deux programmes qui partagent un même *namespace* auront un lien vers la même
structure de données.
Écrivons un script ou un programme, `cmpns`, permettant de déterminer si deux
programmes s'exécutent dans les mêmes *namespaces*.
### Exemples
<div lang="en-US">
```sh
42sh$ ./cmpns $(pgrep influxdb) $(pgrep init)
- cgroup: differ
- ipc: differ
- mnt: differ
- net: differ
- pid: differ
- user: same
- uts: same
```
</div>
<div lang="en-US">
```sh
42sh$ ./cmpns $(pgrep init) self
- cgroup: same
- ipc: same
- mnt: same
- net: same
- pid: same
- user: same
- uts: same
```
</div>
Ici, `self` fait référence au processus actuellement exécuté.
Et pourquoi pas :
<div lang="en-US">
```sh
42sh$ unshare -m ./cmpns $$ self
- cgroup: same
- ipc: same
- mnt: differ
- net: same
- pid: same
- user: same
- uts: same
```
</div>
## Rejoindre un *namespace*
Rejoindre un *namespace* se fait en utilisant l'appel système `setns(2)`,
Rejoindre un espace de noms se fait en utilisant l'appel système `setns(2)`,
auquel on passe le *file descriptor* d'un des liens du dossier
`/proc/<PID>/ns/` :
@ -293,31 +259,15 @@ Dans un shell, on utilisera la commande `nsenter(1)` :
</div>
### `docker exec`
## Durée de vie d'un *namespace* {#ns-lifetime}
Si vous avez bien suivi jusque là, vous avez dû comprendre qu'un `docker exec`,
n'était donc rien de plus qu'un `nsenter(1)`.
Réécrivons, en quelques lignes, la commande `docker exec` !
Pour savoir si vous avez réussi, comparez les sorties des commandes :
- `ip address` ;
- `hostname` ;
- `mount` ;
- `pa -aux` ;
- ...
## Durée de vie d'un *namespace*
Le noyau tient à jour un compteur de référence pour chaque *namespace*. Dès
qu'une référence tombe à 0, le *namespace* est automatiquement libéré, les
Le noyau tient à jour un compteur de références pour chaque *namespace*. Dès
qu'une référence tombe à 0, l'espace de noms est automatiquement libéré, les
points de montage sont démontés, les interfaces réseaux sont réattribués à
l'espace de noms initial, ...
Ce compteur évolue selon plusieurs critères, et principalement selon le nombre
de processus qui l'utilisent. C'est-à-dire que, la plupart du temps, le
de processus qui l'utilise. C'est-à-dire que, la plupart du temps, le
*namespace* est libéré lorsque le dernier processus s'exécutant dedans se
termine.
@ -339,7 +289,8 @@ de fichier valide vers le *namespace* (pour passer à `setns(2)`).
### Faire persister un *namespace*
Il n'est pas possible de faire persister un namespace d'un reboot à l'autre.
Il n'est pas possible de faire persister un espace de noms d'un reboot à
l'autre.
Même en étant attaché à un fichier du disque, il s'agit d'un pointeur vers une
structure du noyau, qui ne persistera pas au redémarrage.
@ -349,12 +300,13 @@ structure du noyau, qui ne persistera pas au redémarrage.
Je vous recommande la lecture des *man* suivants :
* `namespaces(7)` : introduisant et énumérant les namespaces ;
* `namespaces(7)` : introduisant et énumérant les *namespaces* ;
Pour tout connaître en détails, [la série d'articles de Michael Kerrisk sur
les *namespaces*](https://lwn.net/Articles/531114/) est excellente ! Auquel il
faut ajouter [le petit dernier sur le CGroup
faut ajouter [le petit dernier sur le `cgroup`
*namespace*](https://lwn.net/Articles/621006/).
Cet article
[de Michael Crosby montrant l'utilisation de clone(2)](http://crosbymichael.com/creating-containers-part-1.html).
[Cet article de Michael Crosby montrant l'utilisation de clone(2)](http://crosbymichael.com/creating-containers-part-1.html)
est également des plus intéressants, pour ce qui concerne la programmation
plus bas-niveau.

View File

@ -1,6 +1,6 @@
\newpage
Le *namespace* `network`
Le *namespace* `network` {#net-ns}
========================
## Introduction
@ -10,7 +10,7 @@ ce qui est en lien avec le réseau : les interfaces, les ports, les routes, les
règles de filtrage, etc.
En entrant dans un nouvel espace de nom `network`, on se retrouve dans un
environnement qui n'a plus que l'interface de loopback :
environnement qui n'a plus qu'une interface de *loopback* :
<div lang="en-US">
```shell
@ -20,9 +20,13 @@ environnement qui n'a plus que l'interface de loopback :
```
</div>
Qui dit nouvelle pile réseau, dit également que les ports qui étaient assignés
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.
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 entre en conflit avec celui d'un
possible de lancer un serveur web sans qu'il n'entre en conflit avec celui d'un
autre espace de noms.
@ -31,45 +35,51 @@ autre espace de noms.
La suite d'outils `iproute2` propose une interface simplifiée pour utiliser le
*namespace* `network` : `ip netns`.
Tout d'abord, nous allons créer un nouvel espace de nom :
Nous pouvons tout d'abord créer un nouvel espace de nom :
<div lang="en-US">
```shell
42sh$ ip netns add virli
42sh# ip netns add virli
```
</div>
La technique utilisée ici pour avoir des *namespaces* nommés est la même que
celle que nous avons vu dans la première partie sur les *namespaces* : 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.
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.
Maintenant que notre *namespace* est créé, voyons s'il contient des interfaces :
Maintenant que notre *namespace* est créé, nous pouvons regarder s'il contient
des interfaces :
<div lang="en-US">
```sh
42sh$ ip netns exec virli ip link
42sh# ip netns exec virli ip link
1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN mode DEFAULT group default qlen 1
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
```
</div>
Cette commande ne devrait vous montrer que l'interface de *loopback*, car nous
n'avons pour l'instant pas encore attaché la moindre interface.
Cette commande ne nous montre que l'interface de *loopback*, car nous n'avons
pour l'instant pas encore attaché la moindre interface.
D'ailleurs, cette interface est rapportée comme étant désactivée, activons-là
via la commande :
<div lang="en-US">
```shell
42sh$ ip netns exec virli ip link set dev lo up
42sh# ip netns exec virli ip link set dev lo up
```
</div>
Si tout se passe bien, vous devriez maintenant pouvoir lancer un `ping` sur
cette interface :
À ce stade, nous pouvons déjà commencer à lancer un `ping` sur cette interface:
<div lang="en-US">
```shell
42sh$ ip netns exec virli ping 127.0.0.1
42sh# ip netns exec virli ping 127.0.0.1
PING 127.0.0.1 (127.0.0.1) 56(84) bytes of data.
64 bytes from 127.0.0.1: icmp_seq=1 ttl=64 time=0.038 ms
...
```
</div>
@ -86,108 +96,135 @@ type `veth` :
<div lang="en-US">
```
ip link add veth0 type veth peer name veth1
42sh# ip link add veth0 type veth peer name veth1
```
</div>
Une interface `veth` se comporte comme un tube bidirectionnel : tout ce qui
entre d'un côté sort de l'autre et inversement. La commande précédente a donc
créé deux interfaces `veth0` et `veth1` : les paquets envoyés sur `veth0` sont
donc reçu par `veth1` et les paquets envoyés à `veth1` sont reçus par `veth0`.
donc reçus par `veth1` et les paquets envoyés à `veth1` sont reçus par `veth0`.
Dans cette configuration, ces deux interfaces ne sont pas très utiles, mais si
l'on place l'une des deux extrêmité dans une autre *namespace* `network`, il
devient alors possible de réaliser un échange de paquet entre les deux.
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.
Pour déplacer `veth1` dans notre *namespace* `virli` :
<div lang="en-US">
```shell
42sh# ip link set veth1 netns virli
42sh# ip link set veth1 netns virli
```
</div>
Il ne reste plus maintenant qu'à assigner une IP à chacune des interfaces :
Il ne reste maintenant plus qu'à assigner une IP à chacune des interfaces :
<div lang="en-US">
```shell
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
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
```
</div>
Testons maintenant que la communication entre les deux passe bient :
Dès lors[^linkdown], nous pouvons `ping`er chaque extrêmité :
[^linkdown]: Il peut être nécessaire d'activer chaque lien, via `ip link set
vethX up`.
<div lang="en-US">
```shell
42sh# ping 10.10.10.42
- et -
42sh# ip netns exec virli ping 10.10.10.41
42sh# ping 10.10.10.42
- et -
42sh# ip netns exec virli ping 10.10.10.41
```
</div>
Il ne reste donc pas grand chose à faire pour fournir Internet à notre
conteneur, via un peu de NAT ou grâce à un pont Ethernet.
conteneur : via un peu de NAT ou grâce à un pont Ethernet.
## Les autres types d'interfaces
Le bridge ou le NAT obligera tous les paquets à passer à travers de nombreuses
couches du noyau. Utiliser les interface *veth* est plutôt simple et disponible
couches du noyau. Utiliser les interfaces *veth* est plutôt simple et disponible
partout, mais c'est loin d'être la technique la plus rapide ou la moins
gourmande.
### VLAN
Il est possible d'attribuer juste une interface de VLAN, si l'on a switch les
supportant.
Il est possible d'attribuer juste une interface de VLAN, si l'on a switch
supportant la technologie [802.1q](https://fr.wikipedia.org/wiki/IEEE_802.1Q).
<div lang="en-US">
```
42sh# ip link add link eth0 name eth0.100 type vlan id 100
42sh# ip link set dev eth0.100 up
42sh# ip link set eth0.100 netns virli
42sh# ip link add link eth0 name eth0.100 type vlan id 100
42sh# ip link set dev eth0.100 up
42sh# ip link set eth0.100 netns virli
```
</div>
### MACVLAN
Lorsque l'on a pas assez de carte ethernet et que le switch ne supporte pas les
VLAN, le noyau met à disposition un routage basé sur les adresses MAC : le
<!-- https://hicu.be/bridge-vs-macvlan -->
Lorsque l'on n'a pas assez de carte ethernet et que le switch ne supporte pas
les VLAN, le noyau met à disposition un routage basé sur les adresses MAC : le
MACVLAN. S'il est activé dans votre noyau, vous allez avoir le choix entre l'un
des deux modes : VEPA ou *bridge*.
des quatre modes : *private*, VEPA, *bridge* ou *passthru*.
Quelque 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
différences entre les modes se trouvent au niveau de la communication entre les
interfaces.
#### VEPA
Dans ce mode, tous les paquets sortant sont directement envoyé sur l'interface
ethernet de sortie, sans qu'aucun routage préalable n'est été effectué. Ainsi,
si un paquet est à destination d'un des autres conteneur de la machine, c'est à
l'équipement réseau derrière la machine de rerouter le paquet vers la machine
émetrice.
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é
effectué. Ainsi, si un paquet est à destination d'un des autres conteneurs de
la machine, c'est à l'équipement réseau derrière la machine de rerouter le
paquet vers la machine émettrice (par exemple un switch
[802.1Qbg](http://www.ieee802.org/1/pages/802.1bg.html)).
Pour construire une nouvelle interface de ce type :
<div lang="en-US">
```
ip link add link eth0 mac0 type macvlan mode vepa
42sh# ip link add link eth0 mac0 type macvlan mode vepa
```
</div>
#### *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
pas délivré.
Dans ce mode, on est donc assuré qu'aucun conteneur ne puisse parler à un
conteneur de la même machine.
<div lang="en-US">
```
42sh# ip link add link eth0 mac1 type macvlan mode private
```
</div>
#### *Bridge*
À l'inverse du mode *VEPA*, les paquets sont routés selon leur adresse MAC : si
jamais une adresse MAC est connuee, le paquet est délivré à l'interface MACVLAN
correspondante ; dans le cas contraire, le paquet est envoyé sur l'interface de
sortie.
À 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.
Pour construire une nouvelle interface de ce type :
<div lang="en-US">
```
ip link add link eth0 mac1 type macvlan mode bridge
42sh# ip link add link eth0 mac2 type macvlan mode bridge
```
</div>

View File

@ -1,21 +1,21 @@
\newpage
Le *namespace* `PID`
Le *namespace* `PID` {#pid-ns}
=====================
## Introduction
## Introduction {#pid-ns-intro}
Le *namespace* `PID` est celui qui va nous permettre d'isoler un sous-arbre de
processus en créant un nouvel arbre, qui aura son propre processus considéré
L'espace de noms `PID` est celui qui va nous permettre d'isoler un sous-arbre
de processus en créant un nouvel arbre, qui aura son propre processus considéré
comme l'`init`.
Contrairement aux autres *namespaces* où l'on peut demander à se séparer du
*namespace* en question à n'importe quel moment de l'exécution du processus,
via `unshare(2)` ou `setns(2)` par exemple, dans le cas du *namespace* PID, le
changement n'est valable qu'après le prochain `fork(2)` (ou similaire). Le
*namespace* PID du processus courant n'est pas changé, afin que le processus ne
change pas de PID en cours de route (en fonction du *namespace* dans lequel il
se trouve).
via `unshare(2)` ou `setns(2)` par exemple, ici, le changement n'est valable
qu'après le prochain `fork(2)` (ou similaire).
L'espace de noms PID du processus courant n'est pas changé, afin que le
processus ne change pas de PID en cours de route (puisque fonction du
*namespace* dans lequel il se trouve).
## Isolons !
@ -24,12 +24,12 @@ Première étape s'isoler :
<div lang="en-US">
```shell
unshare --pid --fork /bin/bash
42sh# unshare --pid --fork /bin/bash
```
</div>
Nous utilisons ici l'option `-f`, pour que le passage dans le nouvel espace de
noms des PID soit effectif (cf. Introduction).
noms des PID soit effectif (cf. [Introduction](#pid-ns-intro)).
Un coup d'œil à `top` ou `ps aux` devrait nous montrer que l'on est maintenant
seul processus ... pourtant, il n'en est rien, ces deux commandes continuent
@ -39,10 +39,10 @@ Cela est dû au fait que ces deux programmes, sous Linux, se basent sur le
contenu de `/proc`. D'ailleurs, si l'on affiche le PID du processus courant
`echo $$`, on obtient bien 1.
En l'état, beaucoup d'information fuitent. Mais il n'est pas possible de monter
le bon `/proc` car il serait également monté pour les processus de notre
système initial. Pour s'en sortir, il est nécessaire de s'isoler du *namespace*
`mount`.
En l'état, beaucoup d'informations sont divulguées. Mais il n'est pas possible
de monter le bon `/proc` car il serait également monté pour les processus de
notre système initial. Pour s'en sortir, il est nécessaire de s'isoler du
*namespace* `mount`.
### Double isolation : ajout du *namespace* `mount`
@ -51,7 +51,7 @@ Voici la nouvelle ligne de commande que l'on va utiliser :
<div lang="en-US">
```shell
unshare --pid --mount --fork --mount-proc /bin/bash
42sh# unshare --pid --mount --fork --mount-proc /bin/bash
```
</div>
@ -64,16 +64,15 @@ Cette fois, `top` et `ps` nous rapportent bien que l'on est seul dans notre
## Arborescence à l'extérieur du *namespace*
Vous l'avez sans doute remarqué lors de votre première tentative de `top`,
lorsque le `/proc` était encore monté sur le procfs de l'espace initial : votre
processus au PID 1 dans son nouveau *namespace* était présent dans
l'arborescence de l'espace initial avec un PID dans la continuité des autres
processus, étonnant !
Lors de notre première tentative de `top`, lorsque `/proc` était encore monté
sur le `procfs` de l'espace de noms initial : votre processus (au PID 1 dans
son nouveau *namespace*) était présent dans l'arborescence de l'espace initial
avec un PID dans la continuité des autres processus, étonnant !
En fait, l'isolation consiste en une virtualisation des numéros du processus :
la plupart des processus du système intial ne sont pas accessibles, et ceux qui
font parti de l'espace de noms créé disposent d'une nouvelle numérotation. Et
c'est cette nouvelle numérotation qui est montré au processus.
font partie de l'espace de noms créé disposent d'une nouvelle numérotation. Et
c'est cette nouvelle numérotation qui est montrée au processus.
Si l'on veut interagir avec ce processus depuis un de ses espaces de noms
parent, il faut le faire avec son identifiant de processus du même *namespace*
@ -89,13 +88,13 @@ Si un processus est orphelin, il est donc affiché comme étant fils du PID 1
dans son *namespace* ; il n'est pas sorti de l'espace de nom.
Lorsque l'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 à
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 à
l'intérieur pour conserver un comportement cohérent.
## Aller plus loin
N'hésitez pas à jeter un œil à la page de manuel consacré à ce *namespace* :
`pid_namespaces(7)`.
N'hésitez pas à jeter un œil à la page de manuel consacré à cet espace de
noms : `pid_namespaces(7)`.

View File

@ -1,16 +1,21 @@
## Modalité de rendu
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 les
vérifications nécessaires et de vous envoyer un accusé de réception (ou de
rejet).
Ce service écoute sur l'adresse <virli@nemunai.re>, c'est donc à cette adresse
Ce service écoute sur l'adresse <virli@nemunai.re>. 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.
## Tarball
Tarball
-------
Le projet à rendre pour ce cours est à placer dans une tarball (pas d'archive
ZIP, RAR, ...).

47
tutorial/4/rendu.md Normal file
View File

@ -0,0 +1,47 @@
\newpage
Projet et rendu
===============
Projet
------
[Le sujet complet du projet est disponible ici](https://virli.nemunai.re/project-2.pdf). Il
n'est pas à rendre en même temps que le TP. Consultez ses modalités de rendu
pour plus d'informations.
Modalités de rendu
------------------
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 <virli@nemunai.re>. 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.
Par ailleurs, n'oubliez pas de répondre à
[l'évaluation du cours](https://www.epitaf.fr/moodle/mod/quiz/view.php?id=42).
Afin de m'aider à améliorer ce cours, je vous invite à remplir
[ce sondage anonyme](https://www.epitaf.fr/moodle/mod/quiz/view.php?id=44).
Tarball
-------
Tous les exercices de ce TP sont à placer dans une tarball (pas d'archive ZIP,
RAR, ...).
Voici une arborescence type :
<div lang="en-US">
```
login_x-TP4/cmpns.sh
login_x-TP4/mydocker_exec.sh
login_x-TP4/myswitch_root.sh
```
</div>

110
tutorial/4/setup.md Normal file
View File

@ -0,0 +1,110 @@
\newpage
Mise en place
=============
Noyau Linux
-----------
Ce TP requiert un noyau Linux, dans sa version 3.12 au minimum. Il doit de plus
être compilé avec les options suivantes (lorsqu'elles sont disponibles pour
votre version) :
<div lang="en-US">
```
General setup --->
[*] Control Group support --->
-*- Namespaces support
[*] UTS namespace
[*] IPC namespace
[*] User namespace
[*] PID Namespaces
[*] Network namespace
[*] Networking support --->
Networking options --->
<M> 802.1d Ethernet Bridging
Device Drivers --->
[*] Network device support --->
<M> MAC-VLAN support
<M> Virtual ethernet pair device
```
</div>
Les variables de configuration correspondantes sont :
<div lang="en-US">
```
CONFIG_CGROUPS=y
CONFIG_NAMESPACES=y
CONFIG_UTS_NS=y
CONFIG_IPC_NS=y
CONFIG_USER_NS=y
CONFIG_PID_NS=y
CONFIG_NET_NS=y
CONFIG_NET=y
CONFIG_BRIDGE=m
CONFIG_NETDEVICES=y
CONFIG_MACVLAN=m
CONFIG_VETH=m
```
</div>
Référez-vous, si besoin, au TP précédent pour la marche à suivre.
Paquets
-------
Nous allons utiliser des programmes issus des
[`util-linux`](https://www.kernel.org/pub/linux/utils/util-linux/), de
[`procps-ng`](https://gitlab.com/procps-ng/procps) ainsi que ceux de la
[`libcap`](http://www.friedhoff.org/posixfilecaps.html).
Sous Debian et ses dérivés, ses paquets sont respectivement :
* `util-linux`
* `procps`
* `libcap2-bin`
À propos de la sécurité de l'espace de nom `user`
-------------------------------------------------
La sécurité du *namespace* `user` a souvent été remise en cause et on lui
attribue de nombreuses vulnérabilités. Je vous laisse consulter à ce sujet :
* [Anatomy of a user namespaces vulnerability](https://lwn.net/Articles/543273/) ;
* <http://marc.info/?l=linux-kernel&m=135543612731939&w=2> ;
* <http://marc.info/?l=linux-kernel&m=135545831607095&w=2>.
De nombreux projets ont choisi de ne pas autoriser l'utilisation de cet espace
de noms sans disposer de certaines *capabilities*[^userns-caps].
[^userns-caps]: Sont nécessaires, conjointement, `CAP_SYS_ADMIN`, `CAP_SETUID` et `CAP_SETGID`.
De nombreuses distributions ont par exemple choisi d'utiliser un paramètre du
noyau pour adapter le comportement.
### Debian et ses dérivées {.unnumbered}
Si vous utilisez Debian ou l'un de ses dérivés, vous devrez autoriser
explicitement cette utilisation non-privilégiée :
```shell
42sh# sysctl -w kernel.unprivileged_userns_clone=1
```
### Grsecurity {.unnumbered}
D'autres patchs, tels que
[*grsecurity*](https://forums.grsecurity.net/viewtopic.php?f=3&t=3929#p13904) ont
fait le choix de désactiver cette possibilité sans laisser d'option pour la
réactiver éventuellement à l'exécution. Pour avoir un comportement identique à
celui de Debian, vous pouvez
[appliquer ce patch](https://virli.nemunai.re/grsec-enable-user-ns.patch), sur
vos sources incluant le patch de *grsecurity*.

View File

@ -1,25 +1,23 @@
---
title: Virtualisation légère -- TP n^o^ 4
subtitle: Linux Internals partie 2
author: Pierre-Olivier *Nemunaire* Mercier
author: Pierre-Olivier *nemunaire* Mercier
institute: EPITA
date: Jeudi 20 octobre 2016
date: Jeudi 2 novembre 2017
...
Le but de ce second TP sur les mécanismes internes du noyau va nous permettre
d'utiliser les commandes et les appels systèmes relatifs aux *namespaces* ainsi
que d'appréhender la complexité des sytèmes de fichiers.
que d'appréhender la complexité des systèmes de fichiers.
Le projet du cours est à rendre à <virli@nemunai.re> au plus tard le dimanche
20 novembre 2016 à 23 h 42.
Tous les exercices de ce TP sont à rendre à <virli@nemunai.re> au plus tard le
jeudi 9 novembre 2017 à 8 h 42.
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://pgp.mit.edu/pks/lookup?op=vindex&search=0x842807A84573CC96) faire
signer votre clef et n'hésitez pas à
[faire signer votre clef](https://www.meetup.com/fr/Paris-certification-de-cles-PGP-et-CAcert/).
Vous pouvez utiliser l'adresse <signcheck@nemunai.re> pour savoir si vous vous
y prenez correctement.
\hypersetup{linkcolor=black}
\tableofcontents

View File

@ -1,11 +1,11 @@
\newpage
Le *namespace* `user`
Le *namespace* `user` {#user-ns}
=====================
## Introduction
Le *namespace* `user` est plutôt pratiquxe car il permet de virtualiser la
L'espace de noms `user` est plutôt pratique car il permet de virtualiser la
liste et les droits des utilisateurs.
Par exemple, on va pouvoir entrer dans un conteneur en tant que
@ -31,14 +31,14 @@ repose entièrement sur cela.
Comme pour les autres espaces de noms, le *namespace* `user` permet de ne
garder dans le nouvel espace, que les utilisateurs et les groupes utiles au
processus, en les renumérotant si besoin au passage.
processus, en les renumérotant au passage si besoin.
### l'utilisateur -1 : *nobody*
### L'utilisateur -1 : *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,
renvoyés par le noyau est -1 ; valeur qui correspond en toute circonstance à
renvoyés par le noyau sont à -1 ; valeur qui correspond en toute circonstance à
l'utilisateur *nobody* et au groupe *nogroup*.
@ -48,7 +48,7 @@ l'utilisateur *nobody* et au groupe *nogroup*.
Pour établir la correspondance, une fois que l'on a créé le nouveau
*namespace*, ces deux fichiers, accessibles dans `/proc/self/`, peuvent être
écrit une fois.
écrits une fois.
Sur chaque ligne, on doit indiquer :
@ -59,23 +59,25 @@ Sur chaque ligne, on doit indiquer :
- La taille de la page.
Par exemple, le *namespace* `user` initial défini le correspondance suivante :
Par exemple, le *namespace* `user` initial défini la correspondance suivante :
<div lang="en-US">
```
0 0 4294967295
42sh$ cat /proc/self/uid_map
0 0 4294967295
```
</div>
Cela signifie que les utilisateurs dont l'identifiant court de 0 à -2 inclu,
dans cet espace de noms, correspond aux utilisateurs allant de 0 à -1 inclu,
pour le processus affichant ce fichier.
Cela signifie que les utilisateurs dont l'identifiant court de 0 à `MAX_INT -
2` inclu, dans cet espace de noms, correspondent aux utilisateurs allant de 0 à
`MAX_INT - 1` inclu, pour le processus affichant ce fichier.
Lorsque l'on crée un *namespace* `user`, généralement, la correspondance vaut :
<div lang="en-US">
```
0 1000 1
42sh$ cat /proc/self/uid_map
0 1000 1
```
</div>
@ -94,18 +96,18 @@ des groupes au lieu des utilisateurs.
<div lang="en-US">
```shell
42sh$ unshare --mount --pid --mount-proc --fork --net --user --map-root-user /bin/bash
42sh$ unshare --mount --pid --mount-proc --fork --net --user --map-root-user /bin/bash
```
</div>
Un `capsh --print` nous montre que l'on est bien root et que l'on possède
toutes les capabilities. Cependant, cela ne signifie pas que l'on a tous les
droits sur le système ; il y a plusieurs niveau de validation qui entrent en
Un `capsh --print` nous montre que l'on est bien `root` et que l'on possède
toutes les *capabilities*. Cependant, cela ne signifie pas que l'on a tous les
droits sur le système ; il y a plusieurs niveaux de validation qui entrent en
jeu. L'idée étant que l'on a été désigné root dans son conteneur, on devrait
pouvoir y faire ce que l'on veut, tant que l'on agit pas en dehors.
pouvoir y faire ce que l'on veut, tant que l'on n'agit pas en dehors.
## Aller plus loin
N'hésitez pas à jeter un œil à la page de manuel consacré à ce *namespace* :
N'hésitez pas à jeter un œil à la page du manuel consacré à ce *namespace* :
`user_namespaces(7)`.