Aller au contenu principal

Fédération de jetons OIDC

Chaque job CI de fremforge peut recevoir un jeton OIDC de courte durée : un JWT signé en RS256, délivré par l’émetteur OIDC des runners de fremforge. Les fournisseurs cloud (T Cloud Public, AWS, Azure, GCP) peuvent être configurés pour faire confiance à ce jeton et l’échanger contre des identifiants limités en portée et en durée. Aucune clé de déploiement stockée comme secret, rien à faire tourner selon un calendrier.

Référence des claims

Le JWT du runner fremforge contient les claims suivants :

ClaimValeur / formatExemple
isshttps://frem.sh/_app/runner/oidc (émetteur global unique pour toutes les organisations)https://frem.sh/_app/runner/oidc
subrepo:<org>/<repo>:ref:refs/heads/<branch>repo:acme/api:ref:refs/heads/main
audConfigurable ; par défaut https://frem.shhttps://frem.sh
repository_ownerIdentifiant de l’organisation ; ancrez les conditions des politiques de confiance sur ce claim pour isoler chaque organisation sous l’émetteur partagéacme
repository<org>/<repo>acme/api
refRéférence git complèterefs/heads/main
shaSHA du commita1b2c3d4...
environmentNom de l’environnement de déploiement (si le job en déclare un)production
job_workflow_refChemin et référence du fichier de workflowacme/api/.forgejo/workflows/deploy.yaml@refs/heads/main
deployer_group(T Cloud Public uniquement) Nom du groupe IAM auquel rattacher l’utilisateur fédéré, défini par fremforge pour chaque charge de travail ; voir Confiance des agences T Cloud Publicacme-prod-deployers

Le claim sub est l’ancre de confiance principale dans la plupart des politiques de confiance des fournisseurs. Il identifie de façon unique la combinaison organisation, dépôt et branche. Pour isoler chaque organisation dans des comptes cloud partagés, ajoutez une condition ancrée sur repository_owner (l’identifiant de votre organisation), selon le même principe que la confiance de GitHub Actions envers token.actions.githubusercontent.com. fremforge émet un seul émetteur global pour toutes les organisations ; l’isolation de la confiance est assurée au niveau des claims, pas au niveau de l’URL de l’émetteur.

Obtenir le jeton dans un workflow

Ajoutez id-token: write au bloc permissions du job. Le runner renseigne alors les variables d’environnement ACTIONS_ID_TOKEN_REQUEST_TOKEN et ACTIONS_ID_TOKEN_REQUEST_URL, qui vous servent à récupérer le JWT proprement dit :

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

La plupart des étapes propres à un fournisseur (AWS, Azure, GCP) gèrent la récupération du jeton en interne. Vous n’avez à écrire la récupération manuelle ci-dessus que si vous avez besoin du JWT brut pour un flux de confiance personnalisé.

Confiance des agences T Cloud Public

Le modèle de fédération IAM de T Cloud Public est dérivé d’OpenStack : vous enregistrez l’émetteur OIDC comme fournisseur d’identité, vous y attachez un mapping qui traduit les claims du jeton d’identité en groupes IAM locaux, puis vous échangez le jeton d’identité du runner sur le point de terminaison OS-FEDERATION d’OTC contre un jeton Keystone de T Cloud Public limité aux groupes rattachés par le mapping.

fremforge utilise le rattachement aux groupes piloté par les claims (la même approche qu’AWS Identity Center, GCP Workload Identity Federation et Azure Federated Credentials) : le jeton d’identité du runner porte un claim deployer_group propre à chaque charge de travail, et votre mapping OTC lit ce claim tel quel. Une seule règle de mapping couvre toute votre flotte : ajouter une charge de travail ne touche pas du tout à votre IAM.

Étape 1 : créer un fournisseur d’identité OpenID Connect

Dans la console T Cloud Public, allez dans IAM → Identity Providers → Create Identity Provider.

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

Après la création, configurez les paramètres OIDC :

  • Identity provider URL (idp_url) : https://frem.sh/_app/runner/oidc (émetteur global unique ; correspond au claim iss de chaque jeton d’identité de runner fremforge).
  • Client ID (client_id) : https://frem.sh, doit être égal au claim aud du jeton d’identité du runner.
  • Signing key (signing_key) : collez le JSON de clés publiques JWKS. Récupérez-le une fois depuis https://frem.sh/_app/runner/oidc/.well-known/openid-configuration → jwks_uri et copiez le JSON. T Cloud Public ne récupère pas automatiquement le JWKS : collez-le à nouveau quand fremforge annonce une rotation du JWKS (annoncée sur la page Trust ; les rotations ont lieu au plus une fois par an).

