DEV Community

Cover image for Partie 5 — Vault, et l'après-midi où j'ai tout effacé
Dinh Doan Van Bien
Dinh Doan Van Bien

Posted on • Edited on • Originally published at supabase.voieduco.de

Partie 5 — Vault, et l'après-midi où j'ai tout effacé

Partie 5 sur 7 — Supabase en auto-hébergement : retour d'expérience

Version française de Part 5 — Vault, and the afternoon I deleted everything.

Je vais vous raconter l'après-midi où j'ai remplacé tous mes secrets Supabase par le mot change_me.

Mon mot de passe Postgres : change_me. Mon secret JWT : change_me. Ma clé service role : change_me. Tout.

Les services ont démarré. Les vérifications d'état ont réussi (Traefik se contente de vérifier les codes de statut HTTP). Puis chaque appel API a commencé à échouer avec des erreurs d'authentification. Je me suis connecté en SSH, j'ai inspecté l'environnement en cours d'exécution, et j'ai vu le problème immédiatement. Une seule commande mal choisie avait remplacé l'intégralité du coffre de secrets par une unique paire clé-valeur.

Pas un script malveillant. Le modèle Docker Compose de Supabase utilise ${POSTGRES_PASSWORD:-change_me} comme valeur de repli pour chaque variable secrète — un espace réservé censé être remplacé avant le déploiement. Quand mon script de récupération a régénéré le fichier .env depuis Vault, il n'a trouvé qu'une seule clé. Tout le reste est retombé sur la valeur par défaut du modèle. J'explique précisément comment c'est arrivé dans la section suivante.

La bonne nouvelle : HashiCorp Vault conserve un historique complet des versions. J'ai tout récupéré depuis la version 7 de mon secret. Mais ça a été vingt minutes de stress, et je vais vous expliquer précisément comment éviter cette erreur.


Pourquoi utiliser Vault

L'alternative à Vault, c'est stocker les secrets dans des fichiers .env sur le serveur. Ça fonctionne, mais avec des inconvénients réels.

Le plus évident : si un fichier .env se retrouve dans git par erreur, vos identifiants sont dans l'historique de façon permanente. Supprimer le fichier ne suffit pas — l'historique, lui, reste.

Le moins évident : avec deux projets en cours d'exécution, vous avez deux fichiers .env. Il vous faut un système pour renouveler les secrets et savoir quelle version de quel secret était déployée à quel moment. Les fichiers plats n'offrent rien de tout cela.

HashiCorp Vault résout tous ces problèmes. Les secrets sont chiffrés au repos, l'accès est contrôlé par des jetons aux permissions spécifiques, et chaque modification est versionnée — ce qui permet de récupérer n'importe quelle version antérieure de n'importe quel secret.

Nous faisons tourner Vault sur le même serveur que nos stacks Docker, lié uniquement à localhost. Il n'est jamais accessible depuis internet.


Installation de Vault

Installez jq en premier. Le script de récupération l'utilise pour parser la sortie JSON de Vault :

apt install jq -y
Enter fullscreen mode Exit fullscreen mode
wget -O- https://apt.releases.hashicorp.com/gpg | gpg --dearmor \
  -o /usr/share/keyrings/hashicorp-archive-keyring.gpg

echo "deb [signed-by=/usr/share/keyrings/hashicorp-archive-keyring.gpg] \
  https://apt.releases.hashicorp.com $(lsb_release -cs) main" \
  | tee /etc/apt/sources.list.d/hashicorp.list

apt update && apt install vault -y
Enter fullscreen mode Exit fullscreen mode

Créez la configuration :

mkdir -p /etc/vault
cat > /etc/vault/config.hcl << 'EOF'
ui             = false
disable_mlock  = true

storage "file" {
  path = "/opt/vault/data"
}

listener "tcp" {
  address       = "127.0.0.1:8200"
  tls_cert_file = "/etc/vault/tls/vault.crt"
  tls_key_file  = "/etc/vault/tls/vault.key"
}
EOF
Enter fullscreen mode Exit fullscreen mode

Deux éléments méritent une explication.

