Raft logs

Dans cet exercice, nous allons voir d’un peu plus près comment l’état du cluster est sauvegardé dans les logs générés par l’algorithme Raft. Ces logs sont crées sur le Leader des managers et répliqués sur chaque manager.

Swarm architecture

Le schéma suivant met en évidence un “Internal Distributed Data Store” utilisé pour la gestion du cluster. Il s’agit en fait des logs qui sont répliqués entre les différents managers d’un cluster Swarm.

Architecture

Création d’un swarm

si vous avez déjà créé un cluster swarm, vous pouvez passer directement à la section suivante.

Une solution simple est d’utiliser Multipass pour créer 2 machines virtuelles et mettre en place un cluster Swarm sur celles-ci.

:fire: nous utilisons Multipass dans cet exercice mais vous pouvez bien sur utiliser un autre solution afin de créer ces VMs.

Création des VMs

Une fois Multipass installé, utilisez la commande suivante pour créer 2 VMs:

for i in 1 2; do
  multipass launch -n node$i
done

Vérifier ensuite que ces VMs sont dans l’état Running:

$ multipass list 
Name                    State             IPv4             Image
node1                   Running           192.168.64.11    Ubuntu 20.04 LTS
node2                   Running           192.168.64.12    Ubuntu 20.04 LTS

Installez ensuite Docker sur chacune de ces VMs:

for i in 1 2; do
  multipass exec node$i -- /bin/bash -c "curl -sSL https://get.docker.com | sh"
done

Vérifiez ensuite que l’installation s’est déroulée correctement:

for i in 1 2; do
  multipass exec node$i -- sudo docker version
done

Avant de passer à l’étape suivante, récupérez les adresses IP de chacune de ces VMs, vous en aurez besoin dans la suite:

IP1=$(multipass info node1 | grep IPv4 | cut -d':' -f2 )
IP2=$(multipass info node2 | grep IPv4 | cut -d':' -f2 )

Initialisation du swarm

Depuis un hôte Docker qui n’est pas en mode Swarm, le répertoire /var/lib/docker/swarm est vide comme le confirme la commande suivante:

$ multipass exec node1 -- ls /var/lib/docker/swarm

Depuis node1, lancez la commande suivante afin d’initialiser le Swarm:

$ multipass exec node1 -- /bin/bash -c "sudo docker swarm init --advertise-addr $IP1"

Vous obtiendrez un résultat proche de celui ci-dessous:

Swarm initialized: current node (otqo5vn8i3n2tp03jkvgb80to) is now a manager.

To add a worker to this swarm, run the following command:

    docker swarm join --token SWMTKN-1-40uyzsm8bu99sp5vdavyknucg5snvkxfst8nc8h2r48m0pa6cw-7yvb02jk4ja7dezb6bxptvv7h 192.168.64.11:2377

To add a manager to this swarm, run 'docker swarm join-token manager' and follow the instructions.

Depuis node2 lancez la commande de join telle qu’elle est précisée par la commande précédente.

$ multipass exec node2 -- /bin/bash -c "sudo docker swarm join --token SWMTKN-1-40uyzsm8bu99sp5vdavyknucg5snvkxfst8nc8h2r48m0pa6cw-7yvb02jk4ja7dezb6bxptvv7h 192.168.64.11:2377

Listez ensuite les nodes de votre Swarm, vous devriez obtenir un résultat proche de celui ci-dessous:

$ multipass exec node1 -- sudo docker node ls
ID                            HOSTNAME   STATUS    AVAILABILITY   MANAGER STATUS   ENGINE VERSION
otqo5vn8i3n2tp03jkvgb80to *   node1      Ready     Active         Leader           20.10.5
ssr9pn2x2kbb1otcq0mn1yxac     node2      Ready     Active                          20.10.5

A partir du moment ou le daemon Docker est en mode Swarm (suite à la commande d’initialisation précédente), plusieurs éléments sont présents dans le répertoire swarm.

$ multipass exec node1 -- sudo find /var/lib/docker/swarm
/var/lib/docker/swarm
/var/lib/docker/swarm/docker-state.json
/var/lib/docker/swarm/raft
/var/lib/docker/swarm/raft/wal-v3-encrypted
/var/lib/docker/swarm/raft/wal-v3-encrypted/0.tmp
/var/lib/docker/swarm/raft/wal-v3-encrypted/0000000000000000-0000000000000000.wal
/var/lib/docker/swarm/raft/snap-v3-encrypted
/var/lib/docker/swarm/state.json
/var/lib/docker/swarm/worker
/var/lib/docker/swarm/worker/tasks.db
/var/lib/docker/swarm/certificates
/var/lib/docker/swarm/certificates/swarm-node.crt
/var/lib/docker/swarm/certificates/swarm-root-ca.crt
/var/lib/docker/swarm/certificates/swarm-node.key

