Aller au contenu

Signer commits et artefacts contre quelles attaques

Signer commits et artefacts contre quelles attaques

Section intitulée « Signer commits et artefacts contre quelles attaques »

La signature de code n’établit pas qu’un logiciel est sûr. Elle établit qu’un objet précis a été approuvé par une identité donnée, selon une politique donnée. C’est déjà beaucoup, à condition de vérifier cette preuve au bon endroit et de ne pas la confondre avec un autocollant cryptographique sur une boîte en carton.

→ Lire l’explication complète de la marmotte

Signatures Git : protéger l’origine, pas la branche

Section intitulée « Signatures Git : protéger l’origine, pas la branche »

Un commit Git signé lie cryptographiquement son contenu, ses parents, son auteur déclaré et son message à une clé privée. Toute modification du commit produit un nouvel identifiant Git et invalide la signature. Une signature valide répond donc à une question étroite : « cette clé contrôlait-elle cet objet Git au moment de la signature ? »

Elle ne répond pas à « ce changement a-t-il été relu ? », « cette clé appartenait-elle encore à la bonne personne ? » ou « la branche de production ne contient-elle que des changements autorisés ? ». Un attaquant qui compromet un compte de forge peut fusionner un commit signé ancien, pousser une branche non protégée ou modifier une règle de validation sans casser une seule signature. La cryptographie ne remplace pas le contrôle d’accès, elle rend son absence plus élégamment traçable.

La recommandation est nette : signer les commits humains quand la forge sait afficher et journaliser l’état de vérification, mais imposer surtout des tags de version signés pour les frontières de livraison. Le commit signe une intention de développement. Le tag signé désigne une version distribuable. Confondre les deux produit des politiques où chaque micro-commit devient un objet de conformité, puis où personne ne vérifie réellement les versions livrées. Le secteur adore appeler cela « shift left » avant de découvrir que la charge opérationnelle existe aussi à gauche.

Git 2.34 ou plus récent est requis pour la signature SSH native. SSH (Secure Shell) convient aux identités de développeurs déjà gérées avec ce format, à condition de maintenir un fichier allowedSignersFile centralisé et versionné. OpenPGP (Pretty Good Privacy), souvent appelé GPG, reste valable lorsque l’écosystème de clés et de révocation existe déjà. Créer une toile de confiance OpenPGP uniquement pour signer des commits est rarement rationnel : une PKI (Public Key Infrastructure) incomplète finit généralement dans un wiki, puis dans l’oubli.

La vérification doit être bloquante sur la branche protégée, pas seulement décorative dans l’interface web. La règle de protection doit exiger une signature valide, un auteur reconnu par la politique et des validations de revue indépendantes. Une signature venant d’une clé personnelle inconnue ne devient pas acceptable parce qu’elle affiche une coche verte.

Signer un commit ne signe pas l’artefact compilé. Entre les deux se trouvent le compilateur, les dépendances, les scripts de construction, l’image de base, les variables d’environnement et l’agent CI (Continuous Integration). C’est précisément là que se cache une grande partie du risque de chaîne logistique.

Un artefact doit être identifié par son digest, par exemple SHA-256 (Secure Hash Algorithm 256 bits), et non par un tag mutable tel que latest ou 1.4. Une signature doit porter ce digest et une déclaration de provenance doit lier ce digest au dépôt, à la révision Git, au workflow de construction et à l’identité du service de construction. Signer le nom application:prod ne protège rien si ce nom peut être déplacé vers une autre image. Un tag mutable est une étiquette de rayon, pas une identité.

Pour les artefacts construits par CI, la meilleure option est une signature sans clé longue durée dans le pipeline, fondée sur OIDC (OpenID Connect). Le service CI obtient un jeton d’identité à durée courte, puis reçoit un certificat de signature éphémère. Sigstore fournit ce modèle avec Cosign pour signer et vérifier, Fulcio pour émettre des certificats courts et Rekor pour journaliser publiquement les preuves. La contrepartie est assumée : moins de gestion de clés statiques dans la CI, mais une dépendance forte à la politique de fédération d’identité, aux revendications OIDC et au journal de transparence.

Cette approche est préférable à une clé privée déposée dans les secrets CI. Un secret exportable partagé par tous les jobs est une clé de production déguisée en variable d’environnement. Il sera tôt ou tard exposé dans un journal, un cache, une image de débogage ou un plugin qui promet d’optimiser le bonheur. Une clé de signature d’artefact doit rester non exportable dans un HSM (Hardware Security Module) ou être remplacée par une identité éphémère limitée à un workflow précis.

