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 :
| Claim | Valeur / format | Exemple |
|---|---|---|
iss | https://frem.sh/_app/runner/oidc (émetteur global unique pour toutes les organisations) | https://frem.sh/_app/runner/oidc |
sub | repo:<org>/<repo>:ref:refs/heads/<branch> | repo:acme/api:ref:refs/heads/main |
aud | Configurable ; par défaut https://frem.sh | https://frem.sh |
repository_owner | Identifiant 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 |
ref | Référence git complète | refs/heads/main |
sha | SHA du commit | a1b2c3d4... |
environment | Nom de l’environnement de déploiement (si le job en déclare un) | production |
job_workflow_ref | Chemin et référence du fichier de workflow | acme/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 Public | acme-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_ENVLa 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 claimissde chaque jeton d’identité de runner fremforge). - Client ID (
client_id) :https://frem.sh, doit être égal au claimauddu jeton d’identité du runner. - Signing key (
signing_key) : collez le JSON de clés publiques JWKS. Récupérez-le une fois depuishttps://frem.sh/_app/runner/oidc/.well-known/openid-configuration→jwks_uriet 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_groupdu 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 Administratorlimité au bucket OBS de la charge de travail, ouCCE FullAccesslimité 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 automaticallyRemarques propres à T Cloud Public
- OTC utilise
/v3/OS-FEDERATION/identity_providers/{idp}/protocols/oidc/auth, et NON/v3/auth/tokensavecmethods=["id_token"]. La méthode d’authentification par corps de requête d’OpenStack amont renvoie400 "Unsupported method: id_token"sur le fork Keystone d’OTC. Vérifié empiriquement et conformément au document d’API OTCiam_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
- Dans le portail Azure, allez dans Entra ID → App registrations →
<your app>→ Certificates & secrets → Federated credentials. - Cliquez sur Add credential.
- Scénario : Other issuer.
- 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
- Issuer :
- 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 ./distL’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-imageL’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 (
mainau lieu demaster). - 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). UtilisezStringLikeavec 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 valeursaudque 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
subpersonnalisé : lorsque votre politique de confiance existante attend une forme desubdiffé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 valeursaudque l’émetteur acceptera de signer. Toute demande de création de jeton avec unaudhors 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 desubdiffé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/federationEnsuite, 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/federationLes quatre substitutions prises en charge sont :
| Variable | Valeur | Exemple |
|---|---|---|
{{tenant}} | L’identifiant de l’organisation | acme |
{{repo}} | <org>/<repo> | acme/api |
{{branch}} | Nom de la branche (uniquement si ref_type=branch ; vide pour les tags) | main |
{{ref_type}} | branch ou tag | branch |
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/federationToutes 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.shLa 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 :
openidsuffit ; 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.shCompromis
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