Étape 2 : ajouter la règle de mapping pilotée par les claims

Une seule règle, qui convient à n’importe quel nombre de charges de travail :

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

La posture de confiance :

  • L’utilisateur fédéré rejoint le groupe IAM dont le claim deployer_group du jeton d’identité indique le nom ({2} reprend la valeur du claim telle quelle).
  • Le claim est défini par l’émetteur OIDC des runners de fremforge, selon la correspondance charge de travail → groupe convenue lors de l’intégration. Il ne peut être revendiqué que pour les charges de travail pour lesquelles nous avons une ligne, et uniquement pour la référence que fixe cette ligne (par défaut : la branche main).
  • Même épinglage de la signature du JWT, même filtrage par iss + aud + repository_owner : seule la décision charge de travail → groupe passe de « N règles de mapping OTC » à « un claim émis par fremforge ».

Étape 3 : créer les groupes IAM qu’endosseront vos charges de travail

Créez un groupe par profil de déploiement que vous voulez exposer (par ex. acme-prod-deployers, acme-staging-deployers). Attachez-y les rôles de projet et d’EPS dont chaque profil a besoin : ils vous appartiennent entièrement ; fremforge ne touche jamais à la configuration de vos groupes. Les noms de groupes correspondent à la valeur que fremforge placera dans le claim deployer_group, convenue lors de l’intégration.

Étape 4 : indiquer à fremforge quelle charge de travail utilise quel groupe

Par e-mail ou appel à l’API d’administration : « Dépôt acme/api, branche main → groupe acme-prod-deployers. » fremforge insère une ligne dans sa table runner_deployer_groups. À partir de l’exécution de workflow suivante, les jetons d’identité de cette charge de travail portent deployer_group=acme-prod-deployers et votre mapping rattache l’utilisateur fédéré à ce groupe.

Ajouter un nouveau dépôt se résume à un autre e-mail : votre IAM OTC reste inchangé.

Facultatif : créer une agence à endosser

La plupart des configurations sautent cette étape : le rattachement au groupe de l’étape 2 suffit. Si vous utilisez un schéma de rôle endossé, créez une agence (IAM → Agencies → Create Agency) avec les paramètres suivants :

  • Agency type : Account (le service cloud d’un autre compte T Cloud Public ne s’applique pas ici).
  • Delegated party : sélectionnez le fournisseur d’identité OpenID Connect créé à l’étape 1.
  • Permissions : attachez les politiques dont le job CI a besoin (par ex. OBS Administrator limité au bucket OBS de la charge de travail, ou CCE FullAccess limité au projet de la charge de travail).

Notez le nom de l’agence et le projet dans lequel vous la limitez.

Étape 5 : utiliser les identifiants dans un workflow

Le runner récupère un jeton d’identité, l’échange sur le point de terminaison OS-FEDERATION d’OTC contre un jeton Keystone sans portée, puis l’échange à son tour contre un jeton limité à un projet. La CLI T Cloud Public (otc) comme OpenTofu avec le provider opentelekomcloud acceptent OS_AUTH_TOKEN depuis l’environnement, sans autre configuration.

# 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

Remarques propres à T Cloud Public

  • OTC utilise /v3/OS-FEDERATION/identity_providers/{idp}/protocols/oidc/auth, et NON /v3/auth/tokens avec methods=["id_token"]. La méthode d’authentification par corps de requête d’OpenStack amont renvoie 400 "Unsupported method: id_token" sur le fork Keystone d’OTC. Vérifié empiriquement et conformément au document d’API OTC iam_13_0606.
  • OTC ne fait pas tourner automatiquement le JWKS. Vous collez la clé de signature une fois à la création du fournisseur ; collez-la à nouveau quand fremforge annonce une rotation. AWS / Azure / GCP la récupèrent tous automatiquement depuis le document de découverte ; seul T Cloud Public exige de la coller explicitement.
  • Le jeton échangé se trouve dans l’en-tête de réponse X-Subject-Token, pas dans le corps : c’est la convention d’OpenStack Keystone.
  • La limitation par charge de travail provient du claim deployer_group (le mapping rattache l’utilisateur fédéré à ce groupe) ET des rôles de projet de ce groupe. Le claim empêche une charge de travail d’endosser le mauvais groupe ; le rôle limite ce que le groupe peut faire.
  • Facultatif : les agences, pour les schémas de rôle endossé, sont aussi prises en charge, mais la plupart des cas d’usage n’en ont pas besoin : le rattachement au groupe de l’étape 2 suffit.

