Aller au contenu
English
CI/CD & Automatisation medium

Rulesets GitHub : protéger la branche qui porte vos workflows

Read this page in English

30 min de lecture

Un ruleset décide qui peut écrire sur main, donc qui peut modifier vos workflows. C'est le contrôle qui donne du poids à tous les autres. Cette page montre comment le configurer, en quoi il diffère de la protection de branche classique, et pourquoi sa bypass list mérite une décision explicite : sans elle, personne ne passe outre, pas même le propriétaire du dépôt.

  • Distinguer ruleset et protection de branche classique, et leurs API distinctes
  • Configurer les règles qui comptent pour la sécurité de la CI
  • Décider de la bypass list en connaissance de cause
  • Sauvegarder et restaurer un ruleset sans passer par l'interface
  • Anticiper l'impasse que crée un assouplissement temporaire

GitHub propose deux mécanismes qui font un travail proche, coexistent sur le même dépôt, et ne se règlent ni au même endroit ni avec la même API. La confusion coûte du temps au moment où l'on cherche pourquoi une fusion est refusée.

Protection de branche classiqueRuleset
EmplacementSettings > BranchesSettings > Rules
PortéeUne branche à la foisUn motif de références, plusieurs branches
CumulUne seule règle s'appliquePlusieurs rulesets s'additionnent
Contournement adminPossible sauf option contraireUniquement via la bypass list
Mode testNonevaluate, qui journalise sans bloquer
API/branches/{branch}/protection/repos/{owner}/{repo}/rulesets

Le mode evaluate est l'argument décisif pour un dépôt existant : il applique le ruleset sans rien bloquer et journalise ce qui aurait été refusé. Vous mesurez la friction réelle avant de l'imposer.

Toutes les règles ne se valent pas du point de vue de la sécurité des pipelines. Les quatre ci-dessous ferment les chemins réellement empruntés.

RègleCe qu'elle empêche
Pull request obligatoireUn commit direct sur main qui ajoute ou modifie un workflow
Revue code ownerUne modification de .github/workflows/ relue par quelqu'un qui n'y connaît rien
Checks requisUne fusion alors que les scanners de workflow sont rouges
Force push interditLa réécriture de l'historique qui masquerait l'ajout d'un workflow

La troisième mérite une précision : exiger les checks au vert n'a de valeur que si vos checks vérifient quelque chose. Un ruleset qui exige un job de tests mais ignore le scanner de workflows laisse passer exactement ce que ce module cherche à bloquer.

L'interface suffit pour la création. En ligne de commande, la lecture est plus utile que l'écriture : c'est elle qui permet de vérifier, de sauvegarder, et de détecter une dérive.

Fenêtre de terminal
REPO=mon-org/mon-projet
# Lister les rulesets actifs
gh api "repos/$REPO/rulesets" --jq '.[] | {id, name, enforcement}'
# Lire les paramètres de la règle pull_request
gh api "repos/$REPO/rulesets/RULESET_ID" \
--jq '.rules[] | select(.type=="pull_request") | .parameters'

La sortie attendue sur un dépôt gouverné ressemble à ceci :

{
"required_approving_review_count": 1,
"require_code_owner_review": true,
"dismiss_stale_reviews_on_push": true,
"require_last_push_approval": true
}

dismiss_stale_reviews_on_push et require_last_push_approval sont les deux réglages qu'on oublie, et ce sont ceux qui ferment la faille la plus évidente : obtenir une approbation sur un diff anodin, puis pousser le workflow malveillant avant la fusion.

La bypass list, décision à prendre avant d'en avoir besoin

Section intitulée « La bypass list, décision à prendre avant d'en avoir besoin »

C'est la différence de comportement qui surprend le plus, et elle se découvre généralement au mauvais moment.

Sur une protection de branche classique, un administrateur passe outre par défaut. Sur un ruleset, personne ne passe outre : ni l'administrateur, ni le propriétaire du dépôt. Le contournement n'existe que pour les acteurs inscrits dans la bypass list. Une bypass list vide signifie littéralement que la règle s'applique à tout le monde.

La conséquence pratique est nette : sur un dépôt à mainteneur unique, avec une revue code owner obligatoire et une bypass list vide, plus rien ne peut être fusionné. GitHub interdit d'approuver sa propre pull request, il n'y a donc aucun chemin. Même gh pr merge --admin est refusé, contrairement à ce que son nom laisse espérer.