Les logs sont situés dans le répertoire raft, les clés d’encryption dans le répertoire certificates.

Création d’un secret

Lancez un shell dans la VM node1:

$ multipass shell node1

Créez ensuite un secret avec la commande suivante. Celle-ci permet de créer un secret nommé passwd et contenant une chaine de caractère.

ubuntu@node1:~$ echo 'A2e5bc21' | docker secret create passwd -

Si nous listons les secrets existants sur notre swarm, seul le secret précédemment créé apparait, son contenu n’est plus visible.

ubuntu@node1:~$ docker secret ls
ID                          NAME      DRIVER    CREATED         UPDATED
mzxhj2130dvjrz4oxv1zo0tl2   passwd              8 seconds ago   8 seconds ago

Nous verrons par la suite que ce secret est en clair dans les logs de Raft.

A propos des logs de Raft

La version 1.13 de la plateforme Docker a introduit la gestion des secrets dans le contexte d’un swarm. Ce sont des information sensibles, par exemple des identifiants de connexion à des services tiers. Les secrets sont stockées en clair dans les logs utilisés par l’implémentation de l’algorithme Raft et c’est notamment pour cette raison que logs sont cryptés, afin d’assurer la confidentialité de ces informations.

Les secrets sont généralement créés par les Ops lors du lancement de l’application puis fournis au service qui en ont besoin. Ils seront alors accessibles, dans les containers du service, depuis un système de fichiers temporaire sous /run/secrets/NOM_DU_SECRET.

Décryptage des logs

Nous allons utiliser ici l’utilitaire swarm-rafttool, un binaire qui se trouve dans la librairie SwarmKit utilisée par Docker pour la gestion des clusters Swarm.

Swarm Rafttool

Afin d’éviter l’installation de cet utilitaire, nous le lançons directement depuis un container depuis un shell sur node1:

ubuntu@node1:~$ docker run --rm -ti --entrypoint='' \
  -v /var/lib/docker/swarm/:/var/lib/docker/swarm:ro \
  -v $(pwd):/tmp/ lucj/swarm-rafttool:1.0 sh

Nous pouvons alors voir les différentes options possibles:

/go # swarm-rafttool
Tool to translate and decrypt the raft logs of a swarm manager

Usage:
  swarm-rafttool [command]

Available Commands:
  decrypt       Decrypt a swarm manager's raft logs to an optional directory
  dump-wal      Display entries from the Raft log
  dump-snapshot Display entries from the latest Raft snapshot
  dump-object   Display an object from the Raft snapshot/WAL

Flags:
  -h, --help                help for swarm-rafttool
  -d, --state-dir string    State directory (default "/var/lib/swarmd")
      --unlock-key string   Unlock key, if raft logs are encrypted

Use "swarm-rafttool [command] --help" for more information about a command.

Dans la suite, nous utiliserons la command dump-wal afin de décrypter et visualiser les entrées du fichier de logs.

Décryptage

Afin de décrypter le fichier de log, nous créons un script shell qui va tout d’abord copier le fichier puis lancer le binaire swarm-rafttool sur cette copie. L’étape de copie est nécessaire car swarm-rafttool ne permet pas de décrypter les logs en cours d’usage.

Créer un fichier dump.sh dans le container lancé précédemment:

/go # cat<<'EOF' | tee dump.sh
d=$(date "+%Y%m%dT%H%M%S")
SWARM_DIR=/var/lib/docker/swarm
WORK_DIR=/tmp
DUMP_FILE=$WORK_DIR/dump-$d
STATE_DIR=$WORK_DIR/swarm-$d
cp -r $SWARM_DIR $STATE_DIR
$GOPATH/bin/swarm-rafttool dump-wal --state-dir $STATE_DIR > $DUMP_FILE
echo $DUMP_FILE
EOF

Lancez ce script et observer le contenu des logs.

/go # chmod +x ./dump.sh
/go # ./dump.sh | xargs cat

La sortie est relativement verbeuse, et peux être décomposée en plusieurs Entry. Je vous invite à examiner les premières qui sont relatives à la mise en place du Swarm..

La dernière $Entry* (ci-dessous) concerne la création du secret passwd.

Entry Index=17, Term=2, Type=EntryNormal:
id: 132335301136655
action: <
  action: STORE_ACTION_CREATE
  secret: <
    id: "mzxhj2130dvjrz4oxv1zo0tl2"
    meta: <
      version: <
        index: 16
      >
      created_at: <
        seconds: 1616446789
        nanos: 117813739
      >
      updated_at: <
        seconds: 1616446789
        nanos: 117813739
      >
    >
    spec: <
      annotations: <
        name: "passwd"
      >
      data: "A2e5bc21\n"
    >
  >
