Consolider les secrets dans une voûte unique par environnement

Fini les voûtes éparpillées : tous les secrets (token Proxmox + vault_*)
vivent dans group_vars/all/vault.yml, un seul fichier chiffré, un seul mot
de passe. Gabarit committé exemples/vault.exemple.yml (17 clés). make config
écrit/édite cette voûte (semée depuis le gabarit si absente). .gitignore
durci (**/vault.yml). Docs : config-proxmox.md (+ migration), intrants-
communs.md §H, QUICKSTART. Le GUI ne stocke aucun secret.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Daniel Allaire 2026-06-30 15:42:12 -04:00
parent 3a3a80e0ac
commit c4e73f8712
7 changed files with 115 additions and 30 deletions

1
.gitignore vendored
View file

@ -6,6 +6,7 @@ facts_cache/
.ansible/
*.vault
*.vault.yml
**/vault.yml
*.secret
*.pem
*.key

View file

@ -2,6 +2,16 @@
## 2026-06-30
### Modifié
- **Voûte de secrets unique par environnement.** Fini les voûtes éparpillées : tous
les secrets de l'instance (token Proxmox + 17 `vault_*` pour PKI, LDAP/SSO, bases,
forge, observabilité) vivent dans **un seul fichier chiffré**,
`inventories/<env>/group_vars/all/vault.yml`. Gabarit committé
`exemples/vault.exemple.yml`. `make config` (`config_proxmox.py`) écrit/édite
désormais cette voûte (semée depuis le gabarit si absente). `.gitignore` durci
(`**/vault.yml`). Docs mises à jour (config-proxmox.md avec étapes de migration,
intrants-communs.md §H, QUICKSTART). Le GUI ne stocke toujours aucun secret.
### Corrigé
- **`chezlepro_timezone` n'était appliqué nulle part.** Cet intrant de base global
était défini mais aucun rôle ne s'en servait. Le rôle `chrony` (appliqué à tout

View file

