Essayez FFxF : 10 $ offerts avec le code TRY10, sans carte de crédit

GitHub Actions sans clé d'API : l'OIDC chez FFxF

Plus de clé d'API dans les secrets du dépôt : GitHub Actions signe pour chaque tâche un jeton de cinq minutes, et FFxF l'accepte quand il correspond à vos règles. Fournisseur, liaison, workflow et les sorties réelles d'une démonstration.

Un workflow dont les étapes réussissent passe un jeton signé à durée courte à une machine ; une clé barrée à côté

Pour qu'un workflow GitHub Actions crée une machine, la redémarre ou la détruise, il faut qu'il s'authentifie auprès de l'API. Jusqu'ici, cela voulait dire une clé d'API rangée dans les secrets du dépôt : une valeur qui vit des mois, que toute personne capable de modifier un workflow peut faire sortir, et qu'il faut penser à remplacer quand quelqu'un quitte l'équipe.

L'API FFxF accepte maintenant les jetons OIDC. GitHub Actions, GitLab CI et les autres émetteurs OIDC signent pour chaque tâche un jeton de quelques minutes qui dit d'où elle vient : quel dépôt, quelle branche, quel workflow. Vous dites à FFxF quels jetons accepter et avec quels droits ; le dépôt ne contient plus aucun secret. Cet article montre le montage complet, avec les sorties réelles d'une démonstration.

1. Ce qui change

  • Avec une clé, l'API sait qui vous êtes, pas qui l'appelle. Une copie de la clé fonctionne aussi bien depuis l'ordinateur d'un inconnu.
  • Avec l'OIDC, chaque appel porte une identité signée par GitHub : repo:acme/app:ref:refs/heads/main, tel fichier de workflow, tel déclencheur. FFxF vérifie la signature, puis compare ces informations à vos règles.
  • Le jeton expire au bout de cinq minutes. Retrouvé dans un journal le lendemain, il ne sert plus à rien.

2. Déclarer GitHub comme fournisseur

Dans la console, sous Compte → OIDC, ajoutez un fournisseur nommé « GitHub Actions » avec l'émetteur https://token.actions.githubusercontent.com. FFxF lui attribue une audience propre à votre compte, de la forme ffxf-oidc-… : c'est la valeur pour laquelle vos workflows demanderont leurs jetons.

Cette audience est ce qui relie un jeton à votre compte. GitHub délivre des jetons à n'importe quel dépôt pour l'audience de son choix ; un jeton demandé pour une autre valeur que la vôtre est refusé avant même la vérification de sa signature.

3. Une liaison : quels jetons, quels droits

Une liaison réunit des conditions sur le contenu du jeton et les portées qu'elle accorde, les mêmes que celles des clés d'API. Pour un déploiement depuis la branche main, avec un seul fichier de workflow autorisé :

Formulaire d'ajout d'une liaison OIDC dans la console FFxF : conditions sub et workflow_ref, portées de création et de destruction de machines
La liaison « Déploiement depuis main » : deux conditions, cinq portées, pas d'expiration.
  • sub = repo:acme/app:ref:refs/heads/main : le dépôt et la branche.
  • workflow_ref = acme/app/.github/workflows/deploy.yml@refs/heads/main : le fichier de workflow. Un autre workflow du même dépôt, même sur main, n'obtient rien.
  • Les portées : lire, créer, agir sur et détruire des machines, gérer les clés SSH. Rien sur la facturation.

