Gestion des Secrets dans les Pipelines CI/CD : Patterns, Anti-Patterns et Intégration Vault

Introduction : Pourquoi les Secrets Sont la Cause N°1 de Compromission CI/CD

Si vous examinez la cause profonde de presque chaque brèche majeure CI/CD de ces dernières années — de l’attaque de la chaîne d’approvisionnement Codecov à l’incident de sécurité CircleCI — vous trouverez le même coupable : des secrets compromis. Clés API, identifiants cloud, mots de passe de bases de données, certificats de signature — ce sont les passe-partout que les attaquants recherchent, et les pipelines CI/CD sont l’endroit où ils concentrent leurs efforts.

La raison est structurelle. Les pipelines occupent une position singulièrement dangereuse : ils doivent avoir accès aux identifiants de production pour déployer les logiciels, tout en étant par nature éphémères, multi-tenants et exposés à du code non fiable. Chaque pull request, chaque mise à jour de dépendance, chaque push d’un contributeur déclenche l’exécution du pipeline — et chaque exécution est un vecteur potentiel d’exfiltration de secrets.

Le défi ne se résume pas à « ne pas mettre de secrets dans le code ». Il est bien plus profond. Comment donner à un environnement de calcul éphémère et jetable l’accès à vos identifiants les plus sensibles sans que ces identifiants ne fuient dans les logs, les artefacts, les jobs en aval ou entre les mains d’acteurs malveillants ? C’est la question à laquelle ce guide répond.

Nous couvrirons comment les secrets sont exposés, comment les injecter en toute sécurité, comment intégrer HashiCorp Vault et la fédération d’identité cloud-native, et quels anti-patterns éviter. Ceci est un guide de praticien — attendez-vous à du vrai YAML, de vraies commandes CLI et de vraies décisions architecturales.

Comment les Secrets Sont Exposés dans les Pipelines CI/CD

Avant de discuter des solutions, nous devons comprendre le paysage des menaces. Les secrets fuient des pipelines par plusieurs vecteurs bien documentés.

Secrets Codés en Dur dans les Configs de Pipeline et l’IaC

Le vecteur de fuite le plus basique — et encore étonnamment courant — est celui des identifiants codés en dur directement dans les fichiers de configuration de pipeline ou les templates d’Infrastructure as Code. Un développeur testant un déploiement pourrait insérer une clé d’accès AWS dans un fichier .github/workflows/deploy.yml ou un fichier Terraform main.tf, le commiter et l’oublier. Même supprimé dans un commit ultérieur, le secret vit éternellement dans l’historique Git.

# NE FAITES JAMAIS CELA — identifiants codés en dur dans un fichier workflow
jobs:
  deploy:
    runs-on: ubuntu-latest
    env:
      AWS_ACCESS_KEY_ID: AKIAIOSFODNN7EXAMPLE
      AWS_SECRET_ACCESS_KEY: wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
    steps:
      - run: aws s3 sync ./build s3://my-bucket

Secrets dans les Variables d’Environnement Affichés dans les Logs

Les plateformes CI injectent généralement les secrets sous forme de variables d’environnement. Le problème survient lorsque les étapes du pipeline affichent par inadvertance ces variables sur stdout. Une commande env négligente, un printenv de débogage, ou un outil verbeux qui affiche sa configuration peuvent exposer des secrets dans les logs de build qui sont souvent conservés pendant des jours ou des semaines et accessibles à tous les membres du projet.

# Dangereux : cela affiche TOUTES les variables d'environnement, y compris les secrets
- run: printenv | sort

# Également dangereux : les flags verbeux des outils qui affichent la config
- run: terraform plan -debug

Secrets Persistants dans les Artefacts de Build ou les Couches de Conteneur

Un secret injecté pendant un build Docker peut persister dans une couche intermédiaire même après avoir été supprimé dans une instruction RUN ultérieure. De même, les artefacts de build — JARs, ZIPs, binaires compilés — peuvent embarquer des fichiers de configuration contenant des identifiants qui étaient présents au moment du build.

# MAUVAIS : Le secret persiste dans la couche créée par l'instruction COPY
COPY .env /app/.env
RUN /app/setup.sh
RUN rm /app/.env   # Trop tard — il est toujours dans une couche précédente