AWS IAM

Étape 1 : créer un fournisseur d’identité OIDC

Dans la console AWS, allez dans IAM → Identity providers → Add provider.

  • Provider type : OpenID Connect
  • Provider URL : https://frem.sh/_app/runner/oidc (émetteur global unique ; le filtrage par organisation se fait dans la condition de la politique de confiance, pas dans l’URL du fournisseur)
  • Audience : https://frem.sh

AWS récupère le JWKS depuis le document de découverte pour vérifier les jetons.

Étape 2 : créer un rôle IAM avec une politique de confiance 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"
        }
      }
    }
  ]
}

Attachez à ce rôle les permissions dont le job CI a besoin (par ex. AmazonS3FullAccess, AmazonECRPowerUser).

Étape 3 : l’utiliser dans un workflow

Utilisez l’action officielle 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/

L’action gère automatiquement la récupération du jeton, l’appel sts:AssumeRoleWithWebIdentity et l’injection des identifiants.

Azure (Entra Workload Identity)

Étape 1 : créer des identifiants fédérés sur une inscription d’application

  1. Dans le portail Azure, allez dans Entra ID → App registrations → <your app> → Certificates & secrets → Federated credentials.
  2. Cliquez sur Add credential.
  3. Scénario : Other issuer.
  4. Renseignez :
    • Issuer : https://frem.sh/_app/runner/oidc (émetteur global unique ; l’isolation entre organisations est assurée par le Subject identifier ci-dessous)
    • Subject identifier : repo:<your-org>/<your-repo>:ref:refs/heads/main
    • Audience : https://frem.sh
  5. Enregistrez.

Étape 2 : l’utiliser dans un 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

L’identifiant client de l’inscription d’application et l’identifiant de votre tenant Entra ne sont pas des secrets. Ils peuvent être stockés comme simples variables de workflow. Seul le jeton OIDC est sensible, et il expire à la fin du job.

GCP Workload Identity

Étape 1 : créer un pool Workload Identity et un fournisseur OIDC

# 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"

Étape 2 : autoriser l’emprunt d’identité du compte de service

# 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"

Étape 3 : l’utiliser dans un 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

L’action google-github-actions/auth fonctionne sur les runners fremforge. Elle utilise le même mécanisme ACTIONS_ID_TOKEN_REQUEST_TOKEN / ACTIONS_ID_TOKEN_REQUEST_URL.

Dépannage

Audience qui ne correspond pas.

