tuto: Prepare for 2022
This commit is contained in:
parent
37f0ba4b3d
commit
af860b40a0
40 changed files with 246 additions and 1974 deletions
|
@ -1,11 +1,11 @@
|
|||
include ../pandoc-opts.mk
|
||||
|
||||
SOURCES = tutorial.md installation.md chroot.md pseudofs.md capabilities.md cgroups.md oom.md seccomp.md project-intro.md project-body.md project-rendu.md
|
||||
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}
|
||||
tutorial.pdf: ${SOURCES_TUTO}
|
||||
pandoc ${PANDOCOPTS} -o $@ $+
|
||||
|
||||
clean::
|
||||
|
|
|
@ -1,202 +0,0 @@
|
|||
\newpage
|
||||
|
||||
Les *capabilities*
|
||||
==================
|
||||
|
||||
## Présentation
|
||||
|
||||
Historiquement, dans la tradition UNIX, on distinguait deux catégories de
|
||||
processus :
|
||||
|
||||
* les processus *privilégiés* : dont l'identifiant numérique de son utilisateur
|
||||
est 0 ;
|
||||
* les processus *non-privilégiés* : dont l'identifiant numérique de son
|
||||
utilisateur n'est pas 0.
|
||||
|
||||
Lors des différents tests de permission fait par le noyau, les processus
|
||||
privilégiés outrepassaient ces tests, tandis que les autres devaient passer les
|
||||
tests de l'effective UID, effective GID, et autres groupes supplémentaires...
|
||||
|
||||
Depuis Linux 2.2 (en 1998), les processus privilégiés peuvent activer ou
|
||||
désactiver des *capabilities*, chacune donnant accès à un groupe d'actions
|
||||
privilégiées au sein du noyau.
|
||||
|
||||
On trouve par exemple :
|
||||
|
||||
* `CAP_CHOWN` : permet de modifier le propriétaire d'un fichier de manière
|
||||
arbitraire ;
|
||||
* `CAP_KILL` : permet de tuer n'importe quel processus ;
|
||||
* `CAP_SYS_BOOT` : permet d'arrêter ou de redémarrer la machine ;
|
||||
* `CAP_SYS_MODULE` : permet de charger et décharger des modules ;
|
||||
* et beaucoup d'autres, il y en a environ 39 en tout (ça dépend de la
|
||||
version du noyau) !
|
||||
|
||||
|
||||
### `ping`
|
||||
|
||||
Pour émettre un ping, il est nécessaire d'envoyer des paquets ICMP. À la
|
||||
différence des datagrammes UDP ou des segments TCP, il n'existe pas d'interface
|
||||
exposée par le noyau aux utilisateurs pour envoyer des paquets ICMP. Pour le
|
||||
faire, il est nécessaire de pouvoir écrire directement sur l'interface ; ça,
|
||||
seul le super-utilisateur peut le faire.
|
||||
|
||||
Pour permettre à tous les utilisateurs de pouvoir envoyer des ping, le
|
||||
programme est donc généralement *Setuid root*. Cela permet à n'importe quel
|
||||
utilisateur de prendre les droits du super-utilisateur, le temps de l'exécution
|
||||
du programme.
|
||||
|
||||
Les problèmes surviennent lorsque l'on découvre des vulnérabilités dans les
|
||||
programmes *Setuid root*. En effet, s'il devient possible pour un utilisateur
|
||||
d'exécuter du code arbitraire, ce code sera exécuté avec les privilèges de
|
||||
l'utilisateur *root* ! Dans le cas de `ping`, on se retrouverait alors à
|
||||
pouvoir lire l'intégralité de la mémoire, alors que l'on avait juste besoin
|
||||
d'écrire sur une interface réseau.
|
||||
|
||||
C'est donc à ce moment que les *capabilities* entrent en jeu : un processus (ou
|
||||
même un thread) privilégié peut décider, généralement à son lancement, de
|
||||
réduire ses *capabilities*, pour ne garder que celles dont il a réellement
|
||||
besoin. Ainsi, `ping` pourrait se contenter de `CAP_NET_RAW`.
|
||||
|
||||
|
||||
## Les attributs de fichier étendus
|
||||
|
||||
Une grosse majorité des systèmes de fichiers (ext[234], XFS, btrfs, ...)
|
||||
permet d'enregistrer, pour chaque fichier, des attributs (dits attributs
|
||||
*étendus*, par opposition aux attributs *réguliers* qui sont réservés à l'usage
|
||||
du système de fichiers).
|
||||
|
||||
Sous Linux, les attributs sont regroupés dans des espaces de noms :
|
||||
|
||||
* *security* : espace utilisé par les modules de sécurité du noyau, tel que
|
||||
SELinux, ... ;
|
||||
* *system* : espace utilisé par le noyau pour stocker des objets système, tels
|
||||
que les ACL POSIX ;
|
||||
* *trusted*: espace dont la lecture et l'écriture est limité au
|
||||
super-utilisateur ;
|
||||
* *user* : modifiable sans restriction, à partir du moment où l'on est le
|
||||
propriétaire du fichier.
|
||||
|
||||
Par exemple, on peut définir un attribut sur un fichier comme cela :
|
||||
|
||||
<div lang="en-US">
|
||||
```bash
|
||||
42sh$ echo 'Hello World!' > toto
|
||||
42sh$ setfattr -n user.foo -v bar toto
|
||||
42sh$ getfattr -d toto
|
||||
# file: toto
|
||||
user.foo="bar"
|
||||
```
|
||||
</div>
|
||||
|
||||
Encore plus fort, vous pouvez utiliser les ACL POSIX :
|
||||
|
||||
<div lang="en-US">
|
||||
```bash
|
||||
42sh$ sudo chown root:root toto && sudo chmod o-r toto
|
||||
42sh$ cat toto
|
||||
cat: toto: Permission denied
|
||||
42sh$ sudo setfattr -m u:$USER:r toto
|
||||
42sh$ cat toto
|
||||
Hello World!
|
||||
```
|
||||
</div>
|
||||
|
||||
Bien que les droits UNIX traditionnels ne vous donnent pas accès au fichier,
|
||||
les ACL POSIX vous autorisent à le lire.
|
||||
|
||||
Vous pouvez voir ces attributs avec la commande :
|
||||
|
||||
<div lang="en-US">
|
||||
```bash
|
||||
42sh$ getfattr -d -m "^system" toto
|
||||
# file: toto
|
||||
system.posix_acl_access=0sgAAEAD/////AgAEOgDAEAA/////xAABAD////8=
|
||||
```
|
||||
</div>
|
||||
|
||||
|
||||
### `ping`
|
||||
|
||||
De la même manière que l'on peut définir de façon plus fine les droits d'accès
|
||||
par utilisateur, un attribut de l'espace de nom *security* peut être défini
|
||||
pour accroître les *capabilities* d'un processus lorsqu'il est lancé par un
|
||||
utilisateur non-privilégié. On peut alors voir le Setuid root comme
|
||||
l'utilisation de cet attribut auquel on accroîtrait l'ensemble des
|
||||
*capabilities*.
|
||||
|
||||
Si votre distribution profite de ces attributs étendus, vous devriez obtenir :
|
||||
|
||||
<div lang="en-US">
|
||||
```bash
|
||||
42sh$ getfattr -d -m "^security" $(which ping)
|
||||
# file: bin/ping
|
||||
security.capability=0sAQAAAgAgAAAAAAAAAAAAAAAAAAA=
|
||||
```
|
||||
</div>
|
||||
|
||||
Ou, dans sa version plus lisible :
|
||||
|
||||
<div lang="en-US">
|
||||
```bash
|
||||
42sh$ getcap $(which ping)
|
||||
/bin/ping = cap_net_raw+ep
|
||||
```
|
||||
</div>
|
||||
|
||||
|
||||
## Exercice : visualisateur de capabilities d'un processus {-}
|
||||
|
||||
Écrivons maintenant un programme permettant de voir les *capabilities*
|
||||
d'un processus :
|
||||
|
||||
<div lang="en-US">
|
||||
```
|
||||
42sh$ ./view_caps 1
|
||||
cap_user_header_t
|
||||
-----------------
|
||||
Version: 20080522
|
||||
PID: 1
|
||||
|
||||
cap_user_data_t
|
||||
---------------
|
||||
effective: 0x3fffffffff
|
||||
CAP_AUDIT_CONTROL
|
||||
CAP_AUDIT_READ
|
||||
[...]
|
||||
CAP_SYS_TIME
|
||||
CAP_SYS_TTY_CONFIG
|
||||
CAP_SYSLOG
|
||||
CAP_WAKE_ALARM
|
||||
permitted: 0x3fffffffff
|
||||
CAP_AUDIT_CONTROL
|
||||
CAP_AUDIT_READ
|
||||
[...]
|
||||
CAP_SYS_TIME
|
||||
CAP_SYS_TTY_CONFIG
|
||||
CAP_SYSLOG
|
||||
CAP_WAKE_ALARM
|
||||
inheritable: 0x0
|
||||
```
|
||||
</div>
|
||||
|
||||
Astuces : `capget(2)`, X-macros, ...
|
||||
|
||||
|
||||
## Pour aller plus loin {-}
|
||||
|
||||
Je vous recommande la lecture des *man* suivants :
|
||||
|
||||
* `capabilities(7)` : énumérant tous les capabilities, leur utilisation, etc. ;
|
||||
* `xattrs(7)` : à propos des attributs étendus.
|
||||
|
||||
Et de ces quelques articles :
|
||||
|
||||
* [Secure Your Containers with this One Weird Trick](https://www.redhat.com/en/blog/secure-your-containers-one-weird-trick)
|
||||
* [Guidelines for extended attributes](https://www.freedesktop.org/wiki/CommonExtendedAttributes/)
|
||||
* [File-based capabilities](https://lwn.net/Articles/211883/)
|
||||
* [A bid to resurrect Linux capabilities](https://lwn.net/Articles/199004/)
|
||||
* [False Boundaries and Arbitrary Code Execution](https://forums.grsecurity.net/viewtopic.php?f=7&t=2522#p10271)
|
||||
|
||||
Pour revenir à Docker, par défaut, un certain nombre de *capabilities* sont
|
||||
désactivées par défaut ; vous pouvez en ajouter et en retirer via les arguments
|
||||
`--cap-add` et `--cap-drop` du `docker container run`.
|
|
@ -1,287 +0,0 @@
|
|||
\newpage
|
||||
|
||||
Utiliser les *cgroup*s
|
||||
======================
|
||||
|
||||
Les *cgroup*s (pour *Control Group*s) permettent de collecter des statistiques
|
||||
sur des groupes de processus (appelés tâches) et de leur attribuer des
|
||||
propriétés. Par exemple, il est possible leur imposer des limites d'utilisation
|
||||
de ressources ou d'altérer leur comportement.
|
||||
|
||||
|
||||
## Premiers tests
|
||||
|
||||
Nous allons commencer par faire quelques tests avec le *cgroup* *freezer*, qui
|
||||
permet d'interrompre l'exécution d'un groupe de processus et de la reprendre.
|
||||
|
||||
|
||||
### Montage du *cgroup*
|
||||
|
||||
En fonction de la configuration de votre système, il est possible que les
|
||||
*cgroup*s ne soient pas montés au démarrage dans `/sys/fs/cgroup/`. Si vous n'avez
|
||||
pas de dossier `freezer` ou si celui-ci est vide, montez-le en suivant la
|
||||
procédure suivante :
|
||||
|
||||
<div lang="en-US">
|
||||
```bash
|
||||
mkdir /sys/fs/cgroup/freezer/
|
||||
mount -t cgroup -o freezer none /sys/fs/cgroup/freezer/
|
||||
```
|
||||
</div>
|
||||
|
||||
Cette dernière commande monte l'arborescence de groupes relative à ce *cgroup*
|
||||
*freezer*. Tous les dossiers contenus dans cette racine sont donc des
|
||||
sous-groupes.
|
||||
|
||||
|
||||
### Création d'un nouveau groupe
|
||||
|
||||
La première étape dans l'utilisation d'un *cgroup* est de créer un groupe.
|
||||
|
||||
Pour ce faire, il suffit de créer un nouveau dossier dans un groupe existant,
|
||||
par exemple la racine :
|
||||
|
||||
<div lang="en-US">
|
||||
```bash
|
||||
mkdir /sys/fs/cgroup/freezer/virli/
|
||||
ls /sys/fs/cgroup/freezer/virli/
|
||||
```
|
||||
</div>
|
||||
|
||||
Nous avons maintenant un nouveau groupe de processus `virli` dans le *cgroup*
|
||||
*Freezer*. Comme il s'agit d'une hiérarchie, le groupe `virli` hérite des
|
||||
propriétés de son (ses) père(s).
|
||||
|
||||
|
||||
### Rattachement de processus
|
||||
|
||||
Pour le moment, ce nouveau groupe ne contient aucune tâche.
|
||||
|
||||
Ouvrons un nouveau terminal (c'est lui que l'on va geler), et récupérons son
|
||||
PID : `echo $$`.
|
||||
|
||||
La liste des processus rattachés à un *cgroup* se trouve dans le fichier `task`
|
||||
du groupe. Pour ajouter une tâche à ce groupe, cela se passe de cette manière :
|
||||
|
||||
<div lang="en-US">
|
||||
```bash
|
||||
echo $PID > /sys/fs/cgroup/freezer/virli/tasks
|
||||
```
|
||||
</div>
|
||||
|
||||
Il faut ici remplacer `$PID` par le PID du shell que l'on a relevé juste avant.
|
||||
|
||||
En validant cette commande, nous avons déplacé le processus dans ce groupe, il
|
||||
n'est alors plus dans aucun autre groupe (pour ce *cgroup*, il ne bouge pas
|
||||
dans les autres *cgroup*s).
|
||||
|
||||
Malgré l'utilisation de la redirection `>` (et non `>>`), il s'agit belle et
|
||||
bien d'un ajout au fichier et non d'un écrasement. Il faut garder en tête que
|
||||
le système de fichier est entièrement simulé et que certains comportements sont
|
||||
adaptés.
|
||||
|
||||
|
||||
### Consultation de l'état
|
||||
|
||||
En affichant le contenu du dossier `virli`, nous pouvions constater que
|
||||
celui-ci contenait déjà un certain nombre de fichiers. Certain d'entre-eux sont
|
||||
en lecture seule et permettent de lire des statistiques instantanées sur le
|
||||
groupe ; tandis que d'autres sont des propriétés que nous pouvons modifier.
|
||||
|
||||
Nous pouvons consulter l'état de gel du groupe en affichant le contenu du
|
||||
fichier\newline `/sys/fs/cgroup/freezer/virli/freezer.state`.
|
||||
|
||||
Pour plus d'information sur les différents fichiers présents dans ce *cgroup*,
|
||||
consultez
|
||||
[la documentation associée](https://www.kernel.org/doc/html/latest/admin-guide/cgroup-v1/freezer-subsystem.html).
|
||||
|
||||
|
||||
### Changement d'état
|
||||
|
||||
Faisons exécuter à notre interpréteur une commande pour voir effectivement
|
||||
l'exécution s'arrêter. Si vous manquez d'inspiration, utilisez :
|
||||
|
||||
<div lang="en-US">
|
||||
```bash
|
||||
for i in $(seq 9999); do echo -n $i; sleep .1; echo -n " - "; sleep .1; done
|
||||
```
|
||||
</div>
|
||||
|
||||
Maintenant, nous avons donné l'ordre au noyau de ne plus allouer de temps de
|
||||
calcul à notre shell et ses fils :
|
||||
|
||||
<div lang="en-US">
|
||||
```bash
|
||||
echo FROZEN > /sys/fs/cgroup/freezer/virli/freezer.state
|
||||
```
|
||||
</div>
|
||||
|
||||
À cet instant, vous devriez voir votre compteur s'arrêter. Pour reprendre
|
||||
l'exécution :
|
||||
|
||||
<div lang="en-US">
|
||||
```bash
|
||||
echo THAWED > /sys/fs/cgroup/freezer/virli/freezer.state
|
||||
```
|
||||
</div>
|
||||
|
||||
|
||||
## Exercice : script de monitoring
|
||||
|
||||
À nous maintenant de concevoir un script qui va enregistrer vers une base de
|
||||
données des statistiques issues des *cgroup*s.
|
||||
|
||||
|
||||
### Rappel d'InfluxDB
|
||||
|
||||
Commençons par lancer le conteneur Docker d'InfluxDB (pour éviter de
|
||||
l'installer sur notre machine) :
|
||||
|
||||
<div lang="en-US">
|
||||
```bash
|
||||
docker container run --name mytsdb -d -p 8086:8086 influxdb
|
||||
```
|
||||
</div>
|
||||
|
||||
Il nous faut ensuite créer une base de données pour y stocker nos
|
||||
métriques. Voici comment on s'était débrouillé dans un précédent TP pour
|
||||
interagir avec InfluxDB :
|
||||
|
||||
<div lang="en-US">
|
||||
```bash
|
||||
docker container exec -i mytsdb influx <<EOF
|
||||
CREATE DATABASE metrics;
|
||||
SHOW DATABASES;
|
||||
EOF
|
||||
```
|
||||
</div>
|
||||
|
||||
Vérifiez que la base de données `metrics` a bien été créée.
|
||||
|
||||
|
||||
### Monitoring instantané vers la console
|
||||
|
||||
Dans un premier temps, commençons par afficher dans la console, la quantité de
|
||||
mémoire utilisée par le groupe monitoré.
|
||||
|
||||
Vous pouvez utiliser un programme comme
|
||||
[`memhog`](https://virli.nemunai.re/memhog.c) pour remplir rapidement votre
|
||||
mémoire.
|
||||
|
||||
<div lang="en-US">
|
||||
```
|
||||
42sh# mkdir /sys/fs/cgroup...
|
||||
42sh$ echo $$ | sudo tee /sys/fs/cgroup.../tasks
|
||||
42sh# ./monitor group_name memhog 500
|
||||
~~~ 13595 ~~~ Current memory usage: 75194368/550502400 (13%)
|
||||
~~~ 13595 ~~~ Current memory usage: 150290432/550502400 (27%)
|
||||
~~~ 13595 ~~~ Current memory usage: 223690752/550502400 (40%)
|
||||
~~~ 13595 ~~~ Current memory usage: 296828928/550502400 (53%)
|
||||
~~~ 13595 ~~~ Current memory usage: 368001024/550502400 (66%)
|
||||
~~~ 13595 ~~~ Current memory usage: 438517760/550502400 (79%)
|
||||
~~~ 13595 ~~~ Current memory usage: 480329728/550502400 (87%)
|
||||
~~~ 13595 ~~~ Current memory usage: 155648/550502400 (0%)
|
||||
```
|
||||
</div>
|
||||
|
||||
Si vous n'avez pas le *cgroup* *memory*, il est possible qu'il ne soit pas
|
||||
activé par défaut par votre système. Si vous êtes dans ce cas, essayez
|
||||
d'ajouter `cgroup_enable=memory` à la ligne de commande de votre noyau.
|
||||
|
||||
|
||||
### Monitoring vers InfluxDB
|
||||
|
||||
Maintenant, envoyons nos données vers la base
|
||||
<https://docs.influxdata.com/influxdb/v1.6/guides/writing_data/> :
|
||||
|
||||
<div lang="en-US">
|
||||
```bash
|
||||
curl -i -XPOST 'http://localhost:8086/write?db=metrics' --data-binary \
|
||||
"$my_cgroup_name memory.usage_in_bytes=$(cat .../my_cgroup_name/memory.usage_in_bytes)"
|
||||
```
|
||||
</div>
|
||||
|
||||
Pour vérifier que les données ont bien été ajoutées, nous pouvons effectuer la
|
||||
requête suivante dans notre client `influx` :
|
||||
|
||||
<div lang="en-US">
|
||||
```sql
|
||||
SELECT * from "$my_cgroup_name";
|
||||
```
|
||||
</div>
|
||||
|
||||
|
||||
### Monitorer davantage de données
|
||||
|
||||
Liste non exhaustive de données à monitorer :
|
||||
|
||||
* Nombre d'IOs effectué ;
|
||||
* nombre d'octets lu/écrit sur les disques ;
|
||||
* temps de calcul utilisé ;
|
||||
* trafic réseau généré ;
|
||||
* ...
|
||||
|
||||
Tous les cgroups existants dans le dernier noyau publié ont leur documentation
|
||||
accessible ici :
|
||||
<https://www.kernel.org/doc/html/latest/admin-guide/cgroup-v1/index.html>
|
||||
|
||||
|
||||
### Permettre à l'utilisateur de monitorer des processus
|
||||
|
||||
Maintenant, séparons notre script en deux parties afin qu'un utilisateur normal
|
||||
(non-root) puisse utiliser la partie monitoring de notre script.
|
||||
|
||||
Un premier script doit s'occuper de créer le(s) *cgroup*s et lui attribuer les
|
||||
bons droits, tandis que le deuxième va effectuer le monitoring, sans privilèges
|
||||
particuliers.
|
||||
|
||||
#### Exemple
|
||||
|
||||
<div lang="en-US">
|
||||
```
|
||||
42sh$ sudo ./monitor_init my_cgroup_name
|
||||
42sh$ ./monitor my_cgroup_name memhog 500
|
||||
```
|
||||
</div>
|
||||
|
||||
|
||||
## Fixer des limites
|
||||
|
||||
Au delà de la simple consultation, les *cgroup*s peuvent servir à limiter la
|
||||
quantité de ressources mise à disposition à un groupe de processus.
|
||||
|
||||
Pour définir une limite, nous allons écrire la valeur dans le fichier
|
||||
correspondant à une valeur limite, comme par exemple
|
||||
`memory.max_usage_in_bytes`, qui limite le nombre d'octets que notre groupe de
|
||||
processus va pouvoir allouer au maximum :
|
||||
|
||||
<div lang="en-US">
|
||||
```
|
||||
42sh$ cat /sys/fs/cgroup/memory/virli/memory.max_usage_in_bytes
|
||||
0
|
||||
# 0 = Aucune limite
|
||||
42sh$ echo 4M > /sys/fs/cgroup/memory/virli/memory.max_usage_in_bytes
|
||||
# Maintenant, la limite est à 4MB, vérifions...
|
||||
42sh$ cat /sys/fs/cgroup/memory/virli/memory.max_usage_in_bytes
|
||||
4194304
|
||||
```
|
||||
</div>
|
||||
|
||||
Chaque *cgroup*s défini de nombreux indicateurs et possède de nombreux
|
||||
limiteurs, n'hésitez pas à consulter la documentation associée à chaque
|
||||
*cgroup*.
|
||||
|
||||
|
||||
## Pour aller plus loin {-}
|
||||
|
||||
Pour tout connaître en détails, [la série d'articles de Neil Brown sur les
|
||||
Control groups](https://lwn.net/Articles/604609/) est excellente !
|
||||
|
||||
Depuis les noyaux 4.5, il est possible d'utiliser la nouvelle version du
|
||||
pseudo système de fichiers des *CGroup*s. Le principal changement vient du
|
||||
regroupement au sein d'une seule hiérarchie des différents *CGroup*s que l'on
|
||||
avait dans la v1. Davantage d'informations sont disponibles :
|
||||
|
||||
* [Understanding the new control groups API](https://lwn.net/Articles/679786/)
|
||||
;
|
||||
* [Kernel Document about Control Group v2](https://www.kernel.org/doc/html/latest/admin-guide/cgroup-v2.html).
|
|
@ -1,46 +0,0 @@
|
|||
#!/bin/sh
|
||||
|
||||
note_init() {
|
||||
NOTE=0
|
||||
echo -n $@
|
||||
}
|
||||
|
||||
note() {
|
||||
NOTE=$(($NOTE + $1))
|
||||
echo -n ,$@
|
||||
}
|
||||
|
||||
for LOGIN in $@
|
||||
do
|
||||
note_init $LOGIN
|
||||
|
||||
if [ -f "$LOGIN" ]; then
|
||||
QUESTIONSF="$LOGIN"
|
||||
else
|
||||
QUESTIONSF="$LOGIN/questions.txt"
|
||||
fi
|
||||
|
||||
# Questions
|
||||
|
||||
if grep -E -i "(grsec)" "${QUESTIONSF}" 2> /dev/null > /dev/null; then
|
||||
note 1
|
||||
else
|
||||
note 0
|
||||
fi
|
||||
|
||||
|
||||
if grep -E -i "(/proc/.*/oom_|score)" "${QUESTIONSF}" 2> /dev/null > /dev/null; then
|
||||
note 1
|
||||
else
|
||||
note 0
|
||||
fi
|
||||
|
||||
if grep -E -i "root" "${QUESTIONSF}" 2> /dev/null > /dev/null; then
|
||||
note 1
|
||||
else
|
||||
note 0
|
||||
fi
|
||||
|
||||
|
||||
echo #" = $NOTE"
|
||||
done
|
|
@ -1,111 +0,0 @@
|
|||
\newpage
|
||||
|
||||
L'isolation ... du pauvre
|
||||
=========================
|
||||
|
||||
Depuis les premières versions d'Unix, il est possible de changer le répertoire
|
||||
vu comme étant la racine du système de fichiers. En anglais : *change root*:
|
||||
`chroot`. Le processus effectuant cette action ainsi que tous ses fils, verront
|
||||
donc une racine différente du reste du système.
|
||||
|
||||
|
||||
## Mise en place de l'environnement
|
||||
|
||||
Pour se créer un environnement afin de changer notre racine, il va falloir
|
||||
commencer par créer le dossier de notre nouvelle racine :
|
||||
|
||||
<div lang="en-US">
|
||||
```bash
|
||||
mkdir newroot
|
||||
```
|
||||
</div>
|
||||
|
||||
### `busybox`
|
||||
|
||||
Queques mots, pour commencer, à propos du projet Busybox : c'est un programme
|
||||
*linké* statiquement, c'est-à-dire qu'il ne va pas chercher ni charger de
|
||||
bibliothèque dynamique à son lancement. Il se suffit donc à lui-même dans un
|
||||
*chroot*, car il n'a pas de dépendances. Nous pouvons donc tester notre
|
||||
première isolation\ :
|
||||
|
||||
<div lang="en-US">
|
||||
```bash
|
||||
cp $(which busybox) newroot/
|
||||
chroot newroot /busybox ash
|
||||
```
|
||||
</div>
|
||||
|
||||
Jusque là ... ça fonctionne, rien de surprenant ! Mais qu'en est-il pour
|
||||
`bash` :
|
||||
|
||||
<div lang="en-US">
|
||||
```bash
|
||||
42sh$ cp $(which bash) newroot/
|
||||
42sh# chroot newroot /bash
|
||||
chroot: failed to run command ‘bash’: No such file or directory
|
||||
```
|
||||
</div>
|
||||
|
||||
De quel fichier est-il question ici ?
|
||||
|
||||
|
||||
### `debootstrap`
|
||||
|
||||
`debootstrap` est le programme utilisé par l'installeur des distributions
|
||||
Debian et ses dérivées. Il permet d'installer dans un dossier (en général, ce
|
||||
dossier correspond au point de montage de la nouvelle racine choisie par
|
||||
l'utilisateur lors de l'installation) le système de base.
|
||||
|
||||
<div lang="en-US">
|
||||
```bash
|
||||
debootstrap buster newroot/ http://httpredir.debian.org/debian/
|
||||
```
|
||||
</div>
|
||||
|
||||
`pacstrap` est le programme équivalent pour Archlinux.
|
||||
|
||||
|
||||
### *stage3*
|
||||
|
||||
Les distributions *à l'ancienne* proposent encore de télécharger leur système
|
||||
de base sous forme de tarball\ :
|
||||
|
||||
<div lang="en-US">
|
||||
```bash
|
||||
wget http://gentoo.mirrors.ovh.net/gentoo-distfiles/releases/amd64/autobuilds/current-stage3-amd64/stage3-amd64-20201104T214503Z.tar.xz
|
||||
tar xpf stage3-amd64-*.tar.xz -C newroot/
|
||||
```
|
||||
</div>
|
||||
|
||||
L'avantage de télécharger l'archive de Gentoo est que l'on a déjà `gcc` dans un
|
||||
environnement qui tient dans 300 MB.
|
||||
|
||||
|
||||
## Exercice {-}
|
||||
|
||||
Écrivons maintenant un programme dont le seul but est de s'échapper du `chroot` :
|
||||
|
||||
<div lang="en-US">
|
||||
```bash
|
||||
make escape
|
||||
echo bar > ../foo
|
||||
chroot .
|
||||
```
|
||||
</div>
|
||||
|
||||
Dans le nouvel environnement, vous ne devriez pas pouvoir faire :
|
||||
|
||||
<div lang="en-US">
|
||||
```bash
|
||||
cat ../foo
|
||||
```
|
||||
</div>
|
||||
|
||||
Mais une fois votre programme `escape` exécuté, vous devriez pouvoir !
|
||||
|
||||
<div lang="en-US">
|
||||
```
|
||||
(chroot) 42sh# ./escape
|
||||
bash# cat /path/to/foo
|
||||
```
|
||||
</div>
|
58
tutorial/4/cmpns.md
Normal file
58
tutorial/4/cmpns.md
Normal file
|
@ -0,0 +1,58 @@
|
|||
## 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é (comme il existe
|
||||
un dossier `/proc/self/`, vous n'avez pas besoin de gérer de cas particulier
|
||||
pour ça !).
|
||||
|
||||
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/4/docker-exec.md
Normal file
46
tutorial/4/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>
|
|
@ -1,102 +0,0 @@
|
|||
\newpage
|
||||
|
||||
Prérequis
|
||||
=========
|
||||
|
||||
## Noyau Linux
|
||||
|
||||
Ce TP requiert un noyau Linux, dans sa version 3.8 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 --->
|
||||
[*] Freezer cgroup subsystem
|
||||
[*] PIDs cgroup subsystem
|
||||
[*] Device controller for cgroups
|
||||
[*] Cpuset support
|
||||
[*] Simple CPU accounting cgroup subsystem
|
||||
[*] Memory Resource Controller for Control Groups
|
||||
[*] Group CPU scheduler --->
|
||||
[*] Group scheduling for SCHED_OTHER
|
||||
[*] Group scheduling for SCHED_RR/FIFO
|
||||
<*> Block IO controller
|
||||
[*] Networking support --->
|
||||
Networking options --->
|
||||
[*] Network priority cgroup
|
||||
[*] Network classid cgroup
|
||||
```
|
||||
</div>
|
||||
|
||||
|
||||
### Vérification via `menuconfig`
|
||||
|
||||
L'arbre ci-dessous correspond aux options qui seront *built-in* (signalées par
|
||||
une `*`) ou installées en tant que module (signalées par un `M`). En effet,
|
||||
chaque noyau Linux peut être entièrement personnalisé en fonction des options
|
||||
et des pilotes que l'on voudra utiliser.
|
||||
|
||||
Pour parcourir l'arbre des options du noyau, il est nécessaire d'avoir les
|
||||
sources de celui-ci. Les dernières versions stables et encore maintenues sont
|
||||
disponibles sur la page d'accueil de <https://kernel.org>.
|
||||
|
||||
Dans les sources, on affiche la liste des options avec la commande :
|
||||
|
||||
<div lang="en-US">
|
||||
```bash
|
||||
make menuconfig
|
||||
```
|
||||
</div>
|
||||
|
||||
|
||||
### Vérification via `/boot/config-xxx`
|
||||
|
||||
Les distributions basées sur Debian ont pour habitude de placer le fichier de
|
||||
configuration ayant servi à compiler le noyau et ses modules dans le dossier
|
||||
`/boot`, aux côtés de l'image du noyau `vmlinuz-xxx`, de l'éventuel système de
|
||||
fichiers initial (`initramfs-xxx`) et des symboles de débogage
|
||||
`System.map-xxx`.
|
||||
|
||||
Ce fichier répertorie toutes les options qui ont été activées. Par rapport à
|
||||
l'arbre présenté ci-dessus, vous devriez trouver :
|
||||
|
||||
<div lang="en-US">
|
||||
```
|
||||
CONFIG_CGROUPS=y
|
||||
CONFIG_CGROUP_FREEZER=y
|
||||
CONFIG_CGROUP_PIDS=y
|
||||
CONFIG_CGROUP_DEVICE=y
|
||||
CONFIG_CPUSETS=y
|
||||
CONFIG_CGROUP_CPUACCT=y
|
||||
CONFIG_CGROUP_MEMCG=y
|
||||
CONFIG_CGROUP_SCHED=y
|
||||
CONFIG_BLK_CGROUP=y
|
||||
|
||||
CONFIG_NET=y
|
||||
CONFIG_CGROUP_NET_PRIO=y
|
||||
CONFIG_CGROUP_NET_CLASSID=y
|
||||
```
|
||||
</div>
|
||||
|
||||
|
||||
### Vérification via `/proc/config.gz`
|
||||
|
||||
Dans la plupart des autres distributions, la configuration est accessible à
|
||||
travers le fichier `/proc/config.gz`. Comme vous ne pouvez pas écrire dans
|
||||
`/proc` pour décompresser le fichier, utilisez les outils `zcat`, `zgrep`, ...
|
||||
|
||||
Vous devez retrouver les mêmes options que celles de la section précédente.
|
||||
|
||||
|
||||
|
||||
## Présence des en-têtes
|
||||
|
||||
Si vous utilisez un noyau standard fourni par votre distribution, les options
|
||||
requises seront a priori déjà sélectionnées et vous n'aurez donc pas à compiler
|
||||
votre propre noyau. Néanmoins, durant ce TP, nous allons nous interfacer avec
|
||||
le noyau, il est donc nécessaire d'avoir les en-têtes de votre noyau.
|
||||
|
||||
Sous Debian, vous pouvez les installer via le paquet au nom semblable à
|
||||
`linux-headers`. Le paquet porte le même nom sous Arch Linux et ses dérivés.
|
12
tutorial/4/lesson.md
Normal file
12
tutorial/4/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.
|
||||
...
|
302
tutorial/4/mount.md
Normal file
302
tutorial/4/mount.md
Normal file
|
@ -0,0 +1,302 @@
|
|||
\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 le TP
|
||||
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 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 toute 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 (le *hardlink* persiste au redémarrage, mais doit se faire au
|
||||
sein d'une même partition).
|
||||
|
||||
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.
|
||||
|
||||
Pour déplacer un point de montage, on utilise 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>
|
||||
|
||||
Cette possibilité s'emploie notamment lorsque l'on souhaite changer la racine
|
||||
de notre système de fichiers : par exemple pour passer de l'*initramfs* au
|
||||
système démarré, de notre système hôte au système d'un conteneur, ...
|
||||
|
||||
|
||||
## 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/4/mountns.md
Normal file
137
tutorial/4/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>
|
318
tutorial/4/namespaces.md
Normal file
318
tutorial/4/namespaces.md
Normal file
|
@ -0,0 +1,318 @@
|
|||
\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 (le dernier ayant été ajouté dans 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 mount* 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 mount* 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, // 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.
|
||||
|
||||
Un exemple complet d'utilisation de `clone(2)` et du *namespace* `UTS` est
|
||||
donné dans le `man` de l'appel système.
|
||||
|
||||
|
||||
## 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)](https://web.archive.org/web/20190206073558/http://crosbymichael.com/creating-containers-part-1.html)
|
||||
est également des plus intéressants, pour ce qui concerne la programmation
|
||||
plus bas-niveau.
|
241
tutorial/4/networkns.md
Normal file
241
tutorial/4/networkns.md
Normal file
|
@ -0,0 +1,241 @@
|
|||
\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).
|
||||
|
||||
Appliqué à Docker, vous apprécirez cet article : [Understanding Docker
|
||||
Networking Drivers and their use
|
||||
cases](https://www.docker.com/blog/understanding-docker-networking-drivers-use-cases/).
|
|
@ -1,52 +0,0 @@
|
|||
\newpage
|
||||
|
||||
Gestion de la mémoire
|
||||
=====================
|
||||
|
||||
Linux a une gestion de la mémoire bien particulière[^vm-overcommit] : par
|
||||
défaut, `malloc(3)` ne retournera jamais `NULL`. En se basant sur l'euristique
|
||||
qu'un bloc mémoire demandé ne sera pas utilisé directement et que de nombreux
|
||||
process ne feront pas un usage total des blocks qu'ils ont alloués, le noyau
|
||||
permet d'allouer plus de mémoire qu'il n'y en a réellement disponible. La
|
||||
mémoire est donc utilisée de manière plus efficace.
|
||||
|
||||
[^vm-overcommit]: Dépendant de la valeur de `/proc/sys/vm/overcommit_memory`,
|
||||
généralement 0. Voir
|
||||
<https://www.kernel.org/doc/html/latest/vm/overcommit-accounting.html>.
|
||||
|
||||
Mais évidemment, cela peut donner lieu à des situations où le noyau n'est plus
|
||||
en mesure de trouver de bloc physiquement disponible, alors qu'ils avaient
|
||||
effectivement été alloués au processus. Pour autant, ce n'est pas une raison
|
||||
pour tuer ce processus, car il est peut-être vital pour le système (peut-être
|
||||
est-ce `init` qui est en train de gérer le lancement d'un nouveau daemon). On
|
||||
dit alors que le noyau est *Out-Of-Memory*.
|
||||
|
||||
Pour se sortir d'une telle situation, et après avoir tenté de vider les caches,
|
||||
il lance son arme ultime, pour retrouver au plus vite de la mémoire :
|
||||
l'*Out-Of-Memory killer*.
|
||||
|
||||
|
||||
## OOM killer
|
||||
|
||||
Selon un algorithme dont on raconte qu'il ne serait pas basé entièrement sur
|
||||
l'aléatoire[^oom-algo], un processus est tiré au sort (plus un processus occupe
|
||||
de mémoire et plus il a de chance d'être tiré au sort) par l'OOM killer. Le
|
||||
sort qui lui est réservé est tout simplement une mort brutale. Pour permettre
|
||||
au système de disposer à nouveau de mémoire disponible. Si cela n'est pas
|
||||
suffisant, un ou plusieurs autres processus peuvent être tués à tour de rôle,
|
||||
jusqu'à ce que le système retrouve sa sérénité.
|
||||
|
||||
[^oom-algo]: <https://linux-mm.org/OOM_Killer>
|
||||
|
||||
## Esquiver l'OOM killer
|
||||
|
||||
Au sein d'un *cgroup* *memory*, le fichier `memory.oom_control` peut être
|
||||
utilisé afin de recevoir une notification du noyau avant que l'OOM-killer
|
||||
ne s'attaque à un processus de ce groupe.
|
||||
|
||||
Grâce à cette notification, il est possible de figer le processus pour
|
||||
l'envoyer sur une autre machine. Et ainsi libérer la mémoire avant que l'OOM
|
||||
killer ne passe.
|
||||
|
||||
Jetez un œil à [cet article parru sur LWN](https://lwn.net/Articles/590960/) à
|
||||
ce sujet.
|
107
tutorial/4/pidns.md
Normal file
107
tutorial/4/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 dans un
|
||||
*namespace* `mount` séparé.
|
||||
|
||||
|
||||
### 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)`.
|
|
@ -1 +0,0 @@
|
|||
../../subject/2/project-part1.md
|
|
@ -5,20 +5,20 @@ Projet et rendu
|
|||
|
||||
## Sujet
|
||||
|
||||
Vous allez commencer aujourd'hui un projet qui s'étendra au prochain TP et qui
|
||||
consistera à réaliser la partie d'isolation de la moulinette des ACUs !
|
||||
**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 ne puisse pas faire de déni de service sur notre machine.
|
||||
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, etc.
|
||||
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 !
|
||||
|
||||
Gardez en tête que ce projet sera à continuer au prochain TP, où il sera
|
||||
principalement question de faire des appels systèmes.
|
||||
|
|
|
@ -1,39 +1,35 @@
|
|||
## 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.
|
||||
|
||||
Par ailleurs, n'oubliez pas de répondre à
|
||||
[l'évaluation du cours](https://virli.nemunai.re/quiz/6).
|
||||
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.
|
||||
N'hésitez pas à indiquer dans le corps du courriel votre
|
||||
ressenti et vos difficultés ou bien alors écrivez votre meilleure histoire
|
||||
drôle si vous n'avez rien à dire.
|
||||
|
||||
Tarball
|
||||
-------
|
||||
|
||||
## Tarball
|
||||
Le projet à rendre pour ce cours est à placer dans une tarball (pas d'archive
|
||||
ZIP, RAR, ...).
|
||||
|
||||
Tous les fichiers identifiés comme étant à rendre pour ce TP sont à
|
||||
placer dans une tarball (pas d'archive ZIP, RAR, ...).
|
||||
|
||||
Voici une arborescence type (adaptez les extensions et les éventuels
|
||||
fichiers supplémentaires associés au langage que vous aurez choisi
|
||||
pour chaque exercice) :
|
||||
Voici une arborescence type:
|
||||
|
||||
<div lang="en-US">
|
||||
```
|
||||
login_x-TP3/
|
||||
login_x-TP3/escape.c
|
||||
login_x-TP3/procinfo.sh
|
||||
login_x-TP3/suspend_schedule.sh
|
||||
login_x-TP3/view_caps.c
|
||||
login_x-TP3/monitor.sh
|
||||
login_x-TP3/monitor_init.sh
|
||||
login_x-TP3/syscall_filter.c
|
||||
login_x-mymoulette/README
|
||||
login_x-mymoulette/...
|
||||
```
|
||||
</div>
|
||||
|
||||
Les premières étapes du projet ne sont pas à rendre et feront l'objet
|
||||
d'un rendu à part.
|
||||
|
|
|
@ -1,252 +0,0 @@
|
|||
\newpage
|
||||
|
||||
Pseudos systèmes de fichiers
|
||||
============================
|
||||
|
||||
## Rappels sur les points de montage
|
||||
|
||||
Les systèmes Unix définissent le système de fichiers comme étant un arbre
|
||||
unique partant d'une racine[^FHS] et où l'on peut placer au sein de son arborescence
|
||||
des points de montage. Ainsi, l'utilisateur définit généralement deux points de
|
||||
montage :
|
||||
|
||||
[^FHS]: Consultez
|
||||
<https://en.wikipedia.org/wiki/Filesystem_Hierarchy_Standard> pour
|
||||
plus de détails sur l'arboresence.
|
||||
|
||||
<div lang="en-US">
|
||||
```
|
||||
/dev/sda1 on / type ext4 (rw,relatime,data=ordered)
|
||||
/dev/sda3 on /home type ext4 (rw,relatime,data=ordered)
|
||||
```
|
||||
</div>
|
||||
|
||||
Dans ce schéma, la racine correspond à la première partition du premier disque,
|
||||
et les fichiers des utilisateurs sont sur la troisième partition du premier
|
||||
disque.
|
||||
|
||||
|
||||
## Présentation des pseudos systèmes de fichiers
|
||||
|
||||
D'autres points de montage sont utilisés par le système : `/dev`, `/proc`,
|
||||
`/tmp`, ... Ces points de montage vont, la plupart du temps, être montés par le
|
||||
programme d'initialisation en utilisant des systèmes de fichiers virtuels, mis
|
||||
à disposition par le noyau.
|
||||
|
||||
Ces systèmes sont virtuels, car ils ne correspondent à aucune partition d'aucun
|
||||
disque : l'arborescence est créée de toute pièce par le noyau pour trier les
|
||||
informations mises à disposition, mais il n'est pas toujours possible d'y
|
||||
apporter des modifications.
|
||||
|
||||
Linux emploie de nombreux systèmes de fichiers virtuels :
|
||||
|
||||
- `/proc` : contient, principalement, la liste des processus (`top` et ses
|
||||
dérivés se contentent de lire les fichiers de ce point de montage) ;
|
||||
- `/proc/sys` : contient la configuration du noyau ;
|
||||
- `/sys` : contient des informations à propos du matériel (utilisées notamment
|
||||
par `udev` pour peupler `/dev`) et des périphériques (taille des tampons,
|
||||
clignottement des DELs, ...) ;
|
||||
- `/sys/firmware/efi/efivars` : pour accéder et modifier les variables de
|
||||
l'UEFI ;
|
||||
- ...
|
||||
|
||||
Tous ces systèmes de fichiers sont généralement exclusivement stockés en
|
||||
RAM. Pour rendre une modification persistante, il est nécessaire de modifier un
|
||||
fichier de configuration qui sera chargé par le programme d'initialisation. Par
|
||||
exemple, pour modifier les paramètres du noyau, on passe par le fichier
|
||||
`/etc/sysctl.conf` et du programme `sysctl`.
|
||||
|
||||
|
||||
### Consultation et modification
|
||||
|
||||
La consultation d'un élément se fait généralement à l'aide d'un simple `cat` :
|
||||
|
||||
<div lang="en-US">
|
||||
```
|
||||
42sh$ cat /sys/power/state
|
||||
freeze mem
|
||||
```
|
||||
</div>
|
||||
|
||||
La modification d'un élément se fait avec `echo`, comme ceci :
|
||||
|
||||
<div lang="en-US">
|
||||
```bash
|
||||
42sh# echo mem > /sys/power/state
|
||||
```
|
||||
</div>
|
||||
|
||||
Vous devriez constater l'effet de cette commande sans plus attendre !
|
||||
|
||||
|
||||
## Exercices
|
||||
|
||||
### `procinfo`
|
||||
|
||||
Explorons le pseudo système de fichiers `/proc` pour écrire un script qui va
|
||||
afficher des informations sur un processus donné :
|
||||
|
||||
<div lang="en-US">
|
||||
```
|
||||
42sh$ ./procinfo $$
|
||||
PID: 4242
|
||||
Path: /bin/bash
|
||||
Command line: bash
|
||||
Working directory: /home/nemunaire/virli/
|
||||
Root: /
|
||||
State: S (sleeping)
|
||||
Threads: 1
|
||||
|
||||
CGroups
|
||||
=======
|
||||
12:pids:/
|
||||
11:net_prio:/
|
||||
10:perf_event:/
|
||||
9:net_cls:/
|
||||
8:freezer:/
|
||||
7:devices:/
|
||||
6:memory:/
|
||||
5:blkio:/
|
||||
4:cpuacct:/
|
||||
3:cpu:/
|
||||
2:cpuset:/
|
||||
1:name=openrc:/
|
||||
|
||||
Namespaces
|
||||
==========
|
||||
cgroup:[4026531835]
|
||||
ipc:[4026531839]
|
||||
mnt:[4026531840]
|
||||
net:[4026531969]
|
||||
pid:[4026531836]
|
||||
user:[4026531837]
|
||||
uts:[4026531838]
|
||||
```
|
||||
</div>
|
||||
|
||||
|
||||
### `batinfo.sh`, `cpuinfo.sh`
|
||||
|
||||
Explorons le pseudo système de fichiers `/sys` pour écrire un script
|
||||
qui va, en fonction de ce que vous avez de disponible :
|
||||
|
||||
* nous afficher des statistiques sur notre batterie ;
|
||||
* nous afficher des statistiques la fréquence de notre CPU.
|
||||
|
||||
#### `batinfo.sh`
|
||||
|
||||
Voici un exemple d'utilisation :
|
||||
|
||||
<div lang="en-US">
|
||||
```
|
||||
42sh$ ./batinfo.sh
|
||||
BAT0 Discharging
|
||||
====
|
||||
Capacity: 83% (Normal)
|
||||
Voltage: 11.972000 V (minimal: 11.400000 V)
|
||||
Energy: 18.290000/21.830000 Wh
|
||||
Power: 7.937000 W
|
||||
Remaining time: 2.304 h
|
||||
|
||||
BAT1 Unknown
|
||||
====
|
||||
Capacity: 83% (Normal)
|
||||
Voltage: 11.972000 V (minimal: 11.400000 V)
|
||||
Energy: 18.290000/21.830000 Wh
|
||||
Power: 0.0 W
|
||||
Remaining time: N/A
|
||||
```
|
||||
</div>
|
||||
|
||||
Pour les détails sur l'organisation de ce dossier, regardez :
|
||||
<https://www.kernel.org/doc/Documentation/power/power_supply_class.txt>.
|
||||
|
||||
|
||||
#### `cpuinfo.sh`
|
||||
|
||||
Voici un exemple d'utilisation :
|
||||
|
||||
<div lang="en-US">
|
||||
```
|
||||
42sh$ ./cpuinfo.sh
|
||||
cpu0
|
||||
====
|
||||
Current frequency: 2100384 Hz
|
||||
Current governor: powersave
|
||||
Allowed frequencies: between 500000 - 2100000 Hz
|
||||
Thermal throttle count: 0
|
||||
|
||||
cpu1
|
||||
====
|
||||
Current frequency: 2099871 Hz
|
||||
Current governor: powersave
|
||||
Allowed frequencies: between 500000 - 2100000 Hz
|
||||
Thermal throttle count: 0
|
||||
```
|
||||
</div>
|
||||
|
||||
N'hésitez pas à rajouter toute sorte d'information intéressantes !
|
||||
|
||||
|
||||
### `rev_kdb_leds.sh`, `suspend_schedule.sh`
|
||||
|
||||
Maintenant que vous savez lire des informations dans `/sys`, tentons d'aller
|
||||
modifier le comportement de notre système. Au choix, réaliser l'un des scripts
|
||||
suivant, en fonction du matériel dont vous disposez :
|
||||
|
||||
* inverser l'état des diodes de notre clavier ;
|
||||
* mettre en veille votre machine, en ayant programmé une heure de réveil.
|
||||
|
||||
#### `rev_kdb_leds.sh`
|
||||
|
||||
Si vous avez :
|
||||
|
||||
* numlock On,
|
||||
* capslock Off,
|
||||
* scrolllock Off ;
|
||||
|
||||
Après avoir exécuté le script, nous devrions avoir :
|
||||
|
||||
* numlock Off,
|
||||
* capslock On,
|
||||
* scrolllock On.
|
||||
|
||||
Voici un exemple d'utilisation :
|
||||
|
||||
<div lang="en-US">
|
||||
```
|
||||
42sh# ./rev_kdb_leds.sh input20
|
||||
```
|
||||
</div>
|
||||
|
||||
`input20` correspond à l'identifiant de votre clavier, sous
|
||||
`/sys/class/input/`.
|
||||
|
||||
#### `suspend_schedule.sh`
|
||||
|
||||
Votre script prendra en argument l'heure à laquelle votre machine doit être
|
||||
réveillée, avant de la mettre effectivement en veille.
|
||||
|
||||
Bien sûr, vous ne devez utiliser que des écritures (et des lectures) dans le
|
||||
système de fichiers `/sys`. Il n'est pas question de faire appel à un autre
|
||||
programme (vous pourriez cependant avoir besoin de `date(1)` pour faire les
|
||||
calculs horaires).
|
||||
|
||||
Voici un exemple d'utilisation :
|
||||
|
||||
<div lang="en-US">
|
||||
```
|
||||
42sh# ./suspend_schedule.sh 15:42
|
||||
|
||||
```
|
||||
</div>
|
||||
|
||||
Vous aurez besoin de définir une alarme au niveau de votre RTC, via le
|
||||
fichier : `/sys/class/rtc/rtcX/wakealarm`.
|
||||
|
||||
Attention au fuseau horaire utilisé par votre RTC, si votre système principal
|
||||
est Windows, elle utilisera sans doute le fuseau horaire courant. Sinon, ce
|
||||
sera UTC.
|
||||
|
||||
Un article très complet sur le sujet est disponible ici :
|
||||
<https://www.linux.com/tutorials/wake-linux-rtc-alarm-clock/>
|
49
tutorial/4/rendu.md
Normal file
49
tutorial/4/rendu.md
Normal file
|
@ -0,0 +1,49 @@
|
|||
\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.
|
||||
|
||||
Afin d'orienter correctement votre rendu, ajoutez une balise `[TP5]` au sujet
|
||||
de votre courriel. N'hésitez pas à indiquer dans le corps du courriel votre
|
||||
ressenti et vos difficultés ou bien alors écrivez votre meilleure histoire
|
||||
drôle si vous n'avez rien à dire.
|
||||
|
||||
Par ailleurs, n'oubliez pas de répondre à
|
||||
[l'évaluation du cours](https://virli.nemunai.re/quiz/7).
|
||||
|
||||
|
||||
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-TP5/cmpns.sh
|
||||
login_x-TP5/mydocker_exec.sh
|
||||
login_x-TP5/myswitch_root.sh
|
||||
```
|
||||
</div>
|
|
@ -1,92 +0,0 @@
|
|||
\newpage
|
||||
|
||||
Secure Computing Mode
|
||||
=====================
|
||||
|
||||
Plus connue sous l'acronyme *seccomp*, cette fonctionnalité du noyau Linux
|
||||
permet de restreindre les appels systèmes qu'un processus est autorisé à
|
||||
utiliser. En cas d'appel non autorisé, le processus fautif est directement tué
|
||||
(`SIGKILL`) par le noyau, ou, lorsque c'est précisé, un code `errno`
|
||||
particulier peut être renvoyé au programme.
|
||||
|
||||
Depuis la version 3.17 du noyau, l'appel système `seccomp(2)` permet de faire
|
||||
entrer le processus courant dans ce mode. En effet, c'est le processus lui-même
|
||||
qui déclare au noyau qu'il peut désormais se contenter d'une liste réduite
|
||||
d'appel système ; à l'inverse des politiques de sécurité comme SELinux ou
|
||||
AppArmor, qui encapsulent les programmes pour toute la durée de leur exécution.
|
||||
|
||||
*Seccomp* est particulièrement utile lorsqu'un processus a terminé son
|
||||
initialisation (ne dépendant en général pas de données sujettes à
|
||||
l'exploitation de vulnérabilité) et doit commencer à entrer dans des portions
|
||||
de code promptes aux vulnérabilités : c'est notamment le cas des moteurs de
|
||||
rendus des navigateurs Firefox et Chrome.
|
||||
|
||||
|
||||
## Prérequis
|
||||
|
||||
L'utilisation de `seccomp` nécessite d'avoir un noyau compilé avec l'option
|
||||
`CONFIG_SECCOMP`.
|
||||
|
||||
Vous aurez également besoin de la bibliothèque `libseccomp` car l'appel système
|
||||
`seccomp(2)` n'a pas de *wrapper* dans la libc, vous devrez donc passer par
|
||||
cette bibliothèque.
|
||||
|
||||
|
||||
## `MODE_STRICT`
|
||||
|
||||
Le mode traditionnel de *seccomp* est de ne permettre uniquement l'utilisation
|
||||
des appels système `read(2)`, `write(2)` (sur les descripteurs de fichier déjà
|
||||
ouvert), `_exit(2)` et `sigreturn(2)`.
|
||||
|
||||
Historiquement, avant la création de l'appel système `seccomp(2)`, on activait
|
||||
ce mode via :
|
||||
|
||||
<div lang="en-US">
|
||||
```c
|
||||
prctl(PR_SET_SECCOMP, SECCOMP_MODE_STRICT);
|
||||
```
|
||||
</div>
|
||||
|
||||
Une fois passé cet appel système, toute entrée dans un appel système non
|
||||
autorisé conduit à un `SIGKILL` du processus.
|
||||
|
||||
|
||||
## `MODE_FILTER`
|
||||
|
||||
Plus modulable que le mode strict, le mode de filtrage permet une grande
|
||||
amplitude en permettant au programmeur de définir finement quels appels
|
||||
systèmes le programme est autorisé à faire ou non, et quel sentence est
|
||||
exécutée en cas de faute.
|
||||
|
||||
Notons que les processus fils issus (`fork(2)` ou `clone(2)`) d'un processus
|
||||
auquel est appliqué un filtre `seccomp`, héritent également de ce filtre.
|
||||
|
||||
La construction de ce filtre est faite de manière programatique, via
|
||||
des règles BPF (`Berkeley Packet Filter`). On passe ensuite ce filtre BPF en
|
||||
argument de l'appel système :
|
||||
|
||||
<div lang="en-US">
|
||||
```c
|
||||
struct sock_filter filter[];
|
||||
struct sock_fprog prog = {
|
||||
.len = (unsigned short) (sizeof(filter) / sizeof(filter[0])),
|
||||
.filter = filter,
|
||||
};
|
||||
seccomp(SECCOMP_SET_MODE_FILTER, 0, &prog);
|
||||
```
|
||||
</div>
|
||||
|
||||
|
||||
### Exercice {-}
|
||||
|
||||
Écrivez un programme filtrant un appel système, à l'aide de `seccomp` :
|
||||
|
||||
<div lang="en-US">
|
||||
```
|
||||
42sh$ ./syscall_filter sleep 5
|
||||
sleep: cannot read realtime clock: Operation not permitted
|
||||
42sh$
|
||||
```
|
||||
</div>
|
||||
|
||||
Dans cet exemple, l'appel système filtré est `nanosleep(2)`.
|
113
tutorial/4/setup.md
Normal file
113
tutorial/4/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*.
|
|
@ -1,20 +1,19 @@
|
|||
---
|
||||
title: Virtualisation légère -- TP n^o^ 4
|
||||
subtitle: Linux Internals partie 1
|
||||
subtitle: Linux Internals partie 2
|
||||
author: Pierre-Olivier *nemunaire* [Mercier]{.smallcaps}
|
||||
institute: EPITA
|
||||
date: Jeudi 4 novembre 2020
|
||||
date: Jeudi 7 octobre 2021
|
||||
abstract: |
|
||||
Ce premier TP consacré aux Linux Internals va nous permettre
|
||||
d'appréhender les notions de pseudos systèmes de fichiers, de
|
||||
cgroups ainsi que de capabilities.
|
||||
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}
|
||||
|
||||
Certains éléments de ce TP sont à rendre à <virli@nemunai.re> au
|
||||
plus tard le jeudi 12 novembre 2020 à 12 h 42. Consultez la
|
||||
dernière section de chaque partie pour plus d'information sur les
|
||||
éléments à rendre.
|
||||
Tous les exercices de ce TP sont à rendre à <virli@nemunai.re> au
|
||||
plus tard le jeudi 19 novembre 2020 à 12 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 à
|
||||
|
|
File diff suppressed because it is too large
Load diff
116
tutorial/4/userns.md
Normal file
116
tutorial/4/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