Secrets Accessibles aux Workflows de PR Non Fiables

C’est l’un des vecteurs les plus dangereux, particulièrement dans les projets open-source. GitHub Actions, par exemple, ne fournit pas de secrets aux workflows déclenchés par pull_request depuis des forks — par conception. Cependant, l’événement pull_request_target a accès aux secrets, et si le workflow fait un checkout et exécute le code de l’auteur de la PR, cela crée un chemin direct d’exfiltration des secrets.

Portées de Secrets Trop Larges

De nombreuses organisations configurent les secrets au niveau de l’organisation ou du groupe alors qu’ils devraient être limités à des dépôts individuels ou des environnements. Un secret au niveau de l’organisation dans GitHub Actions est disponible pour chaque dépôt de cette organisation. Si l’un de ces dépôts est compromis — ou a simplement un workflow mal configuré — tous les secrets au niveau de l’organisation sont en danger.

Patterns d’Injection de Secrets

Maintenant que nous comprenons comment les secrets fuient, examinons comment les intégrer dans les pipelines en toute sécurité.

Secrets Natifs de la Plateforme

Chaque grande plateforme CI/CD fournit un mécanisme intégré de gestion des secrets. GitHub Actions dispose de secrets au niveau du dépôt, de l’environnement et de l’organisation. GitLab CI dispose de variables CI/CD au niveau du projet et du groupe avec masquage et protection optionnels. Ce sont le point de départ le plus simple.

# GitHub Actions : référencer un secret de dépôt
jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - name: Deploy to production
        env:
          API_KEY: ${{ secrets.PRODUCTION_API_KEY }}
        run: ./deploy.sh
# GitLab CI : utiliser une variable masquée et protégée
deploy:
  stage: deploy
  script:
    - echo "Deploying with masked credentials"
    - ./deploy.sh
  variables:
    API_KEY: $PRODUCTION_API_KEY
  only:
    - main

Les secrets natifs de la plateforme sont adéquats pour de nombreux cas d’usage, mais ils présentent des limitations significatives : pas de génération dynamique, journalisation d’audit limitée, rotation manuelle et pas de gestion centralisée sur plusieurs plateformes.

Gestionnaires de Secrets Externes

Pour les organisations ayant des exigences de sécurité matures, les gestionnaires de secrets externes — HashiCorp Vault, AWS Secrets Manager, GCP Secret Manager, Azure Key Vault — fournissent un contrôle centralisé, une journalisation d’audit, la génération dynamique de secrets, la rotation automatique et des politiques d’accès granulaires. Nous approfondirons l’intégration de Vault dans la section suivante.

Injection Just-in-Time vs Secrets Pré-chargés

Les secrets pré-chargés sont configurés une fois et rendus disponibles pour toutes les exécutions du pipeline. C’est le fonctionnement de la plupart des secrets natifs de plateforme. L’injection just-in-time (JIT) récupère les secrets au moment où ils sont nécessaires, souvent avec des TTL courts. L’injection JIT est supérieure car elle réduit la fenêtre d’exposition, permet l’utilisation d’identifiants dynamiques et fournit des traces d’audit par exécution.

# Injection JIT : récupérer le secret uniquement quand c'est nécessaire
- name: Get database credentials
  run: |
    DB_CREDS=$(vault kv get -format=json secret/data/myapp/db)
    export DB_USER=$(echo $DB_CREDS | jq -r '.data.data.username')
    export DB_PASS=$(echo $DB_CREDS | jq -r '.data.data.password')
    ./run-migrations.sh

Secrets Masqués vs Chiffrés

Une idée reçue courante : « masqué » ne signifie pas « sécurisé ». Lorsque GitHub Actions masque un secret, il effectue un remplacement de chaîne dans la sortie des logs. Si la valeur du secret est courte (par exemple, un token de 4 caractères), le masquage peut ne pas s’activer. Si le secret est encodé en base64 ou transformé de quelque manière que ce soit, la valeur transformée ne sera pas masquée. Le masquage est une commodité, pas une frontière de sécurité. Les secrets chiffrés au repos (que toutes les grandes plateformes fournissent) protègent contre la compromission du stockage côté plateforme mais ne font rien pour empêcher l’exfiltration à l’exécution.

