Transport sécurisé : configuration de bout en bout
Cette page constitue la référence complète de configuration pour connecter un client à un démon de manière sécurisée, selon trois topologies :
- Client direct vers le démon - WSS avec TLS mutuel, sans relais.
- Démon vers le relais - le démon maintient un pont sortant vers un relais désigné afin qu’il soit joignable derrière un NAT/CGNAT.
- Client vers le relais - le client atteint le daemon via ce relais, tandis que le véritable mTLS client<->daemon se termine toujours au niveau du daemon.
Pour le démarrage rapide en 60 secondes, consultez Configuration à distance (WSS) ; cette page est le guide détaillé, option par option.
Modèle mental : deux enveloppes, une frontière de confiance
Il existe deux couches TLS, et une seule d’entre elles constitue la frontière de sécurité :
- mTLS interne (la véritable frontière). TLS 1.3 uniquement, avec authentification mutuelle. Le client présente un certificat émis par le daemon ; le daemon présente son certificat feuille de serveur. Il s’agit du plan RPC. Il n’y a aucun chemin réservé au serveur / non authentifié sur celui-ci - un certificat client est toujours requis.
- TLS externe (une frontière de métadonnées). Lorsqu’un relais se trouve sur le chemin, il termine une session TLS + WebSocket externe et transmet un texte chiffré opaque. Il ne détient jamais de clé lui permettant de lire le RPC interne. Dans la topologie directe, il n’y a pas de couche externe.
Ports par défaut (tous configurables) :
| Plan | Par défaut | Config |
|---|---|---|
| Démon WSS (RPC mTLS interne) | 9781 | [wss].port |
| Point de terminaison d’inscription du daemon | 9782 | [enroll].port |
| Relais (TLS externe + WS) | 8443 | relayer --bind / [bind] |
Partout, <data_dir> désigne le répertoire de données du démon (généralement ~/.zeroclaw) et <config-dir> désigne le répertoire de configuration zerocode du client (--config-dir, généralement ~/.zeroclaw). Les fichiers de configuration ne développent pas ~ ; utilisez des chemins absolus.
Topologie 1 : client connecté directement au démon
zerocode ===== mutual-TLS WSS (TLS 1.3) =====> daemon [wss] :9781
1a. Côté démon
Activez l’écouteur WSS. Le paramètre par défaut sécurisé consiste à laisser le démon générer automatiquement sa propre autorité de certification et son certificat serveur lors du premier démarrage, afin que vous n’ayez pas à gérer manuellement les éléments TLS :
[wss]
enabled = true
# bind = "0.0.0.0" # par défaut
# port = 9781 # par défaut
# Laissez cert_path/key_path vides pour générer automatiquement un certificat serveur dans
# <data_dir>/tls/ au premier démarrage. Définissez-les uniquement pour utiliser votre propre certificat serveur.
Lors du premier démarrage avec [wss].enabled = true, le démon écrit, sous <data_dir>/tls/ (répertoire avec le mode 0700) :
| Fichier | Objectif | Mode |
|---|---|---|
ca.crt | Certificat d’autorité de certification (public) par démon | umask par défaut |
ca.key | Clé privée de l’autorité de certification (signe les certificats client) | 0600 |
server.crt | Certificat feuille du serveur WSS (SAN localhost, 127.0.0.1) | umask par défaut |
server.key | clé privée du serveur WSS | 0600 |
Les clés privées sont écrites avec les permissions 0600 ; les certificats publics utilisent le masque umask du processus. Le répertoire tls/ lui-même est en 0700.
L’autorité de certification (CA) n’est jamais renouvelée silencieusement : si ca.crt et ca.key existent, ils sont réutilisés. La durée de vie de la CA générée automatiquement est de 10 ans ; le certificat feuille du serveur est d’environ ~27 mois ; les certificats client émis ont une durée de 30 jours.
Ouvrez le port (sudo ufw allow 9781/tcp) et démarrez le démon. Vous devriez voir une ligne de journal indiquant que l’écouteur WSS est actif sur 0.0.0.0:9781.
Le client a désormais besoin d’un certificat. Il existe deux façons d’en obtenir un.
1b. Côté client - option A : enrôlement (recommandé)
L’enrôlement fournit à un client sans certificat son premier certificat via un point de terminaison authentifié par le serveur et protégé par appairage, sans gestion manuelle des certificats. Activez-le :
[enroll]
enabled = true
# bind = "0.0.0.0" # default
# port = 9782 # default
# Requires [wss] enabled and a daemon CA key (auto-generated above, or BYO+key).
# If the CA key is absent the endpoint fails closed and certs must be provisioned
# out of band.
Au démarrage, le démon affiche dans sa console/ses journaux un code d’association et une chaîne d’authentification courte (SAS). Le code est à usage unique et expire 10 minutes après sa génération - c’est le seul justificatif d’authentification au porteur pour l’émission de certificats, et il se retrouve dans les consoles et les journaux ; un code copié doit donc cesser de fonctionner peu après que l’opérateur l’a utilisé. Un code expiré est refusé et supprimé ; générez-en un nouveau depuis l’API d’association de la passerelle lorsque vous en avez besoin. Ensuite, sur le poste de travail :
# Interactif : un client sans certificat s’inscrit automatiquement lors de la première connexion.
zerocode --connect wss://<remote-host>:9781
# Ou explicitement / en mode non interactif :
zerocode --enroll --connect wss://<remote-host>:9781
zerocode demande le code d’association, génère localement une clé P-256 et un CSR localement (la clé privée ne quitte jamais l’appareil), et affiche le SAS. Vérifiez que le SAS correspond à celui affiché par le démon (cela permet de détecter une autorité de certification homme-du-milieu), et il met en cache, sous <config-dir>/tls/:
| Fichier | Objectif | Mode |
|---|---|---|
client.crt | Certificat client émis | umask par défaut |
client.key | Clé privée du client (générée localement) | 0600 |
ca.crt | Chaîne d’autorités de certification du démon, épinglée pour le plan RPC | umask par défaut |
profile.json | device_id, not_after, profil de relais mis en cache | umask par défaut |
Chaque exécution ultérieure est sans configuration (zerocode --connect wss://<remote-host>:9781, ou simplement zerocode si uri figure dans la configuration). Le certificat est renouvelé automatiquement à ~50 % de sa durée de validité (~15 jours) via la session mTLS active ; un certificat révoqué ne peut pas se renouveler automatiquement.
Valeurs par défaut du point de terminaison d’inscription : --enroll-host utilise par défaut l’hôte de --connect ; --enroll-port utilise par défaut 9782.
La première version exige intentionnellement un code d’appairage pour chaque enrôlement. Le paramètre réservé allow_unpaired_enrollment est rejeté au démarrage du démon jusqu’à ce que le client dispose d’une ancre de confiance explicite sans code, telle qu’une empreinte d’AC du démon épinglée. Cela empêche le chemin TLS d’enrôlement provisoire de se transformer en confiance aveugle à la première utilisation.
1c. Côté client - option B : certificat émis par l’opérateur
Si vous préférez générer un certificat sur le démon et le copier ailleurs :
# Sur l’hôte du démon. --out-dir écrit également un fichier ca.crt/client.crt/client.key directement utilisable.
zeroclaw security issue-client-cert --name my-laptop --out-dir /tmp/my-laptop-tls
# ajouter --force pour écraser un certificat existant pour ce nom
Copiez les trois fichiers dans le <config-dir>/tls/ du client sous les noms ca.crt, client.crt et client.key (alors zerocode --connect wss://host:9781 les trouve automatiquement), ou indiquez-les explicitement :
zerocode --connect wss://<remote-host>:9781 \
--tls-ca-cert /path/ca.crt \
--tls-client-cert /path/client.crt \
--tls-client-key /path/client.key
Configuration équivalente (pour que zerocode fonctionne tel quel) :
[connection.wss]
uri = "wss://<remote-host>:9781"
[connection.wss.tls]
ca_cert_path = "/abs/path/ca.crt"
client_cert_path = "/abs/path/client.crt"
client_key_path = "/abs/path/client.key"
Un client sans certificat qui atteint le plan WSS sans s’être enrôlé reçoit un message exploitable « inscrivez-vous d’abord » (et le démon consigne le client non migré rejeté) — jamais de blocage silencieux.
--tls-skip-verifyn’assouplit que la vérification du serveur pour un démon de développement autosigné ; le certificat client reste requis.
Topologie 2 : du démon au relais
daemon ====== outbound: register + bridge ======> relay :8443
[relay] (blind forwarder)
Le démon se connecte au relais, prouve une identité Ed25519 stable et enregistre un node-id. Les clients se connectent ensuite à ce node-id (topologie 3). Le relais ne relaie que du texte chiffré.
2a. Lancez le relais (zerorelay)
Configurez avec relay.toml (voir apps/zerorelay/relay.example.toml) ; chaque option de ligne de commande remplace la valeur correspondante du fichier. La section [admission] est rechargée à chaud lors de SIGHUP ; tout rechargement qui transformerait un relais public en admission ouverte sans jeton sans activation explicite est refusé, et la politique précédente reste active.
# relay.toml
bind = "0.0.0.0:8443"
[tls]
# Omettez cert/key pour AUTO-PROVISIONNER un certificat TLS externe dans dir lors de la première
# exécution (sans openssl). Définissez le champ sans avec les noms d’hôte et adresses IP publics du relais.
dir = "/data/tls"
sans = ["relay.example.com"]
# Ou fournissez votre propre certificat (p. ex. un certificat signé par une autorité de certification publique) :
# cert = "/etc/zerorelay/fullchain.pem"
# key = "/etc/zerorelay/privkey.pem"
[admission]
# "open" autorise tout démon signé (sous réserve de la liste deny) ; "allowlist" autorise
# uniquement les empreintes des clés publiques des démons répertoriés. La règle deny est toujours prioritaire.
mode = "open"
allow = []
deny = []
# Un relais public (hors boucle locale) DOIT contrôler l’enregistrement : définissez ici un secret partagé
# (chaque démon le présente via [relay] relay_token) ou utilisez mode = "allowlist".
# Sinon, un relais OPEN sans jeton sur un bind public refuse de démarrer, car
# n’importe quel démon sur Internet pourrait s’enregistrer et s’approprier des identifiants de nœud non revendiqués. (Un
# bind de boucle locale pour le développement local est exempté ; un relais public ouvert intentionnellement
# peut contourner cette règle avec allow_public_open = true.)
relay_token = "change-me-to-a-long-random-secret"
[limits]
max_conns_per_node = 256
idle_timeout_secs = 300
lease_ttl_secs = 300
accept_burst_per_ip = 30
accept_rate_per_ip = 10.0
connect_burst_per_node = 60
connect_rate_per_node = 20.0
Exécutez-le :
zerorelay --config /etc/zerorelay/relay.toml
# de manière équivalente, options uniquement (une liaison publique nécessite un jeton ou une liste d’autorisation, sinon le
# relay refuses to start):
zerorelay --bind 0.0.0.0:8443 --tls-san relay.example.com \
--relay-token change-me-to-a-long-random-secret
Lorsque --tls-cert/--tls-key sont omis, le relais provisionne automatiquement une CA et un certificat serveur dans le répertoire TLS (ordre de résolution : $ZERORELAY_DATA_DIR/tls, sinon $HOME/.zerorelay/tls, sinon ./zerorelay/tls) ; localhost et 127.0.0.1 figurent toujours dans les SAN. La ca.crt provisionnée automatiquement est celle à laquelle un daemon/client fait confiance pour le TLS externe du relais.
Contrôle d’accès. Le mode open, associé à un relay_token facultatif, constitue le contrôle d’accès le plus simple. Le mode allowlist s’appuie sur l’empreinte de la clé publique d’enregistrement du démon (représentation hexadécimale SHA-256 de la clé Ed25519 située dans <data_dir>/relay/registration.key du démon) ; ajoutez les empreintes à allow (et rechargez avec kill -HUP <pid>). Un identifiant de nœud est lié à la clé publique de son premier enregistrant ; une autre clé ne peut donc pas usurper un identifiant de nœud actif (elle obtient node_taken).
Docker. apps/zerorelay/Dockerfile utilise une image distroless avec CMD ["--config", "/etc/zerorelay/relay.toml"] et un HEALTHCHECK sans shell zerorelay healthcheck --addr ; compose.yaml monte un volume sur /data afin que le TLS auto-provisionné persiste. Exposez 8443.
2b. Pointez le démon vers le relais
[wss]
enabled = true # OBLIGATOIRE : le relais transmet vers l'écouteur WSS local
[relay]
enabled = true
url = "relay.example.com:8443"
# node_id : laissez-le VIDE (recommandé) pour générer automatiquement et conserver une capacité aléatoire de 128 bits
# dans <data_dir>/relay/node_id. Ne le définissez que pour épingler un identifiant spécifique.
# token = "change-me" # doit correspondre à [admission].relay_token du relais, s'il est défini
# Faites confiance au certificat EXTERNE du relais — choisissez-en UN :
relay_ca_path = "/path/to/relay/ca.crt" # faites confiance à l'autorité de certification (autosignée) du relais
# tofu = true # OU épinglez le certificat feuille du relais lors de la première utilisation
# relay_insecure = true # OU ignorez la vérification externe (développement uniquement)
# (laissez les trois options non définies pour utiliser les autorités racines publiques intégrées, pour un relais utilisant une autorité de certification publique)
[relay] nécessite l’activation de [wss] (le relais établit un pont vers 127.0.0.1:<wss.port>), et échoue en mode sécurisé si url est vide. Au démarrage, le daemon consigne l’identifiant du nœud avec l’indication “donnez ceci aux clients comme –relay-node” ; vous pouvez également le lire dans <data_dir>/relay/node_id. La clé d’enregistrement stable du daemon est créée dans <data_dir>/relay/registration.key (0600).
Priorité de confiance du certificat externe (de la plus élevée à la plus faible) : relay_insecure > relay_ca_path > un pin stocké dans <data_dir>/relay/relay_pin (explicite ou TOFU) > tofu > les racines publiques. La configuration d’une CA remplace donc un pin obsolète sans le supprimer. Avec tofu = true, l’empreinte observée du certificat feuille du relais est épinglée dans <data_dir>/relay/relay_pin, et l’enrôlement transmet ce même pin aux clients afin qu’ils épinglent le même certificat feuille.
2c. (facultatif) rotation du node-id et mTLS externe
[relay]
node_id_rotation_days = 30 # auto-rotate the auto-minted id every N days (0 = never)
La rotation génère un nouvel identifiant, l’exécute en parallèle de l’ancien pendant une période de grâce de 10 minutes afin de ne pas interrompre les clients en cours, puis retire l’ancien identifiant ; le nouvel identifiant est transmis aux clients via le canal existant lors du prochain renouvellement de leur certificat. Forcez-en une maintenant avec zeroclaw security relay-rotate-node-id (mode d’émission automatique uniquement ; un node_id épinglé ne fait jamais l’objet d’une rotation).
Pour un relais qui authentifie également les démons au niveau de la couche externe, définissez sur le relais [admission].outer_client_auth = "required" + outer_client_ca, et sur le démon [relay].outer_client_cert / outer_client_key. Cela s’ajoute au TLS externe et ne touche jamais au mTLS interne.
Topologie 3 : client vers relais vers démon
zerocode ==outer TLS+WS==> relay ==forwards ciphertext==> daemon
\________________ inner mutual-TLS (TLS 1.3) terminates here _______________/
Cela combine les topologies 1 et 2 : le client a besoin d’un certificat client interne (enrôlement, comme dans 1b) et des coordonnées du relais (adresse, node-id et confiance dans le certificat externe du relais).
3a. La voie simple : l’enrôlement inclut le profil de relais
Lorsque le démon est configuré avec [relay], sa réponse d’inscription inclut un profil de relais (relay_url, node_id et l’empreinte relay_cert_pin du certificat feuille du relais). Ainsi, une seule inscription provisionne tout :
zerocode --enroll --connect wss://<daemon-host>:9781
zerocode met en cache le certificat interne et le profil du relais dans <config-dir>/tls/profile.json. Ensuite, zerocode simple atteint le démon via le relais sans options - il connaît déjà l’adresse du relais, le node-id et le PIN.
3b. Le chemin manuel
Donnez explicitement au client les coordonnées du relais. Le certificat interne provient toujours de l’enrôlement ou de --tls-* (topologie 1) :
zerocode \
--relay relay.example.com:8443 \
--relay-node <node-id-from-daemon-log> \
--relay-ca /path/to/relay/ca.crt
# inner mTLS material: from <config-dir>/tls (after enrolling), or pass --tls-* flags
Choisissez exactement un mode de confiance pour le certificat externe du relais, comme pour le démon :
| Indicateur | Signification |
|---|---|
--relay-ca <pem> | Faire confiance à l’autorité de certification (auto-signée) du relais |
--relay-pin <sha256> | Épinglez la feuille externe du relais (généralement fournie lors de l’enrôlement) |
--relay-tofu | Faire confiance lors de la première utilisation ; enregistrer le code PIN dans <config-dir>/relay/relay_pin |
--relay-insecure | Ignorer la vérification externe (uniquement pour le développement/les certificats auto-signés) |
| (none) | Utiliser des racines publiques intégrées (relais public-CA) |
--relay-host <name> | Remplace le SAN attendu du certificat externe (par défaut, l’hôte indiqué par --relay) |
Équivalent de configuration (pour que zerocode fonctionne seul) :
[connection.wss]
relay_url = "relay.example.com:8443"
relay_node = "<node-id>"
La confiance externe du relais (
--relay-ca/--relay-pin/--relay-tofu/--relay-insecure) est fournie par les options ou le code PIN d’inscription mis en cache, et non par les clés[connection.wss].
3c. Connexion directe en priorité, avec relais de secours
Si vous fournissez au client à la fois une adresse directe et un relais, il privilégie le chemin direct et bascule vers le relais, puis effectue une nouvelle sonde et revient au chemin direct :
zerocode --connect wss://<daemon-host>:9781 \
--relay relay.example.com:8443 --relay-node <node-id>
Paramétrage (dans [connection.wss]) :
| Clé | Par défaut | Signification |
|---|---|---|
direct_attempts | 2 | Tentatives directes avant de recourir au relais |
direct_timeout_secs | 3 | Délai d’attente par tentative de connexion directe |
reprobe_secs | 30 | Fréquence de nouvelle vérification pour repasser en mode direct (0 désactive) |
En mode relais uniquement (sans --connect/uri), l’URL WSS interne est par défaut wss://127.0.0.1:9781, car le mTLS interne se termine au niveau de l’écouteur de bouclage du démon ; l’adresse du relais est uniquement la cible de connexion TCP.
Référence de configuration
Démon [wss]
| Clé | Par défaut | Description |
|---|---|---|
enabled | false | Activer l’écouteur WSS avec TLS mutuel |
bind | 0.0.0.0 | Adresse de liaison |
port | 9781 | Port d’écoute |
cert_path | (vide) | Certificat de serveur fourni par l’utilisateur ; s’il est vide, un certificat est généré automatiquement sous <data_dir>/tls/ |
key_path | (vide) | Clé de serveur fournie par l’utilisateur ; si vide, génération automatique |
Démon [wss.client_auth] (facultatif ; mTLS est toujours activé, quoi qu’il en soit)
| Clé | Par défaut | Description |
|---|---|---|
enabled | false | Utilise une autorité de certification fournie par l’utilisateur ; lorsque la valeur est false, le démon utilise son autorité de certification générée automatiquement |
ca_cert_path | (vide) | Autorité de certification PEM utilisée pour vérifier les certificats client (mode BYO) |
pinned_certs | [] | Si elle n’est pas vide, seuls les certificats clients correspondant à ces empreintes SHA-256 sont acceptés |
crl_path | (vide) | Fichier d’empreintes révoquées ; une valeur vide utilise <data_dir>/tls/revoked matérialisé par le registre |
Démon [enroll]
| Clé | Par défaut | Description |
|---|---|---|
enabled | false | Activer le point de terminaison d’inscription (nécessite [wss] + une clé CA) |
bind | 0.0.0.0 | Adresse de liaison |
port | 9782 | Port d’écoute |
allow_unpaired_enrollment | (vide) | Réservé ; les valeurs non vides sont rejetées jusqu’à ce qu’une ancre de confiance client sans code existe |
Démon [relay]
| Clé | Par défaut | Description |
|---|---|---|
enabled | false | Activer le pont relais (nécessite [wss]) |
url | (vide) | Adresse du relais host:port ; obligatoire lorsqu’il est activé |
node_id | (vide) | Si vide, génère automatiquement et conserve un identifiant de 128 bits ; renseignez-en un pour en figer un. |
token | (vide, secret) | Secret partagé présenté lors de l’inscription |
relay_ca_path | (vide) | CA au format PEM pour le certificat externe du relais ; vide, utilise les autorités racines publiques |
relay_host | (vide) | SAN attendu du certificat externe ; vide, dérivé de url |
relay_insecure | false | Ignorer la vérification du certificat externe (développement uniquement) |
tofu | false | Épingler la feuille de relais lors de la première utilisation dans <data_dir>/relay/relay_pin |
outer_client_cert | (vide) | Certificat client mTLS externe du démon pour l’admission du relais |
outer_client_key | (vide) | Clé pour outer_client_cert |
node_id_rotation_days | 0 | Renouveler automatiquement le node-id généré tous les N jours (0 = jamais) |
Relay relay.toml
| Section.key | Par défaut | Description |
|---|---|---|
bind | 0.0.0.0:8443 | Adresse d’écoute (démon + client) |
[tls].cert / .key | (auto-provisionnement) | Identité TLS externe ; omettez les deux pour un auto-provisionnement |
[tls].dir | répertoire de données /tls | Où est enregistré le certificat auto-provisionné |
[tls].sans | [] | SAN supplémentaires (localhost, 127.0.0.1 toujours inclus) |
[admission].mode | open | open or allowlist |
[admission].allow / .deny | [] | Empreintes des clés publiques du démon (le refus l’emporte) |
[admission].relay_token | (none) | Contrôle facultatif par secret partagé |
[admission].outer_client_auth | off | off / optional / required (mTLS externe) |
[admission].outer_client_ca | (none) | CA PEM pour les certificats client externes |
[admission].route_by_client_cert | false | Router selon le node-id du CN du certificat externe |
[limits].max_conns_per_node | 256 | Connexions simultanées des clients par node-id |
[limits].idle_timeout_secs | 300 | Fermer les connexions client inactives après N secondes |
[limits].lease_ttl_secs | 300 | TTL du bail communiqué lors de l’enregistrement (à titre indicatif dans la v1 : la vivacité de WebSocket est la véritable règle de nettoyage) |
[limits].accept_burst_per_ip / accept_rate_per_ip | 30 / 10.0 | Seau de jetons de négociation par adresse IP |
[limits].connect_burst_per_node / connect_rate_per_node | 60 / 20.0 | Seau de jetons de connexion par nœud |
[limits].max_pending_handshakes | 256 | Sockets ayant passé l’étape d’acceptation mais pas encore classifiées |
[limits].handshake_timeout_secs | 10 | Une seule échéance pour TLS, la mise à niveau WS et l’enregistrement signé |
[limits].max_registered_nodes | 1024 | Démons enregistrés simultanément (N+1 reçoit registry_full) |
CLI zerorelay (remplace relay.toml)
--config --bind --tls-cert --tls-key --tls-dir --tls-san (répétable) --registration-mode --allow (répétable) --deny (répétable) --relay-token --max-conns-per-node --idle-timeout-secs --lease-ttl-secs --status-file. Sous-commandes : healthcheck [--addr 127.0.0.1:8443], status --file <path>.
zerocode [connection.wss] et CLI
clé [connection.wss] | Par défaut | Surcharge via la CLI |
|---|---|---|
uri | (none) | --connect |
relay_url | (none) | --relay (nécessite --relay-node) |
relay_node | (none) | --relay-node (nécessite --relay) |
direct_attempts | 2 | - |
direct_timeout_secs | 3 | - |
reprobe_secs | 30 | - |
clé [connection.wss.tls] | Par défaut | Surcharge via la CLI |
|---|---|---|
ca_cert_path | <config-dir>/tls/ca.crt | --tls-ca-cert |
client_cert_path | <config-dir>/tls/client.crt | --tls-client-cert (clé requise) |
client_key_path | <config-dir>/tls/client.key | --tls-client-key (certificat requis) |
skip_verify | false | --tls-skip-verify |
La confiance externe du relais et l’enrôlement sont uniquement disponibles via la CLI/le cache : --relay-ca --relay-host --relay-insecure --relay-pin --relay-tofu --relay-client-cert --relay-client-key --enroll --enroll-host --enroll-port.
Organisation des fichiers
Démon <data_dir>/:
tls/ca.crt tls/ca.key per-daemon CA (key 0600)
tls/server.crt tls/server.key WSS server leaf (key 0600)
tls/ledger.db issued-cert ledger (SQLite)
tls/revoked revoked fingerprints (handshake-checked)
relay/registration.key Ed25519 relay identity (0600)
relay/node_id auto-minted node-id
relay/relay_pin pinned relay outer-leaf fingerprint (TOFU)
Client <config-dir>/ :
tls/client.crt tls/client.key client identity (key 0600)
tls/ca.crt pinned daemon CA
tls/profile.json device_id, not_after, cached relay profile
relay/relay_pin relay outer-leaf pin (--relay-tofu)
Relais <tls-dir>/ : ca.crt, server.crt, server.key (auto-provisionnés).
Mise à niveau d’un registre existant de certificats émis
tls/ledger.db contient une version de schéma. Un registre écrit par une version antérieure est reconstruit sur place à la première ouverture par le nouveau démon, en conservant chaque certificat émis et révoqué, ainsi que son identifiant d’appareil, sa période de validité et l’auteur de l’audit. La reconstruction s’effectue dans une transaction unique : si elle ne peut pas aboutir, le démon refuse de démarrer et laisse le registre d’origine intact au lieu de le migrer partiellement, et l’erreur indique le nom du fichier. Aucune intervention de l’opérateur n’est requise et les appareils enrôlés n’ont pas besoin d’être réenrôlés.
Certificats non délivrés
Le démon consigne une émission dans le registre avant de remettre le certificat au client en cours d’inscription, à un client qui renouvelle son certificat ou dans les fichiers issue-client-cert de l’opérateur. Cet ordre est délibéré : l’alternative peut laisser un certificat actif signé par l’AC entre les mains de quelqu’un sans aucune entrée dans le registre, et un certificat qui n’y figure pas ne peut être listé ni révoqué.
Le coût est qu’une défaillance intermédiaire — un client qui se déconnecte en cours de réponse, un échec de renommage — laisse une ligne active pour un certificat que personne n’a reçu. Une telle ligne est suivie séparément comme non remise et est révoquée dès qu’elle a plus d’une heure, au premier des événements suivants :
- toute nouvelle émission ou tout renouvellement de certificat,
- toute connexion d’enrôlement,
- tout redémarrage du démon ou toute autre ouverture du registre.
Ce n’est pas un minuteur en arrière-plan. Un daemon qui n’effectue absolument aucune opération sur les certificats reporte le balayage jusqu’à sa prochaine activité de ce type ; jusque-là, le certificat reste absent de tls/revoked et réussirait toujours une négociation WSS, bien qu’il ne puisse pas être renouvelé. Tout trafic d’enrôlement ou de renouvellement — y compris une connexion qui échoue à s’authentifier — suffit à le réconcilier.
Les certificats réconciliés sont révoqués, jamais supprimés : la ligne reste dans le registre et l’empreinte est ajoutée à tls/revoked, exactement comme lors d’une révocation par un opérateur, avec l’acteur d’audit reconcile:undelivered, afin que la trace permette de les distinguer. security list-client-certs n’affiche que les certificats ACTIVE ; un certificat réconcilié disparaît donc de cette liste. Lors d’une réponse à incident, consultez tls/revoked ou le journal d’audit pour connaître l’historique des révocations. Si un appareil signale qu’un certificat qu’il a effectivement reçu ne fonctionne plus, recherchez cet acteur : cela signifie que l’enregistrement de livraison a été perdu, et la solution consiste à réinscrire l’appareil ou à réémettre le certificat.
Vérification et dépannage
- Démon démarré : recherchez le journal de l’écouteur WSS sur
0.0.0.0:9781et, avec un relais, la ligne de journalnode_id. - Connexion sans certificat refusée : comportement attendu sur le plan mTLS — effectuez d’abord l’inscription (Topologie 1b). Le message indique la marche à suivre, ce n’est pas un blocage.
- Relais accessible :
zerorelay healthcheck --addr <host>:8443se termine avec le code de sortie 0. - Métriques Relay : exécutez avec
--status-file <path>, puiszerorelay status --file <path>(comptages uniquement, jamais de charges utiles). - Révoquer un appareil perdu : la révocation dans le registre du démon matérialise
<data_dir>/tls/revoked; le certificat est refusé lors de sa prochaine négociation et ne peut pas se renouveler automatiquement. - Incompatibilité SAS lors de l’inscription : le client refuse de conserver le certificat et abandonne. Une incompatibilité signifie que l’autorité de certification reçue n’est pas celle du démon ; recherchez une éventuelle attaque de l’homme du milieu avant de réessayer.
Il existe un banc de test de bout en bout autonome dans scripts/dev/mtls-relay-testbed.sh qui démarre un démon et un relais, émet un certificat, effectue une inscription via le réseau et teste les trois topologies ; consultez-le comme un exemple concret.