Update tuto2
This commit is contained in:
parent
5f097b4221
commit
2c5317f4f9
35 changed files with 3588 additions and 472 deletions
|
@ -1,6 +1,6 @@
|
|||
include ../pandoc-opts.mk
|
||||
|
||||
SOURCES = tutorial.md oci.md runc.md linuxkit.md rendu.md
|
||||
SOURCES = tutorial.md oci.md runc.md linuxkit.md linuxkit-admin.md linuxkit-content.md rendu.md
|
||||
|
||||
|
||||
all: tutorial.pdf
|
||||
|
|
20
tutorial/docker-internals/clair-tiny.md
Normal file
20
tutorial/docker-internals/clair-tiny.md
Normal file
|
@ -0,0 +1,20 @@
|
|||
[`Clair`](https://github.com/coreos/clair/) est un projet de CoreOS, similaire
|
||||
à Trivy : il va rechercher parmi sa base de vulnérabilités lesquels concernent
|
||||
les images qu'on lui donne.
|
||||
|
||||
Contrairement à Trivy, il ne va pas tenter d'analyser les dépendances
|
||||
supplémentaires des applications métiers (Trivy est capable d'analyser les
|
||||
dépendances PHP, Python, Ruby, Go, ...).
|
||||
|
||||
La mise en œuvre de Clair est un peu plus complexe car il s'agit d'un *daemon*
|
||||
qui s'exécute en permanence, et auquel on peut transmettre via une API, nos
|
||||
images. Dans un contexte d'intégration continue, ou d'un registre d'images qui
|
||||
teste régulièrement ses images hébergées, c'est un outil idéal. Néanmoins il
|
||||
faut le configurer un minimum pour qu'il soit opérationnel. Consultez [l'annexe
|
||||
dédiée](https://virli.nemunai.re/tutorial-2-clair.pdf) si vous souhaitez opter
|
||||
pour cette solution.
|
||||
|
||||
On notera tout de même que les outils sont donnés pour générer des rapports
|
||||
HTML avec des graphiques explicites :
|
||||
|
||||

|
|
@ -1,53 +1,20 @@
|
|||
\newpage
|
||||
### Clair
|
||||
|
||||
Une vision plus Clair de la sécurité
|
||||
====================================
|
||||
|
||||
Nous avons vu, au travers de nos TPs jusqu'à présent, que Docker nous apportait
|
||||
un certain degré de sécurité d'emblée au lancement du conteneur. Cela peut sans
|
||||
doute paraître quelque peu rassurant pour la personne chargée d'administrer la
|
||||
machine hébergeant les conteneurs, car cela lui apporte des garanties quant à
|
||||
l'effort de cloisonnement mis en place.
|
||||
|
||||
Mais doit-on pour autant s'arrêter là et considérer que nous avons réglé
|
||||
l'ensemble des problématiques de sécurité liées aux conteneurs ?
|
||||
|
||||
Évidemment, non : une fois nos services lancés dans des conteneurs, il ne sont
|
||||
pas moins exposés aux bugs et autres failles applicatives ; qu'elles soient
|
||||
dans notre code ou celui d'une bibliothèque, accessible par rebond, ...
|
||||
|
||||
Il est donc primordial de ne pas laisser ses conteneurs à l'abandon une fois
|
||||
leur image créée et envoyée en production. Nos conteneurs doivent être
|
||||
regénérés sitôt que leur image de base est mise à jour (une mise à jour d'une
|
||||
image telle que Debian, Ubuntu ou Redhat n'apparaît que pour cela) ou bien
|
||||
lorsqu'un des programmes ou bibliothèques que l'on a installé ensuite.
|
||||
|
||||
Convaincu ? Cela sonne encore comme des bonnes pratiques difficile à mettre en
|
||||
œuvre, pouvant mettre en péril tout un système d'information. Pour s'en
|
||||
protéger, nous allons avoir besoin de réaliser à intervalles réguliers une
|
||||
analyse statique de nos conteneurs.
|
||||
|
||||

|
||||
|
||||
|
||||
## Clair
|
||||
|
||||
Le principal outil pour indexer et chercher des vulnérabilités est
|
||||
Un outil complet indexer et chercher des vulnérabilités est
|
||||
[`Clair`](https://github.com/coreos/clair/), du projet CoreOS. À partir des
|
||||
informations mises à disposition par les équipes de sécurités des principales
|
||||
distributions, cela alimente en continu une base de données qui sera accéder au
|
||||
moment de l'analyse.
|
||||
|
||||
L'outil se présente sous la forme d'un serveur autonome dans la récupération
|
||||
de ses données sources, auquel nous pourrons interagir au moyen d'une API :
|
||||
de ses données sources, auquel nous pourrons interagir au moyen d'une API :
|
||||
pour lui envoyer des images et lui demander une analyse. Les clients de cette
|
||||
API seront soit les registres directement, soit un programme dédié.
|
||||
|
||||

|
||||
|
||||
---
|
||||
|
||||
Commençons par lancer notre propre instance de `Clair`, à l'aide d'un
|
||||
`docker-compose.yml` :
|
||||
`docker-compose.yml` :
|
||||
|
||||
<div lang="en-US">
|
||||
```yml
|
||||
|
@ -77,10 +44,10 @@ services:
|
|||
```
|
||||
</div>
|
||||
|
||||
Prenez quelques minutes pour comprendre ce `docker-compose.yml` : notez la
|
||||
présence de la variable d'environnement `POSTGRES_PASSWORD`, non définie : ce
|
||||
Prenez quelques minutes pour comprendre ce `docker-compose.yml` : notez la
|
||||
présence de la variable d'environnement `POSTGRES_PASSWORD`, non définie : ce
|
||||
sera la variable présente dans votre environnement, au moment du
|
||||
`docker-compose up` qui sera utilisée. N'oubliez pas de la définir :
|
||||
`docker-compose up` qui sera utilisée. N'oubliez pas de la définir :
|
||||
|
||||
<div lang="en-US">
|
||||
```bash
|
||||
|
@ -99,7 +66,7 @@ pas de changer le nom d'hôte et le mot de passe pour se connecter au conteneur
|
|||
de base de données.**
|
||||
|
||||
Une fois lancé, la base nécessite d'être initialisée. L'opération peut prendre
|
||||
plusieurs minutes. Vous pouvez suivre l'avancement de l'ajout via :
|
||||
plusieurs minutes. Vous pouvez suivre l'avancement de l'ajout via :
|
||||
|
||||
<div lang="en-US">
|
||||
```bash
|
||||
|
@ -109,11 +76,11 @@ curl http://localhost:6060/v1/namespaces/debian:9/vulnerabilities?limit=10
|
|||
</div>
|
||||
|
||||
|
||||
## PAClair
|
||||
### PAClair
|
||||
|
||||
Afin de pouvoir réaliser à la demande et sans registre privé, l'analyse de
|
||||
conteneur, nous allons utiliser le programme
|
||||
[`paclair`](https://github.com/yebinama/paclair) :
|
||||
[`paclair`](https://github.com/yebinama/paclair) :
|
||||
|
||||
<div lang="en-US">
|
||||
```bash
|
||||
|
@ -121,7 +88,7 @@ pip3 install paclair
|
|||
```
|
||||
</div>
|
||||
|
||||
Il nécessite un fichier de configuration pour être utilisé, essayez :
|
||||
Il nécessite un fichier de configuration pour être utilisé, essayez :
|
||||
|
||||
<div lang="en-US">
|
||||
```yml
|
||||
|
@ -134,7 +101,7 @@ Plugins:
|
|||
</div>
|
||||
|
||||
Pour obtenir un rapport d'analyse, on commence par envoyer les couches de
|
||||
l'image à `Clair` :
|
||||
l'image à `Clair` :
|
||||
|
||||
<div lang="en-US">
|
||||
```bash
|
||||
|
@ -142,7 +109,7 @@ paclair --conf conf.yml Docker nemunaire/fic-admin push
|
|||
```
|
||||
</div>
|
||||
|
||||
Puis on lui demande la génération d'un rapport `html` :
|
||||
Puis on lui demande la génération d'un rapport `html` :
|
||||
|
||||
<div lang="en-US">
|
||||
```bash
|
||||
|
@ -150,7 +117,7 @@ paclair --conf conf.yml Docker nemunaire/fic-admin analyse --output-format html
|
|||
```
|
||||
</div>
|
||||
|
||||
Si l'on souhaite uniquement avoir des statistiques dans la console :
|
||||
Si l'on souhaite uniquement avoir des statistiques dans la console :
|
||||
|
||||
<div lang="en-US">
|
||||
```bash
|
||||
|
@ -161,10 +128,3 @@ Medium: 5
|
|||
High: 4
|
||||
```
|
||||
</div>
|
||||
|
||||
|
||||
## Exercice {-}
|
||||
|
||||
Déterminez le nombre de vulnérabilités dans les principales images officielles
|
||||
du [Docker Hub](https://hub.docker.com/explore), notamment `nginx`, `golang`,
|
||||
`redis`, ...
|
||||
|
|
11
tutorial/docker-internals/linuxkit-adlin.md
Normal file
11
tutorial/docker-internals/linuxkit-adlin.md
Normal file
|
@ -0,0 +1,11 @@
|
|||
Si vous vous rappelez du cours d'AdLin[^adlin] en début d'années, toutes les
|
||||
VMs que vous avez utilisées reposaient entièrement sur `linuxkit`. En fait,
|
||||
chaque conteneur représentait alors une machine différente : un conteneur
|
||||
postgres d'un côté, un conteneur `miniflux` directement issu du DockerHub, un
|
||||
conteneur `unbound` pour faire office de serveur DNS, et les machines clientes
|
||||
étaient de simples conteneurs exécutant un client DHCP.
|
||||
|
||||
[^adlin]: toutes les sources des machines sont dans ce dépôt :
|
||||
<https://git.nemunai.re/srs/adlin>. Les fichiers de construction
|
||||
LinuxKit sont `challenge.yml`, `server.yml`, `tuto2.yml`,
|
||||
`tuto3.yml`
|
243
tutorial/docker-internals/linuxkit-content.md
Normal file
243
tutorial/docker-internals/linuxkit-content.md
Normal file
|
@ -0,0 +1,243 @@
|
|||
## Prérequis
|
||||
|
||||
Si vous n'avez pas déjà le binaire `linuxkit`, vous pouvez télécharger ici :\
|
||||
<https://github.com/linuxkit/linuxkit/releases/latest>.
|
||||
|
||||
Notez qu'étant donné qu'il est écrit en Go, aucune dépendance n'est nécessaire
|
||||
en plus du binaire[^lollibc] ;-)
|
||||
|
||||
[^lollibc]: à condition tout de même que vous utilisiez une libc habituelle.
|
||||
|
||||
Vous aurez également besoin de QEMU pour tester vos créations.
|
||||
|
||||
|
||||
## Structure d'un fichier `linuxkit.yml`
|
||||
|
||||
Le fichier utilisé pour construire notre image se décompose en plusieurs
|
||||
parties :
|
||||
|
||||
- `kernel` : il est attendu ici une image OCI contenant le nécessaire pour
|
||||
pouvoir utiliser un noyau : l'image du noyau, ses modules et un initramfs ;
|
||||
- `init` : l'ensemble des images OCI de cette liste seront fusionnés pour
|
||||
donner naissance au *rootfs* de la machine. On n'y place normalement qu'un
|
||||
gestionnaire de conteneur, qui sera chargé de lancer chaque conteneur au bon
|
||||
moment ;
|
||||
- `onboot`, `onshutdown` et `services` : il s'agit de conteneurs qui seront
|
||||
lancés par le système disponible dans l'`init`, au bon moment. Les conteneurs
|
||||
indiqués dans `onboot` seront lancés **séquentiellement** au démarrage de la
|
||||
machine, ceux dans `onshutdown` seront lancés lors de l'arrêt de la
|
||||
machine. Les conteneurs dans `services` seront lancés simultanément une fois
|
||||
que le dernier conteneur de `onboot` aura rendu la main ;
|
||||
- `files` : des fichiers supplémentaires à placer dans le rootfs.
|
||||
|
||||
Le format est documenté
|
||||
[ici](https://github.com/linuxkit/linuxkit/blob/master/docs/yaml.md).
|
||||
|
||||
|
||||
## Hello?
|
||||
|
||||
L'image la plus simple que l'on puisse réaliser pourrait être :
|
||||
|
||||
<div lang="en-US">
|
||||
```yaml
|
||||
kernel:
|
||||
image: linuxkit/kernel:4.14.80
|
||||
cmdline: "console=tty0 console=ttyS0"
|
||||
init:
|
||||
- linuxkit/init:c563953a2277eb73a89d89f70e4b6dcdcfebc2d1
|
||||
- linuxkit/runc:83d0edb4552b1a5df1f0976f05f442829eac38fe
|
||||
- linuxkit/containerd:326b096cd5fbab0f864e52721d036cade67599d6
|
||||
onboot:
|
||||
- name: dhcpcd
|
||||
image: linuxkit/dhcpcd:v0.6
|
||||
command: ["/sbin/dhcpcd", "--nobackground", "-f", "/dhcpcd.conf", "-1"]
|
||||
services:
|
||||
- name: getty
|
||||
image: linuxkit/getty:2eb742cd7a68e14cf50577c02f30147bc406e478
|
||||
env:
|
||||
- INSECURE=true
|
||||
trust:
|
||||
org:
|
||||
- linuxkit
|
||||
```
|
||||
</div>
|
||||
|
||||
L'image `getty` est très pratique pour déboguer, car elle permet d'avoir un
|
||||
shell sur la machine !
|
||||
|
||||
On notera cependant que, positionné dans `services`, le shell que nous
|
||||
obtiendrons sera lui-même exécuté dans un conteneur, nous n'aurons donc pas un
|
||||
accès entièrement privilégier. Pour déboguer, il faut placer cette image dans
|
||||
la partie `init`, elle sera alors lancé comme un équivalent de
|
||||
`init=/bin/sh`.[^infogetty]
|
||||
|
||||
[^infogetty]: Plus d'infos à
|
||||
<https://github.com/linuxkit/linuxkit/blob/master/pkg/getty/README.md#linuxkit-debug>
|
||||
|
||||
|
||||
## `namespaces`
|
||||
|
||||
Chaque nouveau conteneur est donc lancé dans un espace distinct où il ne pourra
|
||||
pas interagir avec les autres, ou déborder s'il s'avérait qu'il expose une
|
||||
faille exploitable.
|
||||
|
||||
Néanmoins, contrairement à Docker qui va de base nous dissocier du maximum de
|
||||
*namespaces*, `linuxkit` ne le fait pas pour les *namespaces* `net`, `ipc` et
|
||||
`uts`. C'est-à-dire que, par défaut, la pile réseau est partagée entre tous les
|
||||
conteneurs, tout comme les IPC et le nom de la machine.
|
||||
|
||||
Il reste possible de se dissocier également de ces namespaces, en précisant :
|
||||
|
||||
<div lang="en-US">
|
||||
```yaml
|
||||
- name: getty
|
||||
image: linuxkit/getty:2eb742cd7a68e14cf50577c02f30147bc406e478
|
||||
net: new
|
||||
```
|
||||
</div>
|
||||
|
||||
Ou inversement, pour persister dans le namespace initial :
|
||||
|
||||
<div lang="en-US">
|
||||
```yaml
|
||||
- name: getty
|
||||
image: linuxkit/getty:2eb742cd7a68e14cf50577c02f30147bc406e478
|
||||
pid: host
|
||||
```
|
||||
</div>
|
||||
|
||||
|
||||
### Partage de `namespace`
|
||||
|
||||
Dans le cas où l'on souhaite que deux conteneurs partagent le même *namespace*,
|
||||
il faut passer le chemin vers la structure du noyau correspondante.
|
||||
|
||||
On commence donc d'abord par créer le nouveau *namespace*, en prenant soin de
|
||||
*bind mount* la structure du noyau à un emplacement connu :
|
||||
|
||||
<div lang="en-US">
|
||||
```yaml
|
||||
- name: getty
|
||||
image: linuxkit/getty:2eb742cd7a68e14cf50577c02f30147bc406e478
|
||||
net: new
|
||||
runtime:
|
||||
bindNS: /run/netns/mynewnetns
|
||||
```
|
||||
</div>
|
||||
|
||||
À la création du *namespace* `net`, le lien vers la structure du noyau
|
||||
correspondante sera *bind mount* sur `/run/netns/synchro`. On pourra alors
|
||||
réutiliser plus tard ce chemin, en remplacement du mot clef `new` :
|
||||
|
||||
<div lang="en-US">
|
||||
```yaml
|
||||
- name: xxxx
|
||||
image: linuxkit/xxxx:v0.6
|
||||
net: /run/netns/mynewnetns
|
||||
```
|
||||
</div>
|
||||
|
||||
|
||||
## Construction et démarrage
|
||||
|
||||
Toute la puissance de `linuxkit` repose dans son système de construction et
|
||||
surtout de lancement. En effet, il peut construire des images pour un grand
|
||||
nombre de plate-forme, mais il est également possible d'utiliser les API de ces
|
||||
plates-formes pour aller y lancer des instances de cette image !
|
||||
|
||||
Pour construire l'image faite précédemment :
|
||||
|
||||
<div lang="en-US">
|
||||
```bash
|
||||
linuxkit build hello.yml
|
||||
```
|
||||
</div>
|
||||
|
||||
Cela va générer plusieurs fichiers dont un noyau (extrait de l'image de la
|
||||
partie `kernel`) ainsi qu'une image. Exactement ce qu'attend QEMU ! Pour
|
||||
tester, n'attendons pas davantage pour lancer :
|
||||
|
||||
<div lang="en-US">
|
||||
```bash
|
||||
linuxkit run qemu -gui hello
|
||||
```
|
||||
</div>
|
||||
|
||||
|
||||
## Ajouter un service
|
||||
|
||||
Maintenant que notre machine fonctionne, que nous pouvons interagir avec elle,
|
||||
tentons de se passer de l'interface de QEMU (option `-gui`) en ajoutant un
|
||||
serveur SSH aux `services` :
|
||||
|
||||
<div lang="en-US">
|
||||
```yaml
|
||||
- name: sshd
|
||||
image: linuxkit/sshd:c4bc89cf0d66733c923ab9cb46198b599eb99320
|
||||
```
|
||||
</div>
|
||||
|
||||
Comme nous n'avons définis aucun mot de passe, il va falloir utiliser une clef
|
||||
SSH pour se connecter. Voilà un bon début d'utilisation de la section `files` :
|
||||
|
||||
<div lang="en-US">
|
||||
```yaml
|
||||
- path: root/.ssh/authorized_keys
|
||||
source: ~/.ssh/id_rsa.pub
|
||||
mode: "0600"
|
||||
```
|
||||
</div>
|
||||
|
||||
Ceci va aller chercher votre clef RSA sur votre machine, pour la placer dans
|
||||
|
||||
Notons qu'il est inutile d'ajouter un *bind mount* du dossier `.ssh` ainsi
|
||||
recopié, car le *package* `linuxkit/sshd` défini déjà cela :
|
||||
[pkg/sshd/build.yml#L5](https://github.com/linuxkit/linuxkit/blob/master/pkg/sshd/build.yml#L5).
|
||||
|
||||
|
||||
## Interface réseau virtuelle
|
||||
|
||||
Lorsque l'on souhaite se dissocier d'un *namespace* `net` afin de s'isoler,
|
||||
mais que l'on veut tout de même pouvoir communiquer, il est nécessaire de créer
|
||||
une interface `virtual ethernet` :
|
||||
|
||||
<div lang="en-US">
|
||||
```yaml
|
||||
- name: db
|
||||
image: mariadb:latest
|
||||
net: new
|
||||
runtime:
|
||||
bindNS:
|
||||
net: /run/netns/db
|
||||
interfaces:
|
||||
- name: vethin-db
|
||||
add: veth
|
||||
peer: veth-db
|
||||
```
|
||||
</div>
|
||||
|
||||
|
||||
## Exercice {-}
|
||||
|
||||
Réalisez une recette `vault.yml` démarrant une instance du gestionnaire de
|
||||
secrets [Hashicorp Vault](https://www.vaultproject.io/), utilisant une [base de
|
||||
données au
|
||||
choix](https://www.vaultproject.io/docs/configuration/storage/index.html)
|
||||
(Consul, Etcd, MySQL, Cassandra, ...).
|
||||
|
||||
Au démarrage, Vault devra déjà être configuré pour parler à sa base de données,
|
||||
qui devra se trouver dans un conteneur isolé et non accessible d'internet. Il
|
||||
faudra donc établir un lien `virtual ethernet` entre les deux conteneurs ; et
|
||||
ne pas oublier de le configurer (automatiquement au *runtime*, grâce à un
|
||||
[`poststart`
|
||||
*hook*](https://github.com/opencontainers/runtime-spec/blob/master/config.md#posix-platform-hooks)
|
||||
ou bien à un conteneur issu du *package*
|
||||
[`ip`](https://github.com/linuxkit/linuxkit/tree/master/pkg/ip)).
|
||||
|
||||
Les permissions étant généralement très strictes, vous aurez sans doute besoin
|
||||
de les assouplir un peu en ajoutant des *capabilities* autorisées à vos
|
||||
conteneurs, sans quoi vos conteneurs risquent d'être tués prématurément.
|
||||
|
||||
En bonus, vous pouvez gérer la [persistance des
|
||||
données](https://github.com/linuxkit/linuxkit/blob/master/examples/swap.yml)
|
||||
stockées dans Vault.
|
|
@ -13,258 +13,3 @@ des images pour les différents fournisseurs de cloud !
|
|||
Bien entendu, au sein de ce système, tout est fait de conteneur ! Alors quand
|
||||
il s'agit de donner une IP publique utilisable par l'ensemble des conteneurs,
|
||||
il faut savoir jouer avec les *namespaces* pour arriver à ses fins !
|
||||
|
||||
Si vous vous rappelez du cours d'AdLin[^adlin] en début d'années, toutes les
|
||||
VMs que vous avez utilisées reposaient entièrement sur `linuxkit`. En fait,
|
||||
chaque conteneur représentait alors une machine différente : un conteneur
|
||||
mattermost d'un côté, un conteneur `unbound` pour faire office de serveur DNS,
|
||||
et les machines clientes étaient de simples conteneurs exécutant un client
|
||||
dhcp.
|
||||
|
||||
[^adlin]: toutes les sources des machines sont dans ce dépôt :
|
||||
<https://git.nemunai.re/?p=lectures/adlin.git;a=tree>
|
||||
|
||||
|
||||
## Prérequis
|
||||
|
||||
Si vous n'avez pas déjà le binaire `linuxkit`, vous pouvez télécharger ici :
|
||||
<https://github.com/linuxkit/linuxkit/releases/latest>.
|
||||
|
||||
Notez qu'étant donné qu'il est écrit en Go, aucune dépendance n'est nécessaire
|
||||
en plus du binaire[^lollibc] ;-)
|
||||
|
||||
[^lollibc]: à condition tout de même que vous utilisiez une libc habituelle.
|
||||
|
||||
Vous aurez également besoin de QEMU pour tester vos créations.
|
||||
|
||||
|
||||
## Structure d'un fichier `linuxkit.yml`
|
||||
|
||||
Le fichier utilisé pour construire notre image se décompose en plusieurs
|
||||
parties :
|
||||
|
||||
- `kernel` : il est attendu ici une image OCI contenant le nécessaire pour
|
||||
pouvoir utiliser un noyau : l'image du noyau, ses modules et un initramfs ;
|
||||
- `init` : l'ensemble des images OCI de cette liste seront fusionnés pour
|
||||
donner naissance au *rootfs* de la machine. On n'y place normalement qu'un
|
||||
gestionnaire de conteneur, qui sera chargé de lancer chaque conteneur au bon
|
||||
moment ;
|
||||
- `onboot`, `onshutdown` et `services` : il s'agit de conteneurs qui seront
|
||||
lancés par le système disponible dans l'`init`, au bon moment. Les conteneurs
|
||||
indiqués dans `onboot` seront lancés **séquentiellement** au démarrage de la
|
||||
machine, ceux dans `onshutdown` seront lancés lors de l'arrêt de la
|
||||
machine. Les conteneurs dans `services` seront lancés simultanément une fois
|
||||
que le dernier conteneur de `onboot` aura rendu la main ;
|
||||
- `files` : des fichiers supplémentaires à placer dans le rootfs.
|
||||
|
||||
Le format est documenté
|
||||
[ici](https://github.com/linuxkit/linuxkit/blob/master/docs/yaml.md).
|
||||
|
||||
|
||||
## Hello?
|
||||
|
||||
L'image la plus simple que l'on puisse réaliser pourrait être :
|
||||
|
||||
<div lang="en-US">
|
||||
```yaml
|
||||
kernel:
|
||||
image: linuxkit/kernel:4.14.80
|
||||
cmdline: "console=tty0 console=ttyS0"
|
||||
init:
|
||||
- linuxkit/init:c563953a2277eb73a89d89f70e4b6dcdcfebc2d1
|
||||
- linuxkit/runc:83d0edb4552b1a5df1f0976f05f442829eac38fe
|
||||
- linuxkit/containerd:326b096cd5fbab0f864e52721d036cade67599d6
|
||||
onboot:
|
||||
- name: dhcpcd
|
||||
image: linuxkit/dhcpcd:v0.6
|
||||
command: ["/sbin/dhcpcd", "--nobackground", "-f", "/dhcpcd.conf", "-1"]
|
||||
services:
|
||||
- name: getty
|
||||
image: linuxkit/getty:2eb742cd7a68e14cf50577c02f30147bc406e478
|
||||
env:
|
||||
- INSECURE=true
|
||||
trust:
|
||||
org:
|
||||
- linuxkit
|
||||
```
|
||||
</div>
|
||||
|
||||
L'image `getty` est très pratique pour déboguer, car elle permet d'avoir un
|
||||
shell sur la machine !
|
||||
|
||||
On notera cependant que, positionné dans `services`, le shell que nous
|
||||
obtiendrons sera lui-même exécuté dans un conteneur, nous n'aurons donc pas un
|
||||
accès entièrement privilégier. Pour déboguer, il faut placer cette image dans
|
||||
la partie `init`, elle sera alors lancé comme un équivalent de
|
||||
`init=/bin/sh`.[^infogetty]
|
||||
|
||||
[^infogetty]: Plus d'infos à
|
||||
<https://github.com/linuxkit/linuxkit/blob/master/pkg/getty/README.md#linuxkit-debug>
|
||||
|
||||
|
||||
## `namespaces`
|
||||
|
||||
Chaque nouveau conteneur est donc lancé dans un espace distinct où il ne pourra
|
||||
pas interagir avec les autres, ou déborder s'il s'avérait qu'il expose une
|
||||
faille exploitable.
|
||||
|
||||
Néanmoins, contrairement à Docker qui va de base nous dissocier du maximum de
|
||||
*namespaces*, `linuxkit` ne le fait pas pour les *namespaces* `net`, `ipc` et
|
||||
`uts`. C'est-à-dire que, par défaut, la pile réseau est partagée entre tous les
|
||||
conteneurs, tout comme les IPC et le nom de la machine.
|
||||
|
||||
Il reste possible de se dissocier également de ces namespaces, en précisant :
|
||||
|
||||
<div lang="en-US">
|
||||
```yaml
|
||||
- name: getty
|
||||
image: linuxkit/getty:2eb742cd7a68e14cf50577c02f30147bc406e478
|
||||
net: new
|
||||
```
|
||||
</div>
|
||||
|
||||
Ou inversement, pour persister dans le namespace initial :
|
||||
|
||||
<div lang="en-US">
|
||||
```yaml
|
||||
- name: getty
|
||||
image: linuxkit/getty:2eb742cd7a68e14cf50577c02f30147bc406e478
|
||||
pid: host
|
||||
```
|
||||
</div>
|
||||
|
||||
|
||||
### Partage de `namespace`
|
||||
|
||||
Dans le cas où l'on souhaite que deux conteneurs partagent le même *namespace*,
|
||||
il faut passer le chemin vers la structure du noyau correspondante.
|
||||
|
||||
On commence donc d'abord par créer le nouveau *namespace*, en prenant soin de
|
||||
*bind mount* la structure du noyau à un emplacement connu :
|
||||
|
||||
<div lang="en-US">
|
||||
```yaml
|
||||
- name: getty
|
||||
image: linuxkit/getty:2eb742cd7a68e14cf50577c02f30147bc406e478
|
||||
net: new
|
||||
runtime:
|
||||
bindNS: /run/netns/mynewnetns
|
||||
```
|
||||
</div>
|
||||
|
||||
À la création du *namespace* `net`, le lien vers la structure du noyau
|
||||
correspondante sera *bind mount* sur `/run/netns/synchro`. On pourra alors
|
||||
réutiliser plus tard ce chemin, en remplacement du mot clef `new` :
|
||||
|
||||
<div lang="en-US">
|
||||
```yaml
|
||||
- name: xxxx
|
||||
image: linuxkit/xxxx:v0.6
|
||||
net: /run/netns/mynewnetns
|
||||
```
|
||||
</div>
|
||||
|
||||
|
||||
## Construction et démarrage
|
||||
|
||||
Toute la puissance de `linuxkit` repose dans son système de construction et
|
||||
surtout de lancement. En effet, il peut construire des images pour un grand
|
||||
nombre de plate-forme, mais il est également possible d'utiliser les API de ces
|
||||
plates-formes pour aller y lancer des instances de cette image !
|
||||
|
||||
Pour construire l'image faite précédemment :
|
||||
|
||||
<div lang="en-US">
|
||||
```bash
|
||||
linuxkit build hello.yml
|
||||
```
|
||||
</div>
|
||||
|
||||
Cela va générer plusieurs fichiers dont un noyau (extrait de l'image de la
|
||||
partie `kernel`) ainsi qu'une image. Exactement ce qu'attend QEMU ! Pour
|
||||
tester, n'attendons pas davantage pour lancer :
|
||||
|
||||
<div lang="en-US">
|
||||
```bash
|
||||
linuxkit run qemu -gui hello
|
||||
```
|
||||
</div>
|
||||
|
||||
|
||||
## Ajouter un service
|
||||
|
||||
Maintenant que notre machine fonctionne, que nous pouvons interagir avec elle,
|
||||
tentons de se passer de l'interface de QEMU (option `-gui`) en ajoutant un
|
||||
serveur SSH aux `services` :
|
||||
|
||||
<div lang="en-US">
|
||||
```yaml
|
||||
- name: sshd
|
||||
image: linuxkit/sshd:c4bc89cf0d66733c923ab9cb46198b599eb99320
|
||||
```
|
||||
</div>
|
||||
|
||||
Comme nous n'avons définis aucun mot de passe, il va falloir utiliser une clef
|
||||
SSH pour se connecter. Voilà un bon début d'utilisation de la section `files` :
|
||||
|
||||
<div lang="en-US">
|
||||
```yaml
|
||||
- path: root/.ssh/authorized_keys
|
||||
source: ~/.ssh/id_rsa.pub
|
||||
mode: "0600"
|
||||
```
|
||||
</div>
|
||||
|
||||
Ceci va aller chercher votre clef RSA sur votre machine, pour la placer dans
|
||||
|
||||
Notons qu'il est inutile d'ajouter un *bind mount* du dossier `.ssh` ainsi
|
||||
recopié, car le *package* `linuxkit/sshd` défini déjà cela :
|
||||
[pkg/sshd/build.yml#L5](https://github.com/linuxkit/linuxkit/blob/master/pkg/sshd/build.yml#L5).
|
||||
|
||||
|
||||
## Interface réseau virtuelle
|
||||
|
||||
Lorsque l'on souhaite se dissocier d'un *namespace* `net` afin de s'isoler,
|
||||
mais que l'on veut tout de même pouvoir communiquer, il est nécessaire de créer
|
||||
une interface `virtual ethernet` :
|
||||
|
||||
<div lang="en-US">
|
||||
```yaml
|
||||
- name: db
|
||||
image: mariadb:latest
|
||||
net: new
|
||||
runtime:
|
||||
bindNS:
|
||||
net: /run/netns/db
|
||||
interfaces:
|
||||
- name: vethin-db
|
||||
add: veth
|
||||
peer: veth-db
|
||||
```
|
||||
</div>
|
||||
|
||||
|
||||
## Exercice {-}
|
||||
|
||||
Réalisez une recette `vault.yml` démarrant une instance du gestionnaire de
|
||||
secrets [Hashicorp Vault](https://www.vaultproject.io/), utilisant une [base de
|
||||
données au
|
||||
choix](https://www.vaultproject.io/docs/configuration/storage/index.html)
|
||||
(Consul, Etcd, MySQL, Cassandra, ...).
|
||||
|
||||
Au démarrage, Vault devra déjà être configuré pour parler à sa base de données,
|
||||
qui devra se trouver dans un conteneur isolé et non accessible d'internet. Il
|
||||
faudra donc établir un lien `virtual ethernet` entre les deux conteneurs ; et
|
||||
ne pas oublier de le configurer (automatiquement au *runtime*, grâce à un
|
||||
[`poststart`
|
||||
*hook*](https://github.com/opencontainers/runtime-spec/blob/master/config.md#posix-platform-hooks)
|
||||
ou bien à un conteneur issu du *package*
|
||||
[`ip`](https://github.com/linuxkit/linuxkit/tree/master/pkg/ip)).
|
||||
|
||||
Les permissions étant généralement très strictes, vous aurez sans doute besoin
|
||||
de les assouplir un peu en ajoutant des *capabilities* autorisées à vos
|
||||
conteneurs, sans quoi vos conteneurs risquent d'être tués prématurément.
|
||||
|
||||
En bonus, vous pouvez gérer la [persistance des
|
||||
données](https://github.com/linuxkit/linuxkit/blob/master/examples/swap.yml)
|
||||
stockées dans Vault.
|
||||
|
|
|
@ -7,7 +7,7 @@ Formée en juin 2015, l'Open Container Initiative (OCI) a pour but d'établir le
|
|||
standard commun aux programmes de contenerisation, afin d'éviter une
|
||||
fragmentation de l'écosystème.
|
||||
|
||||
Trois spécifications ont été écrites :
|
||||
Trois spécifications ont été écrites :
|
||||
|
||||
- [`runtime-spec`](https://github.com/opencontainers/runtime-spec/blob/master/spec.md#platforms): définit les paramètres du démarrage d'un conteneur ;
|
||||
- [`image-spec`](https://github.com/opencontainers/image-spec/blob/master/spec.md): définit la construction, le transport et la préparation des images ;
|
||||
|
@ -24,8 +24,8 @@ d'avoir un fichier de configuration `config.json` ainsi qu'un dossier où l'on
|
|||
peut trouver la racine de notre conteneur.
|
||||
|
||||
Le plus gros de la spécification concerne le format de ce fichier de
|
||||
configuration : il contient en effet l'ensemble des paramètres avec lesquels il
|
||||
faudra créer le conteneur : tant d'un point de vue isolement (on peut par
|
||||
configuration : il contient en effet l'ensemble des paramètres avec lesquels il
|
||||
faudra créer le conteneur : tant d'un point de vue isolement (on peut par
|
||||
exemple choisir de quel *namespace* on souhaite se dissocier, ou le(s)quel(s)
|
||||
rejoindre), quelles *capabilities* resteront disponibles, quels nouveaux points
|
||||
de montages, ... Voir [la
|
||||
|
@ -45,8 +45,8 @@ fichiers, d'une configuration ainsi qu'un index d'image optionnel.
|
|||
|
||||
Le
|
||||
[manifest](https://github.com/opencontainers/image-spec/blob/master/manifest.md)
|
||||
est toujours le point d'entrée d'une image : il référence l'emplacement où
|
||||
trouver les différents éléments : configuration et couches. Lorsqu'une même
|
||||
est toujours le point d'entrée d'une image : il référence l'emplacement où
|
||||
trouver les différents éléments : configuration et couches. Lorsqu'une même
|
||||
image a des variation en fonction de l'architecture du processeur, du système
|
||||
d'exploitation, ... dans ce cas [l'index
|
||||
d'image](https://github.com/opencontainers/image-spec/blob/master/image-index.md)
|
||||
|
@ -54,7 +54,7 @@ est utilisé pour sélectionner le bon manifest.
|
|||
|
||||
Le format des [couches de système de
|
||||
fichiers](https://github.com/opencontainers/image-spec/blob/master/layer.md)
|
||||
sont spécifiées : il est nécessaire de passer par des formats standards (comme
|
||||
sont spécifiées : il est nécessaire de passer par des formats standards (comme
|
||||
les tarballs), contenant éventuellement des fichiers et dossiers spéciaux
|
||||
contenant les modifications, suppressions, ... éventuelles de la couche
|
||||
représentée.
|
||||
|
@ -64,14 +64,14 @@ La
|
|||
contient l'ensemble des métadonnées qui serviront par exemple à construire le
|
||||
`config.json` attendu par `runc` lorsqu'il faudra lancer l'image (c'est
|
||||
là-dedans que finissent toutes les métadonnées que l'on inscrit dans nos
|
||||
`Dockerfile` : `CMD`, `VOLUME`, `PORT`, ...). On y retrouve également l'ordre
|
||||
`Dockerfile` : `CMD`, `VOLUME`, `PORT`, ...). On y retrouve également l'ordre
|
||||
des couches du système de fichiers, ainsi que l'historique de l'image.
|
||||
|
||||
|
||||
## `distribution-spec`
|
||||
|
||||
Dernière née de l'organisme, cette spécification fédère la notion de
|
||||
*registre* : une API REST sur HTTP où l'on peut récupérer des images, mais
|
||||
*registre* : une API REST sur HTTP où l'on peut récupérer des images, mais
|
||||
aussi en envoyer.
|
||||
|
||||
|
||||
|
@ -79,7 +79,7 @@ aussi en envoyer.
|
|||
|
||||
Si maintenant `docker` fait appel à un programme externe pour lancer
|
||||
effectivement nos conteneurs, c'est que l'on peut changer cette implémentation
|
||||
? la réponse dans l'article :
|
||||
? la réponse dans l'article :
|
||||
<https://ops.tips/blog/run-docker-with-forked-runc/>
|
||||
|
||||
Et `containerd` dans l'histoire ?
|
||||
|
|
BIN
tutorial/docker-internals/quay-vulns.png
Normal file
BIN
tutorial/docker-internals/quay-vulns.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 83 KiB |
|
@ -3,14 +3,14 @@
|
|||
Registres
|
||||
=========
|
||||
|
||||
**Outils nécessaires :** `curl`, `gunzip`, `jq`, `tar`.
|
||||
**Outils nécessaires :** `curl`, `gunzip`, `jq`, `tar`.
|
||||
|
||||
* * * * *
|
||||
|
||||
Nous allons appréhender le fonctionnement d'un registre OCI,
|
||||
et préparer le *rootfs* d'une image de base (Debian, Ubuntu, hello, ...) : en
|
||||
et préparer le *rootfs* d'une image de base (Debian, Ubuntu, hello, ...) : en
|
||||
nous préoccupant simplement de la couche la plus basse (qui ne contient pas de
|
||||
modification ou de suppression : chaque fichier est normal).
|
||||
modification ou de suppression : chaque fichier est normal).
|
||||
|
||||
|
||||
## Authentification
|
||||
|
@ -18,7 +18,7 @@ modification ou de suppression : chaque fichier est normal).
|
|||
L'authentification est facultative et est laissée à l'appréciation du
|
||||
fournisseur de service. Étant donné que nous allons utiliser le [Docker
|
||||
Hub](https://hub.docker.com/), le registre par défaut de `docker`, nous allons
|
||||
devoir nous plier à leur mécanisme d'authentification : chaque requête au
|
||||
devoir nous plier à leur mécanisme d'authentification : chaque requête au
|
||||
registre doit être effectuée avec un jeton, que l'on obtient en s'authentifiant
|
||||
auprès d'un service dédié. Ce service peut délivrer un jeton sans authentifier
|
||||
l'interlocuteur, en restant anonyme ; dans ce cas, on ne pourra accéder qu'aux
|
||||
|
@ -26,8 +26,8 @@ images publiques. Ça tombe bien, c'est ce qui nous intéresse aujourd'hui !
|
|||
|
||||
Il n'en reste pas moins que le jeton est forgé pour un service donné (dans
|
||||
notre cas `registry.docker.io`) et avec un objectif bien cerné (pour nous, on
|
||||
souhaite récupérer le contenu du dépôt[^quiddepot] `hello-world` :
|
||||
<span lang="en-US">`repository:hello-world:pull`</span>). Ce qui nous donne :
|
||||
souhaite récupérer le contenu du dépôt[^quiddepot] `hello-world` :
|
||||
<span lang="en-US">`repository:hello-world:pull`</span>). Ce qui nous donne :
|
||||
|
||||
[^quiddepot]: Dans un registre, les fichiers qui composent l'image forment un
|
||||
dépôt (*repository*).
|
||||
|
@ -50,7 +50,7 @@ souhaite récupérer le contenu du dépôt[^quiddepot] `hello-world` :
|
|||
C'est le `token` qu'il faudra fournir lors de nos prochaines requêtes au
|
||||
registre.
|
||||
|
||||
Avec `jq`, on peut l'extraire grâce à :
|
||||
Avec `jq`, on peut l'extraire grâce à :
|
||||
|
||||
<div lang="en-US">
|
||||
```bash
|
||||
|
@ -58,7 +58,7 @@ Avec `jq`, on peut l'extraire grâce à :
|
|||
```
|
||||
</div>
|
||||
|
||||
**Attention :** le token expire ! Pensez à le renouveler régulièrement.
|
||||
**Attention :** le token expire ! Pensez à le renouveler régulièrement.
|
||||
|
||||
En cas d'erreur inexplicable, vous pouvez ajouter un `-v` à la ligne de
|
||||
commande `curl`, afin d'afficher les en-têtes. Prêtez une attention toute
|
||||
|
@ -68,7 +68,7 @@ particulière à `Www-Authenticate`.
|
|||
## Lecture de l'index d'images
|
||||
|
||||
Une fois en possession de notre jeton, nous pouvons maintenant demander l'index
|
||||
d'images à notre registre :
|
||||
d'images à notre registre :
|
||||
|
||||
<div lang="en-US">
|
||||
```bash
|
||||
|
@ -87,7 +87,7 @@ somme de contrôle.
|
|||
## Lecture du manifest
|
||||
|
||||
Demandons maintenant le manifest correspondant à notre matériel et à notre
|
||||
système d'exploitation :
|
||||
système d'exploitation :
|
||||
|
||||
<div lang="en-US">
|
||||
```bash
|
||||
|
@ -109,7 +109,7 @@ répertoire `blobs`, il ne s'agit en effet plus de manifest. Si les manifests
|
|||
sont toujours stockés par le registre lui-même, les blobs peuvent être délégués
|
||||
à un autre service, par exemple dans le cloud, chez Amazon S3, un CDN, etc.
|
||||
|
||||
Pour récupérer la configuration de l'image :
|
||||
Pour récupérer la configuration de l'image :
|
||||
|
||||
<div lang="en-US">
|
||||
```bash
|
||||
|
@ -120,7 +120,7 @@ curl -s --location \
|
|||
</div>
|
||||
|
||||
|
||||
Enfin, armé du `digest` de notre couche, il ne nous reste plus qu'à la demander gentiment :
|
||||
Enfin, armé du `digest` de notre couche, il ne nous reste plus qu'à la demander gentiment :
|
||||
|
||||
<div lang="en-US">
|
||||
```bash
|
||||
|
@ -134,7 +134,7 @@ wget --header "Authorization: Bearer ${TOKEN}" \
|
|||
|
||||
Le type indiqué par le manifest pour cette couche était
|
||||
`application/vnd.docker.image.rootfs.diff.tar.gzip`, il s'agit donc d'une
|
||||
tarball compressée au format gzip :
|
||||
tarball compressée au format gzip :
|
||||
|
||||
<div lang="en-US">
|
||||
```bash
|
||||
|
@ -143,7 +143,7 @@ tar xzf ${DL_LAYER} -C rootfs
|
|||
```
|
||||
</div>
|
||||
|
||||
Et voilà, nous avons extrait notre première image, nous devrions pouvoir :
|
||||
Et voilà, nous avons extrait notre première image, nous devrions pouvoir :
|
||||
|
||||
<div lang="en-US">
|
||||
```bash
|
||||
|
@ -156,7 +156,7 @@ Hello from Docker!
|
|||
|
||||
## Exercice {-}
|
||||
|
||||
Réalisez un script pour automatiser l'ensemble de ces étapes :
|
||||
Réalisez un script pour automatiser l'ensemble de ces étapes :
|
||||
|
||||
<div lang="en-US">
|
||||
```bash
|
||||
|
|
|
@ -26,7 +26,7 @@ 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 (vous pourriez avoir des fichiers supplémentaires,
|
||||
cela dépendra de votre avancée dans le projet) :
|
||||
cela dépendra de votre avancée dans le projet) :
|
||||
|
||||
<div lang="en-US">
|
||||
```
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
======
|
||||
|
||||
`runc` est le programme qui est responsable de la création effective du
|
||||
conteneur : c'est lui qui va mettre en place toute la machinerie, les points de
|
||||
conteneur : c'est lui qui va mettre en place toute la machinerie, les points de
|
||||
montages ou volumes, ... Attention, son rôle reste limité à la mise en place de
|
||||
l'environnement conteneurisé, ce n'est pas lui qui télécharge l'image, ni fait
|
||||
l'assemblage des couches de système de fichiers, entre autres.
|
||||
|
@ -21,17 +21,17 @@ essayer de lancer un shell `alpine` avec un volume dans notre home.
|
|||
## Prérequis
|
||||
|
||||
Vous devriez avoir le binaire `runc` ou `docker-runc`. Si ce n'est pas le cas,
|
||||
vous pouvez télécharger la dernière version :
|
||||
vous pouvez télécharger la dernière version :
|
||||
<https://github.com/opencontainers/runc/releases>. La 1.0.0-rc92 est Ok.
|
||||
|
||||
|
||||
## Extraction du rootfs
|
||||
|
||||
À l'aide du script d'extraction de registre réalisé dans le TP 3, extrayons le
|
||||
rootfs d'alpine : `library/alpine` dans le registre Docker.
|
||||
rootfs d'alpine : `library/alpine` dans le registre Docker.
|
||||
|
||||
Si vous n'avez pas eu le temps de terminer le script d'extraction, vous pouvez
|
||||
utiliser :
|
||||
utiliser :
|
||||
|
||||
<div lang="en-US">
|
||||
```bash
|
||||
|
@ -44,7 +44,7 @@ docker image save alpine | tar xv -C rootfs
|
|||
|
||||
L'écriture complète d'un fichier `config.json` pour `runc` est plutôt
|
||||
fastidieux et répétitif, nous allons donc gagner du temps et utiliser la
|
||||
commande suivante, qui nous créera un modèle que nous adapterons un peu :
|
||||
commande suivante, qui nous créera un modèle que nous adapterons un peu :
|
||||
|
||||
<div lang="en-US">
|
||||
```bash
|
||||
|
@ -52,7 +52,7 @@ runc spec
|
|||
```
|
||||
</div>
|
||||
|
||||
Pour savoir à quoi correspondent tous ces éléments, vous pouvez consulter :
|
||||
Pour savoir à quoi correspondent tous ces éléments, vous pouvez consulter :
|
||||
<https://github.com/opencontainers/runtime-spec/blob/master/config.md>
|
||||
|
||||
Nous verrons dans les prochains TP, plus en détails tout ce qui porte sur les
|
||||
|
@ -61,7 +61,7 @@ aujourd'hui.
|
|||
|
||||
## Test brut
|
||||
|
||||
Voici comment nous pouvons tester le fonctionnement de notre *bundle* :
|
||||
Voici comment nous pouvons tester le fonctionnement de notre *bundle* :
|
||||
|
||||
<div lang="en-US">
|
||||
```
|
||||
|
@ -75,7 +75,7 @@ rootfs/ config.json
|
|||
|
||||
Quelques informations sont disponibles, mais il ne faut pas s'attendre à
|
||||
retrouver tout l'écosystème de `docker` ; ici il n'y a pas de gestion des
|
||||
journaux, etc. :
|
||||
journaux, etc. :
|
||||
|
||||
<div lang="en-US">
|
||||
```bash
|
||||
|
@ -92,7 +92,7 @@ virli1 12345 running /tmp/work/runctest 2012-12-12T12:12:12.123456789Z root
|
|||
## Attacher notre `home`
|
||||
|
||||
Dans le modèle de `config.json`, il y a déjà de nombreux systèmes de fichiers
|
||||
qui sont montés. Nous pouvons les filtrer avec :
|
||||
qui sont montés. Nous pouvons les filtrer avec :
|
||||
|
||||
<div lang="en-US">
|
||||
```bash
|
||||
|
@ -110,7 +110,7 @@ qui sont montés. Nous pouvons les filtrer avec :
|
|||
</div>
|
||||
|
||||
Pour avoir notre équivalent du `-v /home:/home` de `docker`, il va donc falloir
|
||||
ajouter un élément à cette liste, demandant de *bind* :
|
||||
ajouter un élément à cette liste, demandant de *bind* :
|
||||
|
||||
<div lang="en-US">
|
||||
```json
|
||||
|
@ -143,8 +143,8 @@ stocker les photos (dossier `/srv/images`)[^chmod].
|
|||
simple pour l'instant serait d'attribuer les permissions `0777` à la
|
||||
source, temporairement.
|
||||
|
||||
Pour ce TP, considérez que vous avez réussi si vous voyez s'afficher :
|
||||
Pour ce TP, considérez que vous avez réussi si vous voyez s'afficher :
|
||||
|
||||
> `Ready, listening on :8080`
|
||||
> `Ready, listening on :8080`
|
||||
|
||||
Il faudra attendre les TP suivants pour avoir du réseau dans notre conteneur.
|
||||
|
|
12
tutorial/docker-internals/vulnerability-scan-ex.md
Normal file
12
tutorial/docker-internals/vulnerability-scan-ex.md
Normal file
|
@ -0,0 +1,12 @@
|
|||
## Exercice {-}
|
||||
|
||||
Déterminez le nombre de vulnérabilités dans les principales images officielles
|
||||
du [Docker Hub](https://hub.docker.com/explore), notamment `nginx`, `golang`,
|
||||
`redis`, ...
|
||||
|
||||
En utilisant l'outil de votre choix, exporter un rapport HTML (ou la sortie
|
||||
standard de l'outil s'il n'exporte pas en HTML) de l'image **officielle**
|
||||
contenant les vulnérabilités les plus inquiétantes. Vous placerez ce rapport
|
||||
dans un fichier `${IMAGE}:${TAG}.html` ou `${IMAGE}:${TAG}.txt`.
|
||||
|
||||
Veillez à utiliser une image __différente__ de `mysql`, utilisée en exemple.
|
259
tutorial/docker-internals/vulnerability-scan.md
Normal file
259
tutorial/docker-internals/vulnerability-scan.md
Normal file
|
@ -0,0 +1,259 @@
|
|||
\newpage
|
||||
|
||||
Analyse de vulnérabilité
|
||||
========================
|
||||
|
||||
Nous avons vu jusqu'à présent que Docker nous apportait un certain degré de
|
||||
sécurité d'emblée, au lancement de nos conteneurs. Cela peut sans doute
|
||||
paraître quelque peu rassurant pour une personne chargée d'administrer une
|
||||
machine hébergeant des conteneurs, car cela lui apporte des garanties quant à
|
||||
l'effort de cloisonnement mis en place.
|
||||
|
||||
Mais doit-on pour autant s'arrêter là et considérer que nous avons réglé
|
||||
l'ensemble des problématiques de sécurité liées aux conteneurs ?
|
||||
|
||||
Évidemment, non : une fois nos services lancés dans des conteneurs, il ne sont
|
||||
pas moins exposés aux bugs et autres failles applicatives ; qu'elles soient
|
||||
dans notre code ou celui d'une bibliothèque, accessible par rebond, ...
|
||||
|
||||
Il est donc primordial de ne pas laisser ses conteneurs à l'abandon une fois
|
||||
leur image créée et envoyée en production. Nos conteneurs doivent être
|
||||
regénérés sitôt que leur image de base est mise à jour (une mise à jour d'une
|
||||
image telle que Debian, Ubuntu ou Redhat n'apparaît que pour cela) ou bien
|
||||
lorsqu'un des programmes ou l'une des bibliothèques que l'on a installés
|
||||
ensuite est mise à jour.
|
||||
|
||||
Convaincu ? Cela sonne encore comme des bonnes pratiques difficiles à mettre en
|
||||
œuvre, pouvant mettre en péril tout un système d'information. Pour s'en
|
||||
protéger, nous allons avoir besoin de réaliser à intervalles réguliers une
|
||||
analyse statique de nos conteneurs.
|
||||
|
||||
{ width=90% }
|
||||
|
||||
Selon une étude[^dockerhubvulnerability] réalisée sur les images du Docker Hub,
|
||||
elles présenteraient en moyenne 180 vulnérabilités, beaucoup ne sont pas mises
|
||||
à jour depuis trop longtemps et les vulnérabilités ont surtout tendance à se
|
||||
propager à cause de l'usage d'une image parente pas à jour.
|
||||
|
||||
[^dockerhubvulnerability]: <https://www.enck.org/pubs/shu-codaspy17.pdf>
|
||||
|
||||
Une mesure efficace consiste à reconstruire régulièrement (et surtout
|
||||
automatiquement) les images que l'on publie sur un registre public, sans
|
||||
oublier de mettre à jour l'image de base.
|
||||
|
||||
D'ailleurs, avez-vous vérifié qu'une mise à jour de l'image `nemunaire/youp0m`
|
||||
n'était pas disponible depuis que vous avez commencé à l'utiliser ? Docker ne
|
||||
vérifie jamais si une mise à jour des images que vous avez précédemment
|
||||
téléchargées. Pensez donc régulièrement à appeler :
|
||||
|
||||
<div lang="en-US">
|
||||
```
|
||||
42sh$ docker pull IMAGE
|
||||
```
|
||||
</div>
|
||||
|
||||
|
||||
## Docker Scan
|
||||
|
||||
Pour faire face à ces problèmes de sécurité qui prennent de l'ampleur, le
|
||||
Docker Hub, dans son modèle payant, permet d'analyser régulièrement ses images,
|
||||
pour avoir une idée sur la nécessité de les reconstruire.
|
||||
|
||||
Un plugin existe pour réaliser des scans d'images présentes sur votre machine,
|
||||
à travers les données du Docker Hub. Cela nécessite d'avoir un compte Docker,
|
||||
si vous n'en avez pas, nous verrons dans la section suivante `trivy` qui permet
|
||||
de réaliser ses scans directement sur notre machine, sans passer par un
|
||||
intermédiaire.
|
||||
|
||||
#### Attention {-}
|
||||
|
||||
Par cette méthode, vous êtes limité à 10 scans par mois.
|
||||
|
||||
|
||||
### Installation du plugin
|
||||
|
||||
#### Windows et MacOS {-}
|
||||
|
||||
Avec Docker Desktop, le plugin est déjà installé. Il faut que vous vous soyez
|
||||
préalablement connecté à votre compte Docker avec la commande `docker login`.
|
||||
|
||||
#### Linux {-}
|
||||
|
||||
Comme `docker scan` est un plugin, suivant la méthode d'installation que vous
|
||||
avez suivie, il n'a pas forcément été installé. Si vous obtenez un message
|
||||
d'erreur en lançant la commande, [voici comment récupérer le plugin et
|
||||
l'installer manuellement :](https://github.com/docker/scan-cli-plugin#on-linux)
|
||||
|
||||
<div lang="en-US">
|
||||
```
|
||||
mkdir -p ~/.docker/cli-plugins
|
||||
curl https://github.com/docker/scan-cli-plugin/releases/latest/download/docker-scan_linux_amd64 \
|
||||
-L -s -S -o ~/.docker/cli-plugins/docker-scan
|
||||
chmod +x ~/.docker/cli-plugins/docker-scan
|
||||
```
|
||||
</div>
|
||||
|
||||
### Utilisation
|
||||
|
||||
Une fois le plugin installé et la licence du service acceptée, nous pouvons
|
||||
commencer notre analyse :
|
||||
|
||||
<div lang="en-US">
|
||||
```
|
||||
42sh$ docker scan nemunaire/fic-admin
|
||||
|
||||
Testing nemunaire/fic-admin...
|
||||
|
||||
Package manager: apk
|
||||
Project name: docker-image|nemunaire/fic-admin
|
||||
Docker image: nemunaire/fic-admin
|
||||
Platform: linux/amd64
|
||||
Base image: alpine:3.14.2
|
||||
|
||||
✓ Tested 16 dependencies for known vulnerabilities, no vulnerable paths found.
|
||||
|
||||
According to our scan, you are currently using the most secure version of the selected base image
|
||||
```
|
||||
</div>
|
||||
|
||||
<div lang="en-US">
|
||||
```
|
||||
$ docker scan mysql
|
||||
|
||||
Testing mysql...
|
||||
|
||||
✗ Low severity vulnerability found in util-linux/libuuid1
|
||||
[...]
|
||||
|
||||
✗ High severity vulnerability found in gcc-8/libstdc++6
|
||||
Description: Insufficient Entropy
|
||||
Info: https://snyk.io/vuln/SNYK-DEBIAN10-GCC8-469413
|
||||
Introduced through: apt@1.8.2.3, mysql-community/mysql-community-client@8.0.26-1debian10, mysql-community/mysql-community-server-core@8.0.26-1debian10, mecab-ipadic@2.7.0-20070801+main-2.1, meta-common-packages@meta
|
||||
From: apt@1.8.2.3 > gcc-8/libstdc++6@8.3.0-6
|
||||
From: mysql-community/mysql-community-client@8.0.26-1debian10 > gcc-8/libstdc++6@8.3.0-6
|
||||
From: mysql-community/mysql-community-server-core@8.0.26-1debian10 > gcc-8/libstdc++6@8.3.0-6
|
||||
and 7 more...
|
||||
Image layer: Introduced by your base image (mysql:8.0.26)
|
||||
|
||||
Package manager: deb
|
||||
Project name: docker-image|mysql
|
||||
Docker image: mysql
|
||||
Platform: linux/amd64
|
||||
Base image: mysql:8.0.26
|
||||
|
||||
Tested 135 dependencies for known vulnerabilities, found 79 vulnerabilities.
|
||||
|
||||
According to our scan, you are currently using the most secure version of the selected base image
|
||||
```
|
||||
</div>
|
||||
|
||||
Ce dernier exemple est sans appel : `mysql` est une image officielle, et sa
|
||||
dernière version à l'écriture de ses lignes contient pas moins de 79
|
||||
vulnérabilités dont 11 *high*.
|
||||
|
||||
|
||||
## Trivy
|
||||
|
||||
Le principal outil pour chercher des vulnérabilités connues dans les images
|
||||
Docker est [`trivy`](https://www.aquasec.com/products/trivy/), édité par Aqua
|
||||
security.
|
||||
|
||||
À partir des informations mises à disposition par les équipes de sécurité des
|
||||
principales distributions, `trivy` va générer un rapport, pour chacune de nos
|
||||
images, indiquant les vulnérabilités connues au sein de l'image.
|
||||
|
||||
L'outil se présente sous la forme d'un binaire ou d'une image Docker, prenant
|
||||
un certain nombre d'arguments, notamment le nom de l'image à analyser.
|
||||
|
||||
|
||||
### Utilisation
|
||||
|
||||
Tentons à nouveau d'analyser l'image `mysql` :
|
||||
|
||||
<div lang="en-US">
|
||||
```
|
||||
42sh$ docker run --rm aquasec/trivy mysql
|
||||
2021-09-22T10:27:46.509Z INFO Need to update DB
|
||||
2021-09-22T10:27:46.509Z INFO Downloading DB... 100.00% 14.41 MiB p/s 2s
|
||||
2021-09-22T10:27:56.556Z INFO Detected OS: debian
|
||||
2021-09-22T10:27:56.556Z INFO Detecting Debian vulnerabilities...
|
||||
2021-09-22T10:27:56.579Z INFO Number of language-specific files: 0
|
||||
|
||||
mysql (debian 10.10)
|
||||
====================
|
||||
Total: 158 (UNKNOWN: 5, LOW: 19, MEDIUM: 64, HIGH: 61, CRITICAL: 9)
|
||||
```
|
||||
</div>
|
||||
|
||||
Les résultats sont un peu différents qu'avec `docker scan`, mais on constate
|
||||
que l'image `mysql` contient vraiment de nombreuses vulnérabilités. Même si
|
||||
elles ne sont heureusement pas forcément exploitable directement.
|
||||
|
||||
Voyons maintenant s'il y a des différentes avec l'image `nemunaire/fic-admin` :
|
||||
|
||||
<div lang="en-US">
|
||||
```
|
||||
42sh$ docker run --rm aquasec/trivy nemunaire/fic-admin
|
||||
2021-09-22T10:29:48.091Z INFO Need to update DB
|
||||
2021-09-22T10:29:48.091Z INFO Downloading DB... 100.00% 15.98 MiB p/s 1s
|
||||
2021-09-22T10:29:51.902Z INFO Detected OS: alpine
|
||||
2021-09-22T10:29:51.902Z INFO Detecting Alpine vulnerabilities...
|
||||
2021-09-22T10:29:51.903Z INFO Number of language-specific files: 1
|
||||
2021-09-22T10:29:51.903Z INFO Detecting gobinary vulnerabilities...
|
||||
|
||||
nemunaire/fic-admin (alpine 3.14.2)
|
||||
===================================
|
||||
Total: 0 (UNKNOWN: 0, LOW: 0, MEDIUM: 0, HIGH: 0, CRITICAL: 0)
|
||||
|
||||
|
||||
srv/admin (gobinary)
|
||||
====================
|
||||
Total: 0 (UNKNOWN: 0, LOW: 0, MEDIUM: 0, HIGH: 0, CRITICAL: 0)
|
||||
```
|
||||
</div>
|
||||
|
||||
Nous pouvons remarque que Trivy, en plus de faire l'analyse statique des
|
||||
vulnérabilités de l'image, a aussi fait une analyse des dépendances du binaire
|
||||
`/srv/admin`.
|
||||
|
||||
Trivy est en effet capable de rechercher des vulnérabilités par rapport aux
|
||||
dépendances connues de certains langages : Python, PHP, Node.js, .NET, Java,
|
||||
Go, ...
|
||||
|
||||
|
||||
### Usage du cache
|
||||
|
||||
Pour éviter de surcharger les serveurs de distributions de la base de données
|
||||
de vulnérabilités, nous devrions utiliser un cache pour faire nos
|
||||
analyses. Préférez lancer `trivy` avec les options suivantes :
|
||||
|
||||
<div lang="en-US">
|
||||
```
|
||||
42sh$ docker run --rm -v /tmp/trivy-cache:/tmp/trivy/ aquasec/trivy --cache-dir /tmp/trivy
|
||||
```
|
||||
</div>
|
||||
|
||||
### Format de génération du rapport
|
||||
|
||||
Lorsque nous appelons `trivy` directement, il génère un rapport au format texte
|
||||
lisible directement dans notre terminal. Il peut être pourtant pratique de
|
||||
pouvoir l'exporter pour l'afficher dans un navigateur (par exemple pour le
|
||||
mettre à disposition des développeurs, lors d'une analyse automatique).
|
||||
|
||||
Pour ce faire, on peut ajouter les options suivantes à la ligne de commande de
|
||||
notre conteneur :
|
||||
|
||||
<div lang="en-US">
|
||||
```bash
|
||||
--quiet --format template --template "@contrib/html.tpl"
|
||||
```
|
||||
</div>
|
||||
|
||||
En redirigeant la sortie standard vers un fichier, vous pourrez l'ouvrir dans
|
||||
votre navigateur favori.
|
||||
|
||||
---
|
||||
|
||||
{ width=90% }
|
||||
|
||||
## Clair
|
Loading…
Add table
Add a link
Reference in a new issue