disable_mlock = true : par défaut, Vault verrouille toutes ses pages mémoire pour empêcher les secrets d'être écrits en swap. mlock n'alloue pas de mémoire supplémentaire, mais il épingle toutes les pages résidentes de Vault en RAM pour qu'elles ne puissent pas être échangées sur disque. Sur ce serveur, j'ai observé Vault à environ 376 Mo avec mlock activé contre environ 140 Mo sans, probablement parce que le noyau récupère les pages inutilisées plus agressivement quand elles ne sont pas épinglées. Sur notre serveur de 4 Go avec 17 conteneurs en cours d'exécution, désactiver mlock est un compromis raisonnable. Pour un nœud unique sans module de sécurité matériel, le risque de swap est faible.

address = "127.0.0.1:8200" : Vault écoute uniquement sur localhost. Il n'est accessible que depuis le serveur lui-même, pas depuis le réseau.

Vault exige TLS même pour les connexions localhost. Générez un certificat auto-signé :

mkdir -p /etc/vault/tls
openssl req -x509 -nodes -days 3650 -newkey rsa:2048 \
  -keyout /etc/vault/tls/vault.key \
  -out /etc/vault/tls/vault.crt \
  -subj "/CN=vault-localhost" \
  -addext "subjectAltName=IP:127.0.0.1"
Enter fullscreen mode Exit fullscreen mode

Créez l'unité systemd et démarrez Vault :

cat > /etc/systemd/system/vault.service << 'EOF'
[Unit]
Description=HashiCorp Vault
After=network-online.target
Wants=network-online.target

[Service]
ExecStart=/usr/bin/vault server -config=/etc/vault/config.hcl
ExecReload=/bin/kill --signal HUP $MAINPID
KillMode=process
KillSignal=SIGINT
Restart=on-failure
RestartSec=5
LimitNOFILE=65536
LimitMEMLOCK=infinity

[Install]
WantedBy=multi-user.target
EOF

systemctl daemon-reload
systemctl enable vault
systemctl start vault
Enter fullscreen mode Exit fullscreen mode

Initialisez Vault :

vault operator init -key-shares=1 -key-threshold=1
Enter fullscreen mode Exit fullscreen mode

Cette commande affiche une clé de déverrouillage et un jeton racine. Sauvegardez les deux dans un endroit sûr et hors ligne. La note sécurisée d'un gestionnaire de mots de passe convient parfaitement. Si vous perdez la clé de déverrouillage, vos données chiffrées sont définitivement inaccessibles — aucune procédure de récupération n'existe.

Déverrouillez Vault :

VAULT_ADDR=https://127.0.0.1:8200 VAULT_SKIP_VERIFY=true \
  vault operator unseal YOUR_UNSEAL_KEY
Enter fullscreen mode Exit fullscreen mode

Stocker les secrets

Activez le moteur de secrets KV (Key-Value) version 2 :

export VAULT_ADDR=https://127.0.0.1:8200
export VAULT_SKIP_VERIFY=true
export VAULT_TOKEN=YOUR_ROOT_TOKEN

vault secrets enable -path=secret kv-v2
Enter fullscreen mode Exit fullscreen mode

Stockez les secrets de project1 :

vault kv put secret/project1 \
  POSTGRES_PASSWORD="$(openssl rand -hex 16)" \
  JWT_SECRET="$(openssl rand -hex 32)" \
  SUPABASE_ANON_KEY="your-anon-jwt" \
  SUPABASE_SERVICE_ROLE_KEY="your-service-role-jwt" \
  API_EXTERNAL_URL="https://kong.project1.yourdomain.com" \
  GOTRUE_EXTERNAL_URL="https://kong.project1.yourdomain.com" \
  SITE_URL="https://kong.project1.yourdomain.com" \
  DB_ENC_KEY="supabaserealtime" \
  GOTRUE_MAILER_AUTOCONFIRM="false" \
  SECRET_KEY_BASE="$(openssl rand -hex 64)" \
  PG_META_CRYPTO_KEY="$(openssl rand -hex 16)"
Enter fullscreen mode Exit fullscreen mode

L'erreur : put versus patch

KV v2 propose deux commandes pour écrire des secrets.

vault kv put remplace l'intégralité du secret par exactement ce que vous spécifiez. Si vous exécutez vault kv put secret/project1 GOTRUE_MAILER_AUTOCONFIRM=true, le résultat est un secret contenant exactement une clé : GOTRUE_MAILER_AUTOCONFIRM. Tout le reste disparaît de la version courante.

vault kv patch fusionne les nouvelles clés dans le secret existant. Exécuter vault kv patch secret/project1 GOTRUE_MAILER_AUTOCONFIRM=true ajoute la clé en conservant tout le reste intact.