Intégrer HashiCorp Vault avec le CI/CD

HashiCorp Vault est le gestionnaire de secrets externe le plus largement adopté pour les pipelines CI/CD. Il prend en charge plusieurs méthodes d’authentification adaptées aux systèmes automatisés, la génération dynamique de secrets et des politiques granulaires. Voici comment l’intégrer avec les deux plateformes CI/CD les plus courantes.

Authentification Vault AppRole pour les Runners CI

AppRole est la méthode d’authentification orientée machine de Vault. Elle utilise un Role ID (comme un nom d’utilisateur) et un Secret ID (comme un mot de passe) pour s’authentifier. Le Secret ID peut être configuré pour un usage unique et avec un TTL, ce qui le rend adapté aux runners CI.

# Activer la méthode d'authentification AppRole
vault auth enable approle

# Créer une politique pour le CI
vault policy write ci-deploy - <<EOF
path "secret/data/myapp/*" {
  capabilities = ["read"]
}
path "database/creds/myapp-role" {
  capabilities = ["read"]
}
EOF

# Créer un AppRole avec la politique CI
vault write auth/approle/role/ci-deploy \
  token_policies="ci-deploy" \
  token_ttl=15m \
  token_max_ttl=30m \
  secret_id_ttl=10m \
  secret_id_num_uses=1

# Récupérer le Role ID (stocker dans la plateforme CI comme variable non sensible)
vault read auth/approle/role/ci-deploy/role-id

# Générer un Secret ID à usage unique (stocker dans la plateforme CI comme secret)
vault write -f auth/approle/role/ci-deploy/secret-id

Authentification Vault JWT/OIDC avec GitHub Actions

L’approche moderne et préférée pour GitHub Actions est l’authentification JWT/OIDC. GitHub Actions peut émettre un token OIDC pour chaque exécution de workflow, et Vault peut valider ce token pour authentifier le pipeline — éliminant le besoin de stocker des identifiants Vault dans GitHub.

# Configurer l'authentification JWT de Vault pour GitHub Actions
vault auth enable jwt

vault write auth/jwt/config \
  bound_issuer="https://token.actions.githubusercontent.com" \
  oidc_discovery_url="https://token.actions.githubusercontent.com"

# Créer un rôle lié à un dépôt et une branche spécifiques
vault write auth/jwt/role/github-deploy \
  role_type="jwt" \
  bound_audiences="https://github.com/my-org" \
  bound_claims_type="glob" \
  bound_claims='{"sub": "repo:my-org/my-repo:ref:refs/heads/main"}' \
  user_claim="repository_owner" \
  token_policies="ci-deploy" \
  token_ttl="10m"

Ensuite, dans votre workflow GitHub Actions, utilisez hashicorp/vault-action :

jobs:
  deploy:
    runs-on: ubuntu-latest
    permissions:
      id-token: write
      contents: read
    steps:
      - name: Import secrets from Vault
        uses: hashicorp/vault-action@v3
        with:
          url: https://vault.mycompany.com
          method: jwt
          role: github-deploy
          jwtGithubAudience: https://github.com/my-org
          secrets: |
            secret/data/myapp/db username | DB_USER ;
            secret/data/myapp/db password | DB_PASS

      - name: Run deployment
        run: |
          echo "Deploying with fetched credentials"
          ./deploy.sh

Authentification JWT de Vault avec GitLab CI

GitLab CI offre un support natif pour l’intégration Vault en utilisant id_tokens. GitLab peut générer un JWT que Vault valide, de manière similaire à l’approche GitHub Actions.

# Configurer Vault pour l'authentification JWT GitLab
vault auth enable -path=gitlab jwt

vault write auth/gitlab/config \
  bound_issuer="https://gitlab.com" \
  jwks_url="https://gitlab.com/-/jwks" \
  supported_algs="RS256"

vault write auth/gitlab/role/gitlab-deploy \
  role_type="jwt" \
  bound_claims='{"project_id": "12345", "ref_protected": "true"}' \
  user_claim="user_email" \
  token_policies="ci-deploy" \
  token_ttl="10m"

