Naar de hoofdinhoud

OIDC-tokenfederatie

Elke CI-job van fremforge kan een kortlevend OIDC-token krijgen: een met RS256 ondertekende JWT die wordt uitgegeven door de runner-OIDC-issuer van fremforge. Cloudproviders (T Cloud Public, AWS, Azure, GCP) kun je zo instellen dat ze dit token vertrouwen en het inwisselen voor afgebakende, tijdelijke inloggegevens. Geen deploysleutels opgeslagen als secrets, niets om volgens een schema te roteren.

Overzicht van de claims

De runner-JWT van fremforge bevat de volgende claims:

ClaimWaarde / formaatVoorbeeld
isshttps://frem.sh/_app/runner/oidc (één globale issuer voor alle organisaties)https://frem.sh/_app/runner/oidc
subrepo:<org>/<repo>:ref:refs/heads/<branch>repo:acme/api:ref:refs/heads/main
audInstelbaar; standaard https://frem.shhttps://frem.sh
repository_ownerSlug van de organisatie; leg voorwaarden in het vertrouwensbeleid vast op deze claim voor isolatie per organisatie onder de gedeelde issueracme
repository<org>/<repo>acme/api
refVolledige Git-refrefs/heads/main
shaSHA van de commita1b2c3d4...
environmentNaam van de deployment-omgeving (als de job er een declareert)production
job_workflow_refPad en ref van het workflowbestandacme/api/.forgejo/workflows/deploy.yaml@refs/heads/main
deployer_group(Alleen T Cloud Public) Naam van de IAM-groep waaraan de gefedereerde gebruiker wordt gekoppeld, per workload ingesteld door fremforge; zie Vertrouwen via agencies in T Cloud Publicacme-prod-deployers

De claim sub is in de meeste vertrouwensbeleidsregels van providers het belangrijkste ankerpunt. Hij identificeert de combinatie van organisatie, repository en branch eenduidig. Voor isolatie per organisatie in gedeelde cloudaccounts voeg je een voorwaarde toe die repository_owner (de slug van je organisatie) vastlegt; hetzelfde patroon als bij het vertrouwen van GitHub Actions op token.actions.githubusercontent.com. fremforge gebruikt één globale issuer voor alle organisaties; de isolatie van vertrouwen wordt afgedwongen op het niveau van de claims, niet op dat van de issuer-URL.

Het token ophalen in een workflow

Voeg id-token: write toe aan het permissions-blok van de job. De runner vult dan ACTIONS_ID_TOKEN_REQUEST_TOKEN en ACTIONS_ID_TOKEN_REQUEST_URL in als omgevingsvariabelen, waarmee je de eigenlijke JWT ophaalt:

permissions:
  id-token: write
  contents: read

jobs:
  deploy:
    runs-on: fremforge
    steps:
      - name: Get OIDC token
        run: |
          TOKEN=$(curl -sS \
            -H "Authorization: bearer $ACTIONS_ID_TOKEN_REQUEST_TOKEN" \
            "$ACTIONS_ID_TOKEN_REQUEST_URL&audience=https://frem.sh" \
            | jq -r .value)
          echo "TOKEN=$TOKEN" >> $GITHUB_ENV

De meeste providerspecifieke stappen (AWS, Azure, GCP) halen het token zelf op. Je schrijft het handmatig ophalen hierboven alleen als je de ruwe JWT nodig hebt voor een eigen vertrouwensstroom.

Vertrouwen via agencies in T Cloud Public

Het IAM-federatiemodel van T Cloud Public is afgeleid van OpenStack: je registreert de OIDC-issuer als identity provider, koppelt er een mapping aan die claims uit het ID-token vertaalt naar lokale IAM-groepen, en wisselt daarna het ID-token van de runner bij het OS-FEDERATION-endpoint van OTC in voor een Keystone-token van T Cloud Public dat is beperkt tot de groepen die de mapping koppelde.

