Automation

GitHub Actions : valider un workflow réutilisable avant de lui confier un déploiement Azure

Un runbook de production pour qualifier un workflow GitHub Actions réutilisable avec permissions, secrets, OIDC, environnements, traces, canari et rollback avant un déploiement Azure.

09 sept. 2026 github-actionsreusable-workflowazuredevopsoidcworkload-identitysecurityautomationobservabilityguardrailsrunbookrollbackproduction

Une équipe plateforme centralise ses déploiements Azure dans un workflow GitHub Actions réutilisable. Les dépôts applicatifs n’ont plus qu’à fournir un environnement, un artefact et quelques paramètres. Le changement réduit la duplication, mais il déplace aussi la frontière de confiance : une modification du workflow partagé peut désormais affecter plusieurs services, et un appelant mal borné peut lui transmettre plus de secrets ou de permissions que prévu.

Le cas d’usage est un workflow deploy-azure.yml appelé par plusieurs dépôts pour promouvoir une image déjà construite vers un environnement Azure. Il utilise OIDC, une identité de déploiement dédiée et un environnement GitHub protégé. Ce runbook permet de décider si une nouvelle version du workflow peut recevoir des droits de production, doit rester en canari ou doit être rollbackée sans remettre un secret long terme.

Figer le contrat entre appelant et workflow appelé

Un workflow réutilisable n’est pas une simple bibliothèque YAML. Il s’exécute avec le contexte d’un appelant, reçoit des entrées et des secrets, puis demande des permissions au jeton GitHub. Commencez par décrire ce contrat avant d’étudier les étapes internes.

yaml reusable-deployment-contract.yml
contract_version: deploy-azure-v4
caller:
repository: company/orders-api
workflow: .github/workflows/release.yml
ref: refs/heads/main
environment: production

called_workflow:
repository: company/platform-workflows
path: .github/workflows/deploy-azure.yml
ref: <reviewed-commit-sha>

inputs:
environment: production
image_digest: sha256:<digest>
resource_group: rg-orders-prod

expected_permissions:
contents: read
id-token: write

forbidden:
- arbitrary_subscription
- mutable_image_tag
- inherited_secrets
- unreviewed_workflow_ref
- production_without_environment_gate

La fiche doit nommer l’appelant autorisé, la version appelée, l’identité Azure attendue, le scope RBAC et l’effet final. Un paramètre comme subscription_id ou resource_group ne devrait pas permettre à chaque dépôt de choisir une cible arbitraire. Préférez une valeur dérivée d’un environnement approuvé ou une allowlist contrôlée côté workflow partagé.

Typer les entrées et fermer les sorties dangereuses

workflow_call rend l’interface visible, mais une entrée typée string peut encore transporter un scope Azure, une commande ou des options libres. Gardez l’interface au niveau du cas d’usage : promouvoir un digest vers un environnement connu, pas exécuter une commande Azure générique.

yaml deploy-azure.yml
name: deploy-azure

on:
workflow_call:
  inputs:
    environment:
      type: string
      required: true
    image_digest:
      type: string
      required: true
  outputs:
    deployment_id:
      description: Stable deployment identifier
      value: ${{ jobs.deploy.outputs.deployment_id }}

permissions:
contents: read
id-token: write

jobs:
deploy:
  environment: ${{ inputs.environment }}
  runs-on: ubuntu-latest
  steps:
    - name: Validate bounded inputs
      run: ./scripts/validate-deployment-inputs.sh
      env:
        TARGET_ENVIRONMENT: ${{ inputs.environment }}
        IMAGE_DIGEST: ${{ inputs.image_digest }}

La validation déterministe doit refuser les environnements inconnus, les références par tag mutable et les digests mal formés avant l’authentification Azure. Le workflow doit retourner un identifiant de déploiement et un état vérifiable, pas seulement une conclusion GitHub verte.

Calculer les permissions sur toute la chaîne

Les permissions effectives ne se lisent pas uniquement dans le workflow appelé. Elles partent du workflow appelant et ne peuvent pas être élevées au fil d’une chaîne de workflows réutilisables. Une permission absente peut casser OIDC ; une permission globale trop large peut exposer inutilement le dépôt.

Dans l’appelant, déclarez le minimum au niveau du job qui délègue :

yaml release.yml
jobs:
deploy-production:
  permissions:
    contents: read
    id-token: write
  uses: company/platform-workflows/.github/workflows/deploy-azure.yml@<reviewed-commit-sha>
  with:
    environment: production
    image_digest: sha256:<digest>
  secrets:
    deployment_observability_key: ${{ secrets.DEPLOYMENT_OBSERVABILITY_KEY }}

Évitez write-all et relisez aussi les workflows appelés par le workflow partagé. Si une étape n’a besoin que de télécharger un artefact, elle n’a pas besoin de issues: write, pull-requests: write ou actions: write. Séparez le job de validation sans identité Azure du job de déploiement qui obtient le jeton OIDC.

Épingler le code qui franchit la frontière de confiance

Un appel vers @main ou un tag déplaçable permet au workflow partagé de changer sans nouvelle revue côté dépôt applicatif. Pour la production, appelez un commit SHA relu. Appliquez la même discipline aux actions tierces utilisées transitivement dans le workflow réutilisable.

Conservez une table de promotion compacte : SHA actuellement autorisé, SHA candidat, diff des permissions, nouveaux inputs, nouvelles actions, propriétaires et date de validation. Un outil automatique peut ouvrir les mises à jour de SHA, mais la promotion doit rester liée à un diff explicite.

