Skip to content

HashiCorp Vault: Secrets Management

Centraliser, sécuriser et contrôler l'accès aux secrets. Fini les mots de passe statiques.


Pourquoi HashiCorp Vault ?

Le Problème : Secret Sprawl

Scénario classique (cauchemar de sécurité) :

Infrastructure typique SANS Vault :

├─ .env files                    (mots de passe en clair)
├─ Ansible vault.yml             (chiffré mais clé partagée)
├─ Kubernetes Secrets            (base64 ≠ chiffrement)
├─ CI/CD Variables               (GitLab/GitHub Secrets)
├─ AWS Secrets Manager           (verrouillage AWS)
├─ Azure Key Vault               (verrouillage Azure)
├─ Post-it sur l'écran           (classique)
└─ Email "Voici les credentials" (audit nightmare)

Problèmes critiques :

Problème Impact SecNumCloud
Secret Sprawl Secrets éparpillés dans 10+ endroits différents
Rotation manuelle Mots de passe jamais changés (années)
Pas d'audit Impossible de savoir qui a accédé à quoi
Secrets statiques Une fuite = compromission permanente
Révocation impossible Pas de moyen de révoquer un secret divulgué
Pas de chiffrement .env files commitées dans Git (🔥)

Exemple de fuite classique :

# Fichier .env committé par erreur
git log --all --full-history -- "*env*"

# Résultat : 47 commits avec DB_PASSWORD="SuperSecret123"
# → Le mot de passe est dans l'historique Git POUR TOUJOURS
# → Même après suppression, il reste accessible dans les commits

La Solution : HashiCorp Vault

Vault Architecture Complete

flowchart TD
    A[Application] -->|1. Authenticate| B[Vault]
    B -->|2. Policy Check| C{Authorized?}
    C -->|Yes| D[Secrets Engine]
    C -->|No| E[Access Denied]

    D -->|3a. Static Secret| F[KV Store]
    D -->|3b. Dynamic Secret| G[Database Engine]
    D -->|3c. Certificate| H[PKI Engine]

    F -->|4. Return Secret| A
    G -->|4. Create User + Return Creds| I[PostgreSQL]
    I -->|5. Temp User| A
    H -->|4. Generate Cert| A

    A -->|TTL Expired| J[Vault Revokes]
    J -->|Delete User| I

    style B fill:#f9d71c
    style G fill:#4CAF50
    style H fill:#2196F3

Vault = "Fort Knox" pour vos secrets

Bénéfices Critiques

  1. Encryption as a Service : Chiffrement centralisé (AES-256-GCM)
  2. Dynamic Secrets : Credentials créés à la demande, détruits après usage
  3. Leasing & Revocation : Chaque secret a un TTL (Time To Live)
  4. Audit Log : Chaque accès est tracé (conformité SecNumCloud)
  5. Secret Versioning : Rollback possible en cas d'erreur
  6. Multi-Cloud : AWS, Azure, GCP avec la même API

Section 1 : Concepts Fondamentaux

1. Encryption as a Service

Sans Vault :

# Application chiffre elle-même
echo "my-secret" | openssl enc -aes-256-cbc -out secret.enc

# Problème : Où stocker la clé de chiffrement ?
# → Si la clé est dans le code, ça ne sert à rien

Avec Vault (Transit Engine) :

# Vault gère le chiffrement
vault write transit/encrypt/my-app plaintext=$(base64 <<< "my-secret")

# Output : vault:v1:8SDd3WHDOjf7mq69CyCqYjBXAiQQAVZRkFM96XVW...
# → La clé de chiffrement ne quitte JAMAIS Vault
# → L'application n'a jamais accès à la clé maître

2. Dynamic Secrets (Game Changer)

Vault Dynamic Secrets Sequence

Problème des secrets statiques :

