Une fois le code envoyé vers un Mac dans le cloud, la CI procède généralement sans délai à son extraction, à sa compilation et à ses tests. Le problème est que le nom et l’adresse e-mail de l’auteur enregistrés dans un commit Git ne sont que des champs de texte modifiables : ils indiquent qui prétend avoir créé le commit, mais ne prouvent pas que l’objet a réellement été signé par cette personne. Une approche plus fiable consiste à signer les commits avec SSH, puis à demander à la CI de vérifier l’intégralité de la plage comprise entre la base de fusion et le commit actuel à l’aide d’une liste contrôlée de clés publiques.
Ce contrôle ne remplace pas la revue de code et ne détermine pas si le code est sûr. Il répond à une question plus limitée, mais essentielle : les objets Git qui entrent dans la chaîne de compilation sont-ils intègres, leurs signatures sont-elles valides et les clés utilisées appartiennent-elles à des personnes actuellement autorisées à soumettre du code ?
Définir d’abord le périmètre de confiance de la vérification
La vérification des commits comporte au moins trois niveaux de contrôle :
- Le contenu de l’objet Git correspond-il à sa signature ?
- La clé publique de signature figure-t-elle dans la liste des signataires autorisés ?
- Cette identité disposait-elle encore des droits nécessaires au moment du commit ?
Le premier niveau repose sur la signature cryptographique, le deuxième sur une liste maintenue dans le dépôt, et le troisième dépend toujours du processus de révocation des accès de l’équipe. Exécuter uniquement git log --show-signature et voir apparaître « Good signature » ne suffit pas : une clé valide mais non autorisée peut elle aussi produire une signature valide.
L’adresse e-mail sert à l’affichage et aux notifications, la clé publique à la vérification, et la liste des signataires autorisés à l’autorisation. Ces trois éléments ne doivent pas être confondus.
Il est recommandé de placer cette liste dans un répertoire distinct dont les modifications sont strictement contrôlées, par exemple .ci/trusted_signers. Toute modification de la liste devrait faire l’objet d’une revue supplémentaire, afin d’empêcher une personne d’ajouter sa propre clé publique et d’autoriser son code dans un même changement.
Configurer la signature SSH des commits Git
Git 2.34 et les versions ultérieures peuvent signer directement avec des clés SSH. Chaque développeur doit d’abord définir localement le format de signature et le chemin de sa clé publique :
git config --global gpg.format ssh
git config --global user.signingkey ~/.ssh/id_ed25519.pub
git config --global commit.gpgsign true
git config --global tag.gpgSign true
Le fichier configuré ici est celui de la clé publique ; la signature elle-même est créée avec la clé privée correspondante. Après avoir créé un commit, il est possible de vérifier que l’objet contient bien une signature :
git cat-file commit HEAD | sed -n '/^gpgsig /,/^[^ ]/p'
Chaque ligne du fichier des signataires autorisés contient une identité, des contraintes facultatives et une clé publique. L’identité devrait être un identifiant d’équipe stable, et non un nom d’affichage susceptible de changer fréquemment :
ci-release namespaces="git" ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAA...
ios-team namespaces="git" ssh-ed25519 AAAAC3NzaC1lZDI1NTE5BBBB...
Après l’extraction du dépôt par la CI, configurez Git pour utiliser ce fichier :
git config --local gpg.format ssh
git config --local gpg.ssh.allowedSignersFile .ci/trusted_signers
git verify-commit HEAD
Ne placez jamais de clé privée dans le dépôt. L’étape de vérification de la CI n’a besoin que des clés publiques ; seules les machines qui créent des commits ou des tags signés doivent disposer d’une clé privée.
Appliquer le contrôle à toute la plage de commits
Ne vérifier que HEAD est une erreur courante. Une modification malveillante ou non autorisée peut être dissimulée dans le commit précédent, puis masquée en apparence par un dernier commit signé. Le contrôle doit parcourir tous les commits postérieurs à la base de confiance.
#!/bin/bash
set -euo pipefail
base="${MERGE_BASE_SHA:?missing MERGE_BASE_SHA}"
head="${HEAD_SHA:?missing HEAD_SHA}"
git cat-file -e "${base}^{commit}"
git cat-file -e "${head}^{commit}"
count=0
while IFS= read -r commit; do
git verify-commit "$commit"
count=$((count + 1))
done < <(git rev-list --reverse "${base}..${head}")
printf 'verified_commits=%s\n' "$count"
MERGE_BASE_SHA doit être calculé par la CI à partir de la branche cible et de la branche à fusionner, ou lui être transmis de manière fiable. Il ne faut pas le remplacer arbitrairement par HEAD~1. Pour les commits de fusion, il faut également vérifier que le choix de la base respecte la stratégie de l’équipe, faute de quoi des objets introduits par le second parent pourraient échapper au contrôle.
La sortie du contrôle devrait consigner le hash du commit et l’étape ayant échoué, sans recopier l’intégralité du message de commit ni les variables d’environnement dans des journaux publics. En cas d’échec, interrompre la compilation est plus facile à auditer que de continuer à produire un artefact dont l’origine n’a pas été confirmée.
Gérer les clones superficiels, les tags et la rotation des clés
Les clones superficiels provoquent souvent de faux échecs : si l’objet de base n’est pas disponible localement, rev-list ne peut pas déterminer la plage complète. Avant la vérification, il faut récupérer l’historique nécessaire de la branche cible, puis confirmer explicitement la présence de la base avec git cat-file -e. Si la base est introuvable, le contrôle ne doit jamais se rabattre sur la seule vérification de HEAD.
| Scénario | Traitement correct | Raccourci à éviter |
|---|---|---|
| Base absente du clone superficiel | Récupérer l’historique nécessaire, puis recalculer la plage | Vérifier uniquement le dernier commit |
| Tag de publication | Utiliser un tag annoté signé et exécuter git verify-tag |
Vérifier uniquement le nom du tag |
| Transition entre ancienne et nouvelle clés | Conserver temporairement les deux clés publiques | Remplacer directement la clé et faire échouer les tâches historiques |
| Compromission d’une clé | La retirer immédiatement et réexaminer la plage qu’elle a signée | Modifier uniquement l’identité affichée |
| Départ d’un membre | Supprimer son entrée de la liste des signataires autorisés | Désactiver uniquement son accès quotidien |
Il faut décider à l’avance si les commits historiques doivent continuer à être acceptés après le retrait d’une clé. Le mode strict le plus simple consiste à vérifier selon la liste actuelle, ce qui convient aux contrôles de fusion. Si les publications historiques doivent rester vérifiables à long terme, il faut conserver des enregistrements de confiance assortis de périodes de validité et archiver ensemble les tags de publication, les hash des commits et la version de la liste utilisée à l’époque.
Vérifications avant déploiement et diagnostic des échecs
Avant de bloquer effectivement les fusions, il est possible de commencer par une période d’observation pendant laquelle les échecs sont seulement enregistrés, sans pour autant autoriser la publication de versions non signées. Cette période doit servir à vérifier en priorité les points suivants :
- La version de Git utilisée par les développeurs prend en charge les signatures SSH.
- Les commits ordinaires, les commits de fusion et les commits générés automatiquement ont tous un signataire clairement identifié.
- La CI dispose de la base complète et ne dépend pas d’une profondeur fixe.
- Les modifications du fichier des signataires autorisés nécessitent une revue indépendante.
- Les tags de publication et les commits qu’ils désignent sont vérifiés séparément.
- Les identités de robots utilisent des clés distinctes qui ne sont pas partagées avec des personnes.
- Des procédures applicables existent pour la rotation des clés, leur compromission et le départ d’un membre.
En cas d’échec, commencez par exécuter git verify-commit --raw <hash> afin de distinguer un objet non signé, une signature endommagée et une clé publique absente de la liste autorisée. Vérifiez ensuite la configuration du dépôt pour gpg.format et gpg.ssh.allowedSignersFile, ainsi que l’espace de noms, le type de clé et les retours à la ligne dans la liste. Cette méthode permet de séparer les problèmes d’autorisation d’identité de ceux liés à un historique Git incomplet, au lieu de signer de nouveau le même commit à répétition.
Lorsque la vérification de chaque commit, celle des tags et la revue des modifications de la liste sont toutes intégrées au pipeline, le système de compilation dispose d’une chaîne de provenance vérifiable. Celle-ci ne prouve pas que le code est exempt de défauts, mais elle permet d’établir précisément quels objets ont été signés par quelle clé autorisée et pourquoi les objets non confirmés n’ont pas poursuivi le processus de compilation.
Questions fréquentes
Une adresse d’auteur correcte suffit-elle pour faire confiance à un commit ?
Non. Le nom et l’adresse sont de simples champs du commit et peuvent être choisis librement. La signature prouve la possession d’une clé, tandis que la liste des signataires relie cette clé à une identité autorisée.
Peut-on vérifier uniquement le dernier commit de la branche ?
Non. Tous les commits ajoutés depuis la base de confiance doivent passer git verify-commit, car chacun peut modifier le résultat compilé. Un tag de livraison signé doit aussi passer git verify-tag.
Comment effectuer une rotation de clé sans interrompre la CI ?
Ajoutez d’abord la nouvelle clé publique à la liste versionnée, acceptez temporairement les deux clés, validez de nouveaux commits, puis retirez l’ancienne clé.
Configurez un Mac dans le cloud pour votre prochaine tâche de développement
Choisissez Oak M4, Oak M4 Plus ou Oak M4 Pro, puis configurez votre location sur l’un des cinq nœuds physiques selon l’emplacement de votre équipe.