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
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
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
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"
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
Initialisez Vault :
vault operator init -key-shares=1 -key-threshold=1
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
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
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)"
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.
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
Lisez une version spécifique :
vault kv get -version=7 secret/project1
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
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
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"]
}
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
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
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.
La série complète
- Pourquoi auto-héberger
- Le serveur
- Traefik et SSL
- La première instance Supabase
- Vault ← cet article
- Deux instances
- Sécurité et test de charge
Top comments (0)