172 lines
6.0 KiB
Markdown
172 lines
6.0 KiB
Markdown
Utiliser les *namespace*s
|
||
-------------------------
|
||
|
||
### S'isoler dans un nouveau *namespace*
|
||
|
||
Si l'on voit l'isolation procurée par les *namespace*s comme des machines
|
||
virtuelles, on peut se dire qu'il suffit d'exécuter un appel système pour
|
||
arriver dans un conteneur bien isolé. Cependant, le choix fait par les
|
||
développeurs de Linux a été de laisser le choix des espaces de noms dont on
|
||
veut se dissocier.
|
||
|
||
L'intérêt principal de cette approche, exploitée bien après la mise en avant du
|
||
concept, est que l'utilisation des *namespace*s ne se limite pas seulement à
|
||
des machines virtuelles légères. On retrouve ainsi dans Google Chrome et
|
||
Firefox de nombreuses utilisations des *namespace*s dans le simple but
|
||
d'accroître la sécurité de leur navigateur. Ainsi, les *threads* de rendu n'ont
|
||
pas accès au réseau et sont cloisonnés de manière transparente pour
|
||
l'utilisateur.
|
||
|
||
Nous allons voir dans cette partie plusieurs méthodes pour utiliser ces espaces
|
||
de noms.
|
||
|
||
|
||
#### Dans son shell\
|
||
|
||
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 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és dans un autre *namespace* `UTS` :
|
||
|
||
<div lang="en-US">
|
||
```
|
||
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 la machine, sans que cela n'affecte notre
|
||
machine hôte.
|
||
|
||
|
||
#### Les appels système\
|
||
|
||
L'appel système par excellence pour contrôler l'isolation d'un nouveau
|
||
processus est `clone(2)`.
|
||
|
||
Ce *syscall*, propre à Linux, crée habituellement un nouveau processus enfant
|
||
de notre processus courant (mais il peut aussi créer des *threads*, on va voir
|
||
qu'il fait beaucoup de choses), comme `fork(2)` (qui lui est un appel système
|
||
POSIX). Mais il prend en plus de nombreux paramètres. L'isolement ou non du
|
||
processus se fait en fonction des *flags* qui sont passés. On retrouve donc :
|
||
|
||
* `CLONE_NEWNS`,
|
||
* `CLONE_NEWUTS`,
|
||
* `CLONE_NEWIPC`,
|
||
* `CLONE_NEWPID`,
|
||
* `CLONE_NEWNET`,
|
||
* `CLONE_NEWUSER`,
|
||
* `CLONE_NEWCGROUP`,
|
||
* `CLONE_NEWTIME`.
|
||
|
||
::::: {.more}
|
||
|
||
Le nom du *flag* `CLONE_NEWNS` est historique et assez peu explicite
|
||
contrairement aux autres : il désigne en fait l'espace de nom `mount`.
|
||
|
||
Au départ, les *namespace*s ont étés pensés pour former un tout : une couche
|
||
d'isolation complète pour les processus. Mais lors des développements suivants,
|
||
il s'est avéré pratique de pouvoir choisir finement de quels aspects on
|
||
souhaitait se dissocier.
|
||
|
||
C'est ainsi que pour chaque nouveau *namespace*, un nouveau *flag* est
|
||
introduit.
|
||
|
||
:::::
|
||
|
||
On peut bien entendu cumuler un ou plusieurs de ces *flags*, et les combiner
|
||
avec d'autres attendus par `clone(2)`.
|
||
|
||
Ces mêmes *flags* sont utilisés lors des appels à `unshare(2)` ou `setns(2)`,
|
||
que nous verrons plus tard.
|
||
|
||
::::: {.code}
|
||
|
||
Pour créer un nouveau processus qui sera à la fois dans un nouvel espace de
|
||
noms réseau et dans un nouveau *namespace* `cgroup`, on écrirait un code C
|
||
semblable à :
|
||
|
||
<div lang="en-US">
|
||
```c
|
||
#include <sched.h>
|
||
|
||
#define STACKSIZE (1024 * 1024)
|
||
static char child_stack[STACKSIZE];
|
||
|
||
int clone_flags = CLONE_CGROUP | CLONE_NEWNET | SIGCHLD;
|
||
|
||
pid_t pid = clone(do_execvp, // First function executed by child
|
||
child_stack + STACKSIZE, // Assume stack grows downward
|
||
clone_flags, // clone specials flags
|
||
args); // Arguments to pass to
|
||
// do_execvp
|
||
```
|
||
</div>
|
||
|
||
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.
|
||
|
||
::::: {.question}
|
||
|
||
#### Quel est le rôle du *flag* `SIGCHLD` ? {-}
|
||
\
|
||
|
||
Lorsque l'on crée un nouveau processus, on ajoute l'option `SIGCHLD` afin
|
||
d'être notifié par signal lorsque notre processus fil a terminé son
|
||
exécution. Cela permet d'être réveillé de notre `wait(2)`.
|
||
|
||
:::::
|
||
|
||
:::::
|
||
|
||
L'appel système `clone(2)` va donc créer un nouveau processus, ou un nouveau
|
||
*thread*, mais parfois on souhaite juste isoler notre processus actuel.
|
||
|
||
|
||
##### `unshare`\
|
||
|
||
Lorsque l'on souhaite faire entrer notre processus courant ou notre *thread*
|
||
dans un nouvel espace de noms, on peut utiliser l'appel système
|
||
`unshare(2)`. Celui-ci prend uniquement en argument une liste de *flags* des
|
||
*namespace*s dont on souhaite se dissocier.
|
||
|
||
::::: {.warning}
|
||
|
||
**Le comportement de `unshare(2)` (ou `unshare(3)`) avec les *namespace*s *PID*
|
||
et *Time* n'est pas celui que l'on peut attendre !**\
|
||
|
||
En effet, après avoir appelé `unshare`, le processus reste tout de même dans
|
||
son *namespace* *Time* ou *PID* d'origine, seuls ses enfants (après un appel à
|
||
`fork(2)` ou `clone(2)` par exemple) seront dans le nouveau *namespace*.
|
||
|
||
Cela poserait trop de problème de faire changer le PID d'un processus en cours
|
||
d'exécution, de même qu'il serait impensable que la `CLOCK_MONOTONIC` puisse
|
||
faire un saut en avant ou en arrière. Alors le choix qui a été fait est que
|
||
seuls les fils créés après l'appel à `unshare(2)` seront concrètement dans le
|
||
nouveau *namespace*. C'est dans cette situation que `pid` et `pid_for_children`
|
||
peuvent être différents dans le dossier `/proc/<PID>/ns`.
|
||
|
||
On ne remarque pas cette bizarrerie avec `clone(2)`, car il crée déjà un
|
||
nouveau processus, son PID et sa `CLOCK_MONOTONIC` sont directement à la bonne
|
||
valeur dès l'exécution de la fonction `fn`.
|
||
|
||
:::::
|
||
|
||
Nous avons vu comment créer et nous dissocier d'un espace de nom. Maintenant
|
||
voyons comment en rejoindre un déjà existant.
|