Toute modification d'un ruleset doit être précédée d'une sauvegarde. L'API attend un objet complet en écriture : renvoyer une structure partielle écrase ce qui manque.

  1. Sauvegarder l'état courant, avant toute modification.

    Fenêtre de terminal
    gh api "repos/$REPO/rulesets/$RULESET_ID" > ruleset-backup.json
  2. Construire la version modifiée à partir de la sauvegarde, en ne touchant que la règle visée. Le filtre final écarte les clés nulles, que l'API refuse.

    Fenêtre de terminal
    jq '{name, target, enforcement, conditions, bypass_actors,
    rules: (.rules | map(
    if .type == "pull_request"
    then .parameters.required_approving_review_count = 0
    else . end))}
    | with_entries(select(.value != null))' ruleset-backup.json > ruleset-relaxed.json
  3. Appliquer, puis vérifier la valeur réellement en place plutôt que le code de retour.

    Fenêtre de terminal
    gh api -X PUT "repos/$REPO/rulesets/$RULESET_ID" --input ruleset-relaxed.json
    gh api "repos/$REPO/rulesets/$RULESET_ID" \
    --jq '.rules[] | select(.type=="pull_request") | .parameters'
  4. Restaurer depuis la sauvegarde dès que l'opération est terminée, et relancer le scanner de posture pour confirmer le retour à la conformité.

La dernière étape n'est pas cosmétique. Un PUT qui renvoie 200 prouve que l'API a accepté le corps, pas que la protection attendue est en place. Seule la relecture, ou mieux le passage au vert du scanner, le démontre.

Un point souvent ignoré : la protection de branche est lisible par API, donc auditable en continu. Un scanner de posture la compare à vos attentes déclarées et signale l'écart.

Deux conséquences utiles. La première est défensive : un affaiblissement temporaire oublié devient visible au prochain passage de la CI, au lieu de s'installer. La seconde est une contrainte d'outillage : le GITHUB_TOKEN par défaut ne peut pas lire la configuration complète de protection de branche. Un scanner qui s'en contente ne voit qu'une partie des réglages et conclut à tort que la revue code owner n'est pas exigée. Il faut lui fournir un jeton disposant de la permission Administration en lecture, idéalement un jeton éphémère de GitHub App plutôt qu'un secret de longue durée.

Vérifiez que l'essentiel de ce guide est acquis. Les questions portent uniquement sur ce qui vient d'être expliqué ici.

Contrôle de connaissances

Validez vos connaissances avec ce quiz interactif

6 questions
6 min.
70% requis

Informations

  • Le chronomètre démarre au clic sur Démarrer
  • Questions à choix multiples, vrai/faux et réponses courtes
  • Vous pouvez naviguer entre les questions
  • Les résultats détaillés sont affichés à la fin

Lance le quiz et démarre le chronomètre

  • Ruleset et protection classique coexistent, se règlent à deux endroits et s'interrogent par deux API distinctes ; le message de refus permet de les distinguer.
  • Le mode evaluate applique un ruleset sans bloquer et journalise ce qui aurait été refusé : c'est la bonne façon de l'introduire sur un dépôt existant.
  • Quatre règles comptent pour la CI : pull request obligatoire, revue code owner, checks requis et force push interdit.
  • dismiss_stale_reviews_on_push et require_last_push_approval ferment la faille de l'approbation obtenue sur un diff anodin.
  • Sur un ruleset, personne ne contourne hors de la bypass list, pas même le propriétaire : gh pr merge --admin est refusé.
  • Assouplir pour fusionner crée une non-conformité qu'un scanner détecte, et qui peut bloquer la fusion visée : la bypass list se décide à froid.
  • Une écriture d'API réussie ne prouve rien : relire la valeur en place, et confirmer par le passage au vert du scanner.
  • Un scanner de posture a besoin d'un jeton Administration en lecture pour voir la protection complète ; le GITHUB_TOKEN par défaut ne suffit pas.

Ce site vous est utile ?

Sachez que moins de 1% des lecteurs soutiennent ce site.

Je maintiens +700 guides gratuits, sans pub ni tracking. Un soutien, même symbolique, m'aide à couvrir l'hébergement et à garder ces ressources gratuites. Merci pour votre appui.

Le formulaire ne s'affiche pas ? Ouvrir Ko-fi dans un onglet.

Abonnez-vous et suivez mon actualité DevSecOps sur LinkedIn