ecloudserv docs

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 serviceOui, en clairJamais
Visible dans le gestionnaire de fichiersOuiNon
Peut partir dans une sauvegarde / un dépôtOuiNon — il n'y a pas de fichier
Consultable depuis le tableau de bordOui (bouton Afficher)Non si la clé est scellée
Trace à chaque lecture par l'applicationNonOui
Révocable sans redéployerNon vraimentOui

À noter

Les deux systèmes cohabitent sur un même service. Garde les variables ordinaires (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.

ce qui se passe au démarrage de ton application
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é#

1

Ouvre l'onglet Coffre

Tableau de bord → ton conteneur → Coffre.

2

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.

3

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.

4

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é

Si tu perds la valeur d'une clé scellée, personne ne peut te la rendre : il faut la régénérer chez son fournisseur et la reposer. C'est le prix de la garantie. DécocheSceller 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éeLue parQuand l'utiliser
Ce conteneurCe service uniquementUne valeur propre à ce service
Tout le compteTous tes conteneursLa 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

Une clé échue continue d'être servie à ton application. C'est un rappel, pas un verrou : une expiration qui couperait la production le jour J transformerait un pense-bête utile en panne — et la panne arriverait précisément le jour où personne ne regarde. Remplacer la valeur remet le compteur à zéro.

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à :

bash
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.

depuis n'importe où
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

Il n'est affiché qu'une fois, à la création : nous n'en gardons qu'une empreinte. Perdu = régénéré (et l'ancien cesse aussitôt de fonctionner).

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

GEThttp://<passerelle>:7458/v1/secrets
Toutes les clés du service appelant, en JSON. Aucune authentification : l'appelant est identifié par son adresse de conteneur.
GEThttp://<passerelle>:7458/v1/env
Les mêmes, au format .env — pour un eval de shell, sans fichier.
GEThttp://<passerelle>:7458/v1/health
Sonde de vie du courtier. Ne divulgue rien.
GET/vault/v1/secretsportée read
Même contenu, depuis l'extérieur, avec Authorization: Bearer evt_…. Soumis au verrou d'adresse.
GET/vault/v1/envportée read
Idem, au format .env.

Depuis l'API du tableau de bord

GET/servers/:id/vaultportée read
Liste des clés — noms, empreintes, tailles, dates de lecture. Jamais les valeurs.
PUT/servers/:id/vaultportée write
Crée ou remplace une clé : { key, value, sealed?, note? }. sealed vaut true par défaut.
DELETE/servers/:id/vault/:secretIdportée write
POST/servers/:id/vault/:secretId/sealportée write
Scelle une clé consultable. Sens unique.
POST/servers/:id/vault/tokenportée write
Génère (ou remplace) le jeton d'exécution. Seule réponse de toute l'API qui contient un jeton en clair.
GET/servers/:id/vault/accessportée read
Les 50 dernières lectures : origine, adresse, clés servies, refus éventuels.