Naar de hoofdinhoud

Deploy-secrets: OIDC-federatie

fremforge wil je productiesecrets niet bewaren. Langlevende deploy-inloggegevens (AWS_ACCESS_KEY_ID, AZURE_CLIENT_SECRET, GCP_SA_KEY, …) die in de secretopslag van een CI-systeem staan, zijn een eeuwig terugkerende rotatieklus en een eeuwig risico bij een inbraak. De moderne aanpak is OIDC-federatie: je workflow vraagt de CI-server om een kortlevend identiteitstoken, de IAM van je cloud verifieert het token en geeft een kortlevende inloggegeven terug. fremforge levert dit van begin tot eind.

Hoe het werkt

Elke CI-job die op fremforge draait, krijgt een vers aangemaakte JSON Web Token, ondertekend door de runner-OIDC-issuer van fremforge op https://frem.sh/_app/runner/oidc. Het token bevat claims die de context van de job beschrijven: repository, ref, workflow en eventueel omgeving. De trust policy in de IAM van je cloud beslist op basis van die claims of er inloggegevens worden verstrekt.

    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
  

Claims waarop de trust policy van je cloud kan matchen:

ClaimVoorbeeldWat het identificeert
isshttps://frem.sh/_app/runner/oidcfremforge zelf
audsts.amazonaws.combij welke cloud je je authenticeert
subrepo:my-org/my-repo:environment:production (als environment: is gedeclareerd)
repo:my-org/my-repo:ref:refs/heads/main (zonder omgeving)
welke workflowrun
repositorymy-org/my-reponaam van de repository
repository_ownermy-orgslug van de organisatie
refrefs/heads/mainGit-ref
environmentproductionnaam van de omgeving die de workflow declareert
actoraliceForgejo-gebruikersnaam die de run startte

Deze claims volgen de vorm van GitHub Actions, dus bestaande trust policies en de actions actions/configure-aws-credentials / azure/login / google-github-actions/auth werken zonder aanpassingen.

Discovery-URL

Verwijs je cloud naar:

  • Issuer-URL: https://frem.sh/_app/runner/oidc
  • JWKS-URL: https://frem.sh/_app/runner/oidc/.well-known/jwks.json
  • Discovery-document: https://frem.sh/_app/runner/oidc/.well-known/openid-configuration

Alle drie zijn anoniem leesbaar.

Workflowpatronen

Een omgeving declareren

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/

Met environment: production wordt de OIDC-claim sub repo:my-org/my-repo:environment:production, zodat je trust policy alleen toegang kan geven aan jobs die production declareren.

Zonder omgeving

Declareer je geen environment:, dan valt sub terug op repo:<owner>/<repo>:ref:<ref>. Trust policies die op ref matchen, werken net als op GitHub. De omgevingspoort aan de kant van fremforge geldt dan niet (die controleert alleen workflows die een omgeving declareren), dus de trust policy aan de kant van de cloud is je enige verdedigingslinie.

Voorbeelden van trust policies

AWS

IAM-trust policy voor een rol met OIDC-federatie:

{
  "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"
      }
    }
  }]
}

Eenmalige inrichting: registreer fremforge als OIDC-provider:

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)

Registreer de gefedereerde inloggegeven op een App Registration in 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 (OTC IAM Identity Provider)

T Cloud Public gebruikt IAM Identity Providers + token-exchange:

  1. Identity Provider in 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 (of de STS-audience van je project)
  2. Mappingregels vertalen claims naar een gefedereerde gebruiker. Voorbeeld dat een subject repo:my-org/my-repo:environment:production koppelt aan de OTC-groep deploy-prod:
[
  {
    "local": [
      { "user": { "name": "{0}" } },
      { "group": { "name": "deploy-prod" } }
    ],
    "remote": [
      { "type": "sub" },
      { "type": "environment", "any_one_of": ["production"] }
    ]
  }
]
  1. Workflow: wissel de JWT in voor een OTC-token via het token-exchange-endpoint van IAM en gebruik daarna de teruggegeven X-Subject-Token voor elke aanroep van de OTC-API.

Bepalen welke refs waarheen mogen deployen

