Aller au contenu

Déployer avec Docker

Quatre images, un binaire chacune, distroless (pas de shell, nonroot, glibc) :

ImageBinaireRôle
ghcr.io/obsign/obsign-proxyobsign-proxyla passerelle
ghcr.io/obsign/obsign-ledgerobsign-ledgerscellement, ancrage, export de preuves
ghcr.io/obsign/obsign-controlobsign-controlcompilation, publication, export, console
ghcr.io/obsign/obsignobsignvérificateur hors ligne (commodité, voir plus bas)

Construites à chaque tag v* : multi-architecture (amd64/arm64), signées avec cosign (sans clé), étiquetées avec le semver et le sha source exact. Vérifiez avant d’exécuter :

Fenêtre de terminal
cosign verify ghcr.io/obsign/obsign-proxy:1.0.0 \
--certificate-identity-regexp 'github.com/obsign/obsign' \
--certificate-oidc-issuer https://token.actions.githubusercontent.com

glibc plutôt que musl statique, délibérément : les modules PKCS#11 sont chargés avec dlopen, et un binaire entièrement statique ne peut pas héberger la bibliothèque HSM d’un fournisseur.

obsign-proxy enveloppe un serveur MCP qu’il lance en processus enfant : l’image est donc livrée sans. Étendez-la :

FROM ghcr.io/obsign/obsign-proxy:1.0.0
COPY --chown=nonroot:nonroot my-mcp-server /usr/local/bin/my-mcp-server
# Config (signed bundles) is mounted, not baked: whoever can write the
# identity bundle can mint identities.
Fenêtre de terminal
docker run -d \
-v ./config:/etc/obsign:ro \
-v obsign-wal:/var/lib/obsign/wal \
-p 127.0.0.1:8080:8080 \
my-gateway \
--policy /etc/obsign/policy-bundle.json \
--trusted-keys /etc/obsign/trusted-keys.json \
--identity-bundle /etc/obsign/identity-bundle.json \
--http 0.0.0.0:8080 \
--wal /var/lib/obsign/wal \
--env prod \
-- /usr/local/bin/my-mcp-server

Le volume du WAL doit honorer fsync. La garantie de la passerelle est l’fsync avant transmission ; elle ne vaut que ce que vaut le volume qui la porte. Un système de fichiers réseau qui acquitte les écritures avant qu’elles ne soient durables (NFS en async, certains pilotes overlay) annule silencieusement la garantie. Traitez le volume du WAL comme celui d’une base de données.

Le ledger s’exécute dans un conteneur séparé, idéalement sur un hôte séparé. Le conteneur de la passerelle reçoit le volume du WAL en lecture-écriture et aucun matériel de clé ; le conteneur du ledger reçoit le WAL en lecture seule et la clé (ou le HSM), et écrit son propre volume de store. Ne donnez pas restart: always au ledger : il sort avec un code non nul en cas de divergence, précisément pour que quelqu’un soit prévenu (voir Alerter sur une divergence).

Fenêtre de terminal
docker run --rm \
-v obsign-wal:/wal:ro \
-v obsign-store:/store \
-v ./seal-seed.hex:/run/secrets/seal-seed.hex:ro \
ghcr.io/obsign/obsign-ledger \
seal --wal /wal --chain-id <chain> --store /store \
--key /run/secrets/seal-seed.hex --key-id seal-prod

Le port HTTP de la passerelle est en clair. Les jetons voyagent dans l’en-tête Authorization : publiez donc le port uniquement sur la boucle locale, jamais sur une adresse routable. Devant de vrais clients, terminez TLS dans un reverse proxy : TLS devant la passerelle.

La console n’a pas d’authentification. Publiez son port sur la boucle locale ou sur un réseau privé uniquement (l’authentification de la console relève de la couche commerciale).

Les deux utilisateurs d’exécution sont nonroot (uid 65532). Les répertoires hôtes pré-créés et montés en volumes doivent être accessibles en écriture par cet uid.

Déployer les règles sur arguments (obsign-policy/2)

Section intitulée « Déployer les règles sur arguments (obsign-policy/2) »

Le plan de contrôle émet le format de bundle /2 dès qu’un outil déclare policy_args. Une passerelle non mise à niveau refuse un bundle /2 au démarrage plutôt que d’appliquer silencieusement moins que ce que le bundle énonce. L’ordre de bascule est fixe : mettre à niveau toutes les images de passerelle d’abord, publier le premier bundle déclarant des arguments ensuite. Une flotte qui ne déclare jamais d’arguments continue de recevoir du /1 et n’a besoin de rien.

Le module PKCS#11 du fournisseur est chargé à l’exécution avec dlopen : montez le .so (et ce dont il a besoin) dans le conteneur du ledger et passez --hsm-module :

Fenêtre de terminal
docker run --rm \
-v obsign-wal:/wal:ro -v obsign-store:/store \
-v /usr/lib/softhsm:/usr/lib/softhsm:ro \
-v softhsm-tokens:/var/lib/softhsm/tokens \
-v ./hsm-pin:/run/secrets/hsm-pin:ro \
ghcr.io/obsign/obsign-ledger \
seal --wal /wal --chain-id <chain> --store /store \
--hsm-module /usr/lib/softhsm/libsofthsm2.so \
--hsm-key-label seal-prod --hsm-pin-file /run/secrets/hsm-pin \
--key-id seal-prod

L’enrôlement TPM (obsign-tpm-enroll) a besoin du gestionnaire de ressources du noyau (--device /dev/tpmrm0). C’est une cérémonie d’opérateur : exécutez-le depuis l’arbre des sources sur l’hôte à enrôler, pas depuis un conteneur à longue durée de vie.

Le registre est une commodité, pas une dépendance :

Fenêtre de terminal
# Connected side: pull by digest, save, hash.
docker pull ghcr.io/obsign/obsign-proxy@sha256:<digest>
docker save ghcr.io/obsign/obsign-proxy@sha256:<digest> -o obsign-proxy.tar
sha256sum obsign-proxy.tar > obsign-proxy.tar.sha256
# Air-gapped side: verify the hash out of band, then load.
sha256sum -c obsign-proxy.tar.sha256
docker load -i obsign-proxy.tar

Le digest voyage hors bande (il figure dans les notes de release signées) ; la vérification cosign a lieu du côté connecté, avant l’export.

L’argument d’obsign verify est que l’auditeur le compile lui-même, depuis les sources, et l’exécute sans réseau. L’image existe pour les pipelines CI (conditionnement sur le code de sortie) ; ce n’est pas le canal par lequel on remet quelque chose à un auditeur.

Fenêtre de terminal
docker compose up -d gateway console
docker compose run --rm demo # drives the gateway, seals, verifies: exit 0

L’image de démo (shell, forgeur de jetons, graine dans un fichier) est réservée à compose et n’est jamais publiée : le moyen de forger des jetons n’a rien à faire dans un artefact livré.