fremforge gebruikt groepskoppeling op basis van claims (dezelfde vorm als AWS Identity Center, GCP Workload Identity Federation en Azure Federated Credentials): het ID-token van de runner bevat per workload een claim deployer_group, en je OTC-mapping leest die claim letterlijk. Eén mappingregel dekt je hele vloot: een workload toevoegen raakt je IAM helemaal niet.

Stap 1: maak een OpenID Connect-identity provider aan

Ga in de console van T Cloud Public naar IAM → Identity Providers → Create Identity Provider.

  • Type: Virtual user
  • Protocol: OpenID Connect
  • Access type: Programmatic access (program)

Stel na het aanmaken de OIDC-instellingen in:

  • Identity provider URL (idp_url): https://frem.sh/_app/runner/oidc (één globale issuer; komt overeen met de claim iss in elk runner-ID-token van fremforge).
  • Client ID (client_id): https://frem.sh; moet gelijk zijn aan de claim aud in het runner-ID-token.
  • Signing key (signing_key): plak de JSON met de publieke JWKS-sleutel. Haal die eenmalig op via https://frem.sh/_app/runner/oidc/.well-known/openid-configuration → jwks_uri en kopieer de JSON. T Cloud Public haalt de JWKS niet automatisch op, dus plak hem opnieuw wanneer fremforge een rotatie van de JWKS aankondigt (aangekondigd op de trustpagina; rotaties gebeuren hooguit eens per jaar).

Stap 2: voeg de mappingregel op basis van claims toe

Eén regel, schaalbaar voor elk aantal workloads:

[
  {
    "local": [
      { "user":  { "name": "fremforge-{0}-{1}" } },
      { "group": { "name": "{2}" } }
    ],
    "remote": [
      { "type": "repository_owner" },
      { "type": "repository" },
      { "type": "deployer_group" }
    ]
  }
]

De vertrouwensopzet:

  • De gefedereerde gebruiker wordt lid van de IAM-groep die de claim deployer_group in het ID-token noemt ({2} vult de waarde van de claim letterlijk in).
  • De claim wordt gezet door de runner-OIDC-issuer van fremforge, volgens de koppeling tussen workload en groep die je bij de onboarding afspreekt. Hij kan alleen worden opgegeven voor workloads waarvoor we een regel hebben, en alleen voor de ref die die regel vastlegt (standaard: de main-branch).
  • Dezelfde vastgelegde JWT-handtekening, dezelfde filtering op iss + aud + repository_owner; alleen de beslissing welke workload bij welke groep hoort, is verplaatst van “N mappingregels in OTC” naar “één claim die fremforge uitgeeft”.

Stap 3: maak de IAM-groepen aan die je workloads aannemen

Maak één groep aan per deployerprofiel dat je beschikbaar wilt stellen (bijv. acme-prod-deployers, acme-staging-deployers). Ken elk profiel de project- en EPS-rollen toe die het nodig heeft; die zijn 100% van jou, fremforge raakt je groepsinstellingen nooit aan. De groepsnamen komen overeen met de waarde die fremforge in de claim deployer_group zet, zoals bij de onboarding afgesproken.

Stap 4: laat fremforge weten welke workload welke groep gebruikt

Per e-mail of via een aanroep van de beheer-API: “Repository acme/api, main-branch → groep acme-prod-deployers.” fremforge voegt een regel toe aan zijn tabel runner_deployer_groups. Vanaf de volgende workflowrun bevatten ID-tokens voor die workload deployer_group=acme-prod-deployers, en koppelt je mapping de gefedereerde gebruiker aan die groep.

Een nieuwe repository toevoegen is gewoon nog een e-mail; je OTC-IAM blijft ongewijzigd.

Optioneel: maak een agency aan om aan te nemen

De meeste opstellingen slaan dit over: de groepskoppeling uit stap 2 is genoeg. Gebruik je een patroon met aangenomen rollen, maak dan een agency aan (IAM → Agencies → Create Agency) met het volgende:

  • Agency type: Account (een clouddienst van een ander T Cloud Public-account is hier niet van toepassing).
  • Delegated party: kies de OpenID Connect-identity provider die je in stap 1 hebt aangemaakt.
  • Permissions: koppel het beleid dat de CI-job nodig heeft (bijv. OBS Administrator, beperkt tot de OBS-bucket van de workload, of CCE FullAccess, beperkt tot het project van de workload).