OIDC-federatie maakt het OPSLAAN van langlevende secrets overbodig, maar je moet nog steeds bepalen welke branches naar welke omgeving mogen deployen. Een workflow op een featurebranch KAN een JWT aanmaken die environment: production claimt; de poort is wat voorkomt dat die JWT wordt geaccepteerd.

fremforge biedt twee poorten, en je gebruikt ze het best allebei, als gelaagde verdediging.

Poort 1: trust policy aan de kant van de cloud (altijd vereist)

De trust policy in de IAM van je cloud heeft het laatste woord. Pin de claim sub op de vorm repository+omgeving EN pin de claim ref op de branch(es) van waaruit die omgeving mag deployen. Voorbeeld voor 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"
    }
  }
}

Een featurebranch KAN de JWT aanmaken, maar AWS weigert inloggegevens te verstrekken omdat ref refs/heads/feature/foo is in plaats van refs/heads/main. Dit is het basispatroon dat GitHub aanbeveelt, en het werkt vanaf dag één.

Poort 2: deploymentbranchpatronen per omgeving aan de kant van fremforge (aanbevolen)

Onder Organisatiebeheer → Omgevingen (frem.sh/<org>/_admin/environments) stel je elke omgeving in zoals op GitHub. Elke omgeving heeft drie onderdelen:

Deploymentbranches en -tags: glob-patronen die worden vergeleken met de Git-ref die de run startte. Voorbeelden (bij de verkorte schrijfwijze voor branches wordt automatisch refs/heads/ ervoor gezet):