Et dans votre .gitlab-ci.yml :

deploy:
  stage: deploy
  id_tokens:
    VAULT_ID_TOKEN:
      aud: https://vault.mycompany.com
  secrets:
    DB_USER:
      vault: myapp/db/username@secret
      token: $VAULT_ID_TOKEN
    DB_PASS:
      vault: myapp/db/password@secret
      token: $VAULT_ID_TOKEN
  script:
    - ./deploy.sh

Secrets Dynamiques

L’une des fonctionnalités les plus puissantes de Vault est la génération dynamique de secrets. Au lieu de stocker des mots de passe de base de données statiques, Vault peut générer des identifiants à courte durée de vie à la demande. Lorsque le pipeline se termine, les identifiants expirent automatiquement.

# Activer le moteur de secrets de base de données
vault secrets enable database

# Configurer une connexion PostgreSQL
vault write database/config/myapp-db \
  plugin_name=postgresql-database-plugin \
  connection_url="postgresql://{{username}}:{{password}}@db.mycompany.com:5432/myapp" \
  allowed_roles="myapp-role" \
  username="vault_admin" \
  password="vault_admin_password"

# Créer un rôle qui génère des identifiants avec un TTL d'1 heure
vault write database/roles/myapp-role \
  db_name=myapp-db \
  creation_statements="CREATE ROLE \"{{name}}\" WITH LOGIN PASSWORD '{{password}}' VALID UNTIL '{{expiration}}'; GRANT SELECT, INSERT, UPDATE ON ALL TABLES IN SCHEMA public TO \"{{name}}\";" \
  default_ttl="1h" \
  max_ttl="2h"

# Dans votre pipeline, récupérez les identifiants dynamiques
# vault read database/creds/myapp-role
# Retourne une paire nom d'utilisateur/mot de passe unique valide pendant 1 heure

Les secrets dynamiques éliminent entièrement le problème de la rotation des identifiants. Chaque exécution de pipeline obtient ses propres identifiants uniques, et les identifiants compromis expirent automatiquement.

Identifiants à Courte Durée de Vie et Workload Identity

L’avancée la plus significative en matière de gestion des secrets CI/CD ces dernières années est la fédération de workload identity — la capacité pour une plateforme CI/CD de s’authentifier directement auprès d’un fournisseur cloud en utilisant sa propre identité, sans aucun identifiant stocké.

GitHub Actions OIDC avec AWS

GitHub Actions peut assumer un rôle AWS IAM directement en utilisant la fédération OIDC. Aucune clé d’accès AWS n’est stockée nulle part.

# D'abord, créer un fournisseur d'identité OIDC dans AWS (via Terraform)
resource "aws_iam_openid_connect_provider" "github" {
  url             = "https://token.actions.githubusercontent.com"
  client_id_list  = ["sts.amazonaws.com"]
  thumbprint_list = ["6938fd4d98bab03faadb97b34396831e3780aea1"]
}

# Créer un rôle IAM que GitHub Actions peut assumer
resource "aws_iam_role" "github_actions" {
  name = "github-actions-deploy"

  assume_role_policy = jsonencode({
    Version = "2012-10-17"
    Statement = [{
      Effect = "Allow"
      Principal = {
        Federated = aws_iam_openid_connect_provider.github.arn
      }
      Action = "sts:AssumeRoleWithWebIdentity"
      Condition = {
        StringEquals = {
          "token.actions.githubusercontent.com:aud" = "sts.amazonaws.com"
        }
        StringLike = {
          "token.actions.githubusercontent.com:sub" = "repo:my-org/my-repo:ref:refs/heads/main"
        }
      }
    }]
  })
}
# Workflow GitHub Actions utilisant OIDC
jobs:
  deploy:
    runs-on: ubuntu-latest
    permissions:
      id-token: write
      contents: read
    steps:
      - uses: aws-actions/configure-aws-credentials@v4
        with:
          role-to-assume: arn:aws:iam::123456789012:role/github-actions-deploy
          aws-region: us-east-1
          role-duration-seconds: 900   # 15 minutes

      - name: Deploy
        run: aws s3 sync ./build s3://my-bucket