Noteer de naam van de agency en het project waartoe je haar beperkt.

Stap 5: gebruik de inloggegevens in een workflow

De runner haalt een ID-token op, wisselt dat bij het OS-FEDERATION-endpoint van OTC in voor een Keystone-token zonder bereik, en ruilt dat vervolgens voor een token met projectbereik. Zowel de CLI van T Cloud Public (otc) als OpenTofu met de provider opentelekomcloud accepteren OS_AUTH_TOKEN uit de omgeving zonder verdere instellingen.

# Forgejo-specific: opts the task into the OIDC envelope.
# Forgejo jobparser only reads this top-level key — it does NOT
# auto-derive from `permissions: id-token: write` like GitHub does.
enable-openid-connect: true

permissions:
  id-token: write
  contents: read

jobs:
  deploy:
    runs-on: fremforge
    steps:
      - name: Exchange OIDC for T Cloud Public token
        run: |
          # 1. Fetch the runner OIDC JWT
          JWT=$(curl -sS --fail \
            -H "Authorization: bearer $ACTIONS_ID_TOKEN_REQUEST_TOKEN" \
            "$ACTIONS_ID_TOKEN_REQUEST_URL&audience=https://frem.sh" \
            | jq -r .value)

          # 2. Exchange at OTC's OS-FEDERATION endpoint (NOT /v3/auth/tokens
          #    with methods=["id_token"] — OTC's Keystone fork doesn't support
          #    that. Use the IdP-scoped URL with Bearer header instead).
          UNSCOPED=$(curl -sS -i \
            -X POST -H "Authorization: Bearer $JWT" \
            "https://iam.eu-de.otc.t-systems.com/v3/OS-FEDERATION/identity_providers/fremforge-runner-oidc/protocols/oidc/auth" \
            | awk 'BEGIN{IGNORECASE=1} /^x-subject-token:/ { sub(/^x-subject-token:[ \t]*/,"",$0); gsub(/\r/,"",$0); print }')

          # 3. Swap for project-scoped (the unscoped token gives you no
          #    permissions on its own; you need a project scope to do anything).
          SCOPED=$(curl -sS -i -X POST \
            -H "Content-Type: application/json" \
            -d "{\"auth\":{\"identity\":{\"methods\":[\"token\"],\"token\":{\"id\":\"$UNSCOPED\"}},\"scope\":{\"project\":{\"name\":\"eu-de\"}}}}" \
            "https://iam.eu-de.otc.t-systems.com/v3/auth/tokens" \
            | awk 'BEGIN{IGNORECASE=1} /^x-subject-token:/ { sub(/^x-subject-token:[ \t]*/,"",$0); gsub(/\r/,"",$0); print }')

          echo "::add-mask::$SCOPED"
          echo "OS_AUTH_TOKEN=$SCOPED" >> $GITHUB_ENV

      - name: Tofu plan
        run: tofu plan
        # opentelekomcloud provider picks up OS_AUTH_TOKEN automatically

Opmerkingen over de specifieke kenmerken van T Cloud Public

  • OTC gebruikt /v3/OS-FEDERATION/identity_providers/{idp}/protocols/oidc/auth, NIET /v3/auth/tokens met methods=["id_token"]. De authenticatiemethode in de body uit upstream OpenStack geeft op de Keystone-fork van OTC 400 "Unsupported method: id_token". Empirisch geverifieerd en volgens API-document iam_13_0606 van OTC.
  • OTC roteert de JWKS niet automatisch. Je plakt de ondertekeningssleutel eenmalig bij het aanmaken van de provider en opnieuw wanneer fremforge een rotatie aankondigt. AWS, Azure en GCP halen hem allemaal automatisch op uit het discoverydocument; alleen T Cloud Public vereist het expliciet plakken.
  • Het ingewisselde token staat in de responsheader X-Subject-Token, niet in de body; een conventie van OpenStack-Keystone.
  • De afbakening per workload komt van de claim deployer_group (de mapping koppelt de gefedereerde gebruiker aan die groep) EN van de projectrollen op die groep. De claim voorkomt dat de verkeerde workload de verkeerde groep aanneemt; de rol beperkt wat de groep kan doen.
  • Optioneel: agencies voor patronen met aangenomen rollen worden ook ondersteund, maar de meeste toepassingen hebben ze niet nodig; de groepskoppeling uit stap 2 is genoeg.

