Skip to Content
Mélodium 0.11.0 is now available!

Secrets

Les mots de passe, jetons et clés sont donnés aux programmes sous forme de valeurs Secret<T>, le plus souvent Secret<string>. Un secret contient l’endroit d’où vient sa valeur plutôt que la valeur elle-même, et seuls les éléments qui ont besoin de la valeur peuvent la révéler, au moment où ils l’utilisent. Sa valeur n’apparaît jamais dans les paramètres, les journaux, les fichiers de debug ni les rapports, et jamais dans les arguments d’une commande.

Localiser les secrets

Un paramètre secret prend un localisateur, <schéma>:<chemin>, qui indique d’où lire la valeur au moment de la révéler :

LocalisateurValeur
env:MELODIUM_SECRET_NAMEla variable d’environnement MELODIUM_SECRET_NAME
file:PATHle contenu du fichier situé à PATH, sans un unique saut de ligne final
vault:PATH#FIELDun champ lu depuis un serveur HashiCorp Vault ou OpenBao, via le paquet vault

Les programmes n’ont accès qu’aux variables d’environnement dont le nom commence par MELODIUM_SECRET_ : les autres, comme celles qui configurent Mélodium lui-même, restent hors de leur portée. Celui qui exécute un programme lui expose une variable volontairement, par exemple MELODIUM_SECRET_GITHUB_TOKEN: ${{ github.token }} dans un workflow GitHub.

Les localisateurs s’écrivent comme des chaînes littérales, pour les valeurs par défaut comme pour les arguments :

use sql::SqlPool use std/ops/option::|wrap treatment database(const password: Secret<string> = "env:MELODIUM_SECRET_DB_PASSWORD") model pool: SqlPool(url="postgresql://app@db.local/app", password=|wrap<Secret<string>>(password)) { }

En ligne de commande, le même paramètre prend un localisateur, jamais une valeur :

$ melodium run app.mel --password file:/run/secrets/db_password

--check-secrets résout les secrets donnés en paramètres avant l’exécution, afin qu’une variable ou un fichier manquant échoue immédiatement plutôt qu’à la première utilisation.

Note

Les sources env: et file: sont disponibles dans les versions de Mélodium qui ont accès à un environnement et à un système de fichiers, ce qui n’est pas le cas de la version WebAssembly.

Utiliser les secrets

Les éléments qui reçoivent des secrets ne les révèlent qu’au moment de les utiliser :

  • sql::SqlPool place son password dans l’URL de connexion au moment de se connecter, et refuse les paramètres de requête contenant des secrets.
  • http/client::HttpClient ajoute ses secret_headers à chaque requête.
  • Les environnements de process donnent des variables secrètes et une entrée standard secrète aux commandes, jamais via leurs arguments.
  • Les fournisseurs distants de ml (RemoteLlm, RemoteStt, RemoteTts) et le HuggingFace Hub prennent leur clé d’API ou leur jeton sous forme de secret.
  • work/distant::DistantEngine et les secrets de récupération d’images de conteneurs sont révélés pour envoyer la demande de worker.
  • work/access::|new_access prend les clés d’authentification d’un moteur de distribution sous forme de secrets, révélés par distrib::start pour authentifier la connexion.

Les requêtes HTTP entrantes donnent leurs en-têtes Authorization et Cookie comme secrets du contexte @HttpRequest, et les retirent des en-têtes de la requête. Un serveur peut déclarer d’autres en-têtes à ne recevoir que sous forme de secrets, comme le jeton des webhooks GitLab : HttpServer(..., secret_headers="X-Gitlab-Token").

Le paquet crypto vérifie ces valeurs sans les révéler au programme : crypto/token::check compare un jeton reçu avec un secret en temps constant, et crypto/signature/hmac::verify vérifie la signature HMAC d’un corps avec une clé donnée sous forme de secret. Il vérifie aussi les signatures Ed25519 et les JSON Web Tokens, signe des JSON Web Tokens, et génère des jetons aléatoires sous forme de secrets. L’exemple Webhooks signés rassemble tout cela.