Les jokers sont permis (* pour une suite de caractères, ? pour un seul), avec un garde-fou : dans sub, le propriétaire du dépôt doit être nommé avant tout joker. repo:acme/* accepte tous les dépôts d'acme ; repo:* accepterait tous les dépôts de GitHub, et la console le refuse.

La console refuse la condition sub = repo:* et demande de nommer le propriétaire avant tout joker
Une liaison trop large est refusée à l'enregistrement.

Le refus vise aussi repo:acme*, sans barre oblique : ce motif laisserait passer le compte acme-evil. Une seconde liaison, en lecture seule et avec une date d'expiration, sert aux pull requests :

Le fournisseur GitHub Actions et son audience, avec deux liaisons : déploiement depuis main et pull requests en lecture seule
Le fournisseur, son audience et ses deux liaisons.

4. Le workflow

La tâche a besoin de la permission id-token: write. Elle demande un jeton à GitHub pour votre audience, le masque dans le journal, puis s'en sert comme d'une clé :

yaml
permissions:
  contents: read
  id-token: write

jobs:
  deploy:
    runs-on: ubuntu-latest
    env:
      FFXF_API: https://api.ffxf.net/v1
      FFXF_OIDC_AUDIENCE: ffxf-oidc-…   # copiée depuis la console
    steps:
      - name: Jeton FFxF
        run: |
          token=$(curl -sS -H "Authorization: Bearer $ACTIONS_ID_TOKEN_REQUEST_TOKEN" \
            "$ACTIONS_ID_TOKEN_REQUEST_URL&audience=$FFXF_OIDC_AUDIENCE" | jq -r .value)
          echo "::add-mask::$token"
          echo "FFXF_TOKEN=$token" >> "$GITHUB_ENV"

      - name: Lister les machines
        run: curl -sS "$FFXF_API/vms" -H "Authorization: Bearer $FFXF_TOKEN"

Aucun secret dans les réglages du dépôt : l'audience n'en est pas un, puisqu'elle ne suffit pas à s'authentifier. Sur GitLab CI, c'est encore plus court : un bloc id_tokens avec aud: ffxf-oidc-… place le jeton dans une variable.

5. La démonstration

Nous avons fait tourner un workflow de ce genre sur notre environnement de test, qui exécute le même code d'authentification que l'API publique. Les sorties ci-dessous sont réelles ; seuls le nom du dépôt et l'audience sont abrégés. D'abord, ce que contient le jeton délivré par GitHub :

json
{
  "iss": "https://token.actions.githubusercontent.com",
  "aud": "ffxf-oidc-z8ma…",
  "sub": "repo:acme/app:ref:refs/heads/demo-oidc",
  "ref": "refs/heads/demo-oidc",
  "workflow_ref": "acme/app/.github/workflows/oidc-demo.yml@refs/heads/demo-oidc",
  "event_name": "push",
  "lifetime_s": 300
}

Cinq minutes de validité. La liaison de la démonstration accordait billing.read et vms.read à cette branche et à ce fichier. Les deux lectures passent :

bash
GET /v1/account   → HTTP 200   {"currency":"CAD","billing_mode":"prepaid"}
GET /v1/vms       → HTTP 200

Fixer le budget mensuel demande billing.write, que la liaison n'a pas :

bash
PUT /v1/account/budget → HTTP 403
{"code":"insufficient_scope",
 "message":"The OIDC binding is missing the scope required for this call.",
 "details":{"required":["billing.write"]}}

Le même workflow poussé sur une autre branche, demo-oidc-autre, obtient un jeton tout aussi valide, mais aucune liaison ne l'accepte :

bash
GET /v1/account → HTTP 403
{"code":"oidc_claim_mismatch","message":"The token does not match an enabled OIDC binding."}

Enfin, un jeton demandé à GitHub pour une autre audience que celle du compte est refusé d'emblée, sans qu'aucun appel ne parte vers GitHub pour vérifier sa signature :

bash
GET /v1/account → HTTP 401
{"code":"invalid_oidc_token","message":"The provided OIDC token is invalid."}

Ce dernier code est le même pour un jeton mal signé ou expiré : la réponse ne dit jamais si une audience ou une liaison existe dans un autre compte.

6. Ce que FFxF vérifie, dans l'ordre

  • L'émetteur et l'audience désignent un fournisseur actif de votre compte.
  • La signature, avec les clés publiques que GitHub publie, gardées une heure en cache. Seuls les algorithmes asymétriques sont acceptés.
  • L'émetteur exact, l'expiration, la date d'émission et le sub.
  • Les liaisons actives du fournisseur : la première dont toutes les conditions correspondent, qui n'a pas expiré et qui porte la portée de l'appel, l'emporte.

Un jeton accepté agit ensuite comme une clé de votre compte : même quota, même crédit, mêmes plafonds de budget. Les seaux de débit et l'idempotence sont comptés par liaison. Pour couper un accès, désactivez la liaison : l'appel suivant est refusé.

7. Et pour un agent IA

Le serveur MCP FFxF relaie l'en-tête Authorization tel quel. Un agent lancé dans une tâche GitHub Actions peut donc piloter vos machines avec le jeton OIDC de la tâche, dans les limites de la liaison, sans qu'aucune clé ne lui soit confiée. Les liaisons, elles, ne se créent que dans la console : ni une clé ni un jeton ne peut en élargir une.

Checklist

  • Un fournisseur par émetteur, et son audience copiée dans le workflow.
  • Une liaison par usage, avec sub et workflow_ref épinglés.
  • Les portées strictement nécessaires : la destruction pour la seule tâche qui nettoie.
  • id-token: write dans les permissions du workflow, et le jeton masqué dans le journal.
  • Une date d'expiration sur les liaisons d'essai.
  • La clé d'API que la CI utilisait jusqu'ici, révoquée.

Les conditions, les formats de sub chez GitHub et GitLab, et tous les codes de refus sont dans la documentation OIDC.

Requête HTTP commandant la création d'une machine, et la machine livrée

Piloter un VPS par API : créer, suivre, détruire

Une machine commandée par script sert le temps d'une tâche, puis disparaît. Clé et portées, catalogue lisible sans clé, création idempotente, sondage de l'action, destruction garantie par un trap : le tout en un script de trente-cinq lignes, à 0,018 CAD l'heure.

Lire l'article 9 min de lecture

Un coffre de données au centre d'un territoire en pointillés, des flux extérieurs arrêtés à sa frontière

Héberger des données au Canada : Loi 25, CLOUD Act

Aucune loi n'oblige une entreprise privée à héberger au Canada. La Loi 25 impose une évaluation avant que les données quittent le Québec, la LPRPDE vous laisse responsable partout, et le CLOUD Act suit l'entreprise plutôt que le serveur.

Lire l'article 7 min de lecture

Support & discussions

Questions techniques, retours d'incidents ou discussions d'infrastructure, l'équipe est présente sur Discord, Telegram, X, Instagram, Reddit et IRC.