AWS IAM

Stap 1: maak een OIDC-identity provider aan

Ga in de AWS-console naar IAM → Identity providers → Add provider.

  • Provider type: OpenID Connect
  • Provider URL: https://frem.sh/_app/runner/oidc (één globale issuer; filteren per organisatie gebeurt in de voorwaarde van het vertrouwensbeleid, niet in de provider-URL)
  • Audience: https://frem.sh

AWS haalt de JWKS op uit het discoverydocument om tokens te verifiëren.

Stap 2: maak een IAM-rol aan met een vertrouwensbeleid voor web identity

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Federated": "arn:aws:iam::<account-id>:oidc-provider/frem.sh/_app/runner/oidc"
      },
      "Action": "sts:AssumeRoleWithWebIdentity",
      "Condition": {
        "StringEquals": {
          "frem.sh/_app/runner/oidc:aud": "https://frem.sh",
          "frem.sh/_app/runner/oidc:repository_owner": "<your-org>",
          "frem.sh/_app/runner/oidc:sub": "repo:<your-org>/<your-repo>:ref:refs/heads/main"
        }
      }
    }
  ]
}

Koppel de rechten die de CI-job nodig heeft aan deze rol (bijv. AmazonS3FullAccess, AmazonECRPowerUser).

Stap 3: gebruik het in een workflow

Gebruik de officiële action aws-actions/configure-aws-credentials:

permissions:
  id-token: write
  contents: read

jobs:
  deploy:
    runs-on: fremforge
    steps:
      - uses: actions/checkout@v4

      - name: Configure AWS credentials via OIDC
        uses: aws-actions/configure-aws-credentials@v4
        with:
          role-to-assume: arn:aws:iam::<account-id>:role/<role-name>
          aws-region: eu-central-1
          audience: https://frem.sh

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

De action regelt automatisch het ophalen van het token, de aanroep sts:AssumeRoleWithWebIdentity en het injecteren van de inloggegevens.

Azure (Entra Workload Identity)

Stap 1: maak gefedereerde inloggegevens aan op een app-registratie

  1. Ga in de Azure-portal naar Entra ID → App registrations → <your app> → Certificates & secrets → Federated credentials.
  2. Klik op Add credential.
  3. Scenario: Other issuer.
  4. Vul in:
    • Issuer: https://frem.sh/_app/runner/oidc (één globale issuer; de isolatie per organisatie wordt afgedwongen door de Subject identifier hieronder)
    • Subject identifier: repo:<your-org>/<your-repo>:ref:refs/heads/main
    • Audience: https://frem.sh
  5. Sla op.

Stap 2: gebruik het in een workflow

permissions:
  id-token: write
  contents: read

jobs:
  deploy:
    runs-on: fremforge
    steps:
      - uses: actions/checkout@v4

      - name: Get OIDC token
        run: |
          TOKEN=$(curl -sS \
            -H "Authorization: bearer $ACTIONS_ID_TOKEN_REQUEST_TOKEN" \
            "$ACTIONS_ID_TOKEN_REQUEST_URL&audience=https://frem.sh" \
            | jq -r .value)
          echo "OIDC_TOKEN=$TOKEN" >> $GITHUB_ENV

      - name: Azure login via federated token
        run: |
          az login \
            --service-principal \
            --tenant <tenant-id> \
            --client-id <client-id> \
            --federated-token "$OIDC_TOKEN"

      - name: Deploy
        run: az webapp deploy --resource-group my-rg --name my-app --src-path ./dist

