Coffre
Des clés que rien ne peut lire sur le serveur — ni un fichier, ni le code, ni toi.
Le coffre range tes clés d'API, mots de passe et jetons sans jamais les écrire sur le disque de ton service. Ton programme les obtient au démarrage, en mémoire, et le reste du temps il n'y a rien à lire nulle part.
Le problème du fichier .env#
Une variable d'environnement classique (voir Variables & secrets) est chiffrée dans notre base, puis réécrite en clair dans le fichier .env à la racine de ton service. C'est ce fichier que lit ton code — donc il faut bien qu'il soit lisible.
Et c'est précisément le problème : il est lisible par tout le reste aussi.
- il apparaît dans le gestionnaire de fichiers, et dans le SFTP ;
- n'importe quelle dépendance de ton projet peut l'ouvrir — une bibliothèque compromise n'a même pas besoin de chercher, l'emplacement est standard ;
- il part dans les sauvegardes et dans les exports ;
- il finit un jour dans un dépôt Git. Et un secret poussé dans un dépôt reste dans l'historique après suppression : la seule réparation est de le régénérer.
Chiffrer la valeur en base tout en posant une copie en clair sur le serveur, c'est verrouiller une porte et laisser l'autre ouverte. Le coffre supprime la copie.
Ce que change le coffre#
| Variables (.env) | Coffre | |
|---|---|---|
| Écrite sur le disque du service | Oui, en clair | Jamais |
| Visible dans le gestionnaire de fichiers | Oui | Non |
| Peut partir dans une sauvegarde / un dépôt | Oui | Non — il n'y a pas de fichier |
| Consultable depuis le tableau de bord | Oui (bouton Afficher) | Non si la clé est scellée |
| Trace à chaque lecture par l'application | Non | Oui |
| Révocable sans redéployer | Non vraiment | Oui |
À noter
PORT, NODE_ENV, une URL publique) là où elles sont : elles ne sont pas secrètes, et le .env est plus simple. Mets dans le coffre ce dont la fuite coûterait de l'argent ou des données.Comment ça marche#
Une clé est chiffrée en AES-256-GCM par une clé de données propre à ton service, elle-même chiffrée par la clé maîtresse de la plateforme. Le chiffré est en plus lié à l'identité de sa ligne (service + nom de la clé + version) : recopié ailleurs dans la base, renommé, ou remis à une version antérieure, il ne s'ouvre plus du tout.
Reste la vraie question : comment ton programme, lui, obtient la valeur ? Il la demande à sa passerelle réseau — c'est-à-dire au node qui l'héberge.
ton conteneur le node ecloudserv
│ GET /v1/secrets │ │
├────────────────────────────────▶│ qui est 172.18.0.42 ? │
│ │ → le conteneur du service X │
│ ├──────────────────────────────▶│
│ │ clés déchiffrées │
│◀────────────────────────────────┤◀──────────────────────────────┤
│ { "STRIPE_SECRET_KEY": "…" } │ │
│ → en mémoire, et c'est tout │ │Ton programme ne présente rien. Son identité, c'est l'adresse d'où part sa requête : elle n'est pas déclarée par lui, elle est constatée par le système. Il n'y a donc aucun jeton à ranger quelque part, aucun fichier à protéger, rien à oublier dans une archive.
Poser une clé#
Ouvre l'onglet Coffre
Tableau de bord → ton conteneur → Coffre.
Donne un nom et la valeur
Le nom suit les règles d'une variable d'environnement : majuscules, chiffres et tirets bas, sans commencer par un chiffre. C'est sous ce nom que ton code la lira.
Laisse « Sceller » activé
Une clé scellée ne pourra plus jamais être affichée — ni par toi, ni par un administrateur, ni par aucune route de l'API. C'est le réglage à garder : tu n'as pas besoin de relire une clé, tu as besoin qu'elle fonctionne.
Redémarre ton service
Il lira ses clés au démarrage. La colonne Dernière lecture te le confirme.
Scellé veut vraiment dire scellé
Sceller uniquement si tu sais que tu devras recopier cette valeur ailleurs.Deux portées : le conteneur, ou tout le compte#
Une clé peut être posée à deux endroits, et le sélecteur en haut de l'onglet Coffre choisit lequel.
| Portée | Lue par | Quand l'utiliser |
|---|---|---|
| Ce conteneur | Ce service uniquement | Une valeur propre à ce service |
| Tout le compte | Tous tes conteneurs | La même clé Stripe sur trois services : une seule valeur à faire tourner |
À nom égal, la clé du conteneur l'emporte. C'est ce qui permet de surcharger ponctuellement une clé de compte — mettre une clé de test sur un environnement de recette sans dupliquer tout le reste.
Échéance de rotation#
Chaque clé peut porter une date d'échéance. Sept jours avant, puis le jour même, tu reçois un message privé sur Discord : « la clé STRIPE_SECRET_KEY arrive à échéance ».
Elle ne coupe rien
Lire ses clés depuis le code#
L'adresse de la passerelle change d'un node à l'autre : on la lit dans la table de routage plutôt que de l'écrire en dur. C'est deux lignes dans tous les langages.
import { readFileSync } from "node:fs";
/** Passerelle du conteneur = le node qui l'héberge. */
function passerelle() {
for (const l of readFileSync("/proc/net/route", "utf8").split("\n").slice(1)) {
const c = l.split(/\s+/);
if (c[1] === "00000000" && c[2]) {
// Hexadécimal petit-boutiste : 0100A8C0 → 192.168.0.1
return (c[2].match(/../g) ?? []).reverse().map((h) => parseInt(h, 16)).join(".");
}
}
throw new Error("passerelle introuvable");
}
const secrets = await fetch(`http://${passerelle()}:7458/v1/secrets`).then((r) => r.json());
// À utiliser tel quel. Surtout ne pas les réécrire dans un fichier :
// ce serait recréer le .env qu'on vient de supprimer.
const stripe = new Stripe(secrets.STRIPE_SECRET_KEY);Pour vérifier que le courtier répond, depuis la console de ton service — la sonde ne divulgue rien, elle dit seulement qu'elle est là :
curl "http://$(ip route | awk '/default/ {print $3}'):7458/v1/health"
# {"ok":true,"service":"ecloud-vault-broker"}Le jeton d'exécution#
Le courtier ne marche que depuis un conteneur hébergé sur nos nodes. Pour tout le reste — un conteneur LXC, ta machine, une intégration continue — le coffre accepte un jeton d'exécution, à générer dans l'onglet Coffre.
export ECLOUD_VAULT_TOKEN='evt_…'
curl -fsS https://api.ecloudserv.fr/vault/v1/secrets \
-H "Authorization: Bearer $ECLOUD_VAULT_TOKEN"Ce jeton est délibérément beaucoup moins grave à perdre qu'un secret :
- il ne vaut que pour un seul service, et n'autorise qu'une chose : lire ses clés ;
- par défaut, il ne fonctionne que depuis les adresses de nos nodes (verrou d'adresse) — recopié ailleurs, il ne sert à rien ;
- chaque usage apparaît dans le journal des lectures, avec l'adresse d'origine ;
- il se révoque et se régénère d'un clic, sans toucher aux clés.
Attention
Ce que le coffre ne protège pas#
Une page de sécurité qui ne dit que ce qui va bien n'aide personne à décider. Voici les limites, en clair.
Ce qui tourne dans ton conteneur peut lire tes clés
C'est l'objectif, pas une faille : on retire la copie du disque, on ne prive pas ton programme de sa configuration. Une dépendance malveillante déjà en train de s'exécuter chez toi pourra les demander. Elle devra en revanche faire un appel réseau — et cet appel apparaît dans ton journal des lectures, contrairement à uncat .env qui ne laisse aucune trace.
Un secret déjà fuité reste fuité
Déplacer une clé dans le coffre ne rattrape pas une clé déjà poussée dans un dépôt. Régénère-la chez son fournisseur, puis pose la nouvelle dans le coffre.
La plateforme peut techniquement déchiffrer
C'est notre API qui déchiffre pour servir ton programme : elle en a forcément la capacité. Le coffre n'est pas du chiffrement de bout en bout, il n'en existe pas d'utilisable quand c'est nous qui exécutons ton code. Ce qu'il garantit, c'est qu'une valeur n'existe en clair nulle part au repos : ni sur ton disque, ni dans nos sauvegardes, ni dans nos journaux.
Le courtier demande un node à jour
Il faut un agent en version 2.33 ou plus récente sur le node. L'onglet Coffre le dit explicitement quand ce n'est pas le cas, et le jeton d'exécution prend le relais en attendant.
Référence#
Depuis ton programme
http://<passerelle>:7458/v1/secretshttp://<passerelle>:7458/v1/env.env — pour un eval de shell, sans fichier.http://<passerelle>:7458/v1/health/vault/v1/secretsportée readAuthorization: Bearer evt_…. Soumis au verrou d'adresse./vault/v1/envportée read.env.Depuis l'API du tableau de bord
/servers/:id/vaultportée read/servers/:id/vaultportée write{ key, value, sealed?, note? }. sealed vaut true par défaut./servers/:id/vault/:secretIdportée write/servers/:id/vault/:secretId/sealportée write/servers/:id/vault/tokenportée write/servers/:id/vault/accessportée read