@ -44,9 +44,10 @@ Tu peux aussi le faire dans le GUI plus tard (`make inventaire-ui`).
make config # renseigne API host/user/port, nœud, stockage, VMID du template...
```
Chaque paramètre demandé est expliqué dans [`docs/config-proxmox.md`](docs/config-proxmox.md).
Place ton **token API** et tes secrets dans le Vault de l'instance
(`instance/inventories/lab/group_vars/proxmox.vault.yml`, à partir du `.example`,
chiffré avec `ansible-vault`). Exporte ton mot de passe Vault, ex. :
Tous tes secrets (token API + `vault_*`) vont dans **une voûte unique par
environnement** : `instance/inventories/lab/group_vars/all/vault.yml`, à partir du
gabarit [`exemples/vault.exemple.yml`](exemples/vault.exemple.yml), chiffrée avec
`ansible-vault`. Exporte ton mot de passe Vault, ex. :
```bash
export ANSIBLE_VAULT_PASSWORD_FILE=~/.config/setops-vault-pass
```

View file

@ -5,8 +5,9 @@ la connexion au cluster Proxmox et les valeurs de clonage par défaut. Il pose
**16 paramètres non sensibles** puis propose de saisir les **secrets API**.
- Non sensibles → `instance/inventories/lab/group_vars/proxmox.yml`
- Secrets API → `instance/inventories/lab/group_vars/proxmox.vault.yml` (chiffré
par `ansible-vault`)
- Secrets → **voûte unique** `instance/inventories/lab/group_vars/all/vault.yml`
(chiffrée par `ansible-vault`), qui contient **tous** les secrets de l'instance
(token Proxmox + `vault_*`). Voir [§4](#4-secrets-de-linstance-).
À chaque invite, la valeur courante (ou le défaut) est affichée entre crochets :
**appuyer sur Entrée conserve cette valeur**. On peut donc relancer `make config`
@ -56,20 +57,41 @@ Ces valeurs s'appliquent à toute VM clonée, **sauf** si l'hôte les surcharge
| Pare-feu interface Proxmox | `proxmox_clone_parefeu_interface` | `non` | Active le pare-feu Proxmox **au niveau de la NIC**. Laisser `non` : le filtrage se fait dans l'invité (nftables), pas chez l'hyperviseur. |
| Démarrer le clone après création | `proxmox_clone_demarrer` | `oui` | `oui` = booter la VM dès la création (nécessaire pour qu'Ansible la joigne ensuite). |
## 4. Secrets API Proxmox 🔒 *(constantes, chiffrées)*
## 4. Secrets de l'instance 🔒 *(voûte unique)*
L'assistant demande ensuite : « Configurer les secrets API Proxmox maintenant ».
Si `oui` :
Tous les secrets de l'instance vivent dans **une seule voûte chiffrée par
environnement** : `instance/inventories/<env>/group_vars/all/vault.yml`. Un seul
fichier, un seul mot de passe — fini les voûtes éparpillées. Gabarit committé :
[`exemples/vault.exemple.yml`](../exemples/vault.exemple.yml) (token Proxmox +
17 clés `vault_*` pour PKI, LDAP/SSO, bases, forge, observabilité).
L'assistant demande « Configurer la voûte de secrets maintenant ». Si `oui` :
| Invite | Variable | Défaut | Sens / quoi saisir |
| --- | --- | --- | --- |
| Token ID Proxmox | `proxmox_api_token_id` | `set-ops` | Identifiant du **token API** créé côté Proxmox pour l'utilisateur API. |
| Token secret Proxmox | `proxmox_api_token_secret` | *(aucun)* | Secret du token. **Saisie masquée**, écrite uniquement dans le Vault chiffré. |
| Token ID Proxmox | `proxmox_api_token_id` | `set-ops` | Identifiant du **token API** créé côté Proxmox. |
| Token secret Proxmox | `proxmox_api_token_secret` | *(aucun)* | Secret du token. **Saisie masquée**, écrit dans la voûte chiffrée. |
- Si `proxmox.vault.yml` existe déjà, l'assistant ouvre directement
`ansible-vault edit` (pas de re-saisie en clair).
- Un secret vide ⇒ le Vault **n'est pas créé**.
- Ces deux clés ne transitent **jamais** par le GUI ni par aucun fichier en clair.
- Si la voûte **existe déjà**, l'assistant ouvre directement `ansible-vault edit`
(aucune re-saisie en clair).
- Si elle **n'existe pas**, l'assistant la **sème depuis le gabarit** (toutes les
clés présentes, vides), y place le token, puis la chiffre. On renseigne ensuite
les autres secrets avec `ansible-vault edit …/all/vault.yml`.
- Ces secrets ne transitent **jamais** par le GUI ni par aucun fichier en clair ;
le GUI n'en affiche que les **noms** (panneau « Intrants »).
### Migration depuis l'ancienne `proxmox.vault.yml`
Si tu as déjà une `proxmox.vault.yml`, déplace son contenu dans la voûte unique :
```bash
cd instance/inventories/lab/group_vars
cp ../../../../exemples/vault.exemple.yml all/vault.yml
ansible-vault view proxmox.vault.yml # relève token_id + secret
ansible-vault edit all/vault.yml # colle-les, renseigne le reste
ansible-vault encrypt all/vault.yml # si pas déjà chiffrée
git rm --cached --ignore-unmatch proxmox.vault.yml 2>/dev/null; rm proxmox.vault.yml proxmox.vault.yml.example
```
---

View file

@ -48,7 +48,11 @@ journald (rétention) · core_dumps · systemd_ssh_auto.
- Smallstep (step-cli) — `client_pki``serveur_step_ca`.
- Grafana (alloy/loki/grafana) — `client_journal``serveur_loki`/`serveur_grafana`.
### H. Secrets Vault communs 🔒 — `proxmox.vault.yml` + vault d'instance
### H. Secrets Vault communs 🔒 — **voûte unique** `group_vars/all/vault.yml`
> Tous les secrets de l'instance dans **un seul fichier chiffré par environnement**
> (`inventories/<env>/group_vars/all/vault.yml`), gabarit
> [`exemples/vault.exemple.yml`](../exemples/vault.exemple.yml). Un mot de passe, un
> endroit. Édité via `ansible-vault edit` (jamais par le GUI).
`proxmox_api_token_id/secret`, `vault_step_ca_password`, `vault_step_ca_fingerprint`,
`vault_step_ca_provisioner_password`, `vault_openldap_admin`, `vault_ldap_sssd`,
`vault_keycloak_admin`, `vault_postgresql_keycloak`, `vault_bd_keycloak`,

View file

@ -0,0 +1,43 @@
---
# Voûte UNIQUE de l'instance — TOUS les secrets, un seul fichier, un seul mot de passe.
#
# Installation :
# 1. Copier ce gabarit vers la voûte de chaque environnement :
# cp exemples/vault.exemple.yml instance/inventories/lab/group_vars/all/vault.yml
# 2. Renseigner les valeurs, puis chiffrer :
# ansible-vault encrypt instance/inventories/lab/group_vars/all/vault.yml
# 3. Éditer ensuite sans jamais déchiffrer sur disque :
# ansible-vault edit instance/inventories/lab/group_vars/all/vault.yml
#
# Ne JAMAIS versionner le fichier vault.yml (couvert par .gitignore).
# Le GUI ne stocke ni n'affiche ces valeurs : il n'en montre que les NOMS
# (panneau « Intrants de base »). Le mot de passe du vault est saisi au déploiement.
# --- Accès API Proxmox (clonage des VM) ---
proxmox_api_token_id: ""
proxmox_api_token_secret: ""
# --- AC / PKI interne (step-ca) ---
vault_step_ca_password: ""
vault_step_ca_fingerprint: ""
vault_step_ca_provisioner_password: ""
# --- Identité (LDAP / SSO) ---
vault_openldap_admin: ""
vault_ldap_sssd: ""
vault_keycloak_admin: ""
# --- Bases de données ---
vault_postgresql_keycloak: ""
vault_bd_keycloak: ""
vault_bd_forgejo: ""
vault_bd_icingadb: ""
# --- Forge (Forgejo) ---
vault_forgejo_admin: ""
vault_forgejo_secret_key: ""
vault_forgejo_internal_token: ""
# --- Observabilité / divers ---
vault_grafana_admin: ""
vault_redis: ""

View file

@ -15,7 +15,10 @@ import yaml
RACINE = Path(__file__).resolve().parents[1]
INSTANCE = Path(os.environ.get("SETOPS_INSTANCE") or (RACINE / "instance"))
FICHIER_PROXMOX = INSTANCE / "inventories/lab/group_vars/proxmox.yml"
FICHIER_VAULT = INSTANCE / "inventories/lab/group_vars/proxmox.vault.yml"
# Voûte UNIQUE de l'environnement : tous les secrets (token Proxmox + vault_*) au
# même endroit, un seul mot de passe. Gabarit : exemples/vault.exemple.yml.
FICHIER_VAULT = INSTANCE / "inventories/lab/group_vars/all/vault.yml"
GABARIT_VAULT = RACINE / "exemples/vault.exemple.yml"
VALEURS_DEFAUT = {
@ -73,7 +76,7 @@ def ecrire_yaml(path: Path, data: dict) -> None:
with path.open("w", encoding="utf-8") as fichier:
fichier.write("---\n")
fichier.write("# Parametres non sensibles pour les operations Proxmox.\n")
fichier.write("# Les secrets API doivent aller dans proxmox.vault.yml.\n\n")
fichier.write("# Les secrets vont dans la voûte unique group_vars/all/vault.yml.\n\n")
yaml.safe_dump(data, fichier, default_flow_style=False, sort_keys=False, allow_unicode=True)
@ -119,35 +122,36 @@ def demander_oui_non(question: str, defaut: bool = False) -> bool:
def configurer_vault() -> None:
if not demander_oui_non("Configurer les secrets API Proxmox maintenant"):
if not demander_oui_non("Configurer la voûte de secrets maintenant"):
return
if FICHIER_VAULT.exists():
print(f"Ouverture du Vault existant: {os.path.relpath(FICHIER_VAULT, RACINE)}")
print(f"Ouverture de la voûte existante: {os.path.relpath(FICHIER_VAULT, RACINE)}")
subprocess.run(["ansible-vault", "edit", str(FICHIER_VAULT)], check=True)
return
# Voûte absente : on la sème à partir du gabarit (toutes les clés vides),
# on y place le token Proxmox saisi, puis on chiffre. Les autres secrets se
# renseignent ensuite via `ansible-vault edit`.
token_id = input("Token ID Proxmox [set-ops] : ").strip() or "set-ops"
token_secret = getpass("Token secret Proxmox : ").strip()
if not token_secret:
print("Secret vide: Vault non cree.")
print("Secret vide: voûte non créée.")
return
contenu = charger_yaml(GABARIT_VAULT)
contenu["proxmox_api_token_id"] = token_id
contenu["proxmox_api_token_secret"] = token_secret
FICHIER_VAULT.parent.mkdir(parents=True, exist_ok=True)
try:
fd = os.open(FICHIER_VAULT, os.O_WRONLY | os.O_CREAT | os.O_EXCL, 0o600)
with os.fdopen(fd, "w", encoding="utf-8") as fichier:
yaml.safe_dump(
{
"proxmox_api_token_id": token_id,
"proxmox_api_token_secret": token_secret,
},
fichier,
default_flow_style=False,
sort_keys=False,
)
yaml.safe_dump(contenu, fichier, default_flow_style=False, sort_keys=False, allow_unicode=True)
subprocess.run(["ansible-vault", "encrypt", str(FICHIER_VAULT)], check=True)
print(f"Vault cree: {os.path.relpath(FICHIER_VAULT, RACINE)}")
print(f"Voûte créée: {os.path.relpath(FICHIER_VAULT, RACINE)}")
print("Renseigne les autres secrets avec : ansible-vault edit "
f"{os.path.relpath(FICHIER_VAULT, RACINE)}")
except Exception:
if FICHIER_VAULT.exists():
FICHIER_VAULT.unlink()