GitHub Actions OIDC avec GCP

Google Cloud prend en charge le même pattern via Workload Identity Federation.

# Créer un Workload Identity Pool et un Provider (gcloud CLI)
gcloud iam workload-identity-pools create "github-pool" \
  --project="my-project" \
  --location="global" \
  --display-name="GitHub Actions Pool"

gcloud iam workload-identity-pools providers create-oidc "github-provider" \
  --project="my-project" \
  --location="global" \
  --workload-identity-pool="github-pool" \
  --display-name="GitHub Provider" \
  --attribute-mapping="google.subject=assertion.sub,attribute.repository=assertion.repository" \
  --attribute-condition="assertion.repository_owner == 'my-org'" \
  --issuer-uri="https://token.actions.githubusercontent.com"

# Accorder au Workload Identity la capacité d'usurper un compte de service
gcloud iam service-accounts add-iam-policy-binding \
  deploy-sa@my-project.iam.gserviceaccount.com \
  --role="roles/iam.workloadIdentityUser" \
  --member="principalSet://iam.googleapis.com/projects/123456/locations/global/workloadIdentityPools/github-pool/attribute.repository/my-org/my-repo"
# Workflow GitHub Actions pour GCP
jobs:
  deploy:
    runs-on: ubuntu-latest
    permissions:
      id-token: write
      contents: read
    steps:
      - uses: google-github-actions/auth@v2
        with:
          workload_identity_provider: projects/123456/locations/global/workloadIdentityPools/github-pool/providers/github-provider
          service_account: deploy-sa@my-project.iam.gserviceaccount.com

      - name: Deploy to Cloud Run
        run: gcloud run deploy my-service --image=gcr.io/my-project/my-app:latest

Fédération OIDC avec GitLab CI

GitLab CI prend en charge le même pattern de fédération OIDC avec AWS, GCP et Azure. La configuration est similaire — vous configurez le fournisseur cloud pour faire confiance à l’émetteur OIDC de GitLab et liez l’accès à des IDs de projet, des branches ou des environnements spécifiques.

# GitLab CI avec AWS OIDC
assume_role:
  stage: deploy
  id_tokens:
    AWS_OIDC_TOKEN:
      aud: https://sts.amazonaws.com
  script:
    - >
      STS_CREDS=$(aws sts assume-role-with-web-identity
      --role-arn arn:aws:iam::123456789012:role/gitlab-deploy
      --role-session-name "gitlab-ci-${CI_PIPELINE_ID}"
      --web-identity-token "${AWS_OIDC_TOKEN}"
      --duration-seconds 900)
    - export AWS_ACCESS_KEY_ID=$(echo $STS_CREDS | jq -r '.Credentials.AccessKeyId')
    - export AWS_SECRET_ACCESS_KEY=$(echo $STS_CREDS | jq -r '.Credentials.SecretAccessKey')
    - export AWS_SESSION_TOKEN=$(echo $STS_CREDS | jq -r '.Credentials.SessionToken')
    - aws s3 sync ./build s3://my-bucket

Pourquoi les Identifiants à Courte Durée de Vie l’Emportent

Les avantages des identifiants fédérés à courte durée de vie par rapport aux secrets statiques à longue durée de vie sont considérables :

  • Aucun secret à voler. Il n’y a pas d’identifiants stockés à exfiltrer. Le pipeline s’authentifie avec un JWT signé qui n’est valide que pour cette exécution spécifique.
  • Pas de rotation nécessaire. Les identifiants sont générés par exécution et expirent automatiquement. Il n’y a rien à faire tourner.
  • Portée granulaire. L’accès peut être restreint à des dépôts, branches, environnements spécifiques, et même à des jobs de workflow précis.
  • Trace d’audit complète. Les logs du fournisseur cloud montrent exactement quelle exécution de pipeline a accédé à quelles ressources, liée au claim OIDC.
  • Réduction du rayon d’impact. Même si un identifiant est exfiltré d’une manière ou d’une autre, il expire en quelques minutes, pas en plusieurs mois.

