Aller au contenu principal

Secrets de déploiement : fédération OIDC

fremforge ne veut pas détenir vos secrets de production. Les identifiants de déploiement de longue durée (AWS_ACCESS_KEY_ID, AZURE_CLIENT_SECRET, GCP_SA_KEY, …) stockés dans les backends de secrets de CI imposent une rotation permanente et élargissent en permanence l’impact d’une compromission. L’approche moderne est la fédération OIDC : votre workflow demande au serveur de CI un jeton d’identité de courte durée, l’IAM de votre cloud vérifie ce jeton et renvoie un identifiant de courte durée. fremforge fournit cette chaîne de bout en bout.

Fonctionnement

Chaque job de CI exécuté sur fremforge reçoit un JSON Web Token fraîchement émis, signé par l’émetteur OIDC des runners de fremforge à l’adresse https://frem.sh/_app/runner/oidc. Le jeton porte des claims qui décrivent le contexte du job (dépôt, ref, workflow et, le cas échéant, environnement), et la politique de confiance IAM de votre cloud décide d’accorder ou non des identifiants en fonction de ces claims.

    sequenceDiagram
    autonumber
    actor User
    participant FF as fremforge<br/>runner-OIDC issuer
    participant Pod as runner VM
    participant Cloud as Your cloud's<br/>IAM • STS / WIF / …

    User->>FF: git push
    FF->>Pod: dispatch job — mint OIDC JWT
    Pod->>Cloud: "Here's my JWT, give me credentials"
    Cloud->>Cloud: Verify JWT against fremforge JWKS<br/>+ check trust policy claims
    Cloud->>Pod: 1h credentials
    Pod->>Cloud: deploy / read / write
  

Les claims sur lesquels votre politique de confiance peut s’appuyer :

ClaimExempleCe qu’il identifie
isshttps://frem.sh/_app/runner/oidcfremforge lui-même
audsts.amazonaws.comle cloud auprès duquel vous vous authentifiez
subrepo:my-org/my-repo:environment:production (lorsque environment: est déclaré)
repo:my-org/my-repo:ref:refs/heads/main (sans environnement)
l’exécution de workflow
repositorymy-org/my-repole nom du dépôt
repository_ownermy-orgle slug de l’organisation
refrefs/heads/mainla ref git
environmentproductionle nom d’environnement déclaré par le workflow
actoralicele nom d’utilisateur Forgejo qui a déclenché l’exécution

Ils reprennent la forme des claims de GitHub Actions : les politiques de confiance existantes et les actions actions/configure-aws-credentials / azure/login / google-github-actions/auth fonctionnent sans modification.

URL de découverte

Indiquez à votre cloud :

  • URL de l’émetteur : https://frem.sh/_app/runner/oidc
  • URL JWKS : https://frem.sh/_app/runner/oidc/.well-known/jwks.json
  • Document de découverte : https://frem.sh/_app/runner/oidc/.well-known/openid-configuration

Les trois sont lisibles anonymement.

Modèles de workflow

Déclarer un environnement

jobs:
  deploy:
    runs-on: fremforge-runner
    environment: production
    permissions:
      id-token: write    # required to mint the OIDC JWT
      contents: read
    steps:
      - uses: actions/checkout@v4
      - uses: aws-actions/configure-aws-credentials@v4
        with:
          role-to-assume: arn:aws:iam::123456789012:role/fremforge-deploy-prod
          aws-region: eu-central-1
      - run: aws s3 sync ./build s3://my-prod-bucket/

environment: production transforme le claim OIDC sub en repo:my-org/my-repo:environment:production : votre politique de confiance peut ainsi n’accorder l’accès qu’aux jobs qui déclarent production.

Sans environnement

Si vous ne déclarez pas environment:, sub prend la forme repo:<owner>/<repo>:ref:<ref>. Les politiques de confiance qui s’appuient sur ref fonctionnent comme sur GitHub. Le contrôle des environnements côté fremforge ne s’applique pas (il ne contrôle que les workflows qui déclarent un environnement) : la politique de confiance côté cloud est alors votre seule ligne de défense.

Exemples de politiques de confiance

