La signature d’images Docker sur une Registry avec Notary

La Registry Docker est un composant incontournable dès que le besoin de distribuer ses images Docker se fait sentir. Ce composant en est actuellement à sa deuxième version et reste fidèle à la philosophie Unix : “faire une chose et la faire bien”, il stocke des images Docker et le fait bien. Le problème de la signature des images afin d’en garantir la provenance et le contenu doit donc être traité par d’autres moyens.

Plusieurs solutions existent pour ajouter ce niveau de garantie. Parmis elles, il en existe une développée par les membres de la communauté open source Docker : Notary.

Notary est un mécanisme de signatures cryptographiques qui permet de certifier le contenu des images mises à disposition sur une Registry. Il permet d’éviter qu’une personne tierce altère le contenu d’une image avant qu’elle soit instanciée sur un environnement de production par exemple.

Notary n’est ni un système de contrôle d’accès, ni un outil de gestion de clés privées et certificats (Public Key Infrastructure, PKI). Il ne requiert pas non plus une PKI ou l’utilisation de TLS (HTTPS).

Distinction entre Notary et Docker Content Trust

Notary est une implémentation réalisée par la communauté Docker d’une partie de la spécification “The Update Framework” (TUF), qui décrit un système de signatures destiné à sécuriser la diffusion de logiciels. Son implémentation a été pensée pour être indépendante de la Registry Docker : il est possible d’utiliser Notary avec d’autres dépôts de fichiers, paquets, etc.

Docker Content Trust, introduit avec la version 1.8 de Docker Engine, s'appuie sur Notary pour fournir un mécanisme de certification et de validation des images provenant d’une Registry Docker.

Architecture et composants

La communauté Docker a fait en sorte de rendre l’intégration de Notary avec Docker la plus transparente possible. Après l’activation du mode Docker Content Trust, l’utilisation des commandes docker pull et docker push ne change pas mais le contenu manipulé est soumis au serveur Notary (vérification au pull et signature au push).

Un client Notary est inclus dans Docker Engine. Lorsque Docker Content Trust est activé (désactivé par défaut), Docker Engine effectue directement et de manière transparente pour l’utilisateur les appels au serveur Notary pour vérifier la validité d’une image Docker.

Exemple avec l’opération docker pull :

En revanche, les opérations de gestion des données de Notary (gestion des clés et des métadonnées, délégation de droits, ...) se font avec un client en ligne de commande spécifique : notary.

Le recours à ce client spécifique peut se justifier :

  • par la volonté de ne pas modifier le flux d’utilisation des commandes Docker.
  • par la possibilité d’utiliser Notary de manière indépendante de Docker.

Exemple pour l’opération de rotation des clés :

Activation de Docker Content Trust

L’activation de Docker Content Trust se fait via des variables d’environnement lors de l’utilisation du client Docker en ligne de commande. Elle ne modifie pas le flux de travail standard et n’ajoute pas d’opérations spécifiques.

Une fois le mode sécurisé activé, les commandes docker pull et docker push effectueront des actions supplémentaires :

  • docker pull vérifiera auprès du serveur Notary les signatures des images téléchargées depuis des Registry et refusera le pull si le tag demandé n’est pas signé ou la signature est invalide.

  • docker push tentera de signer l’image à pousser sur une Registry en cherchant dans les clés Notary disponibles localement une clé autorisée à signer des tags dans le repository visé.

Si aucune information de signature n’est enregistrée sur le serveur Notary, Docker initialisera les clés nécessaires à la signature et les publiera dans Notary.

Après activation de Docker Content Trust, toutes les opérations effectuées utiliseront ce mode. La récupération d’images non signées ne sera possible qu’après désactivation du mode.

Exemple de code pour activer le mode sécurisé dans Docker :

export DOCKER_CONTENT_TRUST=1

Docker échangera alors des informations de signature auprès du même serveur que le serveur de Registry (cas nominal dans l’offre Docker Trusted Registry de Docker Inc.).

Si le serveur Notary est accessible à une URL particulière, elle peut être fixée à l’aide d’une autre variable d’environnement :

export DOCKER_CONTENT_TRUST_SERVER=https://notaryserver:4443

Publication de la première image signée

Si ce n’est pas déjà fait, Docker Engine va créer les clés permettant de construire les métadonnées de certification dans Notary.

À la publication de l’image, Docker Engine échange avec Notary pour effectuer les opérations de signature.

Pour fournir les différentes garanties de TUF, plusieurs clés sont utilisées, certaines locales et d'autres gérées par le serveur Notary. Les images restent quant à elles stockées sur la Registry.

Quatre clés différentes interviennent dans le processus de signature :

  • La root_key : il s’agit de la clé “maître”. Son rôle est de signer les autres clés. Elle n’est utilisée qu’en cas de création de nouvelles clés ou de révocation de clés compromises. C’est la clé la plus sensible, elle doit absolument être stockée hors-ligne.

  • La targets_key : elle signe les métadonnées garantissant que l’association d’un tag à une image dans la Registry est valide.

  • La snapshot_key : elle permet de certifier l’ensemble des métadonnées signées par la targets_key et la root_key, afin de garantir l’intégrité de chaque nouvelle version de ces métadonnées.

  • La timestamp_key : elle permet d’horodater les métadonnées de snapshot afin de garantir leur fraîcheur.

Récupération d’une image certifiée