Anti-Patterns à Éviter

Savoir ce qu’il ne faut pas faire est aussi important que connaître les bons patterns. Ces anti-patterns sont régulièrement observés dans les environnements de production.

Utiliser des Personal Access Tokens dans le CI

Les personal access tokens (PATs) liés à des comptes de développeurs individuels sont l’un des patterns les plus courants et les plus dangereux. Lorsqu’un développeur quitte l’organisation, son PAT peut continuer à fonctionner. Les PATs ont généralement des permissions larges — bien plus que ce dont le pipeline a besoin. S’ils sont exfiltrés, l’attaquant obtient accès à tout ce à quoi le développeur pouvait accéder.

À la place : Utilisez des comptes machine avec des tokens à portée limitée, ou mieux encore, utilisez des tokens d’installation GitHub App ou la fédération OIDC.

Partager les Secrets entre Environnements

Utiliser le même mot de passe de base de données pour le développement, le staging et la production — ou la même clé API pour tous les environnements — signifie qu’une compromission de votre environnement le moins sécurisé (généralement le dev) donne aux attaquants accès à la production. La séparation des environnements n’a aucun sens si les identifiants sont les mêmes.

À la place : Utilisez des secrets scopés par environnement. Dans GitHub Actions, configurez des environnements de déploiement avec leurs propres magasins de secrets. Dans GitLab, utilisez des variables protégées scopées à des environnements spécifiques.

Ne Pas Faire Tourner les Secrets Après une Exposition

Lorsqu’un secret est accidentellement journalisé, commité dans un dépôt ou exposé dans un artefact de build, de nombreuses équipes se contentent de supprimer le log ou de retirer le commit sans faire tourner l’identifiant. C’est insuffisant. Vous devez considérer que le secret a été observé et le faire tourner immédiatement.

À la place : Traitez toute exposition comme une compromission. Faites la rotation immédiatement. Automatisez la rotation lorsque possible. Utilisez des secrets dynamiques pour rendre le problème sans objet.

Faire Confiance à pull_request_target avec les Secrets

L’événement pull_request_target dans GitHub Actions s’exécute dans le contexte de la branche de base, ce qui signifie qu’il a accès aux secrets. Ceci est prévu pour des opérations sûres comme l’étiquetage des PRs. Cependant, si votre workflow fait un checkout de la ref head de la PR et exécute ce code, vous avez donné à un contributeur externe un accès complet à vos secrets.

# DANGEREUX : Cela donne à l'auteur de la PR accès à tous les secrets du dépôt
on: pull_request_target
jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          ref: ${{ github.event.pull_request.head.sha }}  # Checkout de code non fiable !
      - run: make test  # Exécution de code non fiable avec accès aux secrets !

À la place : Ne faites jamais un checkout et n’exécutez jamais le code d’une PR dans un workflow pull_request_target. Si vous devez exécuter des tests sur du code de PR avec des secrets, utilisez une approche à deux workflows : exécutez le code non fiable dans un workflow pull_request (sans secrets), puis utilisez un déclencheur workflow_run séparé pour les opérations de confiance.

Défense en Profondeur : Une Approche en Couches

Aucun contrôle unique n’est suffisant. Une gestion efficace des secrets nécessite plusieurs couches de défense superposées.

Analyse de Secrets (Secret Scanning)

Implémentez l’analyse à trois étapes :

  • Pré-commit : Utilisez des outils comme gitleaks ou detect-secrets comme hooks de pré-commit pour empêcher les secrets d’entrer dans le dépôt.
  • Dans le pipeline : Exécutez l’analyse de secrets comme étape CI sur chaque pull request. Des outils comme trufflehog peuvent scanner les diffs, l’historique des commits et même les fichiers binaires.
  • Post-commit : Activez l’analyse de secrets intégrée de GitHub ou la détection de secrets de GitLab pour scanner en continu tout le contenu du dépôt et alerter sur les résultats.
# Hook de pré-commit avec gitleaks
# .pre-commit-config.yaml
repos:
  - repo: https://github.com/gitleaks/gitleaks
    rev: v8.21.2
    hooks:
      - id: gitleaks