Le claim aud du jeton doit correspondre à ce qu’attend le fournisseur. Si la politique de confiance ou le fournisseur est configuré pour une autre audience (par ex. sts.amazonaws.com au lieu de https://frem.sh), l’échange échoue avec une erreur de validation de l’audience. Définissez explicitement audience: https://frem.sh dans la configuration du fournisseur et dans la demande de jeton (utilisez &audience=https://frem.sh dans l’URL de curl).

Le claim sub ne correspond pas à la condition de la politique de confiance.

Le format du claim sub est exact : repo:<org>/<repo>:ref:refs/heads/<branch>. Causes fréquentes de non-correspondance :

  • Faute de frappe dans le nom de branche (main au lieu de master).
  • La politique de confiance utilise une référence de tag (refs/tags/v1.0), mais le job s’exécute sur une branche.
  • La politique utilise refs/heads/main, mais le job s’exécute sur une branche de pull request (refs/pull/42/head). Utilisez StringLike avec un caractère générique si vous devez autoriser les pull requests : "repo:acme/api:ref:refs/pull/*".

Jeton expiré, jobs de plus d’une heure.

Le jeton OIDC a une durée de vie d’une heure. Les jobs qui durent plus d’une heure reçoivent un nouveau jeton chaque fois qu’ils appellent le point de terminaison des jetons. Chaque étape qui appelle ACTIONS_ID_TOKEN_REQUEST_URL obtient un nouveau jeton. Si vous conservez le jeton d’une étape à l’autre sur plus d’une heure, il expire. Récupérez un nouveau jeton à chaque étape qui a besoin d’identifiants.

Permission id-token: write manquante.

Si permissions: n’est pas défini dans le workflow ou le job, le runner utilise l’ensemble de permissions par défaut en lecture seule, qui n’inclut pas id-token: write. La variable ACTIONS_ID_TOKEN_REQUEST_TOKEN est alors absente et la demande de jeton renvoie 403. Ajoutez id-token: write au bloc permissions: au niveau du job (ou au niveau du workflow si tous les jobs en ont besoin).

Remplacements de fédération par organisation

La page d’administration de l’organisation https://frem.sh/<org>/_admin/runner-federation est l’endroit où lire les paramètres de fédération de votre organisation et les resserrer si nécessaire.

Dans la plupart des configurations, vous n’avez besoin que de l’URL de l’émetteur : copiez-la depuis cette page dans la politique de confiance de votre cloud (le champ Provider/Issuer URL pour AWS, Azure, GCP, Scaleway, etc.). La page affiche aussi l’URL JWKS pour les fournisseurs qui ne récupèrent pas automatiquement le document de découverte (T Cloud Public exige de la coller explicitement ; AWS / Azure / GCP la récupèrent automatiquement).

Sous Remplacements avancés, sur la même page, vous pouvez :

  • Épingler allowed_audiences : une liste d’autorisation stricte des valeurs aud que l’émetteur acceptera de signer. Utilisez-la lorsque vous voulez créer des jetons uniquement pour certaines politiques de confiance cloud et bloquer toute audience trop large par inadvertance.
  • Définir un modèle de claim sub personnalisé : lorsque votre politique de confiance existante attend une forme de sub différente de celle de fremforge par défaut.

La plupart des clients laissent les remplacements avancés à leurs valeurs par défaut et n’utilisent que l’URL de l’émetteur. Les mêmes paramètres sont aussi disponibles par programme (voir ci-dessous).

Remplacements de fédération par organisation via l’API

Par défaut, l’émetteur signe les jetons avec la forme de claims de la plateforme (sub = repo:<org>/<repo>:ref:<ref>, aucune restriction sur aud). Deux réglages vous permettent d’imposer des contrats plus stricts, organisation par organisation :

  • allowed_audiences : une liste d’autorisation stricte des valeurs aud que l’émetteur acceptera de signer. Toute demande de création de jeton avec un aud hors de la liste est rejetée avant la création du JWT. Utile lorsque vous voulez créer des jetons pour des politiques de confiance AWS et T Cloud Public tout en bloquant tout claim d’audience trop large par inadvertance. Liste vide = aucune restriction (valeur par défaut de la plateforme).
  • sub_claim_template : un modèle de type Mustache, rendu au moment de la création du jeton à partir de quatre variables ({{tenant}}, {{repo}}, {{branch}}, {{ref_type}}). Utile lorsque votre politique de confiance existante pour GitHub Actions / Bitbucket Pipelines attend une forme de sub différente de celle de fremforge par défaut.

Lire les remplacements actuels

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
# }

Épingler allowed_audiences

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

Ensuite, toute tentative de création d’un jeton avec un aud absent de la liste est rejetée, et le job échoue en mode fermé avec une erreur de refus d’audience. Scope de PAT requis : runners:write.

Personnaliser sub_claim_template

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

Les quatre substitutions prises en charge sont :

VariableValeurExemple
{{tenant}}L’identifiant de l’organisationacme
{{repo}}<org>/<repo>acme/api
{{branch}}Nom de la branche (uniquement si ref_type=branch ; vide pour les tags)main
{{ref_type}}branch ou tagbranch

Les variables inconnues sont laissées telles quelles dans le résultat : par exemple, {{nope}} reste {{nope}} dans le sub rendu. C’est voulu : un modèle mal configuré échoue bruyamment à l’étape de correspondance avec la politique de confiance, au lieu d’émettre en silence une chaîne vide.

Revenir aux valeurs par défaut

Envoyez un allowed_audiences vide et un sub_claim_template à null :

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

Toutes les mises à jour sont consignées dans le journal d’audit de l’organisation sous runner_federation.updated. L’instantané du corps omet le sub_claim_template complet, mais indique si un modèle a été défini.

Scaleway IAM External App

Scaleway est un cloud souverain européen basé en France, avec une résidence des données en France, aux Pays-Bas et en Pologne. Sa primitive IAM External Apps (disponible en version GA depuis octobre 2024) accepte un fournisseur d’identité OIDC amont et vous permet de rattacher des principaux de charge de travail via des claims JWT, selon le même modèle de fédération qu’AWS / GCP / Azure.

Étape 1 : créer une IAM External App

Dans la console Scaleway : IAM → External Apps → Add an External App. Configurez :

  • Issuer URL : l’URL de l’émetteur fremforge indiquée par l’assistant (par ex. https://frem.sh/_app/runner/oidc).
  • JWKS URI : l’URI JWKS de fremforge.
  • Subject mapping : rattachez sur sub. Limitez si possible à un seul dépôt (repo:<your-slug>/<repo>:*).
  • Audience : l’URN de votre organisation Scaleway (scw:org:<org-id>).

Étape 2 : attacher une Policy

Attachez à l’External App une Policy Scaleway accordant les actions dont votre CI a besoin. Limitez-la à un seul Project pour appliquer le moindre privilège.

Étape 3 : l’utiliser dans un 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

La forme du point de terminaison d’échange de jetons est documentée dans la documentation IAM de Scaleway ; la primitive External App émet des clés de courte durée de type STS, liées au sujet OIDC.

Broker OIDC générique (Authentik, Keycloak)

Pour les configurations où le cloud ne prend pas directement en charge l’OIDC de charge de travail (cibles d’entreprise anciennes uniquement SAML, outils de déploiement sur site, correspondances de confiance multicloud gérées de façon centralisée), utilisez un broker : Authentik, Keycloak, ou toute configuration OIDC où un RP en alimente un autre en aval.

Quand utiliser un broker

  • Vous utilisez déjà Authentik / Keycloak pour le SSO des personnes et voulez placer les charges de travail CI sous la même surface de fédération.
  • Vous voulez gérer un seul enregistrement de politique de confiance, et non un par cloud. Ajouter un nouveau cloud revient alors à « ajouter un nouveau provider Authentik », et non à « refaire la politique de confiance dans chaque cloud ».
  • Votre cible ne prend en charge que SAML (certains systèmes réglementés sur site). Le broker fait le pont OIDC → SAML.
  • Vous ciblez un cloud sans OIDC de charge de travail natif (Stackit, OVH, Hetzner) : fédérez via le broker plutôt que d’attendre une prise en charge native.

Étape 1 : ajouter fremforge comme source amont

Dans Authentik : Directory → Federation → OAuth/OIDC Source. Définissez :

  • OIDC Provider : l’URL de l’émetteur indiquée par l’assistant.
  • Audience : laissez vide ou définissez selon votre fournisseur en aval.
  • Scopes : openid suffit ; les jetons fremforge ne portent pas de scopes profile/email.

L’équivalent dans Keycloak est Identity Providers → OpenID Connect v1.0, avec Use JWKS URL activé.

Étape 2 : faire correspondre les claims aux groupes / rôles locaux

Dans Authentik : Customisation → Property Mappings → OIDC. Faites correspondre le sub du JWT du runner (de forme repo:<slug>/<repo>:ref:refs/heads/<branch>) à des groupes locaux à l’aide d’une petite expression Python :

# 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 []

Étape 3 : utiliser le broker comme émetteur pour le cloud en aval

La politique de confiance de votre cloud correspond alors à un jeton signé par Authentik, et non directement par fremforge. Audience = le client_id du Provider Authentik. Échangez le JWT fremforge contre un JWT signé par Authentik via le point de terminaison /application/o/token/ d’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

Compromis

Le broker ajoute un saut dans le chemin runner → cloud (plus de latence, un endroit de plus à déboguer). Il est rentable si vous avez au moins 3 clouds, des cibles SAML réglementées, ou si vous utilisez déjà un broker pour le SSO des personnes. Pour les configurations à un seul cloud, fédérez directement vers le cloud (sections ci-dessus).

Voir aussi

  • Runners CI : référence des runners hébergés et fournis par vous
  • Secrets : stocker et limiter les identifiants à longue durée de vie
  • Security : modèle d’isolation des runners et sécurité de la chaîne d’approvisionnement