AWS

Politique de confiance IAM pour un rôle fédéré par OIDC :

{
  "Version": "2012-10-17",
  "Statement": [{
    "Effect": "Allow",
    "Principal": {
      "Federated": "arn:aws:iam::123456789012:oidc-provider/frem.sh/_app/runner/oidc"
    },
    "Action": "sts:AssumeRoleWithWebIdentity",
    "Condition": {
      "StringEquals": {
        "frem.sh/_app/runner/oidc:aud": "sts.amazonaws.com"
      },
      "StringLike": {
        "frem.sh/_app/runner/oidc:sub": "repo:my-org/my-repo:environment:production"
      }
    }
  }]
}

Configuration unique : enregistrez fremforge comme fournisseur OIDC :

aws iam create-open-id-connect-provider \
  --url https://frem.sh/_app/runner/oidc \
  --client-id-list sts.amazonaws.com \
  --thumbprint-list $(curl -s https://frem.sh/_app/runner/oidc/.well-known/jwks.json | jq -r '.keys[0].x5t')

Azure (Workload Identity Federation)

Enregistrez l’identifiant fédéré sur un App Registration Entra ID :

az ad app federated-credential create \
  --id $APP_OBJECT_ID \
  --parameters '{
    "name": "fremforge-prod-deploy",
    "issuer": "https://frem.sh/_app/runner/oidc",
    "subject": "repo:my-org/my-repo:environment:production",
    "audiences": ["api://AzureADTokenExchange"]
  }'

Workflow :

- uses: azure/login@v2
  with:
    client-id: ${{ vars.AZURE_CLIENT_ID }}
    tenant-id: ${{ vars.AZURE_TENANT_ID }}
    subscription-id: ${{ vars.AZURE_SUBSCRIPTION_ID }}

GCP (Workload Identity Federation)

gcloud iam workload-identity-pools providers create-oidc fremforge-provider \
  --workload-identity-pool=fremforge-pool \
  --issuer-uri="https://frem.sh/_app/runner/oidc" \
  --attribute-mapping="google.subject=assertion.sub,attribute.repository=assertion.repository,attribute.environment=assertion.environment" \
  --attribute-condition="attribute.environment == 'production'"

Workflow :

- uses: google-github-actions/auth@v2
  with:
    workload_identity_provider: projects/PROJECT_NUMBER/locations/global/workloadIdentityPools/fremforge-pool/providers/fremforge-provider
    service_account: deploy@my-project.iam.gserviceaccount.com

T Cloud Public (fournisseur d’identité IAM OTC)

T Cloud Public utilise les fournisseurs d’identité IAM et l’échange de jetons :

  1. Fournisseur d’identité dans OTC Console → IAM → Identity Providers → Create :
    • Name : fremforge-runner-oidc
    • Protocol : OpenID Connect
    • Issuer URL : https://frem.sh/_app/runner/oidc
    • JWKS URL : https://frem.sh/_app/runner/oidc/.well-known/jwks.json
    • Client ID : iam.eu-de.otc.t-systems.com (ou l’audience STS de votre projet)
  2. Règles de mappage : elles traduisent les claims en un utilisateur fédéré. Exemple qui associe un sujet repo:my-org/my-repo:environment:production au groupe OTC deploy-prod :
[
  {
    "local": [
      { "user": { "name": "{0}" } },
      { "group": { "name": "deploy-prod" } }
    ],
    "remote": [
      { "type": "sub" },
      { "type": "environment", "any_one_of": ["production"] }
    ]
  }
]
  1. Workflow : échangez le JWT contre un jeton OTC via le point de terminaison d’échange de jetons de l’IAM, puis utilisez le X-Subject-Token renvoyé pour tout appel à l’API OTC.

Contrôler quelles refs peuvent déployer où

La fédération OIDC supprime le besoin de STOCKER des secrets de longue durée, mais vous devez toujours décider quelles branches peuvent déployer vers quel environnement. Un workflow sur une branche de fonctionnalité PEUT émettre un JWT qui revendique environment: production ; c’est le contrôle qui empêche ce JWT d’être accepté.