>

Comme nous pouvons le voir, le contenu du secret est en clair dans le log. L’encryption des logs est donc obligatoire pour préserver la sécurité de ces informations sensibles.

Sortez ensuite du container

/go # exit

Autolock

Si un manager est compromis, les logs cryptés et les clés d’encryption sont récupérables. Il est alors facile pour un hacker de décrypter les logs et d’avoir ainsi accès aux données sensibles, comme nous venons de le faire. Pour empêcher cela, un Swarm peut être locké. Une clé d’encryption est alors générée et utilisée pour encrypter les clés publique / privée (celles servant à encrypter / décrypter les logs).

Cette nouvelle clé, appelée Unlock key, doit être sauvegardée offline et fournie manuellement au daemon Docker après un restart.

Nous visualisons le contenu de la clé située dans le sous répertoire certificates.

ubuntu@node1:~$ sudo cat /var/lib/docker/swarm/certificates/swarm-node.key
-----BEGIN PRIVATE KEY-----
kek-version: 0
raft-dek: EiD7edBc+9jsI0lJEH63AMCPsOp7jMargnFZOB31PDmopg==

MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQga5k0N//QHIsAdsOd
r0VMCYUBOj9j5lX4zYYsvwpqFluhRANCAAQi0wveBqEjA7D+3GxBq85+gQMBFxF/
REgfOoHpKUvlS2Hn/jn5CeqHKjM9gu4ethvbaKUDXJWdzSvOtjRn9RsF
-----END PRIVATE KEY-----

La commande suivante met à jour le Swarm et active la fonctionnalité d’Autolock.

Note: il est également possible d’activer l’Autolock lors de la création du Swarm.

$ docker swarm update --autolock=true
Swarm updated.
To unlock a swarm manager after it restarts, run the `docker swarm unlock`
command and provide the following key:

    SWMKEY-1-/OV8LnYcw7QGhnwGAs4CIjM0v1I39biZVwSM+2E4x6U

Please remember to store this key in a password manager, since without it you
will not be able to restart the manager.

Si nous observons une nouvelle fois le contenu de la clé, nous pouvons voir qu’elle a été encryptée.

ubuntu@node1:~$ sudo cat /var/lib/docker/swarm/certificates/swarm-node.key
-----BEGIN ENCRYPTED PRIVATE KEY-----
kek-version: 17
raft-dek: CAESMINEe3+FIiQxS0MnippCDXEl+deTJF0OzE1yHQAuYTvMO1BvDj+Rp0EHPy+MfEITPxoYk2hJlimLi8TAa+J/SuNggLT99wjm3N/s

MIHeMEkGCSqGSIb3DQEFDTA8MBsGCSqGSIb3DQEFDDAOBAj6szwVT23QvwICCAAw
HQYJYIZIAWUDBAEqBBAMi04962zn1hHQBhDHht9cBIGQVERJNp2ol2yjkSJo1Si2
RpPZ5vp4Ukei5YdLD+O7d/+FjeK6WCvMiszNjJ2ZwBKIpCIWYKWWReVDTrfkzl+Y
RM37mFeBr6mq/PAlNRRN20vR77Zgx74fHejFlA8yMKr0EkVVWlQ5YEDdxq6DsFNx
6mEGEVeopHgMMf/fdHC1HSxydZmrj2MTnvlbUU6fOXdh
-----END ENCRYPTED PRIVATE KEY-----

Redémarage du daemon

Reperez le processus dockerd:

$ sudo systemctl restart docker

Une fois le daemon redémarré, il ne vous sera pas possible d’interagir avec celui-ci car le Swarm est vérouillé.

ubuntu@node1:~$ sudo docker node ls
Error response from daemon: Swarm is encrypted and needs to be unlocked before it can be used. Please use "docker swarm unlock" to unlock it.

Nous déverouillons alors le manager en lui précisant le token récupéré précédement:

$ docker swarm unlock
Please enter unlock key:

Vérifez ensuite que vous pouvez bien lister les nodes:

ubuntu@node1:~$ sudo docker node ls
ID                            HOSTNAME   STATUS    AVAILABILITY   MANAGER STATUS   ENGINE VERSION
otqo5vn8i3n2tp03jkvgb80to *   node1      Ready     Active         Leader           20.10.5
ssr9pn2x2kbb1otcq0mn1yxac     node2      Unknown   Active                          20.10.5

En résumé

Nous avons donné ici un aperçu des logs générés par l’algorithme de consensus Raft. Nous l’avons illustré en nous basant sur un Swarm simplement composé d’un seul manager. Vous trouverez des détails supplémentaires dans cet article.