De client-ID van de app-registratie en de ID van je Entra-tenant zijn geen secrets. Je kunt ze als gewone workflowvariabelen opslaan. Alleen het OIDC-token is gevoelig, en dat verloopt zodra de job klaar is.

GCP Workload Identity

Stap 1: maak een Workload Identity-pool en een OIDC-provider aan

# Create the pool
gcloud iam workload-identity-pools create fremforge-pool \
  --location global \
  --display-name "fremforge CI"

# Create the OIDC provider inside the pool
gcloud iam workload-identity-pools providers create-oidc fremforge-provider \
  --location global \
  --workload-identity-pool fremforge-pool \
  --issuer-uri "https://frem.sh/_app/runner/oidc" \
  --allowed-audiences "https://frem.sh" \
  --attribute-mapping "google.subject=assertion.sub,attribute.repository=assertion.repository,attribute.repository_owner=assertion.repository_owner"

Stap 2: sta impersonatie van het serviceaccount toe

# Allow the pool to impersonate a service account, scoped by sub claim
gcloud iam service-accounts add-iam-policy-binding <sa>@<project>.iam.gserviceaccount.com \
  --role roles/iam.workloadIdentityUser \
  --member "principalSet://iam.googleapis.com/projects/<project-number>/locations/global/workloadIdentityPools/fremforge-pool/attribute.sub/repo:<your-org>/<your-repo>:ref:refs/heads/main"

Stap 3: gebruik het in een workflow

permissions:
  id-token: write
  contents: read

jobs:
  deploy:
    runs-on: fremforge
    steps:
      - uses: actions/checkout@v4

      - name: Authenticate to GCP via OIDC
        uses: google-github-actions/auth@v2
        with:
          workload_identity_provider: projects/<project-number>/locations/global/workloadIdentityPools/fremforge-pool/providers/fremforge-provider
          service_account: <sa>@<project>.iam.gserviceaccount.com
          audience: https://frem.sh

      - name: Deploy
        run: gcloud run deploy my-service --image gcr.io/my-project/my-image

De action google-github-actions/auth werkt op de runners van fremforge. Ze gebruikt hetzelfde mechanisme met ACTIONS_ID_TOKEN_REQUEST_TOKEN / ACTIONS_ID_TOKEN_REQUEST_URL.

Problemen oplossen

Audience komt niet overeen.

