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 :
| Localisateur | Valeur |
|---|---|
env:MELODIUM_SECRET_NAME | la variable d’environnement MELODIUM_SECRET_NAME |
file:PATH | le contenu du fichier situé à PATH, sans un unique saut de ligne final |
vault:PATH#FIELD | un 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.
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::SqlPoolplace sonpassworddans l’URL de connexion au moment de se connecter, et refuse les paramètres de requête contenant des secrets.http/client::HttpClientajoute sessecret_headersà chaque requête.- Les environnements de
processdonnent 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::DistantEngineet les secrets de récupération d’images de conteneurs sont révélés pour envoyer la demande de worker.work/access::|new_accessprend les clés d’authentification d’un moteur de distribution sous forme de secrets, révélés pardistrib::startpour 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) ouvalue(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::revealpeut le transformer en donnée ordinaire : non par défaut, et jamais pour les secrets venant deenv:etfile:.
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
localsont refusés, avec une erreur explicite ; - les secrets
referencesont résolus par le moteur distant, avec son propre environnement, ses fichiers et ses sources ; - les secrets
valuesont 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.
Le 📖 Mélodium Language Book décrit les secrets de façon exhaustive.