DevSecOps

Secret rotation automatisée : Vault + Terraform + ArgoCD

Faire tourner les secrets à la main est faillible et lent. Architecture qui rotation automatiquement, sans toucher au code, en 4 semaines de mise en place.

Aroua Biri 9 min

La rotation manuelle des secrets est faillible (oublis, désaccord planning, erreurs humaines), lente (chaque rotation prend des heures) et anxiogène (peur de casser la prod). Résultat : la majorité des PME et scale-ups ont des secrets qui n'ont pas tourné depuis 6, 12 ou 24 mois. La rotation automatisée — Vault + IaC + GitOps — résout ça en 4 semaines de mise en place. Voici l'architecture.

Pourquoi rotation automatisée

Le secret idéal :

  • Court : durée de vie 1h-24h pour les secrets les plus exposés, 7 jours pour les moins critiques.
  • Scopé : périmètre minimal, par service consommateur.
  • Auditable : qui a généré, qui a consommé, quand.
  • Révocable : peut être désactivé instantanément en cas de compromis.
  • Renouvelé sans humain : zéro intervention pour la rotation routine.

Manuellement, tout cela est impossible à grande échelle. Avec un secret manager + IaC + GitOps, c'est natif.

L'architecture cible

`` ┌─────────────────────────────────────┐ │ HashiCorp Vault (ou AWS Secrets │ │ Manager, GCP Secret Manager) │ └──────────────┬──────────────────────┘ │ │ génère et rotation ▼ ┌─────────────────────────────────────┐ │ Secrets dynamiques : │ │ - DB credentials (24h TTL) │ │ - AWS credentials (1h TTL) │ │ - PKI certificates (24h) │ │ - API tokens (7 jours) │ └──────────────┬──────────────────────┘ │ │ injection automatique ▼ ┌─────────────────────────────────────┐ │ External Secrets Operator (K8s) │ │ ou Vault Agent (VMs) │ └──────────────┬──────────────────────┘ │ ▼ ┌─────────────────────────────────────┐ │ Application consume secrets │ │ via env vars ou volume mount │ └─────────────────────────────────────┘ ``

Étape 1 — Vault setup (semaine 1)

Choix entre Vault, AWS Secrets Manager, GCP Secret Manager

| Critère | Vault | AWS SM | GCP SM | |---|---|---|---| | Multi-cloud | ✓ | Limité | Limité | | Secrets dynamiques | ✓ excellent | Partiel (RDS) | Partiel (CloudSQL) | | PKI dynamique | ✓ | ✗ | ✗ | | Coût | Self-host gratuit, HCP payant | $0.40/secret/mois | $0.06/version/mois | | Apprentissage | Plus complexe | Simple | Simple |

Recommandation : Vault si vous avez les compétences ou un besoin multi-cloud. AWS/GCP Secret Manager si vous êtes mono-cloud et privilégiez la simplicité.

Pour la suite, j'utilise Vault comme exemple — les principes sont transposables.

Installation

Pour un cluster Kubernetes :

`` helm install vault hashicorp/vault \ --set "server.ha.enabled=true" \ --set "server.ha.replicas=3" ``

Pour HCP Vault Cloud (managé par HashiCorp) : configuration via console.

Engines à activer

`` vault secrets enable database vault secrets enable -path=aws aws vault secrets enable -path=pki pki vault secrets enable -path=kv -version=2 kv ``

Étape 2 — Authentification des consommateurs (semaine 2)

Pour Kubernetes

Vault Kubernetes auth method :

`` vault auth enable kubernetes vault write auth/kubernetes/config \ kubernetes_host="https://kubernetes.default.svc" ``

Chaque pod s'authentifie via son ServiceAccount JWT, sans gérer de secrets statiques.

Pour les VMs

Vault Agent ou IAM Auth (AWS) :

`` vault auth enable aws vault write auth/aws/role/myapp \ auth_type=iam \ bound_iam_principal_arn=arn:aws:iam::123:role/myapp \ policies=myapp-policy ``

Pour CI/CD