De claim aud in het token moet overeenkomen met wat de provider verwacht. Is het vertrouwensbeleid of de provider ingesteld op een andere audience (bijv. sts.amazonaws.com in plaats van https://frem.sh), dan mislukt het inwisselen met een validatiefout voor de audience. Zet audience: https://frem.sh expliciet in de instellingen van de provider en in het tokenverzoek (gebruik &audience=https://frem.sh in de curl-URL).

De claim sub komt niet overeen met de voorwaarde in het vertrouwensbeleid.

Het formaat van de claim sub is exact: repo:<org>/<repo>:ref:refs/heads/<branch>. Veelvoorkomende afwijkingen:

  • Tikfout in de branchnaam (main of master).
  • Het vertrouwensbeleid gebruikt een tag-ref (refs/tags/v1.0), maar de job draait op een branch.
  • Het beleid gebruikt refs/heads/main, maar de job draait op een PR-branch (refs/pull/42/head). Gebruik StringLike met een jokerteken als je PR’s wilt toestaan: "repo:acme/api:ref:refs/pull/*".

Token verlopen bij jobs die langer dan 1 uur duren.

Het OIDC-token heeft een TTL van 1 uur. Jobs die langer dan 1 uur draaien, krijgen elke keer dat ze het tokenendpoint aanroepen een nieuw token. Elke stap die ACTIONS_ID_TOKEN_REQUEST_URL aanroept, krijgt een nieuw token. Bewaar je het token in een cache over stappen die samen langer dan 1 uur duren, dan verloopt het. Haal bij elke stap die inloggegevens nodig heeft een nieuw token op.

Recht id-token: write ontbreekt.

Is permissions: niet ingesteld in de workflow of de job, dan gebruikt de runner de standaardset met alleen-lezen rechten, en daar zit id-token: write niet in. De variabele ACTIONS_ID_TOKEN_REQUEST_TOKEN ontbreekt dan en het tokenverzoek geeft 403. Voeg id-token: write toe aan het permissions:-blok op jobniveau (of op workflowniveau als alle jobs het nodig hebben).

Federatie-instellingen per organisatie overschrijven

Op de beheerpagina https://frem.sh/<org>/_admin/runner-federation lees je de federatie-instellingen van je organisatie en scherp je ze aan als dat nodig is.

Voor de meeste opstellingen heb je alleen de uitgever-URL nodig: kopieer die van deze pagina naar het vertrouwensbeleid van je cloud (het veld Provider/Issuer URL bij AWS, Azure, GCP, Scaleway enz.). De pagina toont ook de JWKS-URL voor providers die het discoverydocument niet automatisch ophalen (T Cloud Public vereist expliciet plakken; AWS, Azure en GCP halen hem automatisch op).

Onder Geavanceerde overschrijvingen op dezelfde pagina kun je:

  • allowed_audiences vastleggen: een harde allowlist van aud-waarden die de issuer ondertekent. Gebruik dit als je alleen tokens wilt uitgeven voor specifieke vertrouwensbeleidsregels in de cloud en elke per ongeluk ruimere audience wilt blokkeren.
  • Een eigen sjabloon voor de claim sub instellen: als je bestaande vertrouwensbeleid een andere vorm van sub verwacht dan de standaard van fremforge.

De meeste klanten laten de geavanceerde overschrijvingen op hun standaardwaarden staan en gebruiken alleen de uitgever-URL. Dezelfde instellingen zijn ook programmatisch beschikbaar; zie hieronder.

Federatie-instellingen per organisatie via de API

Standaard ondertekent de issuer tokens met de claimvorm van het platform (sub = repo:<org>/<repo>:ref:<ref>, geen beperking op aud). Met twee instellingen leg je per organisatie strengere afspraken vast:

  • allowed_audiences: een harde allowlist van aud-waarden die de issuer ondertekent. Elk verzoek om een token met een aud buiten de lijst wordt geweigerd voordat de JWT wordt aangemaakt. Handig als je tokens wilt uitgeven voor vertrouwensbeleid in AWS en T Cloud Public, maar elke per ongeluk ruimere audience-claim wilt blokkeren. Lege lijst = geen beperking (standaard van het platform).
  • sub_claim_template: een sjabloon in Mustache-stijl dat bij het uitgeven van het token wordt ingevuld met vier waarden ({{tenant}}, {{repo}}, {{branch}}, {{ref_type}}). Handig als je bestaande vertrouwensbeleid voor GitHub Actions / Bitbucket Pipelines een andere vorm van sub verwacht dan de standaard van fremforge.

De huidige instellingen lezen

curl -H "Authorization: Bearer $FREMFORGE_PAT" \
  https://frem.sh/_app/api/v1/orgs/acme/runners/federation
# {
#   "issuer": "https://frem.sh/runner/oidc",
#   "jwks_uri": "https://frem.sh/runner/oidc/.well-known/jwks.json",
#   "tenant_org_slug": "acme",
#   "allowed_audiences": [],
#   "sub_claim_template": null
# }

allowed_audiences vastleggen

curl -X PUT -H "Authorization: Bearer $FREMFORGE_PAT" \
  -H "Content-Type: application/json" \
  -d '{
    "allowed_audiences": [
      "sts.amazonaws.com",
      "sts.eu-west-1.amazonaws.com"
    ]
  }' \
  https://frem.sh/_app/api/v1/orgs/acme/runners/federation

Daarna wordt elke poging om een token uit te geven met een aud die niet in de lijst staat, geweigerd, en faalt de job dicht met een fout dat de audience is geweigerd. Vereiste PAT-scope: runners:write.

sub_claim_template aanpassen

