Phase A of the multi-environment evolution agreed in the erp repo design thread. Both modules gain an optional env coordinate that defaults to "prod"; by the elision rule, env=prod produces the existing single-env derived names character-for-character, so every existing app's tofu plan is a no-op. app_roles (per-instance module — caller iterates over envs): - variables.tf: add optional env = "prod" - main.tf: compute local.instance via elision rule + local.owner_role (snake-case <name>_<env>_role for the Postgres owner). The name/env/ database locals are grouped so fmt keeps the existing `name` alignment (no whitespace churn on unchanged keys). - main.tf: substitute local.name -> local.instance / local.owner_role in the dynamic role name, k8s role name, SA bindings, token_policies - outputs.tf: add env + instance outputs; kvv2_path_prefix derives from local.instance (== local.name when env=prod → backwards-compat) app_policy (per-repo module — accepts list of envs): - variables.tf: add optional envs = ["prod"] - main.tf: compute local.instances + local.non_prod_instances; remove the now-dead bound_service_account_* alias locals (the allowed_parameter blocks build their values from per_instance_sa_* maps instead) - main.tf: kvv2 ops rules become dynamic blocks iterating local.instances in the original order (data, delete, undelete, destroy, metadata), so a prod-only app renders a byte-identical policy document - main.tf: allowed_parameter for bound_service_account_* + token_policies use comprehensions over local.instances (1-element → identical to old static values for prod-only apps) - main.tf: keep vault_policy.app (env=prod runtime policy) at its original address; add vault_policy.app_non_prod via for_each over non_prod_instances (empty set for prod-only apps → no new resources) Top-level wiring: - iac/variables.tf: add envs = optional(list(string), ["prod"]) to the applications set(object) type - iac/main.tf: pass envs = each.value.envs to app_policies Verified: `tofu fmt -check` clean on all touched files, `tofu validate` passes. Backwards-compat reasoning for the no-op plan is in the PR body. Phase B (factory postgres iac + argocd + runbook docs) and Phase D (erp iac/main.tf for_each + activate sandbox) follow in their own PRs. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Modules
app_policy
Ce module à déclarer dans ce projet permet au projet subordonné de déclarer le module app_roles suivant.
Ce module Terraform associe un projet Git à un ensemble de ressources Vault :
- Une policy
-opspour la CI/CD du projet (dépôt Git). - Une policy
apppour le runtime applicatif (pods). - Un groupe Vault lié au projet. (pour ajouter les utilisateurs vault associé à leur compte gitea)
- Un rôle JWT Vault lié à ton SCM (ex: Gitea).
- Les droits nécessaires pour gérer les rôles Kubernetes et Postgres associés au projet.
🚀 Usage
module "webapp_vault" {
source = "./modules/vault_project"
name = "webapp"
gitea_app_id = "my-gitea-oauth-app-id" # secret récupéré via vault dans la CI
}
app_roles
Ce module Terraform configure les rôles Vault nécessaires pour qu’une application déployée dans Kubernetes puisse :
- s’authentifier auprès de Vault via son
ServiceAccount, - obtenir des identifiants Postgres dynamiques,
- accéder à ses secrets dans Vault.
🚀 Usage
module "webapp_vault_app" {
source = "./modules/vault_app"
name = "webapp"
database = "mydb" # optionnel, par défaut = name
}
principe
+-----------------+
| Dépôt Git |
| (CI/CD Terraform|
+--------+--------+
|
[Auth via Vault JWT Role]
|
+----------v-----------+
| Vault (Policy -ops) |
| - Peut gérer |
| - Roles K8s |
| - Roles Postgres|
| - Secrets KV |
+----------+-----------+
|
[Token éphémère CI/CD]
|
+----------------v----------------+
| Kubernetes API |
| - Applique CRDs / Secrets |
| - Configure Longhorn / RBAC |
+---------------------------------+
--------------------------- Flux runtime ---------------------------
+-----------------+
| Pod App |
| (SA: webapp) |
+--------+--------+
|
[Auth via Vault K8s Role]
|
+----------v-----------+
| Vault (Policy app) |
| - Peut lire |
| - kvv2/data/... |
| - postgres/... |
+---------------------+
|
[Secrets dynamiques: PW DB, etc.]
|
+--------v---------+
| Postgres DB |
+------------------+
documentation destinée aux dépots des applications:
🔑 Gestion des secrets avec Vault Secrets Operator (VSO)
Ce repository utilise Vault Secrets Operator pour gérer les secrets de l’application (notamment les identifiants Postgres).
L’objectif est d’éviter de stocker des credentials statiques, en déléguant la génération et la rotation à HashiCorp Vault.
⚙️ Architecture
-
Terraform côté admin configure Vault :
- un backend Postgres (
postgres/) connecté à la base via pgbouncer, - un rôle Vault
webapp(postgres/roles/webapp) qui définit la manière dont les credentials dynamiques sont créés, - un rôle Kubernetes
webapp(auth/kubernetes/role/webapp) qui autorise le ServiceAccountwebappdu namespace à s’authentifier auprès de Vault.
- un backend Postgres (
-
L’application (dans ce repo) déclare :
- un
VaultAuthqui associe le ServiceAccountwebappau rôle Vaultwebapp, - un
VaultDynamicSecretqui demande un secret dynamique (postgres/creds/webapp), - un
SecretKubernetes généré automatiquement par VSO (vso-db-credentials), injecté dans le Pod de l’application.
- un
🛠️ Ressources déployées
VaultConnection
apiVersion: secrets.hashicorp.com/v1beta1
kind: VaultConnection
metadata:
finalizers:
- vaultconnection.secrets.hashicorp.com/finalizer
labels:
name: default
namespace: {{ .Release.Namespace }}
spec:
address: http://hashicorp-vault.tools.svc.cluster.local:8200
skipTLSVerify: false
VaultAuth
apiVersion: secrets.hashicorp.com/v1beta1
kind: VaultAuth
metadata:
name: auth
namespace: {{ .Release.Namespace }}
spec:
vaultConnectionRef: default
method: kubernetes
mount: kubernetes
kubernetes:
role: webapp
serviceAccount: {{ include "webapp.serviceAccountName" . }}
audiences:
- vault
Permet à VSO (et donc à l’app) de s’authentifier auprès de Vault avec le rôle webapp.
VaultDynamicSecret
apiVersion: secrets.hashicorp.com/v1beta1
kind: VaultDynamicSecret
metadata:
name: vso-db
namespace: {{ .Release.Namespace }}
spec:
mount: postgres
path: creds/webapp # chemin du rôle dynamique Postgres
destination:
create: true
name: vso-db-credentials
rolloutRestartTargets:
- kind: Deployment
name: {{ include "webapp.fullname" . }}
vaultAuthRef: auth
Demande un secret dynamique Postgres depuis Vault et le stocke dans un Secret Kubernetes nommé vso-db-credentials. Le Deployment de l’app est redémarré automatiquement à chaque rotation de credentials.
📦 Consommation du secret Une fois VSO en place, les credentials Postgres sont disponibles dans le Secret Kubernetes :
apiVersion: v1
kind: Pod
metadata:
name: example
spec:
containers:
- name: app
image: myapp:latest
env:
- name: DB_USERNAME
valueFrom:
secretKeyRef:
name: vso-db-credentials
key: username
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: vso-db-credentials
key: password
🔄 Rotation Vault génère des identifiants éphémères (par défaut TTL = 1h).
VSO renouvelle ou régénère automatiquement ces credentials.
Lorsqu’un nouveau secret est émis, le Deployment ciblé est redémarré pour recharger les variables d’environnement.
✅ Résumé Pas de secrets stockés en clair dans Git ou Helm.
Rotation automatique des credentials Postgres.
Intégration fluide avec Kubernetes via ServiceAccounts.