Des secrets peuvent être construits à partir d’autres secrets sans être révélés. Les fonctions de std/secret, comme |format, |base64 et |url_encode, ne calculent leur résultat qu’au moment où il est révélé :

use http/client::HttpClient use std/data/map::Map use std/data/map::|map as |secret_map use std/data/map::|entry as |secret_entry use std/data/string_map::|map use std/ops/option::|wrap use std/secret::|format treatment api(const token: Secret<string> = "env:MELODIUM_SECRET_API_TOKEN") model client: HttpClient( base_url=|wrap<string>("https://api.example.com"), headers=|map([]), secret_headers=|wrap<Map>(|secret_map([ |secret_entry<Secret<string>>("Authorization", |format( "Bearer {token}", |secret_map([|secret_entry<Secret<string>>("token", token)]), "authorization" )) ])) ) { }

Certains secrets ne sont connus qu’à l’exécution, comme un jeton donné par la réponse d’une API, transformé en secret par le traitement std/secret::conceal, ou un secret lu par vault::get depuis un chemin construit à l’exécution. Ils arrivent sur des entrées, et les traitements de std/secret/block, process/environment/block et http/client::requestWithSecretHeaders les manipulent de la même façon, sans les révéler avant qu’ils ne soient nécessaires.

Politiques

Chaque secret a une politique, fixée à sa création, et qui ne peut ensuite qu’être restreinte :

  • la transmission, où le secret peut aller quand le programme est distribué : local (ne quitte jamais le moteur, par défaut), reference (seul le localisateur est envoyé, et le moteur distant le résout avec ses propres sources) ou value (la valeur est envoyée, uniquement via TLS) ;
  • la révélation, quels éléments peuvent le révéler : tous par défaut, ou seulement ceux listés ;
  • la révélation en clair, si std/secret::reveal peut le transformer en donnée ordinaire : non par défaut, et jamais pour les secrets venant de env: et file:.
use std/secret::|from_environment use std/secret::|locate use std/secret::|reveal_only_by // Sent by value to distant engines, never plainly revealed. |locate<string>("vault:kv/data/ci#token", "ci_token", "value", false) // Only revealed by `SqlPool`. |reveal_only_by<string>(|from_environment("MELODIUM_SECRET_DB_PASSWORD", "db_password"), ["sql::SqlPool"])

Un secret construit à partir d’autres reçoit la combinaison la plus restrictive de leurs politiques.

Audit et masquage

Chaque révélation, et chaque secret envoyé à un moteur distant, est enregistré comme événement de debug nommant l’élément et son libellé. Les révélations refusées ou en échec sont aussi écrites dans le journal.

Les valeurs révélées pendant une exécution sont remplacées par <secret "name"> dans les messages de journal, y compris sous leurs formes base64 et encodées pour URL, dès lors qu’elles font au moins 8 octets.

Le texte en clair détenu par Mélodium est écrasé dès qu’il n’est plus nécessaire, et avant la fin du processus, y compris en cas d’interruption. C’est un effort au mieux : la mémoire écrite dans l’espace d’échange ou dans des vidages mémoire, et les copies faites par les bibliothèques ou les commandes qui reçoivent la valeur, sont hors de sa portée.

Distribution

Quand un traitement s’exécute sur un moteur distant, ses paramètres et les données qui lui sont envoyées suivent la politique de transmission de chaque secret qu’ils contiennent :

  • les secrets local sont refusés, avec une erreur explicite ;
  • les secrets reference sont résolus par le moteur distant, avec son propre environnement, ses fichiers et ses sources ;
  • les secrets value sont résolus par le moteur émetteur et envoyés via TLS, jamais en TCP simple.

Le moteur distant conserve la politique des secrets qu’il reçoit, et la fait respecter.

Note

Le 📖 Mélodium Language Book  décrit les secrets de façon exhaustive.