Database Password : "Prod2019!"
Créé en          : 2019-03-15
Dernière rotation : JAMAIS
Personnes ayant eu accès : 47 (dont 12 qui ont quitté l'entreprise)
Risque de compromission : CRITIQUE

Solution avec Dynamic Secrets :

sequenceDiagram
    participant App
    participant Vault
    participant DB as PostgreSQL

    App->>Vault: GET /database/creds/readonly
    Vault->>DB: CREATE USER vault_user_abc123
    Vault->>DB: GRANT SELECT ON ALL TABLES
    DB-->>Vault: User created
    Vault-->>App: {user: vault_user_abc123, pass: xyz789, ttl: 1h}

    Note over App: Use credentials for 1 hour

    Note over Vault: TTL expires after 1h
    Vault->>DB: DROP USER vault_user_abc123
    DB-->>Vault: User deleted

    Note over App,DB: Credentials no longer valid

Exemple concret :

# Demander un accès temporaire à PostgreSQL
$ vault read database/creds/readonly

Key                Value
---                -----
lease_id           database/creds/readonly/2f6a614c...
lease_duration     1h
lease_renewable    true
password           A1a-xyZ9kP3mN2vB
username           v-token-readonly-abc123def456-1634567890

# Après 1 heure : Le user PostgreSQL est AUTOMATIQUEMENT supprimé
# → Impossible de réutiliser ces credentials
# → Fuite d'un mot de passe = impact limité à 1h maximum

Bénéfices : - ✅ Pas de rotation manuelle (automatique) - ✅ Audit précis (qui a demandé quoi, quand) - ✅ Révocation immédiate en cas d'incident - ✅ Pas de "shared passwords" (chaque app a ses credentials)

3. Leasing & Revocation

Chaque secret a un cycle de vie :

Vault Secret Lifecycle Leasing

┌─────────────────────────────────────────────────────────┐
│                    Secret Lifecycle                      │
├─────────────────────────────────────────────────────────┤
│                                                           │
│  Creation          Usage             Renewal      Death  │
│     │               │                   │           │    │
│     ▼               ▼                   ▼           ▼    │
│  [Generate] ─> [TTL: 1h] ─> [Renew +30m] ─> [Revoke]   │
│     │               │                   │           │    │
│     │               │                   │           │    │
│  Vault creates   App uses         App requests   Vault  │
│  credentials     secret for       extension      deletes│
│  on-demand       limited time     (if allowed)   secret │
│                                                           │
└─────────────────────────────────────────────────────────┘

Commandes de gestion du lease :

# Voir tous les leases actifs
vault list sys/leases/lookup/database/creds/readonly

# Renouveler un lease (si renewable)
vault lease renew database/creds/readonly/2f6a614c

# Révoquer un lease spécifique (urgence)
vault lease revoke database/creds/readonly/2f6a614c

# Révoquer TOUS les leases d'un path (incident de sécurité)
vault lease revoke -prefix database/creds/readonly

Section 2 : Architecture & Initialisation

Seal/Unseal : Le Mécanisme de Sécurité

Problème : Comment protéger Vault lui-même ?

Solution : Shamir's Secret Sharing

Vault Shamir Secret Sharing

Master Key (déchiffre toutes les données)
    ├─ Split en 5 clés (Unseal Keys)
    ├─ Unseal Key 1 : aH3kP...
    ├─ Unseal Key 2 : 9mVzQ...
    ├─ Unseal Key 3 : 2nBxR...
    ├─ Unseal Key 4 : 7wKpS...
    └─ Unseal Key 5 : 5tDfT...

Threshold : 3 clés minimum requises pour déverrouiller

États de Vault :

État Description Actions possibles
Sealed Verrouillé (au démarrage) Aucune opération
Unsealed Déverrouillé (opérationnel) Toutes opérations
Standby Réplica en attente (HA) Read-only

Workflow d'initialisation :

# 1. Initialiser Vault (PREMIÈRE FOIS UNIQUEMENT)
vault operator init -key-shares=5 -key-threshold=3

# Output (SAUVEGARDER IMMÉDIATEMENT DANS UN ENDROIT SÛR)
Unseal Key 1: aH3kP9mVzQ2nBxR7wKpS5tDfT...
Unseal Key 2: 9mVzQ2nBxR7wKpS5tDfTaH3kP...
Unseal Key 3: 2nBxR7wKpS5tDfTaH3kP9mVzQ...
Unseal Key 4: 7wKpS5tDfTaH3kP9mVzQ2nBxR...
Unseal Key 5: 5tDfTaH3kP9mVzQ2nBxR7wKpS...

Initial Root Token: hvs.CAESIKqP3mN2vB9kXyZ1...

# 2. Unsealer Vault (après chaque redémarrage)
vault operator unseal  # Entrer Unseal Key 1
vault operator unseal  # Entrer Unseal Key 2
vault operator unseal  # Entrer Unseal Key 3
# → Vault is now unsealed

# 3. Se connecter avec le root token
vault login hvs.CAESIKqP3mN2vB9kXyZ1...

CRITIQUE : Sauvegarde des Unseal Keys

PERTE DES UNSEAL KEYS = PERTE DÉFINITIVE DE TOUTES LES DONNÉES

Options de sauvegarde sécurisées : - ✅ PGP Encryption : Chiffrer chaque clé avec la clé publique d'un admin - ✅ Cloud KMS : AWS KMS, Azure Key Vault (Auto-Unseal) - ✅ Hardware Security Module (HSM) : Pour SecNumCloud Niveau 3 - ✅ Offline Storage : Coffre-fort physique (air-gapped) - ❌ JAMAIS dans Git, Slack, Email

Auto-Unseal avec Cloud KMS (Production) :

# vault.hcl
seal "awskms" {
  region     = "eu-west-1"
  kms_key_id = "arn:aws:kms:eu-west-1:123456789012:key/abc-def-123"
}

# Avantage : Vault s'unseal automatiquement au démarrage
# Inconvénient : Dépendance au cloud provider

Storage Backend : Raft (Integrated Storage)

Évolution des backends :

2015 : Consul (complexe, infrastructure supplémentaire)
2019 : Raft Integrated Storage (simple, tout-en-un)
2024 : Raft = Standard recommandé

Configuration Raft (HA Cluster) :

# vault.hcl (Node 1)
storage "raft" {
  path    = "/opt/vault/data"
  node_id = "vault-node-1"
}

listener "tcp" {
  address     = "0.0.0.0:8200"
  tls_disable = 0
  tls_cert_file = "/etc/vault/tls/vault.crt"
  tls_key_file  = "/etc/vault/tls/vault.key"
}

api_addr = "https://vault-1.company.com:8200"
cluster_addr = "https://vault-1.company.com:8201"

ui = true

Créer un cluster HA (3 nodes) :

# Node 1 : Initialiser
vault operator init

# Node 2 & 3 : Rejoindre le cluster
vault operator raft join https://vault-1.company.com:8200

# Vérifier le cluster
vault operator raft list-peers

Node       Address                  State     Voter
----       -------                  -----     -----
vault-1    vault-1.company.com:8201 leader    true
vault-2    vault-2.company.com:8201 follower  true
vault-3    vault-3.company.com:8201 follower  true

Section 3 : Les Moteurs de Secrets (Secrets Engines)

1. KV (Key-Value) v2 : Stockage Statique Versionné

Le secret engine le plus utilisé (équivalent d'un coffre-fort).

# Activer le secret engine KV v2
vault secrets enable -path=secret kv-v2

# Écrire un secret
vault kv put secret/database/config \
  username="admin" \
  password="SuperSecret123" \
  host="db.internal.company.com"

# Lire le secret
vault kv get secret/database/config

# Output
====== Data ======
Key         Value
---         -----
host        db.internal.company.com
password    SuperSecret123
username    admin

Versioning (rollback en cas d'erreur) :

# Mettre à jour le secret (crée la version 2)
vault kv put secret/database/config password="NewPassword456"

# Voir l'historique des versions
vault kv metadata get secret/database/config

# Output
======= Metadata =======
Key              Value
---              -----
created_time     2024-01-15T10:30:00Z
current_version  2
oldest_version   1
versions         1, 2

# Rollback vers la version 1
vault kv rollback -version=1 secret/database/config

# Supprimer définitivement une version (GDPR compliance)
vault kv destroy -versions=2 secret/database/config

Politique d'accès (read-only) :

# policy-readonly.hcl
path "secret/data/database/*" {
  capabilities = ["read", "list"]
}

# Appliquer la policy
vault policy write readonly policy-readonly.hcl

2. PKI (Public Key Infrastructure) : Certificats TLS à la Volée

Remplace les Certificate Authorities (CA) internes.

# Activer le PKI engine
vault secrets enable pki

# Configurer le TTL max (10 ans)
vault secrets tune -max-lease-ttl=87600h pki

# Générer le Root CA
vault write pki/root/generate/internal \
  common_name="Company Internal Root CA" \
  ttl=87600h

# Configurer les URLs du CA
vault write pki/config/urls \
  issuing_certificates="https://vault.company.com:8200/v1/pki/ca" \
  crl_distribution_points="https://vault.company.com:8200/v1/pki/crl"

# Créer un rôle pour générer des certificats (*.company.com)
vault write pki/roles/company-dot-com \
  allowed_domains="company.com" \
  allow_subdomains=true \
  max_ttl="720h"  # 30 jours

Générer un certificat pour un service :

# Demander un certificat pour api.company.com
vault write pki/issue/company-dot-com \
  common_name="api.company.com" \
  ttl="720h"

# Output
Key                 Value
---                 -----
certificate         -----BEGIN CERTIFICATE-----
                    MIIDXTCCAkWgAwIBAgIUZQ3...
                    -----END CERTIFICATE-----
private_key         -----BEGIN RSA PRIVATE KEY-----
                    MIIEpAIBAAKCAQEA1tZ3K...
                    -----END RSA PRIVATE KEY-----
ca_chain            [-----BEGIN CERTIFICATE-----
                    MIIDNTCCAh2gAwIBAgIUf...
                    -----END CERTIFICATE-----]

Cas d'usage (mTLS entre microservices) :

# kubernetes/deployment.yaml
apiVersion: v1
kind: Pod
metadata:
  name: api-service
  annotations:
    vault.hashicorp.com/agent-inject: "true"
    vault.hashicorp.com/role: "api-service"
    vault.hashicorp.com/agent-inject-secret-tls.crt: "pki/issue/company-dot-com"
    vault.hashicorp.com/agent-inject-template-tls.crt: |
      {{- with secret "pki/issue/company-dot-com" "common_name=api.company.com" }}
      {{ .Data.certificate }}
      {{- end }}
spec:
  containers:
  - name: api
    image: api-service:latest
    volumeMounts:
    - name: vault-tls
      mountPath: /vault/secrets

Avantages PKI avec Vault : - ✅ Certificats générés en 100ms (vs 2 semaines avec une CA classique) - ✅ Rotation automatique (TTL court = meilleure sécurité) - ✅ Révocation instantanée - ✅ Audit de tous les certificats émis

3. Database : Rotation Automatique des Mots de Passe

Configuration PostgreSQL :

# Activer le database engine
vault secrets enable database

# Configurer la connexion à PostgreSQL
vault write database/config/my-postgresql-database \
  plugin_name=postgresql-database-plugin \
  allowed_roles="readonly,readwrite" \
  connection_url="postgresql://{{username}}:{{password}}@postgres:5432/mydb?sslmode=require" \
  username="vault_admin" \
  password="VaultAdminPassword"

# Créer un rôle "readonly" (génère des users temporaires)
vault write database/roles/readonly \
  db_name=my-postgresql-database \
  creation_statements="CREATE ROLE \"{{name}}\" WITH LOGIN PASSWORD '{{password}}' VALID UNTIL '{{expiration}}'; \
    GRANT SELECT ON ALL TABLES IN SCHEMA public TO \"{{name}}\";" \
  default_ttl="1h" \
  max_ttl="24h"

Utilisation par une application :

# app.py
import hvac
import psycopg2

# Se connecter à Vault
client = hvac.Client(url='https://vault.company.com:8200')
client.auth.approle.login(role_id='...', secret_id='...')

# Demander des credentials temporaires
db_creds = client.secrets.database.generate_credentials(name='readonly')

# Utiliser les credentials (valides 1h)
conn = psycopg2.connect(
    host="postgres",
    database="mydb",
    user=db_creds['data']['username'],  # v-token-readonly-abc123...
    password=db_creds['data']['password']
)

# Après 1h : Le user PostgreSQL est automatiquement supprimé

Rotation du mot de passe Root :

# Vault peut changer son propre mot de passe de connexion (zéro downtime)
vault write -f database/rotate-root/my-postgresql-database

# → Le mot de passe "vault_admin" est changé
# → Personne ne connaît le nouveau mot de passe (pas même l'admin)
# → Vault est le seul à avoir accès

Section 4 : Authentification & Policies (RBAC)

Auth Methods : Comment se Connecter à Vault

1. Token Auth (Admin / Bootstrap)

# Se connecter avec le root token (DANGEREUX)
vault login hvs.CAESIKqP3mN2vB9kXyZ1...

# Créer un token temporaire pour un collègue (TTL 8h)
vault token create -ttl=8h -policy=readonly

# Output
Key                  Value
---                  -----
token                hvs.CAESIJ3kP9mVzQ2nBxR...
token_accessor       abc123def456
token_duration       8h
token_policies       [readonly]

# Révoquer le root token (OBLIGATOIRE en production)
vault token revoke <ROOT_TOKEN>

2. AppRole (Machines / CI/CD / Ansible)

Le standard pour l'authentification des applications.

# Activer AppRole auth
vault auth enable approle

# Créer un rôle pour Ansible
vault write auth/approle/role/ansible \
  token_policies="ansible-policy" \
  token_ttl=1h \
  token_max_ttl=4h

# Récupérer le RoleID (identifiant public)
vault read auth/approle/role/ansible/role-id

# Output
role_id: 12345678-abcd-1234-abcd-1234567890ab

# Générer un SecretID (mot de passe temporaire)
vault write -f auth/approle/role/ansible/secret-id

# Output
secret_id: 98765432-dcba-4321-dcba-0987654321ba
secret_id_ttl: 0s

Utilisation dans Ansible :

# playbook.yml
- name: Get secret from Vault
  hosts: localhost
  vars:
    vault_addr: "https://vault.company.com:8200"
    role_id: "12345678-abcd-1234-abcd-1234567890ab"
    secret_id: "{{ lookup('env', 'VAULT_SECRET_ID') }}"  # From CI/CD secret
  tasks:
    - name: Authenticate to Vault
      uri:
        url: "{{ vault_addr }}/v1/auth/approle/login"
        method: POST
        body_format: json
        body:
          role_id: "{{ role_id }}"
          secret_id: "{{ secret_id }}"
      register: vault_login

    - name: Get database password
      uri:
        url: "{{ vault_addr }}/v1/secret/data/database/config"
        method: GET
        headers:
          X-Vault-Token: "{{ vault_login.json.auth.client_token }}"
      register: db_secret

    - debug:
        msg: "DB Password: {{ db_secret.json.data.data.password }}"

3. Kubernetes Auth (Pods)

Authentification via ServiceAccount (le plus élégant).

# Activer Kubernetes auth
vault auth enable kubernetes

# Configurer la connexion au cluster K8s
vault write auth/kubernetes/config \
  kubernetes_host="https://kubernetes.default.svc:443" \
  kubernetes_ca_cert=@/var/run/secrets/kubernetes.io/serviceaccount/ca.crt \
  token_reviewer_jwt=@/var/run/secrets/kubernetes.io/serviceaccount/token

# Créer un rôle lié à un ServiceAccount
vault write auth/kubernetes/role/myapp \
  bound_service_account_names=myapp-sa \
  bound_service_account_namespaces=production \
  policies=myapp-policy \
  ttl=1h

Déploiement Kubernetes (Agent Injector) :

# deployment.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
  name: myapp-sa
  namespace: production
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: myapp
  namespace: production
spec:
  template:
    metadata:
      annotations:
        # Activer l'injection Vault
        vault.hashicorp.com/agent-inject: "true"
        vault.hashicorp.com/role: "myapp"

        # Injecter le secret database/config
        vault.hashicorp.com/agent-inject-secret-db.env: "secret/data/database/config"

        # Template pour formater le secret en .env
        vault.hashicorp.com/agent-inject-template-db.env: |
          {{- with secret "secret/data/database/config" }}
          export DB_HOST="{{ .Data.data.host }}"
          export DB_USER="{{ .Data.data.username }}"
          export DB_PASS="{{ .Data.data.password }}"
          {{- end }}
    spec:
      serviceAccountName: myapp-sa
      containers:
      - name: app
        image: myapp:latest
        command: ["/bin/sh"]
        args:
          - -c
          - |
            source /vault/secrets/db.env
            ./start-app.sh

Ce qui se passe : 1. Vault Agent Injector ajoute un sidecar au pod 2. Le sidecar s'authentifie avec le ServiceAccount JWT 3. Il récupère les secrets et les écrit dans /vault/secrets/ 4. L'app lit les secrets (jamais d'API call dans le code)

Policies (HCL) : RBAC Granulaire

Exemple de policy read-only stricte :

# policy-developer.hcl

# Lire les secrets dev uniquement
path "secret/data/dev/*" {
  capabilities = ["read", "list"]
}

# Interdire l'accès à la prod
path "secret/data/prod/*" {
  capabilities = ["deny"]
}

# Créer des secrets temporaires (auto-expiration)
path "secret/data/dev/temp/*" {
  capabilities = ["create", "update", "delete"]

  # Limiter le TTL à 1h
  allowed_parameters = {
    "ttl" = ["1h"]
  }
}

# Lire ses propres métadonnées de token
path "auth/token/lookup-self" {
  capabilities = ["read"]
}

# Renouveler son token
path "auth/token/renew-self" {
  capabilities = ["update"]
}

Appliquer la policy :

vault policy write developer policy-developer.hcl

# Créer un token avec cette policy
vault token create -policy=developer

# Tester la policy
vault login <TOKEN>
vault kv get secret/dev/api-key     # ✅ Autorisé
vault kv get secret/prod/api-key    # ❌ Permission denied

Policy avancée (Sentinel pour SecNumCloud) :

# policy-prod-admin.hcl

# Accès complet à la prod (mais audit obligatoire)
path "secret/data/prod/*" {
  capabilities = ["create", "read", "update", "delete", "list"]

  # Forcer le Multi-Factor Authentication (MFA)
  mfa_methods = ["totp"]

  # Logging renforcé
  control_group = {
    ttl = "4h"
    authorizations_required = 2  # 2 approbations nécessaires
  }
}

# Interdire la suppression des secrets de paiement
path "secret/data/prod/payment/*" {
  capabilities = ["read", "list"]

  # Deny deletion
  denied_parameters = {
    "delete" = ["*"]
  }
}

Section 5 : Intégration DevOps (Les Patterns)

Pattern 1 : Ansible avec hashi_vault Lookup

Ne jamais écrire de secret dans un playbook.

# playbook.yml
- name: Deploy application with Vault secrets
  hosts: webservers
  vars:
    vault_addr: "https://vault.company.com:8200"
  tasks:
    - name: Get database password from Vault
      set_fact:
        db_password: "{{ lookup('hashi_vault', 'secret=secret/data/database/config:password auth_method=approle role_id={{ role_id }} secret_id={{ secret_id }} url={{ vault_addr }}') }}"
      no_log: true  # Ne pas logger le mot de passe

    - name: Configure application
      template:
        src: app.conf.j2
        dest: /etc/app/app.conf
        mode: '0600'
      vars:
        database_password: "{{ db_password }}"

Template app.conf.j2 :

# app.conf.j2
[database]
host = {{ database_host }}
username = {{ database_user }}
password = {{ database_password }}  # Injecté depuis Vault

Avantages : - ✅ Pas de secret dans Git - ✅ Rotation sans redéploiement (relancer le playbook) - ✅ Audit Vault de chaque accès

Pattern 2 : Kubernetes Agent Injector (Sidecar)

Vault K8s Agent Injector Sidecar

Architecture :

┌─────────────────────────────────────────┐
│               Pod                        │
│  ┌────────────┐      ┌────────────┐     │
│  │  Vault     │      │            │     │
│  │  Agent     │─────>│  App       │     │
│  │  (Sidecar) │      │  Container │     │
│  └────────────┘      └────────────┘     │
│        │                    │            │
│        │ Writes secrets     │            │
│        ▼                    ▼            │
│  /vault/secrets/      Reads files       │
│    ├─ db.env                             │
│    └─ api-key.txt                        │
└─────────────────────────────────────────┘
   HashiCorp Vault

Exemple complet :

# deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: webapp
  namespace: production
spec:
  replicas: 3
  selector:
    matchLabels:
      app: webapp
  template:
    metadata:
      labels:
        app: webapp
      annotations:
        # Activer l'injection
        vault.hashicorp.com/agent-inject: "true"
        vault.hashicorp.com/role: "webapp"

        # Injecter la DB config
        vault.hashicorp.com/agent-inject-secret-db.json: "secret/data/database/config"
        vault.hashicorp.com/agent-inject-template-db.json: |
          {{- with secret "secret/data/database/config" }}
          {
            "host": "{{ .Data.data.host }}",
            "username": "{{ .Data.data.username }}",
            "password": "{{ .Data.data.password }}"
          }
          {{- end }}

        # Injecter une API key
        vault.hashicorp.com/agent-inject-secret-api-key.txt: "secret/data/api/stripe"
        vault.hashicorp.com/agent-inject-template-api-key.txt: |
          {{- with secret "secret/data/api/stripe" }}
          {{ .Data.data.key }}
          {{- end }}

        # Auto-rotation toutes les 5 minutes
        vault.hashicorp.com/agent-cache-enable: "true"
        vault.hashicorp.com/agent-cache-ttl: "5m"

    spec:
      serviceAccountName: webapp-sa
      containers:
      - name: webapp
        image: webapp:latest
        env:
        - name: DB_CONFIG_FILE
          value: /vault/secrets/db.json
        - name: STRIPE_API_KEY_FILE
          value: /vault/secrets/api-key.txt

Code application (lecture des secrets) :

# app.py
import json
import os

# Lire la config DB (injectée par Vault)
with open(os.getenv('DB_CONFIG_FILE')) as f:
    db_config = json.load(f)

# Lire l'API key Stripe
with open(os.getenv('STRIPE_API_KEY_FILE')) as f:
    stripe_api_key = f.read().strip()

# Utiliser les secrets
import psycopg2
conn = psycopg2.connect(
    host=db_config['host'],
    user=db_config['username'],
    password=db_config['password']
)

Avantages : - ✅ Aucun code Vault dans l'application - ✅ Secrets mis à jour automatiquement - ✅ Zero-trust (ServiceAccount = identité forte)

Pattern 3 : CI/CD (GitLab/GitHub Actions)

GitHub Actions avec Vault :

# .github/workflows/deploy.yml
name: Deploy to Production

on:
  push:
    branches: [main]

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3

      # Importer les secrets depuis Vault
      - name: Import Secrets from Vault
        uses: hashicorp/vault-action@v2
        with:
          url: https://vault.company.com:8200
          method: approle
          roleId: ${{ secrets.VAULT_ROLE_ID }}
          secretId: ${{ secrets.VAULT_SECRET_ID }}
          secrets: |
            secret/data/aws/production access_key | AWS_ACCESS_KEY_ID ;
            secret/data/aws/production secret_key | AWS_SECRET_ACCESS_KEY ;
            secret/data/docker/registry password | DOCKER_PASSWORD

      # Les secrets sont maintenant disponibles comme variables d'environnement
      - name: Configure AWS CLI
        run: |
          aws configure set aws_access_key_id $AWS_ACCESS_KEY_ID
          aws configure set aws_secret_access_key $AWS_SECRET_ACCESS_KEY

      - name: Deploy to ECS
        run: |
          ./deploy.sh

GitLab CI avec Vault :

# .gitlab-ci.yml
deploy_production:
  stage: deploy
  image: vault:latest
  script:
    # Authentification avec JWT (GitLab OIDC)
    - export VAULT_TOKEN=$(vault write -field=token auth/jwt/login role=gitlab-ci jwt=$CI_JOB_JWT)

    # Récupérer les secrets
    - export DB_PASSWORD=$(vault kv get -field=password secret/database/prod)
    - export API_KEY=$(vault kv get -field=key secret/api/stripe)

    # Déployer
    - ./deploy.sh
  only:
    - main

Référence Rapide

Commandes Essentielles

# État du serveur
vault status                          # Vérifier si Vault est unsealed
vault operator unseal                 # Unsealer Vault (3x avec différentes clés)
vault operator seal                   # Sealer Vault (urgence)

# Authentification
vault login <TOKEN>                   # Se connecter avec un token
vault token lookup                    # Voir les infos de son token
vault token renew                     # Renouveler son token

# KV Secrets
vault kv put secret/path key=value    # Écrire un secret
vault kv get secret/path              # Lire un secret
vault kv get -field=key secret/path   # Lire une clé spécifique
vault kv list secret/                 # Lister les secrets
vault kv delete secret/path           # Soft delete (récupérable)
vault kv destroy -versions=1 secret/path  # Hard delete (définitif)
vault kv metadata get secret/path     # Voir l'historique

# Policies
vault policy list                     # Lister les policies
vault policy read <NAME>              # Voir une policy
vault policy write <NAME> file.hcl    # Créer/Mettre à jour une policy

# Lease Management
vault lease lookup <LEASE_ID>         # Voir les détails d'un lease
vault lease renew <LEASE_ID>          # Renouveler un lease
vault lease revoke <LEASE_ID>         # Révoquer un lease
vault lease revoke -prefix path/      # Révoquer tous les leases d'un path

# Secrets Engines
vault secrets list                    # Lister les engines actifs
vault secrets enable <TYPE>           # Activer un engine
vault secrets disable <PATH>          # Désactiver un engine

# Auth Methods
vault auth list                       # Lister les méthodes d'auth
vault auth enable <TYPE>              # Activer une méthode
vault auth disable <PATH>             # Désactiver une méthode

# High Availability
vault operator raft list-peers        # Voir les nodes du cluster
vault operator raft join <ADDR>       # Rejoindre un cluster
vault operator step-down              # Forcer l'élection d'un nouveau leader

Secrets Engines Courants

Engine Usage Commande d'activation
kv-v2 Secrets statiques versionnés vault secrets enable -path=secret kv-v2
database Credentials dynamiques BDD vault secrets enable database
pki Certificats TLS/mTLS vault secrets enable pki
transit Encryption as a Service vault secrets enable transit
ssh Clés SSH temporaires vault secrets enable ssh
aws IAM credentials dynamiques vault secrets enable aws
azure Azure credentials vault secrets enable azure
gcp GCP service accounts vault secrets enable gcp

Auth Methods par Cas d'Usage

Cas d'Usage Auth Method Commande
Humain Token, OIDC, LDAP vault auth enable oidc
CI/CD AppRole, JWT vault auth enable approle
Kubernetes Kubernetes vault auth enable kubernetes
Cloud AWS IAM, Azure MSI vault auth enable aws
Terraform Token, AppRole vault token create

Dépannage

Problème Diagnostic Solution
Error: Vault is sealed Vault verrouillé après reboot vault operator unseal (3x)
Permission denied Policy insuffisante Vérifier avec vault token lookup
Invalid lease ID Lease expiré ou révoqué Régénérer le secret
Connection refused Vault pas démarré systemctl start vault
TLS certificate verify failed Certificat invalide export VAULT_SKIP_VERIFY=1 (dev only)

Ressources

Documentation Officielle : - HashiCorp Vault Docs - Vault Tutorials - Vault API Reference

Formations : - HashiCorp Certified: Vault Associate - Vault on Kubernetes

Outils Complémentaires : - vault-k8s : Helm chart officiel - external-secrets : Sync Vault → Kubernetes Secrets - vaultenv : Charger secrets Vault comme variables d'environnement - consul-template : Templates dynamiques avec secrets Vault

Bonnes Pratiques SecNumCloud : - ANSSI - Recommandations de sécurité - SecNumCloud Référentiel


Next Steps : - Déployer Vault en HA avec Raft (3+ nodes) - Configurer Auto-Unseal avec Cloud KMS - Implémenter le pattern Agent Injector sur Kubernetes - Migrer tous les secrets statiques vers Vault - Activer l'audit logging (fichier + Splunk/ELK) - Mettre en place des Control Groups (approbation multi-parties)


Voir aussi