NaamPatronenEffect
productionmainalleen de branch main
stagingmain
develop
release/*
de branches main + develop + release/*
pr-preview*elke branch (de poort aan de kant van de cloud heeft het echte gezag)
releaserefs/tags/v[0-9]+.*alleen versietags (expliciet voorvoegsel refs/tags/)

Workflows die environment: production declareren, worden bij het dispatchen geweigerd als de ref die de run startte niet overeenkomt. Workflows ZONDER declaratie van environment: draaien zonder beperking, zodat gewone CI op featurebranches blijft werken.

Secrets van een omgeving: versleuteld, beperkt tot de omgeving, en geïnjecteerd als ${{ secrets.NAME }} in de workflow EN als gewone omgevingsvariabelen als de environment: van de workflow overeenkomt. Dekt de secrets buiten de cloud-IAM (API-sleutels van derden, ondertekeningssleutels, webhook-secrets) die OIDC-federatie niet oplost.

Variabelen van een omgeving: niet-geheime waarden die tot de omgeving zijn beperkt. Hetzelfde injectiemodel.

En goedkeuringspoorten?

GitHub Actions Environments heeft een functie “Required reviewers”: een job pauzeert na de merge tot N mensen op Approve klikken in de Actions-UI. fremforge levert dit bewust niet, omdat Forgejo op hetzelfde moment in de tijdlijn al een sterkere poort heeft: branchbescherming op main met CODEOWNERS op .forgejo/workflows/* vereist een review van de pull request voordat een workflow die kan deployen binnenkomt. In 99% van de gevallen (1 PR = 1 deploy) IS de PR-review van Forgejo de goedkeuring van de deploy. Heb je het GitHub-geval nodig (1 PR kan N onafhankelijke deploys opleveren, met per deploy aparte goedkeurders), meld dan een issue; we geven goedkeurders op het moment van deployen (gepland) prioriteit op basis van de werkelijke vraag.

De poort aan de kant van fremforge voorkomt dat de JWT überhaupt wordt aangemaakt. De poort aan de kant van de cloud voorkomt dat een aangemaakte JWT wordt geaccepteerd. Combineer ze: ook als een toekomstige bug in fremforge een geweigerde dispatch zou doorlaten, weigert de IAM van de cloud nog steeds inloggegevens te verstrekken. En ook als de trust policy in de cloud van een klant een typfout heeft en een breder ref-patroon accepteert dan bedoeld, heeft fremforge nog steeds geweigerd de JWT aan te maken.

Geleidelijke onboarding

Declareer je environment: x in een workflow maar heb je nog geen environments-rij voor x ingesteld, dan staat fremforge de dispatch toe in plaats van hem te weigeren. Dat is bewust, zodat de workflows van een nieuwe klant niet breken zodra die een omgeving declareert; je trust policy aan de kant van de cloud is het vangnet tot je de rij aan de kant van fremforge toevoegt. Zodra er een rij bestaat, wordt de poort afgedwongen.

Vergrendelde modus: alleen beschermde branches

Voor omgevingen met hoge beveiligingseisen waarin je CI-uitvoering op onbeschermde refs VOLLEDIG wilt verbieden (niet alleen deploy-inloggegevens afschermen), zet je Organisatiebeheer → Omgevingen → Organisatiebrede vergrendeling → Workflows alleen op beveiligde branches uitvoeren aan:

Workflows alleen op beveiligde branches uitvoeren

Staat dit aan, dan weigert het platform ELKE workflow te dispatchen waarvan de branch die hem startte NIET in de lijst met beschermde branches van de repository staat (ingesteld via Forgejo’s eigen Instellingen → Branches → Voeg nieuwe regel toe). Dat geldt ook voor gewone CI (tests, lint): op onbeschermde branches draait niets. Handig in gereguleerde omgevingen waar je geen enkele code-uitvoering buiten gereviewde refs wilt; niet aanbevolen voor de normale ontwikkelstroom, omdat het testen op featurebranches dan niet meer werkt.

Zet je dit aan, dan bescherm je in Forgejo meestal de patronen main, develop en release/*, zodat je gewone ontwikkelbranches CI kunnen blijven draaien.

Aanbevolen aanpak

Voor gangbare SaaS- en productteams:

  1. Zet de deploypoort voor alleen beschermde branches niet aan, zodat gewone CI op featurebranches draait.
  2. Stel wel omgevingen in onder Organisatiebeheer → Omgevingen, met passende patronen voor deploymentbranches en -tags voor elk (production, staging, enz.).
  3. Stel wel trust policies in de cloud in met zowel een sub-voorwaarde (vastgepind op de omgeving) ALS een ref-voorwaarde (vastgepind op de branch).
  4. Bescherm main in Forgejo met CODEOWNERS-dekking op .forgejo/workflows/*, zodat wijzigingen in workflows een review nodig hebben.

Voor gereguleerde of vergrendelde omgevingen (bankwezen, defensie, zorg):

  1. Zet de deploypoort voor alleen beschermde branches wel aan.
  2. Stel daarbovenop omgevingen in voor fijnmazige controle per omgeving.
  3. Dezelfde aanpak voor de trust policy in de cloud als hierboven.

Deze combinatie volgt het deploypoortmodel dat GitHub Enterprise en AWS aanbevelen (omgevingen verplicht bij deploys + alleen beschermde branches), plus de optionele vergrendelschakelaar voor gereguleerde tenants.

Vergelijking: wat fremforge bewaart en wat je cloud bewaart

Langlevende secretsOIDC-federatie (deze pagina)
Waar deploy-inloggegevens staanOrganisatiebeheer → Secrets of Repository → Instellingen → Actions → Geheimen in fremforgeIn de IAM van je cloud. fremforge ziet ze nooit.
Levensduur van de inloggegevensTot je ze roteert1 uur (per trust policy in te stellen)
Als fremforge wordt gehacktDe aanvaller heeft je AWS-sleutelsDe aanvaller kan OIDC-JWT’s aanmaken, maar de trust policy in de cloud begrenst wat die JWT’s kunnen (per omgeving, per repository, per ref)
RotatieklusElk kwartaal, per secret per cloudGeen, de sleutels roteren bij elke job
AudittrailHandmatig: wie heeft het secret wanneer geëxporteerdElke OIDC-aanmaak staat in audit_events + de CloudTrail/Activity Log van je cloud

Beperkingen

  • environment: in objectvorm (met name: + url:) wordt op naam ondersteund; url: wordt geparsed, maar momenteel niet als claim doorgegeven.
  • Omgevingsnamen uit een matrix (environment: ${{ matrix.target }}) worden bij het aanmaken van het OIDC-token niet opgelost, waardoor er geen claim environment wordt meegegeven. Gebruik per matrixjob een vaste environment: als je trust policies op basis van de omgevingsclaim nodig hebt.
  • Verplichte goedkeurders per omgeving (zoals de functie “wait for approval” van GitHub Environments) staan gepland als vervolg in fase 2; de handhaving is vandaag binair (beschermde branch ja/nee).

Zie ook