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 store 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 ce furent 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 vous committez un fichier .env dans git par erreur, vos identifiants se retrouvent dans l'historique de façon permanente. Supprimer le fichier ne suffit pas. L'historique est là.
Le moins évident : avec deux projets en cours d'exécution, vous avez deux fichiers .env. Il vous faut un système pour faire tourner 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 avec des permissions spécifiques. Chaque modification est versionnée. Vous pouvez 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 points à expliquer ici.
disable_mlock = true : par défaut, Vault verrouille toutes ses pages mémoire pour empêcher les secrets d'être écrits en swap. C'est une bonne idée en principe, mais cela signifie que Vault réserve environ 376 Mo de RAM même au repos, puisque toutes les pages sont épinglées. Sur notre serveur de 4 Go avec 17 conteneurs en cours d'exécution, c'est une option raisonnable à désactiver. Avec disable_mlock = true, Vault utilise environ 140 Mo. Pour un nœud unique sans module de sécurité matériel, c'est un compromis acceptable. Je ne pense pas qu'un HSM cloud vaille la peine pour un projet personnel.
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. Il n'existe aucune procédure de récupération.
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 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 chaque version antérieure d'un secret. 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. Nous ne l'utilisons pas 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éverrouillliez 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. Vous ne pouvez pas la récupérer une fois perdue.
La série complète
- Pourquoi auto-héberger
- Le serveur
- Traefik et SSL
- La première instance Supabase
- Vault, vous êtes ici
- Deux instances
- Sécurité et test de charge
Top comments (0)