fremforge propose deux contrôles, et vous devriez utiliser les deux, en défense en profondeur.

Contrôle 1 : politique de confiance côté cloud (toujours obligatoire)

La politique de confiance IAM de votre cloud est l’autorité finale. Fixez le claim sub sur la forme dépôt + environnement ET fixez le claim ref sur la ou les branches depuis lesquelles cet environnement peut être déployé. Exemple AWS :

{
  "Condition": {
    "StringEquals": {
      "frem.sh/_app/runner/oidc:sub": "repo:my-org/my-repo:environment:production"
    },
    "StringLike": {
      "frem.sh/_app/runner/oidc:ref": "refs/heads/main"
    }
  }
}

Une branche de fonctionnalité PEUT émettre le JWT, mais AWS refuse de délivrer des identifiants car ref vaut refs/heads/feature/foo au lieu de refs/heads/main. C’est le modèle de base recommandé par GitHub, et il fonctionne dès le premier jour.

Contrôle 2 : motifs de branches de déploiement par environnement côté fremforge (recommandé)

Dans Administration de l’organisation → Environnements (frem.sh/<org>/_admin/environments), configurez chaque environnement à la manière de GitHub. Trois sections par environnement :

Branches et étiquettes de déploiement : des motifs glob comparés à la ref git déclenchante. Exemples (préfixés automatiquement par refs/heads/ pour la forme abrégée des branches) :