Lors de la récupération ultérieure de cette image, Docker Engine récupère auprès de Notary les dernières métadonnées du repository visé.

Docker Engine refusera d’effectuer le pull si les métadonnées de tagging récupérées ne correspondent pas à l’image proposée par la Registry ou si ces métadonnées sont absentes.

Lorsqu’un client interagit avec Notary au sujet d’un repository pour la première fois, il établit la confiance avec les clés publiques présentée par Notary (notamment la root_key). Toutes les interactions suivantes vérifieront que les nouvelles métadonnées reçues sont cohérentes avec l’état précédemment connu de celles-ci. Par exemple le client n’acceptera pas un changement de root_key non signé par la root_key précédente.

Ce modèle de confiance établie lors de la première interaction avec Notary est appelé “Trust On First Use” (TOFU).

Stockage conjoint de tags certifiés et non certifiés

Il est important de noter que la Registry n’est pas informée des données de signature : elle stocke indifféremment des tags signés et non signés. Une version signée et non signée d’un tag peuvent même cohabiter.

Un utilisateur ayant activé le mode Docker Content Trust récupérera toujours le dernier tag signé depuis la Registry. Là où un utilisateur n’ayant pas activé le mode Docker Content Trust récupérera le dernier tag pushé, qu’il soit signé ou non.

Délégation du droit de publication d’une image

TUF prévoit un mécanisme pour déléguer l’autorisation de signature à des personnes tierces. Ce mécanisme de délégation a également été implémenté dans Notary.

Le mécanisme de délégation est le suivant :

  • Si ce n’est pas déjà fait, le délégataire crée sa paire de clés privée et publique et génère une CSR (Certificate Signing Request).

  • Il fournit ensuite cette CSR à la personne qui dispose de l’autorité de certification (CA) de son choix (par exemple celle de son organisation, ou sa propre clé) pour la signer et produire un certificat x509.

  • Le propriétaire de la clé de signature du repository (qui possède la targets_key) signe et enregistre la délégation dans Notary en référençant le certificat produit précédemment.

Exemple de code pour enregistrer la délégation de signature dans Notary :

# l’utilisateur possède son certificat signé dans le dossier courant sous le nom de user.crt
notary delegation add <registry>/<repository> targets/releases user.crt --all-paths
notary publish <registry>/<repository>

Une fois cette délégation effectuée, la clé délégataire peut signer les tags du repository de la même façon que la targets_key, à la différence près que cette délégation peut être révoquée simplement par la targets_key, alors que la révocation de cette dernière nécessite la root_key.

Lorsque plusieurs utilisateurs signent des images, chaque signataire doit avoir la snapshot_key pour signer les métadonnées qu’il publie. Afin d’éviter cela, il est possible de confier la snapshot_key au serveur Notary pour qu’il signe lui même les métadonnées lors de leur publication.

Organisation de la confiance autour de Notary

Le cas simple d’organisation consiste à avoir une seule et même personne qui développe et gère les droits de signature sur une image.

Dans ce cas cet utilisateur détient à la fois la root_key, la targets_key et la snapshot_key (la timestamp_key est toujours détenue par le serveur) et signe directement ses tags avec la targets_key.

Pour mieux répondre au besoin de séparation des rôles et responsabilités au sein d’une entreprise ou d’un projet open source, il est toutefois souhaitable de répartir les clés de la manière suivante :

  • Un administrateur ayant accès à la root_key et utilisant la targets_key pour déléguer les droits de signature aux développeurs.

  • Des développeurs disposant uniquement de leurs clés permettant de signer les tags qu’ils poussent.

Comme vu précédemment, il est alors préférable de confier également la snapshot_key au serveur Notary pour ne pas avoir à la diffuser à tous les développeurs.

Take away

Docker Content Trust permet :

  • De garantir la provenance, l’intégrité et la fraîcheur de l’image associée à un tag dans une Registry Docker.

  • D’être adopté progressivement en autorisant :

    • La cohabitation de tags signés et non signés sur une Registry standard, y compris des versions signées et non signées d’un même tag
    • L'activation ou non du mode Docker Content Trust au niveau de la ligne de commande Docker

Docker Content Trust ne permet pas :

  • De contrôler l’accès à une Registry. Cet aspect est géré par le schéma d’autorisation OAuth2 configurable dans la Registry

  • De gérer une PKI

En synthèse, si vous avez besoin de certifier les images que vous utilisez, Notary est tout indiqué. L’utilisation d’un serveur Notary avec une Registry interne ou la Registry publique sera rapide à mettre en oeuvre : Son intégration dans Docker Engine et l’absence d’intervention sur la Registry permettent d’ajouter à peu de frais une couche de confiance sur votre Registry.

Dans le cas où vous souhaitez exploiter plusieurs serveurs Notary associés à plusieurs Registry ou mettre en place le mécanisme de délégation, un travail plus important devra être fourni (de routage des requêtes pour le premier cas et d’administration de Notary pour le second).

Si l’intégration “sans couture” de Notary dans le client Docker est plutôt réussie, il ne faut pas sous estimer le travail d'apprentissage nécessaire pour l'administrer de manière sécurisée et effectuer des opérations avancées (par exemple: gestion des clés, délégations). De plus, la communauté Docker a fait le choix d’adopter dans Notary la terminologie du framework TUF, il est donc nécessaire de comprendre cette terminologie et notamment le parallèle avec la terminologie Docker (repository, tag, ...).