Le SHA protège la version appelée ; il ne protège pas contre un code déjà excessif. La revue doit rechercher les commandes construites depuis des entrées, les téléchargements non vérifiés, les écritures hors cible, les sorties qui contiennent des tokens et les étapes exécutées avant le gate d’environnement.

Ne pas hériter les secrets par commodité

secrets: inherit simplifie l’appel, mais agrandit silencieusement le contrat. Le workflow partagé reçoit alors des secrets sans que son interface nomme ceux dont il a réellement besoin. Préférez des secrets déclarés un par un, et utilisez OIDC pour Azure afin de ne pas transmettre de secret client persistant.

L’environnement mérite une vérification spécifique. Les secrets d’environnement ne se transmettent pas comme de simples paramètres de workflow_call, et un job appelé qui déclare son propre environnement peut sélectionner les secrets associés à cet environnement. Vérifiez donc ensemble : nom de l’environnement, règles d’approbation, branches autorisées, identité Azure et scope RBAC.

Avant le rollout, prouvez les claims OIDC émis pour le vrai appelant. Le dépôt appelant, la ref, l’environnement et la référence du workflow exécuté doivent correspondre au contrat accepté par la fédération. N’élargissez pas un federated credential pour faire passer un canari ; corrigez d’abord l’écart entre contexte observé et contexte attendu.

Reconstruire une preuve de bout en bout

Une exécution exploitable relie le commit applicatif, le SHA du workflow partagé, le digest, l’approbation, l’identité Azure et l’opération de déploiement. Capturez ces identifiants avant de lire une simple conclusion success.

bash collect-reusable-workflow-evidence.sh
set -euo pipefail

repo="company/orders-api"
run_id="<run-id>"

gh run view "$run_id" --repo "$repo" --json databaseId,headSha,event,workflowName,status,conclusion,url,jobs

gh api -H "Accept: application/vnd.github+json" "/repos/$repo/actions/runs/$run_id/approvals"

Côté Azure, cherchez l’identité réellement utilisée et corrélez la fenêtre UTC avec l’opération attendue. Le schéma dépend de la ressource cible, mais la requête doit conserver la cible et le résultat, pas seulement compter les appels.

kusto 01-correlate-reusable-workflow-deployment.kql
let StartUtc = datetime(<start-utc>);
let EndUtc = datetime(<end-utc>);
let ExpectedCaller = "<deployment-service-principal-object-id>";
AzureActivity
| where TimeGenerated between (StartUtc .. EndUtc)
| where Caller == ExpectedCaller
| project TimeGenerated,
        CorrelationId,
        OperationNameValue,
        ResourceGroup,
        ResourceId,
        ActivityStatusValue,
        Caller
| order by TimeGenerated asc

L’absence de ligne ne prouve pas l’absence d’effet : certaines opérations écrivent dans des logs de ressource ou un service de déploiement distinct. Définissez la source d’autorité avant le test et conservez l’identifiant retourné par le backend.

Passer par un canari qui exerce les vrais contrôles

Un test de syntaxe ne valide ni OIDC, ni l’environnement, ni le scope Azure. Créez un dépôt ou un service canari autorisé à appeler le même SHA avec une identité distincte et une cible sans impact. Le canari doit suivre le vrai chemin d’approbation et produire les mêmes traces.

yaml reusable-workflow-evaluation.yml
cases:
- id: allow_reviewed_caller_and_sha
  caller: company/orders-api
  called_ref: <candidate-commit-sha>
  environment: staging
  expected: deployment_succeeds

- id: reject_mutable_tag
  called_ref: main
  expected: policy_blocks

- id: reject_unknown_environment
  environment: production-copy
  expected: input_validation_blocks_before_oidc

- id: reject_unapproved_caller
  caller: company/sandbox
  expected: federation_or_policy_blocks

- id: preserve_environment_approval
  environment: production
  approval: missing
  expected: no_azure_token_and_no_deployment

- id: prove_bounded_target
  requested_resource_group: rg-other-prod
  expected: target_allowlist_blocks

Ajoutez un cas où le workflow appelé réussit techniquement mais où la validation applicative échoue. Le résultat attendu est alors un déploiement qualifié comme non promouvable, avec une procédure de retour au digest précédent.

Décider promotion, maintien ou rollback

Promouvez le SHA candidat lorsque les entrées sont bornées, les permissions minimales, les secrets explicites, les claims conformes, l’approbation réellement exercée et les effets Azure reconstruisibles. Maintenez le canari si le déploiement fonctionne mais que la provenance, l’identité ou les traces restent ambiguës.

Rollbackez le workflow partagé si une nouvelle version élargit une cible, contourne l’environnement, demande des permissions sans justification ou ne permet plus de relier le run à l’opération Azure. Le retour arrière consiste à réépingler le dernier SHA validé dans les appelants, puis à révoquer l’identité ou le federated credential candidat si leur scope a été élargi. Conservez les runs et journaux du candidat pour l’analyse.

Ne restaurez pas un client secret par défaut. Si le dernier SHA connu reste utilisable, il constitue un rollback plus étroit et plus explicable que la réintroduction d’un credential persistant.

Conclusion

Un workflow réutilisable réduit la duplication seulement s’il garde une frontière de confiance lisible. L’appelant, le SHA appelé, les entrées, les permissions, les secrets, l’environnement et l’identité Azure forment un seul contrat de production.

La décision peut alors rester simple : SHA immuable, interface bornée, permissions minimales, OIDC et approbation prouvés, canari représentatif et trace Azure complète avant promotion. Au moindre élargissement non expliqué, revenez au dernier SHA validé et bloquez la nouvelle identité. La mutualisation reste utile parce qu’elle devient exploitable, pas parce qu’elle cache la complexité dans un dépôt central.