NomMotifsEffet
productionmainbranche main uniquement
stagingmain
develop
release/*
branches main, develop et release/*
pr-preview*n’importe quelle branche (le contrôle côté cloud fait vraiment autorité)
releaserefs/tags/v[0-9]+.*étiquettes de version uniquement (préfixe refs/tags/ explicite)

Les workflows qui déclarent environment: production sont refusés au lancement lorsque la ref déclenchante ne correspond pas. Les workflows SANS déclaration environment: s’exécutent sans restriction : la CI de base sur les branches de fonctionnalité continue de fonctionner.

Secrets d’environnement : chiffrés, limités à un environnement, injectés sous forme de ${{ secrets.NAME }} dans le workflow ET de variables d’environnement simples lorsque l’environment: du workflow correspond. Ils couvrent les secrets hors IAM cloud (clés d’API tierces, clés de signature, secrets de webhook) que la fédération OIDC ne résout pas.

Variables d’environnement : des valeurs non secrètes, limitées à un environnement. Même modèle d’injection.

Et les contrôles d’approbation ?

Les Environments de GitHub Actions proposent une fonctionnalité « Required reviewers » : un job se met en pause après la fusion jusqu’à ce que N personnes cliquent sur Approve dans l’interface Actions. fremforge ne la propose volontairement pas, car Forgejo dispose déjà d’un contrôle plus fort au même moment : la protection de branche sur main, avec CODEOWNERS sur .forgejo/workflows/*, exige une relecture de pull request avant que tout workflow capable de déployer ne soit intégré. Dans 99 % des cas (1 PR = 1 déploiement), la relecture de PR de Forgejo EST l’approbation du déploiement. Si vous avez besoin du cas GitHub (1 PR peut produire N déploiements indépendants, avec des approbateurs distincts par déploiement), ouvrez un ticket : nous prioriserons les approbateurs au moment du déploiement (prévus) en fonction de la demande réelle.

Le contrôle côté fremforge empêche purement et simplement l’émission du JWT. Le contrôle côté cloud empêche qu’un JWT émis soit accepté. Combinez les deux : même si un futur bug de fremforge laissait passer un lancement refusé, l’IAM du cloud refuserait toujours de délivrer des identifiants. Et même si la politique de confiance cloud d’un client contient une coquille et accepte un motif de ref plus large que prévu, fremforge aura tout de même refusé d’émettre le JWT.

Intégration progressive

Lorsque vous déclarez environment: x dans un workflow sans avoir encore configuré de ligne environments pour x, fremforge autorise le lancement au lieu de le refuser. C’est délibéré : les workflows d’un nouveau client ne cassent pas dès qu’il déclare un environnement, et votre politique de confiance côté cloud sert de filet de sécurité jusqu’à ce que vous ajoutiez la ligne côté fremforge. Dès qu’une ligne existe, le contrôle est appliqué.

Mode verrouillé : branches protégées uniquement

Pour les environnements à haute sécurité où vous voulez interdire TOTALEMENT l’exécution de la CI sur des refs non protégées (et pas seulement contrôler les identifiants de déploiement), activez Administration de l’organisation → Environnements → Verrouillage global de l’organisation → N’exécuter les workflows que sur les branches protégées :

N’exécuter les workflows que sur les branches protégées

Une fois activé, la plateforme refuse de lancer TOUT workflow dont la branche déclenchante ne figure PAS dans la liste des branches protégées du dépôt (configurée via l’écran natif de Forgejo Paramètres → Branches → Ajouter une nouvelle règle). Cela inclut la CI de base (tests, lint) : rien ne s’exécute sur les branches non protégées. Utile dans les environnements réglementés où vous ne voulez aucune exécution de code en dehors des refs relues ; déconseillé pour un flux de développement normal, car cela empêche de tester les branches de fonctionnalité.

Si vous l’activez, vous protégerez généralement les motifs main, develop et release/* dans Forgejo pour que vos branches de développement habituelles restent éligibles à la CI.

Configuration recommandée

Pour les équipes SaaS / produit classiques :

  1. N’activez pas le contrôle de déploiement « branches protégées uniquement » : laissez la CI de base s’exécuter sur les branches de fonctionnalité.
  2. Configurez les environnements dans Administration de l’organisation → Environnements, avec des motifs de branches et d’étiquettes de déploiement adaptés à chacun (production, staging, etc.).
  3. Configurez les politiques de confiance cloud avec à la fois une condition sub (fixée sur l’environnement) ET une condition ref (fixée sur la branche).
  4. Protégez main dans Forgejo, avec une couverture CODEOWNERS sur .forgejo/workflows/*, pour que les modifications de workflow nécessitent une relecture.

Pour les environnements réglementés / verrouillés (banque, défense, santé) :

  1. Activez le contrôle de déploiement « branches protégées uniquement ».
  2. Configurez les environnements pour un contrôle fin par environnement en complément.
  3. Même configuration de la politique de confiance côté cloud que ci-dessus.

Cette combinaison correspond au modèle de contrôle des déploiements que recommandent GitHub Enterprise et AWS (environnements obligatoires pour déployer + branches protégées uniquement), avec en plus l’interrupteur de verrouillage optionnel pour les organisations réglementées.

Comparaison : ce que stocke fremforge et ce que stocke votre cloud

Secrets de longue duréeFédération OIDC (cette page)
Où se trouvent les identifiants de déploiementParamètres → Secrets dans fremforgeDans l’IAM de votre cloud. fremforge ne les voit jamais.
Durée de vie des identifiantsJusqu’à votre prochaine rotation1 heure (configurable par politique de confiance)
Si fremforge est compromisL’attaquant détient vos clés AWSL’attaquant peut émettre des JWT OIDC, mais la politique de confiance côté cloud limite ce qu’ils permettent (par environnement, par dépôt, par ref)
Corvée de rotationTrimestrielle, par secret et par cloudAucune : les clés changent à chaque job
Piste d’auditManuelle : qui a exporté le secret et quandChaque émission OIDC figure dans audit_events et dans le CloudTrail / Activity Log de votre cloud

Limites

  • La forme objet de environment: (avec name: + url:) est prise en charge par son nom ; url: est analysé mais n’est pas exposé comme claim pour l’instant.
  • Les noms d’environnement issus d’une matrice (environment: ${{ matrix.target }}) ne sont pas résolus au moment de l’émission OIDC, et aucun claim environment n’est alors émis. Utilisez un environment: codé en dur par job de matrice si vous avez besoin de politiques de confiance fondées sur le claim d’environnement.
  • Les contrôles d’approbateurs obligatoires par environnement (à la manière de la fonctionnalité « wait for approval » des Environments de GitHub) sont suivis comme une évolution de phase 2 ; l’application actuelle est binaire (branche protégée oui/non).

Voir aussi