curl -X PUT -H "Authorization: Bearer $FREMFORGE_PAT" \
  -H "Content-Type: application/json" \
  -d '{
    "sub_claim_template": "tenant:{{tenant}}:repo:{{repo}}:branch:{{branch}}"
  }' \
  https://frem.sh/_app/api/v1/orgs/acme/runners/federation

Dit zijn de vier ondersteunde vervangingen:

TokenWordtVoorbeeld
{{tenant}}De slug van de organisatieacme
{{repo}}<org>/<repo>acme/api
{{branch}}Branchnaam (alleen als ref_type=branch; leeg voor tags)main
{{ref_type}}branch of tagbranch

Onbekende plaatshouders blijven letterlijk staan in de uitvoer; {{nope}} blijft bijvoorbeeld {{nope}} in de ingevulde sub. Dat is bewust: een verkeerd ingesteld sjabloon faalt dan duidelijk zichtbaar bij het matchen met het vertrouwensbeleid, in plaats van ongemerkt een lege tekenreeks uit te geven.

Terugzetten naar de standaardwaarden

Stuur een lege allowed_audiences en null als sub_claim_template:

curl -X PUT -H "Authorization: Bearer $FREMFORGE_PAT" \
  -H "Content-Type: application/json" \
  -d '{ "allowed_audiences": [], "sub_claim_template": null }' \
  https://frem.sh/_app/api/v1/orgs/acme/runners/federation

Alle wijzigingen worden vastgelegd in het auditlog van de organisatie onder runner_federation.updated. De momentopname van de body laat het volledige sub_claim_template weg, maar legt wel vast of er een is ingesteld.

Scaleway IAM External App

Scaleway is een EU-soevereine cloud met hoofdkantoor in Frankrijk en dataresidentie in FR / NL / PL. De IAM-bouwsteen External Apps (GA sinds oktober 2024) accepteert een upstream-OIDC-IdP en laat je workloadprincipals koppelen via JWT-claims; dezelfde federatievorm die AWS, GCP en Azure bieden.

Stap 1: maak een IAM External App aan