# Analyse dans le pipeline avec trufflehog
- name: Scan for secrets
  run: |
    docker run --rm -v "$PWD:/repo" trufflesecurity/trufflehog:latest \
      git file:///repo --only-verified --fail

Journalisation d’Audit pour l’Accès aux Secrets

Chaque accès à un secret doit être journalisé. Vault fournit des logs d’audit détaillés par défaut. Les gestionnaires de secrets des fournisseurs cloud (AWS Secrets Manager, GCP Secret Manager) s’intègrent respectivement avec CloudTrail et Cloud Audit Logs. Pour les secrets natifs de la plateforme, activez les fonctionnalités de journalisation d’audit disponibles dans GitHub Enterprise ou GitLab Ultimate.

# Activer la journalisation d'audit de Vault
vault audit enable file file_path=/var/log/vault/audit.log

# Chaque accès génère une entrée de log comme :
# {"type": "response", "auth": {"token_type": "service", "policies": ["ci-deploy"]},
#  "request": {"path": "secret/data/myapp/db", "operation": "read"}, ...}

Portée au Moindre Privilège

Appliquez le principe du moindre privilège de manière agressive :

  • Limitez les secrets au dépôt spécifique qui en a besoin, pas à l’organisation.
  • Utilisez des secrets au niveau de l’environnement pour que les identifiants de production ne soient disponibles qu’aux workflows qui déploient en production.
  • Configurez la protection des branches pour que seuls les workflows exécutés sur des branches protégées puissent accéder aux secrets de production.
  • Dans Vault, écrivez des politiques qui accordent l’accès au chemin le plus restreint possible avec des capacités en lecture seule.
# Politique Vault : accès minimal pour le CI d'un microservice spécifique
path "secret/data/payments-service/production" {
  capabilities = ["read"]
}

# Refuser l'accès à tout le reste par défaut (comportement par défaut de Vault)
# Pas de wildcards, pas de chemins larges

Rotation Automatisée

Les secrets statiques doivent être soumis à une rotation régulière et immédiatement après toute exposition suspectée. Automatisez ce processus :

  • Utilisez les secrets dynamiques de Vault pour éliminer entièrement le besoin de rotation.
  • Pour les secrets qui doivent être statiques (par exemple, des clés API tierces), utilisez la rotation intégrée d’AWS Secrets Manager avec des fonctions Lambda ou des solutions cloud-native similaires.
  • Implémentez des alertes pour les secrets qui n’ont pas été soumis à une rotation dans le délai prévu.
# AWS Secrets Manager : configurer la rotation automatique
aws secretsmanager rotate-secret \
  --secret-id myapp/api-key \
  --rotation-lambda-arn arn:aws:lambda:us-east-1:123456789012:function:rotate-api-key \
  --rotation-rules '{"ScheduleExpression": "rate(30 days)"}'

Conclusion : La Gestion des Secrets Est un Processus Continu

La gestion des secrets n’est pas une case à cocher lors de la configuration initiale du pipeline. C’est une discipline continue qui doit évoluer au fur et à mesure que votre infrastructure grandit, que de nouvelles techniques d’attaque émergent et que votre équipe change. Les patterns décrits dans ce guide — fédération OIDC, secrets dynamiques, injection just-in-time, portée au moindre privilège et analyse en couches — représentent l’état de l’art actuel, mais ils nécessitent une attention continue.

Commencez par auditer vos pipelines actuels. Identifiez chaque identifiant stocké. Pour chacun d’eux, demandez-vous : peut-il être remplacé par un identifiant à courte durée de vie ou une fédération de workload identity ? Sa portée peut-elle être réduite ? Ce secret est-il journalisé quelque part ? Existe-t-il une trace d’audit pour chaque accès ?

Les organisations qui subissent des brèches CI/CD ne sont pas celles qui n’ont jamais stocké de secret — c’est impossible. Ce sont celles qui ont traité la gestion des secrets comme une tâche de configuration ponctuelle plutôt que comme une pratique de sécurité vivante. Construisez l’automatisation, appliquez les politiques, surveillez les logs d’accès et itérez. Vos pipelines seront considérablement plus difficiles à compromettre en conséquence.