La provenance n’est utile que si le déploiement la vérifie. Un registre qui stocke une signature sans admission policy est un entrepôt avec un détecteur de fumée branché sur une lampe de bureau. Kubernetes doit refuser les images non signées, dont le digest ne correspond pas, ou dont la provenance ne correspond pas au dépôt et au workflow autorisés.

Exemple de vérification d’image signée avec Cosign

Section intitulée « Exemple de vérification d’image signée avec Cosign »

L’exemple suivant vérifie une image OCI (Open Container Initiative) signée par une identité OIDC attendue. La vérification porte sur le digest, l’émetteur du certificat et le sujet du workflow. Elle échoue si une image différente est substituée derrière le même tag.

Fenêtre de terminal
# Installer Cosign depuis une version figée et contrôlée par l'organisation.
# Remplacer le digest, le dépôt, l'émetteur et le sujet par les valeurs autorisées.
cosign verify \
--certificate-identity-regexp='^https://github\.com/acme/paiement/\.github/workflows/release\.yml@refs/tags/v[0-9]+\.[0-9]+\.[0-9]+$' \
--certificate-oidc-issuer='https://token.actions.githubusercontent.com' \
registry.example.net/acme/paiement@sha256:4d7c8f5b6a2f9d0e1c3b8a7e6f5d4c3b2a190817161514131211100f0e0d0c0b

Le motif d’identité doit désigner le workflow de publication et la référence autorisée, idéalement une branche protégée ou un tag de version. Accepter n’importe quel workflow du dépôt revient à autoriser un fichier YAML (YAML Ain’t Markup Language) modifié dans une branche de fonctionnalité à produire des artefacts de production. Les fichiers de pipeline sont du code privilégié, même lorsqu’ils portent des noms qui font penser à de la plomberie.

La politique d’admission doit répéter cette vérification côté plateforme. Une vérification effectuée uniquement dans la CI protège la publication, pas l’exécution d’une image injectée ultérieurement dans le registre ou référencée par un manifeste modifié.

Surface d’attaque des clés et identités de signature

Section intitulée « Surface d’attaque des clés et identités de signature »

Le premier scénario est le vol de clé privée. Pour les développeurs, une clé de signature stockée sur poste doit être protégée par un agent, un verrouillage de session et, lorsque le niveau de risque le justifie, un jeton matériel. Une clé exportée dans un répertoire de sauvegarde personnel est déjà une clé partagée, même si personne n’a encore admis la chose.

Le second scénario est le détournement du workflow CI. Un attaquant capable de modifier le fichier de pipeline peut ajouter une étape de signature ou détourner l’identité OIDC du job. La parade consiste à séparer le workflow de construction du workflow de publication, à protéger les fichiers de pipeline par des règles de revue renforcées, et à restreindre les jetons OIDC par dépôt, branche, workflow et environnement. Une autorisation basée seulement sur le nom du dépôt laisse une surface d’attaque beaucoup trop large.

Le troisième scénario est la confusion d’identité. Deux certificats peuvent être valides tout en représentant des contextes différents : branche de test, dépôt miroir, environnement de préproduction ou workflow manuel. La vérification doit contrôler l’émetteur, le sujet, le dépôt, la référence Git et le workflow. Vérifier uniquement que « la signature est valide » revient à contrôler que la porte possède une serrure, sans regarder qui détient la clé.

Le quatrième scénario est le rejeu. Une signature valable pour un ancien artefact reste valable tant que la politique l’accepte. Pour les composants déployés, imposer un numéro de version, une révision minimale ou une date de publication maximale dans la politique de déploiement. La signature garantit l’intégrité d’un ancien binaire vulnérable avec une rigueur admirable.

Les mécanismes cryptographiques sous-jacents sont détaillés dans les principes de signature. Pour une clé non exportable, les contraintes réelles d’intégration PKCS#11 (Public-Key Cryptography Standards numéro 11) sont couvertes dans le module cryptographique matériel.

Une politique de signature exploitable définit au minimum les autorités de confiance, les identités autorisées, les algorithmes acceptés, les dates de validité et la procédure de retrait. Sans inventaire des clés et certificats, la révocation devient une cérémonie triste : l’équipe sait qu’il faut agir, sans savoir quelle politique modifier ni quels artefacts réévaluer.

