New tuto 3 done
This commit is contained in:
parent
5f4880dc50
commit
ba77aca73b
57 changed files with 1026 additions and 137 deletions
12
tutorial/5/Makefile
Normal file
12
tutorial/5/Makefile
Normal file
|
@ -0,0 +1,12 @@
|
|||
include ../pandoc-opts.mk
|
||||
|
||||
SOURCES_TUTO = tutorial.md setup.md mount.md namespaces.md cmpns.md docker-exec.md networkns.md pidns.md mountns.md userns.md rendu.md
|
||||
|
||||
|
||||
all: tutorial.pdf
|
||||
|
||||
tutorial.pdf: ${SOURCES_TUTO}
|
||||
pandoc ${PANDOCOPTS} -o $@ $+
|
||||
|
||||
clean::
|
||||
rm tutorial.pdf
|
56
tutorial/5/cmpns.md
Normal file
56
tutorial/5/cmpns.md
Normal file
|
@ -0,0 +1,56 @@
|
|||
## Exercice : 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">
|
||||
```
|
||||
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">
|
||||
```
|
||||
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">
|
||||
```
|
||||
42sh$ unshare -m ./cmpns $$ self
|
||||
- cgroup: same
|
||||
- ipc: same
|
||||
- mnt: differ
|
||||
- net: same
|
||||
- pid: same
|
||||
- user: same
|
||||
- uts: same
|
||||
```
|
||||
</div>
|
46
tutorial/5/docker-exec.md
Normal file
46
tutorial/5/docker-exec.md
Normal file
|
@ -0,0 +1,46 @@
|
|||
Exercice : `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 {-}
|
||||
|
||||
<div lang="en-US">
|
||||
```
|
||||
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>
|
12
tutorial/5/lesson.md
Normal file
12
tutorial/5/lesson.md
Normal file
|
@ -0,0 +1,12 @@
|
|||
---
|
||||
title: Virtualisation légère -- Linux Internals partie 2
|
||||
subtitle: Support de cours
|
||||
author: Pierre-Olivier *nemunaire* [Mercier]{.smallcaps}
|
||||
institute: EPITA
|
||||
date: Mercredi 7 novembre 2018
|
||||
abstract: |
|
||||
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.
|
||||
...
|
301
tutorial/5/mount.md
Normal file
301
tutorial/5/mount.md
Normal file
|
@ -0,0 +1,301 @@
|
|||
\newpage
|
||||
|
||||
Des particularités de `mount` {#mount}
|
||||
=============================
|
||||
|
||||
Petite parenthèse avant de parler des *namespaces* ...
|
||||
|
||||
## Les points de montage
|
||||
|
||||
Au premier abord, les points de montage dans l'arborescence d'un système de
|
||||
fichiers n'ont pas l'air d'être remplis de notions complexes : un répertoire
|
||||
peut être le point d'entrée d'un montage vers la partition d'un disque
|
||||
physique... ou d'une partition virtuelle, comme nous l'avons vu dans la partie
|
||||
précédente.
|
||||
|
||||
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 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
|
||||
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.
|
||||
|
||||
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* :
|
||||
|
||||
<div lang="en-US">
|
||||
```bash
|
||||
mount --bind olddir newdir
|
||||
```
|
||||
</div>
|
||||
|
||||
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`.
|
||||
|
||||
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">
|
||||
```bash
|
||||
cd newroot
|
||||
mount --bind /dev dev
|
||||
mount --bind /proc proc
|
||||
mount --bind /sys sys
|
||||
```
|
||||
</div>
|
||||
|
||||
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*.
|
||||
|
||||
`--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
|
||||
montages sous-jacents. Pour effectuer cette action récursivement, et donc
|
||||
monter au nouvel emplacement le système de fichier ainsi que tous les points
|
||||
d'accroche qu'il contient, il faut utiliser `--rbind`. Il serait donc plus
|
||||
correct de lancer :
|
||||
|
||||
<div lang="en-US">
|
||||
```bash
|
||||
cd newroot
|
||||
mount --rbind /dev dev
|
||||
mount -t proc none proc
|
||||
mount --rbind /sys sys
|
||||
```
|
||||
</div>
|
||||
|
||||
|
||||
## Les montages parfumés
|
||||
|
||||
On distingue quatre variétés de répercution des montages pour un sous-arbre :
|
||||
partagé, esclave, privé et non-attachable.
|
||||
|
||||
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*
|
||||
|
||||
Dans un montage partagé, une nouvelle accroche sera propagée parmi tous les
|
||||
systèmes de fichiers de ce partage (on parle de *peer group*). Voyons avec un
|
||||
exemple :
|
||||
|
||||
<div lang="en-US">
|
||||
```bash
|
||||
# Création de notre répertoire 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
|
||||
|
||||
# Duplication de l'accroche, sans s'occuper des éventuels sous-accroches
|
||||
mount --bind /tmp /mnt/test-shared
|
||||
```
|
||||
</div>
|
||||
|
||||
Si l'on attache un nouveau point de montage dans `/tmp` ou dans
|
||||
`/mnt/test-shared`, avec la politique `shared`, l'accroche sera propagée :
|
||||
|
||||
<div lang="en-US">
|
||||
```bash
|
||||
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
|
||||
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 à son *maître*.
|
||||
|
||||
<div lang="en-US">
|
||||
```bash
|
||||
# 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
|
||||
|
||||
# On rend notre dossier esclave
|
||||
mount --make-slave /mnt/test-slave
|
||||
```
|
||||
</div>
|
||||
|
||||
Si l'on effectue un montage dans `/mnt/test-shared` :
|
||||
|
||||
<div lang="en-US">
|
||||
```bash
|
||||
mkdir /mnt/test-shared/foo
|
||||
mount -t tmpfs none /mnt/test-shared/foo
|
||||
```
|
||||
</div>
|
||||
|
||||
Le point de montage apparaît bien sous `/mnt/test-slave/foo`. Par contre :
|
||||
|
||||
<div lang="en-US">
|
||||
```bash
|
||||
mkdir /mnt/test-slave/bar
|
||||
mount -t tmpfs none /mnt/test-slave/bar
|
||||
```
|
||||
</div>
|
||||
|
||||
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 tout
|
||||
simplement pas propagés.
|
||||
|
||||
Pour forcer un point d'accroche à ne pas propager et à ne pas recevoir de
|
||||
propagation, on utilise l'option suivante :
|
||||
|
||||
<div lang="en-US">
|
||||
```bash
|
||||
mount --make-private mountpoint
|
||||
```
|
||||
</div>
|
||||
|
||||
|
||||
### non-attachable -- *unbindable mount*
|
||||
|
||||
Ce mode interdira tout tentative d'attache à un autre endroit.
|
||||
|
||||
<div lang="en-US">
|
||||
```bash
|
||||
mount --make-unbindable /mnt/test-slave
|
||||
```
|
||||
</div>
|
||||
|
||||
Il ne sera pas possible de faire :
|
||||
|
||||
<div lang="en-US">
|
||||
```bash
|
||||
mkdir /mnt/test-unbindable
|
||||
mount --bind /mnt/test-slave /mnt/test-unbindable
|
||||
```
|
||||
</div>
|
||||
|
||||
|
||||
### Parfums récursifs
|
||||
|
||||
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
|
||||
contenus dans leur sous-arbre :
|
||||
|
||||
<div lang="en-US">
|
||||
```bash
|
||||
mount --make-rshared mountpoint
|
||||
mount --make-rslave mountpoint
|
||||
mount --make-rprivate mountpoint
|
||||
mount --make-runbindable mountpoint
|
||||
```
|
||||
</div>
|
||||
|
||||
|
||||
## `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 (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](#net-ns), une utilisation
|
||||
d'attache sur un fichier.
|
||||
|
||||
|
||||
## Déplacer un point de montage
|
||||
|
||||
À 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 susceptibles d'aller chercher un fichier à l'ancien
|
||||
emplacement soient prévenus du changement.
|
||||
|
||||
On utilise pour cela l'option `--move` de `mount(8)` :
|
||||
|
||||
<div lang="en-US">
|
||||
```bash
|
||||
mount --move olddir newdir
|
||||
```
|
||||
</div>
|
||||
|
||||
Par exemple :
|
||||
|
||||
<div lang="en-US">
|
||||
```bash
|
||||
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 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 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) ;
|
||||
* [Mount namespaces and shared subtrees](https://lwn.net/Articles/689856) ;
|
||||
* [Mount namespaces, mount propagation, and unbindable mounts](https://lwn.net/Articles/690679).
|
137
tutorial/5/mountns.md
Normal file
137
tutorial/5/mountns.md
Normal file
|
@ -0,0 +1,137 @@
|
|||
\newpage
|
||||
|
||||
Le *namespace* `mount`
|
||||
======================
|
||||
|
||||
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 ?
|
||||
|
||||
|
||||
## Préparation du changement de racine
|
||||
|
||||
### Avant propos
|
||||
|
||||
Nous allons essayer de changer la racine de notre système de fichier. À la
|
||||
différence d'un `chroot(2)`, changer de racine est quelque chose d'un peu plus
|
||||
sportif car il s'agit de ne plus avoir aucune trace de l'ancienne racine. Au
|
||||
moins ici, il ne sera certainement pas possible de revenir en arrière dans
|
||||
l'arborescence\ !
|
||||
|
||||
Pour l'instant, votre système utilise sans doute la partition d'un disque
|
||||
physique comme racine de son système de fichier. Le changement de racine, va
|
||||
nous permettre d'utiliser un autre système.
|
||||
|
||||
Bien sûr, nous n'allons pas changer la racine de votre système hôte, nous
|
||||
allons faire cela dans un *namespace* qui nous permet d'avoir des points de
|
||||
montage virtuels. Le changement de racine sera donc effectif uniquement dans
|
||||
cet espace de noms.
|
||||
|
||||
|
||||
### L'environnement
|
||||
|
||||
Pour pouvoir changer de racine, il est nécessaire que la nouvelle racine soit
|
||||
la racine d'un point de montage, comme l'explique `pivot_root(2)`. En effet, il
|
||||
serait encore possible hypothétiquement de remonter dans l'arborescence si l'on
|
||||
ne se trouvait pas à la racine d'une partition au moment du basculement.
|
||||
|
||||
Si vous n'avez pas de partition à disposition, vous pouvez utiliser un `tmpfs` :
|
||||
|
||||
<div lang="en-US">
|
||||
```bash
|
||||
42sh# mkdir /mnt/newroot
|
||||
42sh# mount -t tmpfs none /mnt/newroot
|
||||
```
|
||||
</div>
|
||||
|
||||
Placez ensuite dans cette nouvelle racine le système de votre choix (cf. le TP
|
||||
précédent pour les différentes méthodes et liens).
|
||||
|
||||
|
||||
## Changer de racine
|
||||
|
||||
Voici les grandes étapes du changement de racine :
|
||||
|
||||
1. S'isoler dans les *namespaces* adéquats ;
|
||||
2. Démonter ou déplacer toutes les partitions de l'ancienne racine vers la
|
||||
nouvelle racine ;
|
||||
3. `pivot_root` !
|
||||
|
||||
|
||||
### S'isoler
|
||||
|
||||
Notre but étant de démonter toutes les partitions superflues, nous allons
|
||||
devoir nous isoler sur :
|
||||
|
||||
* les points de montages, ça semble évident ;
|
||||
* les PIDs : car on ne pourra pas démonter une partition en cours
|
||||
d'utilisation. S'il n'y a pas de processus, il n'y a personne pour nous
|
||||
empêcher de démonter une partition !
|
||||
* les autres *namespaces* ne sont pas forcément nécessaires.
|
||||
|
||||
Isolons-nous :
|
||||
|
||||
<div lang="en-US">
|
||||
```bash
|
||||
42sh# unshare -p -m -f --mount-proc
|
||||
```
|
||||
</div>
|
||||
|
||||
|
||||
### Dissocier la propagation des démontages
|
||||
|
||||
Attention ! avant de pouvoir commencer à démonter les partitions, il faut
|
||||
s'assurer que les démontages ne se propagent pas via une politique de *shared
|
||||
mount*.
|
||||
|
||||
Commençons donc par étiqueter tous nos points de montage (de ce *namespace*),
|
||||
comme esclave :
|
||||
|
||||
<div lang="en-US">
|
||||
```bash
|
||||
42sh# mount --make-rslave /
|
||||
```
|
||||
</div>
|
||||
|
||||
|
||||
### Démonter tout !
|
||||
|
||||
À vous maintenant de démonter vos points d'attache. Il ne devrait vous rester
|
||||
après cette étape que : `/`, `/dev`, `/sys`, `/proc`, `/run` et leurs fils.
|
||||
|
||||
|
||||
### Switch !
|
||||
|
||||
À ce stade, dans votre console, vous avez plusieurs solutions : utiliser
|
||||
`switch_root(8)` ou `pivot_root(8)`. La première abstrait plus de choses que la
|
||||
seconde.
|
||||
|
||||
|
||||
#### `switch_root`
|
||||
|
||||
Cette commande s'occupe de déplacer les partitions restantes pour vous, et lance
|
||||
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é
|
||||
les partitions systèmes à leur place dans la nouvelle racine.
|
||||
|
||||
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.
|
||||
|
||||
Pour lancer la première commande dans la nouvelle racine, on passe généralement
|
||||
par :
|
||||
|
||||
<div lang="en-US">
|
||||
```bash
|
||||
42sh# exec chroot / command
|
||||
```
|
||||
</div>
|
315
tutorial/5/namespaces.md
Normal file
315
tutorial/5/namespaces.md
Normal file
|
@ -0,0 +1,315 @@
|
|||
\newpage
|
||||
|
||||
Les espaces de noms -- *namespaces* {#namespaces}
|
||||
===================================
|
||||
|
||||
## Introduction
|
||||
|
||||
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 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/766089/).
|
||||
|
||||
### L'espace de noms `mount` {#mount-ns}
|
||||
|
||||
Depuis Linux 2.4.19.
|
||||
|
||||
Cet espace de noms isole la liste des points de montage.
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
<div lang="en-US">
|
||||
```bash
|
||||
mount --make-rslave /
|
||||
```
|
||||
</div>
|
||||
|
||||
|
||||
### L'espace de noms `UTS` {#uts-ns}
|
||||
|
||||
Depuis Linux 2.6.19.
|
||||
|
||||
Cet espace de noms isole le nom de machine et son domaine NIS.
|
||||
|
||||
|
||||
### L'espace de noms `IPC` {#ipc-ns}
|
||||
|
||||
Depuis Linux 2.6.19.
|
||||
|
||||
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 espace de noms (lorsque ceux-ci passent par
|
||||
l'API IPC du noyau).
|
||||
|
||||
|
||||
### L'espace de noms `PID`
|
||||
|
||||
Depuis Linux 2.6.24.
|
||||
|
||||
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
|
||||
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é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.
|
||||
|
||||
|
||||
### L'espace de nom `network`
|
||||
|
||||
Depuis Linux 2.6.29.
|
||||
|
||||
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
|
||||
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
|
||||
ramenées dans l'espace initial/racine (et non pas dans l'espace parent, en cas
|
||||
d'imbrication).
|
||||
|
||||
|
||||
### L'espace de noms `user`
|
||||
|
||||
Depuis Linux 3.8.
|
||||
|
||||
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é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.
|
||||
|
||||
|
||||
### L'espace de noms `cgroup` {#cgroup-ns}
|
||||
|
||||
Depuis Linux 4.6.
|
||||
|
||||
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'arborescence des cgroups n'a alors
|
||||
plus d'importance car le processus ne voit que son groupe.
|
||||
|
||||
|
||||
|
||||
## S'isoler dans un nouveau *namespace*
|
||||
|
||||
### Avec son coquillage
|
||||
|
||||
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èmes
|
||||
|
||||
L'appel système par excellence pour contrôler l'isolation d'un nouveau
|
||||
processus est `clone(2)`.
|
||||
|
||||
L'isolement ou non du processus est faite en fonction des `flags` qui sont
|
||||
passés à la fonction :
|
||||
|
||||
* `CLONE_NEWNS`,
|
||||
* `CLONE_NEWUTS`,
|
||||
* `CLONE_NEWIPC`,
|
||||
* `CLONE_NEWPID`,
|
||||
* `CLONE_NEWNET`,
|
||||
* `CLONE_NEWUSER`,
|
||||
* `CLONE_NEWCGROUP`.
|
||||
|
||||
On peut bien entendu cumuler un ou plusieurs de ces `flags`, et les combiner
|
||||
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 espace de
|
||||
noms réseau et dans un nouveau *namespace* `cgroup`, on écrirait un code
|
||||
similaire à :
|
||||
|
||||
<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,
|
||||
child_stack + STACKSIZE,
|
||||
clone_flags,
|
||||
&args);
|
||||
```
|
||||
</div>
|
||||
|
||||
Le premier argument est un pointeur sur fonction. Il s'agit de la fonction qui
|
||||
sera appelée par le nouveau processus.
|
||||
|
||||
|
||||
## Rejoindre un *namespace*
|
||||
|
||||
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/` :
|
||||
|
||||
<div lang="en-US">
|
||||
```c
|
||||
#define _GNU_SOURCE
|
||||
#include <fcntl.h>
|
||||
#include <sched.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
// ./a.out /proc/PID/ns/FILE cmd args...
|
||||
|
||||
int
|
||||
main(int argc, char *argv[])
|
||||
{
|
||||
int fd = open(argv[1], O_RDONLY);
|
||||
if (fd == -1)
|
||||
{
|
||||
perror("open");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
if (setns(fd, 0) == -1)
|
||||
{
|
||||
perror("setns");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
execvp(argv[2], &argv[2]);
|
||||
|
||||
perror("execve");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
```
|
||||
</div>
|
||||
|
||||
Dans un shell, on utilisera la commande `nsenter(1)` :
|
||||
|
||||
<div lang="en-US">
|
||||
```bash
|
||||
42sh# nsenter --uts=/proc/42/ns/uts /bin/bash
|
||||
```
|
||||
</div>
|
||||
|
||||
|
||||
## Durée de vie d'un *namespace* {#ns-lifetime}
|
||||
|
||||
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'utilise. C'est-à-dire que, la plupart du temps, le
|
||||
*namespace* est libéré lorsque le dernier processus s'exécutant dedans se
|
||||
termine.
|
||||
|
||||
Lorsque l'on a besoin de référencer un *namespace* (par exemple pour le faire
|
||||
persister après le dernier processus), on peut utiliser un `mount bind` :
|
||||
|
||||
<div lang="en-US">
|
||||
```bash
|
||||
42sh# touch /tmp/ns/myrefns
|
||||
42sh# mount --bind /proc/<PID>/ns/mount /tmp/ns/myrefns
|
||||
```
|
||||
</div>
|
||||
|
||||
De cette manière, même si le lien initial n'existe plus (si le `<PID>` s'est
|
||||
terminé), `/tmp/ns/myrefns` pointera toujours au bon endroit.
|
||||
|
||||
On peut très bien utiliser directement ce fichier pour obtenir un descripteur
|
||||
de fichier valide vers le *namespace* (pour passer à `setns(2)`).
|
||||
|
||||
### Faire persister un *namespace*
|
||||
|
||||
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.
|
||||
|
||||
|
||||
## Aller plus loin {-}
|
||||
|
||||
Je vous recommande la lecture des *man* suivants :
|
||||
|
||||
* `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`
|
||||
*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)
|
||||
est également des plus intéressants, pour ce qui concerne la programmation
|
||||
plus bas-niveau.
|
237
tutorial/5/networkns.md
Normal file
237
tutorial/5/networkns.md
Normal file
|
@ -0,0 +1,237 @@
|
|||
\newpage
|
||||
|
||||
Le *namespace* `network` {#net-ns}
|
||||
========================
|
||||
|
||||
## Introduction
|
||||
|
||||
L'espace de noms `network`, comme son nom l'indique permet de virtualiser tout
|
||||
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 qu'une interface de *loopback* :
|
||||
|
||||
<div lang="en-US">
|
||||
```
|
||||
42sh# unshare -n ip a
|
||||
1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN group default qlen 1
|
||||
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
|
||||
```
|
||||
</div>
|
||||
|
||||
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 n'entre en conflit avec celui d'un
|
||||
autre espace de noms.
|
||||
|
||||
|
||||
## Premiers pas avec `ip netns`
|
||||
|
||||
La suite d'outils `iproute2` propose une interface simplifiée pour utiliser le
|
||||
*namespace* `network` : `ip netns`.
|
||||
|
||||
Nous pouvons tout d'abord créer un nouvel espace de nom :
|
||||
|
||||
<div lang="en-US">
|
||||
```bash
|
||||
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 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éé, nous pouvons regarder s'il contient
|
||||
des interfaces :
|
||||
|
||||
<div lang="en-US">
|
||||
```
|
||||
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 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">
|
||||
```bash
|
||||
42sh# ip netns exec virli ip link set dev lo up
|
||||
```
|
||||
</div>
|
||||
|
||||
À ce stade, nous pouvons déjà commencer à lancer un `ping` sur cette interface:
|
||||
|
||||
<div lang="en-US">
|
||||
```
|
||||
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>
|
||||
|
||||
|
||||
## *Virtual Ethernet*
|
||||
|
||||
Étant donné qu'une interface réseau ne peut être présente que dans un seul
|
||||
espace de noms à la fois, il n'est pas bien pratique d'imposer d'avoir une
|
||||
interface physique par conteneur, d'autant plus si l'on a plusieurs
|
||||
centaines de conteneurs à gérer.
|
||||
|
||||
Une technique couramment employée consiste à créer une interface virtuelle de
|
||||
type `veth` :
|
||||
|
||||
<div lang="en-US">
|
||||
```
|
||||
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ç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é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">
|
||||
```bash
|
||||
42sh# ip link set veth1 netns virli
|
||||
```
|
||||
</div>
|
||||
|
||||
Il ne reste maintenant plus qu'à assigner une IP à chacune des interfaces :
|
||||
|
||||
<div lang="en-US">
|
||||
```bash
|
||||
42sh# ip netns exec virli ip a add 10.10.10.42/24 dev veth1
|
||||
42sh# ip a add 10.10.10.41/24 dev veth0
|
||||
```
|
||||
</div>
|
||||
|
||||
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">
|
||||
```
|
||||
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.
|
||||
|
||||
|
||||
## Les autres types d'interfaces
|
||||
|
||||
Le bridge ou le NAT obligera tous les paquets à passer à travers de nombreuses
|
||||
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 un switch
|
||||
supportant la technologie [802.1q](https://fr.wikipedia.org/wiki/IEEE_802.1Q)
|
||||
derrière notre machine.
|
||||
|
||||
<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
|
||||
```
|
||||
</div>
|
||||
|
||||
|
||||
### MACVLAN
|
||||
|
||||
<!-- 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 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 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">
|
||||
```
|
||||
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 pourra parler à un autre
|
||||
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 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">
|
||||
```
|
||||
42sh# ip link add link eth0 mac2 type macvlan mode bridge
|
||||
```
|
||||
</div>
|
||||
|
||||
|
||||
## Aller plus loin {-}
|
||||
|
||||
Pour approfondir les différentes techniques de routage, je vous
|
||||
recommande cet article :
|
||||
[Linux Containers and Networking](https://blog.flameeyes.eu/2010/09/linux-containers-and-networking).
|
107
tutorial/5/pidns.md
Normal file
107
tutorial/5/pidns.md
Normal file
|
@ -0,0 +1,107 @@
|
|||
\newpage
|
||||
|
||||
Le *namespace* `PID` {#pid-ns}
|
||||
=====================
|
||||
|
||||
## Introduction {#pid-ns-intro}
|
||||
|
||||
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, ici, le changement ne sera valable
|
||||
qu'après le prochain `fork(2)` (ou similaire).
|
||||
En effet, l'espace de noms n'est pas changé, afin que le processus ne change
|
||||
pas de PID en cours de route, puisqu'il dépend du *namespace* dans lequel il se
|
||||
trouve.
|
||||
|
||||
|
||||
## Isolons !
|
||||
|
||||
Première étape s'isoler :
|
||||
|
||||
<div lang="en-US">
|
||||
```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](#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
|
||||
d'afficher la liste complète des processus de notre système.
|
||||
|
||||
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'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`
|
||||
|
||||
Voici la nouvelle ligne de commande que l'on va utiliser :
|
||||
|
||||
<div lang="en-US">
|
||||
```bash
|
||||
42sh# unshare --pid --mount --fork --mount-proc /bin/bash
|
||||
```
|
||||
</div>
|
||||
|
||||
Avec l'option `--mount-proc`, `unshare` va s'occuper de monter le nouveau
|
||||
`/proc`.
|
||||
|
||||
Cette fois, `top` et `ps` nous rapportent bien que l'on est seul dans notre
|
||||
*namespace*.
|
||||
|
||||
|
||||
## Arborescence à l'extérieur du *namespace*
|
||||
|
||||
Lors de notre première tentative de `top`, lorsque `/proc` était encore monté
|
||||
sur le `procfs` de l'espace de noms initial : notre 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 initial ne sont pas accessibles, et ceux qui
|
||||
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*
|
||||
que le processus appelant.
|
||||
|
||||
|
||||
## Processus orphelins et `nsenter`
|
||||
|
||||
Au sein d'un *namespace*, le processus au PID 1 est considéré comme le
|
||||
programme `init`, les mêmes propriétés s'appliquent donc.
|
||||
|
||||
Si un processus est orphelin, il est donc affiché comme étant fils du PID 1
|
||||
dans son *namespace*[^PR_SET_CHILD_SUBREAPER] ; il n'est pas sorti de l'espace
|
||||
de nom.
|
||||
|
||||
[^PR_SET_CHILD_SUBREAPER]: en réalité, ce comportement est lié à la propriété
|
||||
`PR_SET_CHILD_SUBREAPER`, qui peut être définie pour n'importe quel processus
|
||||
de l'arborescence. Le processus au PID 1 hérite forcément de cette propriété\ ;
|
||||
il va donc récupérer tous les orphelins, si aucun de leurs parents n'ont la
|
||||
propriété définie.
|
||||
|
||||
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 à
|
||||
l'intérieur pour conserver un comportement cohérent.
|
||||
|
||||
|
||||
## Aller plus loin {-}
|
||||
|
||||
N'hésitez pas à jeter un œil à la page de manuel consacré à cet espace de
|
||||
noms : `pid_namespaces(7)`.
|
24
tutorial/5/project-intro.md
Normal file
24
tutorial/5/project-intro.md
Normal file
|
@ -0,0 +1,24 @@
|
|||
\newpage
|
||||
|
||||
Projet et rendu
|
||||
===============
|
||||
|
||||
## Sujet
|
||||
|
||||
**Ce projet, étalé sur ce TP et le TP précédent, constitue le cœur de la
|
||||
notation de ce cours.**
|
||||
|
||||
Vous allez continuer aujourd'hui le projet qui s'étendra depuis le TP précédent
|
||||
et qui consistera à réaliser la partie d'isolation de la moulinette des ACUs !
|
||||
|
||||
Cette semaine, il faudra faire en sorte de restreindre un groupe de processus
|
||||
pour qu'il s'exécute indépendemment de votre système.
|
||||
|
||||
Il n'y a pas de restriction sur le langage utilisé, vous pouvez tout aussi bien
|
||||
utiliser du C, du C++, du Python, du shell, etc.
|
||||
|
||||
L'usage de bibliothèques **non relatives** au projet est autorisé : le but de
|
||||
ce sujet est d'évaluer votre compréhension et votre utilisation de la
|
||||
tuyauterie bas-niveau du noyau liée à la virtualisation légère. À partir du
|
||||
moment où vous n'utilisez pas une bibliothèque qui abstrait complètement cette
|
||||
plomberie, n'hésitez pas à l'utiliser !
|
32
tutorial/5/project-rendu.md
Normal file
32
tutorial/5/project-rendu.md
Normal file
|
@ -0,0 +1,32 @@
|
|||
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
|
||||
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.
|
||||
|
||||
Pour différencier le rendu du TP, du rendu du projet, ajoutez une balise
|
||||
`[PROJET]` au sujet de votre courriel, afin qu'il soit traité comme tel.
|
||||
|
||||
Tarball
|
||||
-------
|
||||
|
||||
Le projet à rendre pour ce cours est à placer dans une tarball (pas d'archive
|
||||
ZIP, RAR, ...).
|
||||
|
||||
Voici une arborescence type:
|
||||
|
||||
<div lang="en-US">
|
||||
```
|
||||
login_x-mymoulette/README
|
||||
login_x-mymoulette/...
|
||||
```
|
||||
</div>
|
44
tutorial/5/rendu.md
Normal file
44
tutorial/5/rendu.md
Normal file
|
@ -0,0 +1,44 @@
|
|||
\newpage
|
||||
|
||||
Projet et rendu
|
||||
===============
|
||||
|
||||
Projet
|
||||
------
|
||||
|
||||
[Le sujet complet du projet est disponible ici](https://virli.nemunai.re/project-3.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=309).
|
||||
|
||||
|
||||
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>
|
113
tutorial/5/setup.md
Normal file
113
tutorial/5/setup.md
Normal file
|
@ -0,0 +1,113 @@
|
|||
\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`](https://sites.google.com/site/fullycapable/).
|
||||
|
||||
Sous Debian et ses dérivés, ces 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 :
|
||||
|
||||
* [Security Implications of User Namespaces](https://blog.araj.me/security-implications-of-user-namespaces/) ;
|
||||
* [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 :
|
||||
|
||||
<div lang="en-US">
|
||||
```bash
|
||||
42sh# sysctl -w kernel.unprivileged_userns_clone=1
|
||||
```
|
||||
</div>
|
||||
|
||||
|
||||
### 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*.
|
24
tutorial/5/tutorial.md
Normal file
24
tutorial/5/tutorial.md
Normal file
|
@ -0,0 +1,24 @@
|
|||
---
|
||||
title: Virtualisation légère -- TP n^o^ 4
|
||||
subtitle: Linux Internals partie 2
|
||||
author: Pierre-Olivier *nemunaire* [Mercier]{.smallcaps}
|
||||
institute: EPITA
|
||||
date: Mercredi 6 novembre 2019
|
||||
abstract: |
|
||||
Le but de ce second TP sur les mécanismes internes du noyau va nous
|
||||
permettre d'utiliser les commandes et les appels systèmes relatifs
|
||||
aux *namespaces* ainsi que d'appréhender la complexité des systèmes
|
||||
de fichiers.
|
||||
|
||||
\vspace{1em}
|
||||
|
||||
Tous les exercices de ce TP sont à rendre à <virli@nemunai.re> au
|
||||
plus tard le mercredi 20 novembre 2017 à 13 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://keys.openpgp.org/search?q=nemunaire%40nemunai.re)
|
||||
faire signer votre clef et n'hésitez pas à [faire signer la
|
||||
votre](https://www.meetup.com/fr/Paris-certification-de-cles-PGP-et-CAcert/).
|
||||
...
|
116
tutorial/5/userns.md
Normal file
116
tutorial/5/userns.md
Normal file
|
@ -0,0 +1,116 @@
|
|||
\newpage
|
||||
|
||||
Le *namespace* `user` {#user-ns}
|
||||
=====================
|
||||
|
||||
## Introduction
|
||||
|
||||
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
|
||||
super-utilisateur à partir d'un compte d'un simple utilisateur. Il nous sera
|
||||
alors possible d'effectuer toutes les actions privilégiées dont nous pourrions
|
||||
avoir besoin à l'intérieur de cet espace de noms, sans que cela ne réduise la
|
||||
sécurité des composants à l'extérieur de cet espace.
|
||||
|
||||
|
||||
## Comportement vis-à-vis des autres *namespaces*
|
||||
|
||||
Alors qu'il est normalement nécessaire d'avoir des privilèges pour créer de
|
||||
nouveaux espaces de noms, en commençant par demander un *namespace*
|
||||
utilisateurs, on obtient les privilèges requis pour créer tous les autres types
|
||||
de *namespaces*.
|
||||
|
||||
Grâce à cette technique, il est possible de lancer des conteneurs en tant que
|
||||
simple utilisateur ; le projet [Singularity](https://sylabs.io/) repose
|
||||
entièrement sur cela.
|
||||
|
||||
|
||||
## Correspondance des utilisateurs et des groupes
|
||||
|
||||
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 au passage si besoin.
|
||||
|
||||
|
||||
### L'utilisateur -2 : *nobody*
|
||||
|
||||
Lorsque l'on arrive dans un nouvel espace, aucun utilisateur ni groupe n'est
|
||||
défini. Dans cette situation, tous les identifiants d'utilisateur et de groupe,
|
||||
renvoyés par le noyau sont à -2 ; valeur qui correspond par convention à
|
||||
l'utilisateur *nobody* et au groupe *nogroup*.
|
||||
|
||||
-1 étant réservé pour indiqué une erreur dans le retour d'une commande, ou la
|
||||
non-modification d'un paramètres passé en argument d'une fonction.
|
||||
|
||||
|
||||
### `uid_map` et `gid_map`
|
||||
|
||||
#### `uid_map`
|
||||
|
||||
Pour établir la correspondance, une fois que l'on a créé le nouveau
|
||||
*namespace*, ces deux fichiers, accessibles dans `/proc/self/`, peuvent être
|
||||
écrits une fois.
|
||||
|
||||
Sur chaque ligne, on doit indiquer :
|
||||
|
||||
- L'identifiant marquant le début de la plage d'utilisateurs, pour le processus
|
||||
en question.
|
||||
- L'identifiant marquant le début de la plage d'utilisateurs, pour le processus
|
||||
affichant le fichier.
|
||||
- La taille de la plage.
|
||||
|
||||
|
||||
Par exemple, le *namespace* `user` initial défini la correspondance suivante :
|
||||
|
||||
<div lang="en-US">
|
||||
```
|
||||
42sh$ cat /proc/self/uid_map
|
||||
0 0 4294967295
|
||||
```
|
||||
</div>
|
||||
|
||||
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">
|
||||
```
|
||||
42sh$ cat /proc/self/uid_map
|
||||
0 1000 1
|
||||
```
|
||||
</div>
|
||||
|
||||
Dans cette situation, on comprend que notre processus considère que
|
||||
l'utilisateur root, dans le conteneur équivaut à l'utilisateur 1000 hors de
|
||||
l'espace de noms.
|
||||
|
||||
|
||||
#### `gid_map`
|
||||
|
||||
Le principe est identique pour ce fichier, mais agit sur les correspondances
|
||||
des groupes au lieu des utilisateurs.
|
||||
|
||||
|
||||
## Utilisation de l'espace de noms
|
||||
|
||||
<div lang="en-US">
|
||||
```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 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 n'agit pas en dehors.
|
||||
|
||||
|
||||
## Aller plus loin {-}
|
||||
|
||||
N'hésitez pas à jeter un œil à la page du manuel consacré à ce *namespace* :
|
||||
`user_namespaces(7)`.
|
Loading…
Add table
Add a link
Reference in a new issue