J'avais besoin d'ajouter GOTRUE_MAILER_AUTOCONFIRM=true pour activer la confirmation automatique des e-mails dans le cadre d'un test de charge. J'ai utilisé vault kv put. Toutes les autres clés ont été effacées de la version courante.


⚠️ Utilisez vault kv patch pour ajouter ou mettre à jour des clés individuelles. Réservez vault kv put aux situations où vous souhaitez intentionnellement remplacer l'intégralité du secret.

Récupération depuis l'historique des versions

KV v2 conserve les versions antérieures d'un secret (jusqu'à 10 par défaut, configurable). Listez les versions :

vault kv metadata get secret/project1
Enter fullscreen mode Exit fullscreen mode

Lisez une version spécifique :

vault kv get -version=7 secret/project1
Enter fullscreen mode Exit fullscreen mode

J'ai retrouvé tous mes secrets d'origine en version 7, puis j'ai utilisé vault kv patch pour les restaurer dans la version courante. Vingt minutes de stress, mais entièrement récupérables.

Cet historique de versions n'est pas un simple bonus — c'est la raison d'utiliser KV v2 plutôt que KV v1.


Récupérer les secrets pour le déploiement

Nous utilisons un script qui lit depuis Vault et écrit un fichier .env :

#!/usr/bin/env bash
PROJECT=$1
VAULT_ADDR=https://127.0.0.1:8200
VAULT_SKIP_VERIFY=true
VAULT_TOKEN=$(cat /root/${PROJECT}-token.txt)

vault kv get -format=json secret/${PROJECT} \
  | jq -r '.data.data | to_entries[] | "\(.key)=\(.value)"' \
  > /root/supabase-vps-cluster/instances/${PROJECT}/.env
Enter fullscreen mode Exit fullscreen mode

La séquence de déploiement devient :

bash scripts/fetch-env-from-vault.sh project1
set -a && source instances/project1/.env && set +a
docker stack deploy -c instances/project1/docker-compose.yml project1
Enter fullscreen mode Exit fullscreen mode

Jetons par projet

Le jeton racine de Vault donne un accès illimité à tout — hors de question de l'utiliser pour les déploiements. À la place, nous créons une politique d'accès qui accorde un accès en lecture seule aux secrets d'un seul projet :

# vault-policy-project1.hcl
path "secret/data/project1" {
  capabilities = ["read", "list"]
}
path "secret/metadata/project1" {
  capabilities = ["read", "list"]
}
Enter fullscreen mode Exit fullscreen mode
vault policy write project1-readonly vault-policy-project1.hcl
vault token create -policy=project1-readonly -ttl=8760h -format=json \
  | jq -r '.auth.client_token' > /root/project1-token.txt
Enter fullscreen mode Exit fullscreen mode

Le script de déploiement lit depuis /root/project1-token.txt. Si vous utilisez GitHub Actions pour automatiser les déploiements, stockez ce jeton comme secret de dépôt (VAULT_TOKEN_PROJECT1) et transmettez-le au script de récupération. Il peut lire les secrets de project1 et rien d'autre.


Une limitation : les redémarrages

Quand le VPS redémarre, Vault se retrouve dans un état scellé. Toutes les requêtes sont refusées jusqu'à ce que vous le déverrouilliez manuellement.

ssh root@YOUR_VPS_IP
VAULT_ADDR=https://127.0.0.1:8200 VAULT_SKIP_VERIFY=true vault operator unseal
# saisissez votre clé de déverrouillage
Enter fullscreen mode Exit fullscreen mode

Les conteneurs en cours d'exécution conservent leurs variables d'environnement et continuent de fonctionner. Mais tout nouveau déploiement échouera à récupérer les secrets tant que Vault n'est pas déverrouillé.

Le déverrouillage automatique nécessite soit un service HSM hébergé dans le cloud, soit de stocker la clé de déverrouillage sur disque, ce qui en annule l'intérêt. Le déverrouillage manuel après les redémarrages est le bon choix pour un nœud unique en usage personnel. Les serveurs Hetzner ne redémarrent pas sans que vous le demandiez.

Faites une sauvegarde de votre clé de déverrouillage — une fois perdue, elle est irrécupérable.

Partie 6 — Deux instances →


La série complète

  1. Pourquoi auto-héberger
  2. Le serveur
  3. Traefik et SSL
  4. La première instance Supabase
  5. Vault ← cet article
  6. Deux instances
  7. Sécurité et test de charge

Top comments (0)