Pour les clés de développeurs, prévoir une durée de validité limitée, une procédure de remplacement documentée et une liste de signataires autorisés distribuée avec le dépôt ou la forge. Retirer une clé de cette liste bloque les nouvelles validations, mais ne modifie pas l’historique Git. Il faut alors identifier les commits signés pendant la fenêtre de compromission et décider, objet par objet, s’ils restent acceptables. La révocation n’efface pas le passé, contrairement aux démonstrations commerciales qui semblent parfois alimentées par un broyeur de documents.

Pour les signatures OIDC, surveiller les émissions de certificats, les erreurs de vérification et les refus de politique d’admission. Conserver les attestations de provenance, les signatures et les journaux de transparence aussi longtemps que les artefacts restent déployables. Sauvegarder seulement le registre ne suffit pas si les politiques, les identités de confiance et les métadonnées de provenance disparaissent.

La reprise impose un mode dégradé explicite. Si le fournisseur d’identité ou le journal de transparence est indisponible, ne pas désactiver globalement la vérification. Autoriser, si nécessaire, une liste étroite d’artefacts déjà vérifiés et épinglés par digest, avec expiration courte et journalisation. Une exception sans date de fin est une politique permanente qui a oublié de mettre son badge.

Une signature ne détecte pas une dépendance malveillante déjà présente dans la construction. Elle ne prouve pas qu’une revue de code a été sérieuse, qu’un compilateur n’a pas été compromis, ni qu’un développeur n’a pas signé sous contrainte ou après compromission de poste.

Elle ne crée pas non plus de non-répudiation juridique automatique. La preuve technique établit le contrôle d’une clé selon des règles techniques. L’attribution à une personne, la conservation des journaux et la valeur probatoire relèvent de procédures d’identité, d’horodatage et de conservation distinctes. Les confondre est un raccourci fréquent, généralement suivi d’une réunion nettement moins cryptographique.

Le complément nécessaire est une architecture de confiance : protection des branches, séparation des rôles CI, revue obligatoire, gestion des secrets, analyse des dépendances et politiques d’admission à l’exécution. Les fondations de ces contrôles sont liées à la gestion des identités et à l’architecture cyber. Signer sans vérifier à l’exécution apporte surtout une preuve très précise de l’absence de contrôle.

Sans signature ni provenance vérifiées, la porte de la ruche reste ouverte : n’importe quel objet portant le bon nom entre. L’enquête après incident perd son point d’ancrage, puisque rien ne permet de distinguer un binaire reconstruit légitimement d’un binaire substitué dans le registre, et que le rapprochement entre une image en production et la révision Git qui l’a produite repose alors sur des tags mutables, réécrits sans trace. Le périmètre d’un incident devient indéterminable : faute d’identité attachée aux objets, le tri se fait par supposition, et la remédiation impose de tout reconstruire au lieu de cibler la fenêtre compromise. La réponse aux exigences de traçabilité d’une chaîne logistique logicielle repose sur des journaux de build, supprimables et non opposables. Le retour à un état sain connu n’a plus de référence à laquelle comparer.

Un changement de code peut être signé avec une clé secrète détenue par son auteur. La signature est calculée à partir du contenu exact du changement. Quand une forge ou un outil reçoit ce changement, il utilise la partie publique de la clé pour vérifier deux choses : le contenu n’a pas changé et la signature provient bien de la clé attendue. Si le contenu est modifié, la vérification échoue.

Le même principe s’applique au programme construit à partir du code. Le programme reçoit une empreinte calculée sur tous ses octets. La signature doit viser cette empreinte, pas seulement son nom ou son numéro de version. Un système de déploiement vérifie alors que le programme reçu correspond à l’empreinte signée et que le signataire est autorisé pour ce dépôt, cette version et cette procédure de construction.

La signature peut échouer parce que le fichier a changé, parce que la mauvaise clé est utilisée, parce que le certificat du signataire est expiré ou parce que la règle locale refuse son identité. Dans ces cas, le déploiement doit être bloqué. Si une clé est volée, les nouvelles signatures faites avec elle peuvent sembler valides : il faut retirer cette clé des identités autorisées, rechercher les programmes signés pendant la compromission et les reconstruire si nécessaire. La signature prouve une origine technique, pas l’absence de bug ni la qualité du contrôle humain.

À lire aussi

Pages voisines

: les deux pages se citent l’une l’autre.