OIDC :

`` vault auth enable -path=github jwt vault write auth/github/config \ bound_issuer="https://token.actions.githubusercontent.com" \ oidc_discovery_url="https://token.actions.githubusercontent.com" ``

GitHub Actions s'authentifie via le token OIDC du runner — zéro secret long-terme stocké.

Étape 3 — Secrets dynamiques (semaine 3)

Database credentials

Vault génère un user PostgreSQL temporaire à chaque demande :

``` vault write database/config/myapp-postgres \ plugin_name=postgresql-database-plugin \ connection_url="postgresql://{{username}}:{{password}}@db.example.com:5432" \ allowed_roles="myapp-readonly,myapp-readwrite"

vault write database/roles/myapp-readwrite \ db_name=myapp-postgres \ creation_statements="CREATE ROLE \"{{name}}\" WITH LOGIN PASSWORD '{{password}}' VALID UNTIL '{{expiration}}'; GRANT ... TO \"{{name}}\";" \ default_ttl="1h" \ max_ttl="24h" ```

L'application demande à Vault un credential : Vault crée un user PG temporaire, retourne user/password, expire après 1h.

AWS credentials

`` vault write aws/roles/myapp-s3 \ credential_type=iam_user \ policy_document=-<``

L'application demande à Vault des AWS credentials : Vault crée un IAM user temporaire, retourne access key et secret, valide 1h.

Étape 4 — Injection dans les pods (semaine 4)

External Secrets Operator (ESO)

``yaml apiVersion: external-secrets.io/v1beta1 kind: ExternalSecret metadata: name: myapp-db-credentials namespace: production spec: refreshInterval: 5m secretStoreRef: name: vault-store kind: SecretStore target: name: myapp-db-credentials creationPolicy: Owner dataFrom: - extract: key: database/creds/myapp-readwrite ``

ESO synchronise un secret Kubernetes natif depuis Vault, automatiquement renouvelé. L'application consomme un secret K8s standard, sans connaître Vault.

Vault Agent Sidecar (alternative)

Le Vault Agent tourne en sidecar du pod, injecte les secrets via volume ou env var, gère le renouvellement.

``yaml annotations: vault.hashicorp.com/agent-inject: "true" vault.hashicorp.com/role: "myapp" vault.hashicorp.com/agent-inject-secret-db: "database/creds/myapp-readwrite" ``

Étape 5 — GitOps via ArgoCD (semaine 4)

ArgoCD synchronise vos manifestes Kubernetes depuis Git. Les ExternalSecrets sont versionés.

``yaml apiVersion: argoproj.io/v1alpha1 kind: Application metadata: name: myapp spec: source: repoURL: https://github.com/myorg/myapp-k8s path: production destination: server: https://kubernetes.default.svc namespace: production ``

Chaque modification de policy ou role passe par PR + merge + ArgoCD sync. Audit complet par git log.

Bonus — Terraform pour gérer Vault

```hcl resource "vault_database_secret_backend_role" "myapp_readwrite" { name = "myapp-readwrite" backend = vault_database_secrets_mount.myapp.path db_name = "myapp-postgres" default_ttl = 3600 max_ttl = 86400 creation_statements = [...] }

resource "vault_policy" "myapp" { name = "myapp" policy = <

Vault config-as-code, versionnée, reviewée. Pas de cliquage console.

Le résultat

Après 4 semaines :

  • Aucun secret long-terme dans le code, les configs, les CI/CD.
  • Tous les secrets rotés automatiquement (1h-7j selon type).
  • Audit log complet : qui a généré, consommé, révoqué.
  • Révocation instantanée via Vault UI ou API.
  • Onboarding d'une nouvelle app : ajout de policy + role en PR.

C'est aussi un argument commercial : SOC 2, ISO 27001, DORA, PCI DSS — tous demandent rotation et auditability des secrets. Avec cette stack, c'est natif.

Un sujet connexe chez vous ?

20 minutes pour cadrer ensemble. Aucune offre commerciale envoyée à froid.

Réserver un échange Calendly