Le déclencheur qui a tout changé : lire un diff de workflow pour repérer une fuite de secret
Le défi
L'intégration continue de Harbourline a fonctionné sans histoire pendant des mois. Puis un mainteneur a remarqué que le commentaire de couverture n'apparaissait jamais sur les pull requests venant de forks, et l'a corrigé en une ligne, dans la révision 13. Le pipeline compile toujours, teste toujours, commente toujours, et il fait désormais tout cela pour n'importe qui sur Internet qui ouvre une pull request. Trois révisions du workflow sont ici, plus le workflow de publication et package.json pour le contexte. Lisez ce que la révision 13 a changé, déterminez quelle étape exécute du code écrit par le contributeur, et nommez le secret que cette étape lui remet.
Ce que tu vas apprendre
- Expliquer la différence entre pull_request et pull_request_target
- Identifier quel job et quelle étape exécutent du code contrôlé par le contributeur
- Déterminer quels secrets sont accessibles depuis une étape donnée
- Reconnaître les scripts de cycle de vie npm comme point d'exécution dans la CI
- Utiliser les frontières entre jobs et les conditions pour inclure ou exclure des secrets
Compétences testées
Prérequis
- Lire du YAML
- Ce que fait un pipeline CI sur une pull request
Comment ça marche
Un système de CI qui construit des pull requests doit répondre à une question avant tout le reste : à qui appartient ce code, et à quoi a-t-il le droit d'accéder. pull_request y répond prudemment. Le job s'exécute dans le contexte du fork, les secrets sont retenus, et le jeton automatique est en lecture seule, si bien que le code d'un inconnu s'exécute mais n'a rien à voler.
pull_request_target y répond dans l'autre sens. Le job s'exécute dans le contexte du dépôt de base, avec l'accès complet au magasin de secrets et un jeton en écriture, ce qui est le seul moyen pour un workflow d'étiqueter, de commenter ou de trier une contribution extérieure. La sécurité de ce compromis repose entièrement sur une hypothèse : le job n'exécute jamais rien de ce qu'a écrit le contributeur. Il récupère la branche de base par défaut, précisément pour que cela reste vrai.
Dès que quelqu'un ajoute ref: github.event.pull_request.head.sha pour que le build teste réellement le changement, l'hypothèse s'effondre et les deux moitiés se combinent en exécution de code à distance avec des identifiants de production. Dans un projet Node, il n'y a même pas d'étape de build à détourner : npm ci exécute les scripts de cycle de vie du package.json du contributeur avant que vos propres commandes ne s'exécutent.
Erreurs fréquentes
- Répondre GITHUB_TOKEN. Il est élevé sous ce déclencheur, mais il est référencé dans le job
comment, qui ne récupère jamais le code du contributeur. - Répondre CODECOV_TOKEN. Même job que l'étape de commentaire, même raison.
- Répondre SLACK_WEBHOOK. Son étape est protégée par
github.event_name == 'push', et cette exécution est un événement de pull request. - Répondre GPG_SIGNING_KEY. Il se trouve dans
release.yml, qui ne se déclenche que sur un push de tag. - Blâmer r12. r12 réutilisait déjà le jeton de publication, ce qui est négligent, mais sous
pull_requestaucun fork n'aurait jamais pu le voir. L'exposition commence à r13.
Comment s'en protéger
Séparez le contexte privilégié et le code non fiable dans des jobs différents.
- Compilez et testez sur
pull_request, sans aucun secret. Si un traitement privilégié est nécessaire ensuite, exécutez-le surworkflow_runà partir de l'artefact stocké, jamais à partir d'un nouveau checkout de la branche du contributeur. - Si
pull_request_targetest réellement nécessaire, ne récupérez pas du tout la ref de la tête. Un workflow qui se contente d'étiqueter ou de commenter n'a pas besoin du code. - Limitez la portée des identifiants au job qui en a besoin. Un jeton de registre en lecture seule pour les installations et un jeton de publication séparé qui n'existe que dans le workflow de release auraient rendu cet incident supportable.
- Effectuez les installations avec les scripts de cycle de vie désactivés, par exemple
npm ci --ignore-scripts, pour qu'unpackage.jsonnon fiable ne puisse pas s'exécuter avant vos propres commandes. - Exigez un environnement avec approbation manuelle pour tout job détenant un identifiant de publication.