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.
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.
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.
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 :
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.
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.
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.
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.