Work on TP4

This commit is contained in:
nemunaire 2016-10-19 05:24:05 +02:00
commit 58a228ef2d
13 changed files with 694 additions and 97 deletions

View file

@ -1,13 +1,16 @@
SOURCES = tutorial.md installation.md lxc.md cgroups.md namespaces.md
TEMPLATE = ../../template.tex
SOURCES = tutorial.md mount.md namespaces.md networkns.md userns.md pidns.md clone.md seccomp.md project.md
PANDOCOPTS = --latex-engine=xelatex \
--standalone \
--normalize \
--number-sections \
-M lang=frenchb \
--smart \
-M lang=french \
-M fontsize=12pt \
-M papersize=a4paper \
--template=${TEMPLATE}
-M mainfont="Linux Libertine O" \
-M monofont="Inconsolata" \
-M sansfont="Linux Biolinum O" \
--include-in-header=../header.tex
all: tutorial.pdf

19
tutorial/4/clone.md Normal file
View file

@ -0,0 +1,19 @@
\newpage
Plus bas !
==========
## `clone(2)`
* `CLONE_NEWNS`
* `CLONE_NEWUTS`
* `CLONE_NEWIPC`
* `CLONE_NEWPID`
* `CLONE_NEWNET`
* `CLONE_NEWUSER`
* `CLONE_NEWCGROUP`
## Aller plus loin
* [](http://crosbymichael.com/creating-containers-part-1.html)

View file

@ -1,214 +0,0 @@
\newpage
# Utiliser `lxc`
Le but de cette première partie est d'appréhender la virtualisation légère au
travers d'un programme, `lxc`, qui va mettre en place pour nous tout un
environnement distinct.
## Lancer un conteneur
Avec le paquet `lxc` que nous avons précédemment installé, nous avons également
récupéré un certain nombre de *modèles* de système (souvent installés dans le
dossier `/usr/share/lxc/templates/`) : il s'agit d'une suite de commandes
(principalement des `wget`, `chroot` ou `debootstrap`) permettant d'obtenir un
système basic fonctionnel, en suivant les étapes d'installation habituelle de
la distribution.
La méthode la plus simple pour lancer un conteneur `lxc` est d'utiliser l'un de
ces modèles pour obtenir un nouveau système. On utilise pour cela la commande
`lxc-create` :
```
lxc-create --name toto_first --template debian
```
Ce modèle va créer un dossier dans `/var/lib/lxc/` (pouvant varier d'une
distribution à l'autre) portant le nom que nous avons précisé. Ce dossier va
contenir la configuration `lxc` du conteneur (`config`), la table des
partitions (`fstab`) s'il y a besoin de faire des montages particuliers et
enfin le dossier `rootfs` contenant le système en lui-même.
Une fois l'installation terminée, on peut démarrer le conteneur :
```
lxc-start --name toto_first
```
`lxc` va appeler `/sbin/init` et démarrer tous les services que l'on peut
s'attendre à trouver dans n'importe quelle machine virtuelle (et même physique)
plus classique (la seule différence réside donc dans le fait que le noyau est
partagé avec l'hôte).
Généralement on lance `lxc-start` avec l'option `--daemon`, car on n'a pas
vraiment envie d'avoir un conteneur bloquant un terminal. En mode daemon, on va
utiliser la commande `lxc-console` pour nous attacher aux conteneurs. À tout
moment, nous pouvons nous détacher de la console (sans que cela n'affecte
l'état du conteneur) en pressant les touches : `^A q`.
Connectons-nous, lancons quelques commandes puis éteignons la machine en
lançant la commande `poweroff` dans le conteneur. Il est également possible de
lancer la commande `lxc-stop --name toto_first` dans un autre terminal, depuis
la machine hôte.
## Le réseau
Le modèle *Debian*, que nous avons utilisé, préremplit un fichier de
configuration sans définir de paramètre pour le réseau. Il n'y a donc pas
d'interface dans le conteneur pour le connecter :
```
lxc.network.type = empty
```
Un excellent article détaillant les différents types de configuration réseau
est accessible à
<https://blog.flameeyes.eu/2010/09/linux-containers-and-networking>.
N'ayant qu'une seule interface physique sur la machine et n'ayant pas accès à
la configuration des VLAN de la pièce, il ne nous reste que deux méthodes pour
obtenir du réseau dans nos conteneurs : Virtual Ethernet ou MACVLAN.
### MACVLAN
Cette méthode est la plus simple : le noyau va orienter les paquets en fonction
de leur adresse MAC de destination. Le conteneur sera donc comme une machine
supplémentaire sur le réseau.
Modifions notre fichier de configuration afin qu'il ressemble à quelque chose
comme :
```
lxc.network.type = macvlan
lxc.network.macvlan.mode = bridge
lxc.network.flags = up
lxc.network.link = eth0
```
Après avoir démarré le conteneur, il devrait avoir obtenu une IP du serveur
DHCP de l'école. L'inconvénient dans cette configuration est qu'il faille un
client netsoul dans chaque conteneur, puisque chacun est considéré comme une
machine différente aux yeux du routeur.
### Virtual Ethernet
Virtual Ethernet est la configuration la moins optimale, mais sans doute la
plus flexible.
Voici un extrait de configuration correspondant au paramétrage d'une interface
virtuelle pour un conteneur donné :
```
lxc.network.type = veth
lxc.network.ipv4 = 172.23.42.2/24
lxc.network.flags = up
```
Dans cette situation, au démarrage du conteneur, `lxc` va créer une interface
veth, avec un côté placé dans la machine hôte et l'autre côté placé dans le
conteneur. `lxc` configure l'interface dans le conteneur, il nous appartient
ensuite de configurer la machine hôte.
Commençons par attribuer une IP à cette nouvelle interface, en adaptant à votre
identifiant d'interface :
```
ip addr add 172.23.42.1/24 dev vethYJWD6R
```
À partir de là, nous devrions pouvoir pinger notre conteneur depuis notre
machine hôte : `ping 172.23.42.2`.
Notre conteneur ne peut cependant pas encore accéder à Internet. Pour cela, la
machine hôte doit faire office de routeur et donc router les paquets d'un
réseau à l'autre : en l'occurence, du réseau 172.23.42.1 vers Internet
via 10.0.0.0/8, le réseau de l'école.
Pour que notre machine hôte route les paquets, exécuter la commande :
```
sysctl -w net.ipv4.ip_forward=1
```
Cette variable, que nous retrouvons dans `/proc/sys/net/ipv4/ip_forward`,
indique au noyau qu'il peut faire passer les paquets réseau d'une interface à
l'autre. Sans plus de directives, les paquets vont conserver leur adresse
source (172.23.42.2 pour les paquets en provenance du conteneur). Cette adresse
est une adresse privée, non routable sur Internet, ni même par le bocal. Il
faut donc ajouter une couche de NAT/PAT pour réécrire les adresses sources
avant d'envoyer les paquets sur internet :
```
iptables -t nat -A POSTROUTING ! -o vethYJWD6R -s 172.23.42.0/24 -j MASQUERADE
```
Dernière étape, dans notre conteneur, nous devons indiquer la route à utiliser
pour accéder à internet :
```
ip route add default via 172.23.42.1
```
Nous avons maintenant internet dans notre conteneur !
## Utilisation du conteneur
### Installation de InfluxDB
```
apt-get update
apt-get install wget
wget https://s3.amazonaws.com/influxdb/influxdb_0.9.4.2_amd64.deb
dpkg -i influxdb_0.9.4.2_amd64.deb
```
### Test de l'installation
```
/opt/influxdb/influxd
```
Une fois que le service est démarré, vous devriez pouvoir accéder à l'interface
à : <http://172.23.42.2:8083/>
Créons une nouvelle base de données "metrics", elle nous servira dans la partie suivante.
## Rendu
### Configuration du conteneur
En plus des modifications que vous avez effectuées durant le TP, modifiez la
configuration du conteneur afin qu'il ne puisse pas utiliser plus que 256 MB de
RAM et 512 MB de swap.
Limitez ensuite les `capabilities(7)` de ce conteneur afin qu'il s'exécute avec
le strict minimum de droits, nécessaire au bon fonctionnement des programmes
installés.
**Rendez le fichier `config` de ce premier conteneur.** N'hésitez pas à laisser
des commentaires justifiant vos éventuels choix.
### Questions
1. Quels sont les autres types de virtualisation réseau existants ? Expliquez
en chacun une phrase leurs particularités.
1. Quel fichier de configuration devriez-vous changer pour rendre persistante la
valeur d'`ip_forward` ?
1. Dans quel langage InfluxDB a-t-il été écrit ? Quelle est la particularité
des binaires générés par ce langage ?
1. Quels sont les avantages et les inconvénients associés au linkage statique
et au linkage dynamique ? (pas forcément que dans le cadre de la
virtualisation légère).
1. J'ai utilisé la méthode *Virtual Ethernet* pour relier mes conteneurs à
Internet, via l'interface `br0`. Quelle(s) règle(s) `iptables` devrais-je
écrire sur mon hôte afin de permettre l'accès à InfluxDB depuis une autre
machine ?

22
tutorial/4/mount.md Normal file
View file

@ -0,0 +1,22 @@
\newpage
Des particularités de `mount`
=============================
## Les points de montage
## `bind`
## `shared`
## `switch_root` ou `pivot_root`
## Aller plus loin
Voici quelques articles qui valent de détour :
* [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).

View file

@ -1,17 +1,163 @@
\newpage
# Utiliser les *namespaces*
Les *namespaces*
================
## Introduction
Les espaces de noms du noyau, *namespaces*, permettent de dupliquer certaines
structures du noyau, dans le but de les isoler d'un groupe de processus à un
autre.
On en dénombre 7 depuis Linux 4.6 : `CGroup`, `IPC`, `network`, `mount`, `PID`,
`user` et `UTS`.
### `mount` *namespaces*
Depuis Linux 2.4.19.
Isole la liste des points de montage.
Chaque processus d'un namespace différent peut monter, démonter et réorganiser
à sa guise les points de montage. Une partition ne sera donc pas nécessairement
démonté après un appel à `umount(2)`, elle le sera lorsqu'elle aura
effectivement été démontée de chaque *namespace* dans lequel elle était montée.
### `UTS` *namespaces*
Depuis Linux 2.6.19.
Isole le nom de machine et son domaine NIS.
### `IPC` *namespaces*
Depuis Linux 2.6.19.
Isole les objets IPC et les files de messages POSIX.
Une fois le *namespace* attaché à un processus, il ne peut alors plus parler
qu'avec les autres processus de son *namespace*.
### `PID` *namespaces*
Depuis Linux 2.6.24.
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é ; ainsi, le premier processus de cet espace porte le numéro 1 et aura
les mêmes propriétés que le processus `init` usuel ; entre autres, si un
processus est rendu orphelin dans ce *namespace*, il devient un fils de ce
processus, et non un fils de l'`init` de l'arbre global.
### `network` *namespaces*
Depuis Linux 2.6.29.
Fourni une isolation pour toutes les ressources associées aux réseaux : les
interfaces, les piles protocolaires IPv4 et IPv6, les tables de routage,
pare-feu, ports numérotés, etc.
Une interface réseau (`eth0`, `wlan0`, ...) ne peut se trouver que dans un seul
*namespace* à la fois, mais il est possible de les déplacer.
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 (et non pas dans l'espace parent, en cas
d'imbrication).
### `user` *namespaces*
Depuis Linux 3.8.
Isole la liste des utilisateurs, des groupes, leurs identifiants, les
capabilities, la racine et le trousseau de clefs du noyau.
La principale caractéristique est que les identifiants d'utilisateur et de
groupe pour un processus peuvent être différent entre l'intérieur et
l'extérieur du conteneur. Il est alors possible, alors que l'on est un simple
utilisateur à l'extérieur du *namespace*, d'avoir l'UID 0 dans le conteneur.
### `CGroup` *namespaces*
Depuis Linux 4.6.
Isole la vue de la racine des *Control Group* en la plaçant sur un
sous-groupe de l'arborescence.
Ainsi, un processus dans un `CGroup` *namespace* ne peut pas voir le contenu
des sous-groupes parents (pouvant laisser fuiter des informations sur le reste
du système). Cela peut également permettre de faciliter la migration de
processus (d'un système à un autre) : l'arborescences des cgroups n'a alors
plus d'importance car le processus ne voit que son groupe.
## Partir dans un nouveau *namespace*
De la même manière que l'on peut utiliser l'appel système `chroot(2)` depuis un
shell via la commande `chroot(1)`, la commande `unshare(1)` permet de faire le
nécessaire pour appeler l'appel système `unshare(2)`, puis, tout comme
`chroot(1)`, exécuter le programme passé en paramètre.
En fonction des options qui lui sont passées, `unshare(1)` va créer le/les
nouveaux *namespaces* et placer le processus dedans.
Par exemple, nous pouvons modifier sans crainte le nom de notre machine, si
nous sommes passé dans un autre *namespace* `UTS` :
```shell
42sh# hostname --fqdn
koala.zoo.paris
42sh# sudo unshare -u /bin/bash
bash# hostname --fqdn
koala.zoo.paris
bash# hostname lynx.zoo.paris
bash# hostname --fqdn
lynx.zoo.paris
bash# exit
42sh# hostname --fqdn
koala.zoo.paris
```
Nous avons pu ici modifier le nom de machine, sans que cela n'affecte notre
machine hôte.
Essayons maintenant avec d'autres options de notre programme pour voir les
effets produits : par exemple, comparons un `ip address` à l'extérieur et à
l'intérieur d'un `unshare -n`.
## Comparaison de *namespace*
Écrivez un script ou un programme, `cmpns`, dans le langage courant de votre
choix, permettant de déterminer si deux programmes s'exécutent dans les mêmes
*namespaces*.
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
```sh
42sh$ cmpns $(pgrep influxdb) $(pgrep init)
42sh$ ./cmpns $(pgrep influxdb) $(pgrep init)
- cgroup: differ
- ipc: differ
- mnt: differ
- net: differ
@ -21,7 +167,8 @@ choix, permettant de déterminer si deux programmes s'exécutent dans les mêmes
```
```sh
42sh$ cmpns $(pgrep init) self
42sh$ ./cmpns $(pgrep init) self
- cgroup: same
- ipc: same
- mnt: same
- net: same
@ -32,102 +179,119 @@ choix, permettant de déterminer si deux programmes s'exécutent dans les mêmes
Ici, `self` fait référence au processus actuellement exécuté.
Et pourquoi pas :
```sh
42sh$ unshare -m ./cmpns $$ self
- cgroup: same
- ipc: same
- mnt: differ
- net: same
- pid: same
- user: same
- uts: same
```
## Rejoindre un *namespace*
Dans le langage courant de votre choix, écrivez un programme : `setns`,
permettant, à la manière de `unshare(1)` et `unshare(2)`, d'utiliser `setns(2)`
via votre interpréteur.
Rejoindre un *namespace* 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/` :
Les options attendues sont :
```c
#define _GNU_SOURCE
#include <fcntl.h>
#include <sched.h>
#include <stdlib.h>
* rejoindre un *namespace* IPC : `-i`, `--ipc` ;
* rejoindre un *namespace* mount : `-m`, `--mount` ;
* rejoindre un *namespace* net : `-n`, `--net` ;
* rejoindre un *namespace* PID : `-p`, `--pid` ;
* rejoindre un *namespace* UTS : `-u`, `--uts` ;
* rejoindre un *namespace* user : `-U`, `--user`.
// ./a.out /proc/PID/ns/FILE cmd args...
### Exemples
int main(int argc, char *argv[])
{
int fd = open(argv[1], O_RDONLY);
if (fd == -1)
{
perror("open");
return EXIT_FAILURE;
}
```sh
42sh# setns /bin/bash
bash# _
if (setns(fd, 0) == -1)
{
perror("setns");
return EXIT_FAILURE;
}
execvp(argv[2], &argv[2]);
perror("execve");
return EXIT_FAILURE;
}
```
#### IPC and PID Namespaces
Dans un shell, on utilisera la commande `nsenter(1)` :
```sh
42sh# setns --ipc=/proc/42/ns/ipc -p /proc/42/ns/pid /bin/echo toto
toto
```
#### Net Namespace
```sh
42sh# setns --net=/proc/42/ns/net ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default
link/loopback 00:00:00:00:00:00 brd 00:00:00:00::00
inet 127.0.0.1/8 brd 127.255.255.255 scope lo
valid_lft forever preferred_lft
inet6 ::1/128 scope host
valid_lft forever preferred_lft forever
```
#### UTS Namespace
```sh
42sh# hostname --fqdn
koala.zoo.paris
42sh# setns --uts=/proc/42/ns/uts hostname --fqdn
lynx.zoo.paris
```shell
42sh# nsenter --uts=/proc/42/ns/uts /bin/bash
```
## My Little Container
### `docker exec`
En utilisant le langage courant de votre choix, concevez l'exécutable `mlc`,
permettant de lancer une application dans un environnement différent (comme un
`chroot`, mais sans permettre de s'en échapper) et avec des privilèges réduits.
Si vous avez bien suivi jusque là, vous avez dû comprendre qu'un `docker exec`,
n'était donc rien de plus qu'un `nsenter(1)`.
Votre solution doit créer au moins un nouveau *namespace* mount et PID.
Réécrivons, en quelques lignes de shell, la commande `docker exec` ! Pour
savoir si vous avez réussi, comparez les sorties des commandes :
Vous aurez sans doute besoin de : `clone(2)`, `capabilities(7)`, `capset(2)`, `pivot_root(2)`,
- `ip address` ;
- `hostname` ;
- `mount` ;
- `pa -aux` ;
- ...
### Exemples
```sh
42sh# ls newroot
bin etc home usr root proc var
## Durée de vie d'un *namespace*
42sh# mlc newroot/ /bin/bash
bash# ls ../../../
bin etc home usr root proc var
Le noyau tient à jour un compteur de référence pour chaque *namespace*. Dès
qu'une référence tombe à 0, le *namespace* est automatiquement libéré, les
points de montage sont démontés, les interfaces réseaux sont réattribués à
l'espace de noms initial, ...
bash# escape_chroot ls
bin etc home usr root proc var
Ce compteur évolue selon plusieurs critères, et principalement selon le nombre
de processus qui l'utilisent. C'est-à-dire que, la plupart du temps, le
*namespace* est libéré lorsque le dernier processus s'exécutant dedans se
termine.
bash# ls -ld /proc/[0-9]* | wc -l
2
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` :
bash# curl http://www.linuxcontainers.org/ | md5sum
0123456789abcdef
bash# ping 8.8.8.8
Operation not permitted
```shell
42sh# touch /tmp/ns/myrefns
42sh# mount --bind /proc/<PID>/ns/mount /tmp/ns/myrefns
```
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.
## Rendu
On peut très bien utiliser directement ce fichier pour obtenir un descripteur
de fichier valide vers le *namespace* (pour passer à `setns(2)`).
Pour chaque exercice de cette partie, vous pouvez rendre un seul fichier s'il
s'agit d'un script ; sinon, vous devez rendre une tarball contenant un
`Makefile` permettant de générer les éventuels exécutables et/ou un `README`
expliquant comment s'en servir.
### Faire persister un *namespace*
Vous devez donc rendre 3 fichiers : `cmpns` ou `cmpns.tar.bz2`, `setns` ou
`setns.tar.bz2` et `mlc` ou `mlc.tar.bz2`.
Il n'est pas possible de faire persister un namespace d'un reboot à l'autre.
\vspace{3em}
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.
Bon courage !
## 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/).

175
tutorial/4/networkns.md Normal file
View file

@ -0,0 +1,175 @@
\newpage
Le *namespace* `network`
========================
## 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 que l'interface de loopback :
```shell
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
```
Qui dit nouvelle pile réseau, dit également que les ports qui étaient assignés
dans l'espace principal, ne le sont plus dans le conteneur : il est donc
possible de lancer un serveur web sans qu'il entre en conflit avec celui d'un
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`.
Tout d'abord, nous allons créer un nouvel espace de nom :
```shell
42sh$ ip netns add virli
```
La technique utilisée ici pour avoir des *namespaces* nommés est la même que
celle que nous avons vu dans la première partie sur les *namespaces* : via un
`mount --bind` dans le dossier `/var/run/netns/`. Cela permet de faire
persister le namespace malgré le fait que plus aucun processus ne s'y exécute.
Maintenant que notre *namespace* est créé, voyons s'il contient des interfaces :
```sh
42sh$ ip netns exec virli ip link
```
Cette commande ne devrait vous montrer 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 :
```shell
42sh$ ip netns exec virli ip link set dev lo up
```
Si tout se passe bien, vous devriez maintenant pouvoir lancer un `ping` sur
cette interface :
```shell
42sh$ ip netns exec virli ping 127.0.0.1
```
## *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` :
```
ip link add veth0 type veth peer name veth1
```
Une interface `veth` se comporte comme un tube bidirectionnel : tout ce qui
entre d'un côté sort de l'autre et inversement. La commande précédente a donc
créé deux interfaces `veth0` et `veth1` : les paquets envoyés sur `veth0` sont
donc reçu par `veth1` et les paquets envoyés à `veth1` sont reçus par `veth0`.
Dans cette configuration, ces deux interfaces ne sont pas très utiles, mais si
l'on place l'une des deux extrêmité dans une autre *namespace* `network`, il
devient alors possible de réaliser un échange de paquet entre les deux.
Pour déplacer `veth1` dans notre *namespace* `virli` :
```shell
42sh# ip link set veth1 netns virli
```
Il ne reste plus maintenant qu'à assigner une IP à chacune des interfaces :
```shell
42sh# ip netns exec virli ip a add 10.10.10.42/24 dev veth1
42sh# ip a add 10.10.10.41/24 dev veth0
```
Testons maintenant que la communication entre les deux passe bient :
```shell
42sh# ping 10.10.10.42
- et -
42sh# ip netns exec virli ping 10.10.10.41
```
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 interface *veth* est plutôt simple et disponible
partout, mais c'est loin d'être la technique la plus rapide ou la moins
gourmande.
### VLAN
Il est possible d'attribuer juste une interface de VLAN, si l'on a switch les
supportant.
```
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
```
### MACVLAN
Lorsque l'on a pas assez de carte ethernet et que le switch ne supporte pas les
VLAN, le noyau met à disposition un routage basé sur les adresses MAC : le
MACVLAN. S'il est activé dans votre noyau, vous allez avoir le choix entre l'un
des deux modes : VEPA ou *bridge*.
#### VEPA
Dans ce mode, tous les paquets sortant sont directement envoyé sur l'interface
ethernet de sortie, sans qu'aucun routage préalable n'est été effectué. Ainsi,
si un paquet est à destination d'un des autres conteneur de la machine, c'est à
l'équipement réseau derrière la machine de rerouter le paquet vers la machine
émetrice.
Pour construire une nouvelle interface de ce type :
```
ip link add link eth0 mac0 type macvlan mode vepa
```
#### *Bridge*
À l'inverse du mode *VEPA*, les paquets sont routés selon leur adresse MAC : si
jamais une adresse MAC est connuee, le paquet est délivré à l'interface MACVLAN
correspondante ; dans le cas contraire, le paquet est envoyé sur l'interface de
sortie.
Pour construire une nouvelle interface de ce type :
```
ip link add link eth0 mac1 type macvlan mode bridge
```
## 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).

13
tutorial/4/pidns.md Normal file
View file

@ -0,0 +1,13 @@
\newpage
Le *namespace* `PID`
=====================
## Introduction
## Remonter `/proc`
## Besoin du *namespace* `mount`
## Aller plus loin

152
tutorial/4/project.md Normal file
View file

@ -0,0 +1,152 @@
\newpage
Projet et rendu
===============
## Sujet
**Ce projet, étalé sur ce TP et le TP précédent, constitue le cœur de la
notation de ce cours.**
Vous allez continuer aujourd'hui le projet qui s'étendra depuis le TP précédent
et qui consistera à réaliser la partie d'isolation de la moulinette des ACUs !
Cette semaine, il faudra faire en sorte de restreindre un groupe de processus
pour qu'il s'exécute indépendemment de votre système.
Il n'y a pas de restriction sur le langage utilisé, vous pouvez tout aussi bien
utiliser du C, du C++, du Python, 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 !
### Stage 5 : Une vraie isolation
En plus du `chroot`, assignez de nouveaux namespaces au processus que vous
allez lancer : CGroups, IPC, mount, net, PID, UTS, user.
Il est requis que le nouveau processus ne puisse pas s'échapper de ses
namespaces !
Astuce : `clone(2)`.
### Stage 6 : Empêcher les fuites d'information
Démonter tous les sytèmes de fichiers qui ne sont pas nécessaire au
fonctionnement de votre conteneur et remontez les partitions
N'oubliez pas de remonter les systèmes de fichiers pour lequel cette opération
est nécessaire pour terminer l'étape d'isolation.
Astuce : `mount(2)`.
### Stage 7 : Identification du conteneur
Maintenant que vous avez votre conteneur, personalisez-le un peu en lui donnant
un nom unique.
Astuce : `sethostname(2)`
### Stage 8 : `pivot_root`
Effectuer un `pivot_root(2)` de telle sorte qu'il ne reste plus de trace du
système de fichiers hôte.
Astuce : `pivot_root(2)`, `umount(2)`.
### Stage 9 : Bac à sable connecté
Partant d'une liste d'interfaces sur la machine hôte similaire à :
```
42sh$ ip link
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP mode DEFAULT group default qlen 1000
link/ether 90:2b:34:5e:fa:a7 brd ff:ff:ff:ff:ff:ff
```
Vous devrez pouvoir `ping` votre conteneur depuis votre hôte :
```
42sh$ ip address
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1
[...]
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
[...]
3: veth3e06cad@if82: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master docker0 state UP mode DEFAULT group default
link/ether 42:a2:a0:89:54:ef brd ff:ff:ff:ff:ff:ff
inet 10.10.10.41/24 brd 10.10.10.255 scope global veth3e06cad
valid_lft forever preferred_lft forever
42sh$ ping 10.10.10.42
PING 10.10.10.42 (10.10.10.42) 56(84) bytes of data.
64 bytes from 10.10.10.42: icmp_seq=1 ttl=56 time=3.90 ms
64 bytes from 10.10.10.42: icmp_seq=2 ttl=56 time=3.78 ms
^C
--- 10.10.10.42 ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 1003ms
rtt min/avg/max/mdev = 3.789/3.847/3.906/0.085 ms
```
Dans l'exemple ci-dessus, l'interface dans le conteneur a l'IP `10.10.10.42`,
tandis que la machine hôte a l'IP `10.10.10.41`.
Astuces : vous pouvez utiliser la `libnetlink(3)` ou même faire des appels aux
programmes `ip(8)`, `brctl(8)`, ...
<https://github.com/shemminger/iproute2/blob/master/ip/ipnetns.c>
### Stage 10 (bonus) : seccomp
Filtrez les appels systèmes de telle sorte qu'aucun programme exécuté dans
votre bac à sable ne puisse plus appeler les appels systèmes suivants :
* `nfsservctl(2)` ;
* `personality(2)` ;
* `pivot_root(2)`.
Astuce : `seccomp(2)`.
## Modalité de rendu
Un service automatique s'occupe de réceptionner vos rendus, de faire les
vérifications nécessaires et de vous envoyer un accusé de réception (ou de
rejet).
Ce service écoute sur l'adresse <virli@nemunai.re>, c'est donc à cette adresse
et exclusivement à celle-ci que vous devez envoyer vos rendus. Tout rendu
envoyé à une autre adresse et/ou non signé et/ou reçu après la correction ne
sera pas pris en compte.
## Tarball
Tous les fichiers identifiés comme étant à rendre pour ce TP sont à
placer dans une tarball (pas d'archive ZIP, RAR, ...).
Les réponses aux questions sont à regrouper dans un fichier `questions.txt` à
placer à la racine de votre rendu.
Voici une arborescence type:
```
login_x-TP4/questions.txt
login_x-TP4/chroot/escape.c
login_x-TP4/pseudofs/rev_kdb_leds.sh
login_x-TP4/pseudofs/procinfo
login_x-TP4/caps/view_caps
login_x-TP4/cgroups/monitor
login_x-TP4/cgroups/monitor_init
login_x-TP4/mymoulette/README
login_x-TP4/mymoulette/...
```

29
tutorial/4/seccomp.md Normal file
View file

@ -0,0 +1,29 @@
\newpage
Encore plus sûr ?
=================
## Introduction
Filtrage des appels systèmes.
## Avec Docker
Documentation : <https://docs.docker.com/engine/security/seccomp/>
## Avec LXC
Comme d'habitude avec LXC, il suffit d'ajouter une ligne à notre fichier de
configuration de conteneur, pointant vers un fichier de politique :
```conf
1
whitelist
103
```
```conf
lxc.seccomp = "/path/to/seccomp/profile"
```

View file

@ -1,21 +1,25 @@
% Virtualisation légère -- TP n^o^ 1
% Pierre-Olivier *nemunaire* Mercier
% Jeudi 8 octobre 2015
---
title: Virtualisation légère -- TP n^o^ 4
subtitle: Linux Internals partie 2
author: Pierre-Olivier *Nemunaire* Mercier
institute: EPITA
date: Jeudi 20 octobre 2016
...
Le but de ce premier TP est d'utiliser les commandes et les appels systèmes vu
durant le cours.
Le but de ce second TP sur les mécanismes internes du noyau va nous permettre
d'utiliser les commandes et les appels systèmes relatifs aux *namespaces* ainsi
que d'appréhender la complexité des sytèmes de fichiers.
Tous les éléments de ce TP (exercices et questions) sont à rendre à
<virli@nemunai.re> au plus tard le **mercredi 21 octobre 2015 à 23
h 42**. Consultez la dernière section de chaque partie pour plus d'information
sur les éléments à rendre. Vous pouvez placer les réponses aux questions dans
le corps du courriel ou dans un fichier texte joint.
Le projet du cours est à rendre à <virli@nemunai.re> au plus tard le dimanche
20 novembre 2016 à 23 h 42.
En tant que personnes sensibilisées à la sécurité des échanges électroniques,
vous devriez m'envoyer vos rendus signés avec votre clef PGP. Pensez à
vous devrez m'envoyer vos rendus signés avec votre clef PGP. Pensez à
[me](http://pgp.mit.edu/pks/lookup?op=vindex&search=0x842807A84573CC96) faire
signer votre clef et n'hésitez pas à
[faire signer votre clef](http://www.meetup.com/fr/Paris-certification-de-cles-PGP-et-CAcert/).
Vous pouvez utiliser l'adresse <signcheck@nemunai.re> pour savoir si vous vous
y prenez correctement.
\hypersetup{linkcolor=black}
\tableofcontents

15
tutorial/4/userns.md Normal file
View file

@ -0,0 +1,15 @@
\newpage
Le *namespace* `user`
=====================
## Introduction
## mapping inside/outside users/groups
## l'utilisateur -1 : nobody
## blabla sur le comportement vis-à-vis des autres namespaces
## Aller plus loin