In de Scaleway-console: IAM → External Apps → Add an External App. Stel in:

  • Issuer URL: de issuer-URL van fremforge uit de wizard (bijv. https://frem.sh/_app/runner/oidc).
  • JWKS URI: de JWKS-URI van fremforge.
  • Subject mapping: koppel op sub. Beperk tot één repository als dat kan (repo:<your-slug>/<repo>:*).
  • Audience: de URN van je Scaleway-organisatie (scw:org:<org-id>).

Stap 2: koppel een Policy

Koppel een Scaleway-Policy aan de External App die de acties toestaat die je CI nodig heeft. Beperk die tot één Project voor minimale rechten.

Stap 3: gebruik het in een workflow

permissions:
  id-token: write
steps:
  - name: Get the fremforge OIDC token
    run: |
      TOKEN=$(curl -sS \
        -H "Authorization: bearer $ACTIONS_ID_TOKEN_REQUEST_TOKEN" \
        "$ACTIONS_ID_TOKEN_REQUEST_URL&audience=scw:org:<org-id>" \
        | jq -r .value)
      echo "::add-mask::$TOKEN"
      echo "FF_TOKEN=$TOKEN" >> $GITHUB_ENV
  - name: Exchange for Scaleway STS keys
    run: |
      # POST $FF_TOKEN to Scaleway's External-App token endpoint and export the
      # returned short-lived keys. See the Scaleway IAM docs for the exact shape.
      ./scripts/scw-sts-exchange.sh          # writes SCW_ACCESS_KEY / SCW_SECRET_KEY
  - name: Deploy
    run: ./deploy.sh

De vorm van het endpoint voor het inwisselen van tokens staat in de IAM-documentatie van Scaleway; de External App geeft kortlevende sleutels in STS-stijl uit, gekoppeld aan het OIDC-subject.

Generieke OIDC-broker (Authentik, Keycloak)

Voor opstellingen waarin de cloud niet rechtstreeks workload-OIDC spreekt (oudere zakelijke doelen die alleen SAML kennen, on-premises deployers, vertrouwensoverzichten voor meerdere clouds die centraal worden beheerd), gebruik je een broker: Authentik, Keycloak, of een andere opstelling met een OIDC-RP en een downstream-RP.

Wanneer je een broker gebruikt

  • Je draait al Authentik / Keycloak voor SSO van mensen en wilt CI-workloads onder dezelfde federatie brengen.
  • Je wilt één registratie van vertrouwensbeleid beheren, niet N per cloud. Een nieuwe cloud toevoegen is dan “een nieuwe Authentik-provider toevoegen”, niet “het vertrouwensbeleid in elke cloud opnieuw instellen”.
  • Je doel kent alleen SAML (sommige gereguleerde on-premises systemen). De broker vormt de brug van OIDC naar SAML.
  • Je richt je op een cloud zonder volwaardige workload-OIDC (Stackit, OVH, Hetzner): federeer via de broker in plaats van te wachten op volwaardige ondersteuning.

Stap 1: voeg fremforge toe als upstream-bron

In Authentik: Directory → Federation → OAuth/OIDC Source. Stel in:

  • OIDC Provider: de issuer-URL uit de wizard.
  • Audience: laat leeg of stel in volgens je downstream-provider.
  • Scopes: openid is genoeg; tokens van fremforge bevatten geen profile- of email-scopes.

Het equivalent in Keycloak is Identity Providers → OpenID Connect v1.0 met Use JWKS URL ingeschakeld.

Stap 2: koppel claims aan lokale groepen / rollen

In Authentik: Customisation → Property Mappings → OIDC. Koppel de sub van de runner-JWT (vorm repo:<slug>/<repo>:ref:refs/heads/<branch>) met een kleine Python-expressie aan lokale groepen:

# Example: anyone deploying main → 'prod-deploy' group
import re
if re.match(r'^repo:[^/]+/[^:]+:ref:refs/heads/main$', token.get('sub', '')):
    return ['prod-deploy']
return []

Stap 3: gebruik de broker als issuer voor de downstream-cloud

Het vertrouwensbeleid van je cloud matcht dan op een token dat door Authentik is ondertekend, niet rechtstreeks door fremforge. Audience = de client_id van de Authentik-provider. Wissel de JWT van fremforge in voor een door Authentik ondertekende JWT via het endpoint /application/o/token/ van Authentik:

permissions:
  id-token: write
steps:
- name: Get fremforge runner OIDC token
  run: |
    TOKEN=$(curl -sS \
      -H "Authorization: bearer $ACTIONS_ID_TOKEN_REQUEST_TOKEN" \
      "$ACTIONS_ID_TOKEN_REQUEST_URL&audience=<authentik-provider-client-id>" \
      | jq -r .value)
    echo "::add-mask::$TOKEN"
    echo "FF_TOKEN=$TOKEN" >> $GITHUB_ENV
- name: Exchange for Authentik token
  id: ak-token
  run: |
    curl -fsS -X POST https://idp.acme.com/application/o/token/ \
      -d grant_type=urn:ietf:params:oauth:grant-type:token-exchange \
      -d subject_token=$FF_TOKEN \
      -d subject_token_type=urn:ietf:params:oauth:token-type:jwt \
      -d audience=<downstream-rp> \
      | jq -r .access_token > /tmp/ak-token
- name: Deploy
  env:
    BROKER_TOKEN: $(cat /tmp/ak-token)
  run: ./deploy.sh

Afweging

De broker is een extra tussenstap in het pad van runner naar cloud (meer latentie, één plek meer om te debuggen). Hij verdient zich terug als je ≥3 clouds hebt, gereguleerde SAML-doelen, of al een broker draait voor SSO van mensen. Federeer bij opstellingen met één cloud rechtstreeks naar die cloud (zie de secties hierboven).

Zie ook

  • CI-runners: referentie voor gehoste en eigen runners
  • Secrets: langlevende inloggegevens opslaan en afbakenen
  • Beveiliging: het isolatiemodel van runners en de beveiliging van de toeleveringsketen