Aller au contenu
English
Compte : non connecté, synchronisation possible Mon apprentissage
Selectionner le thème
English
Read in English

De la provenance SLSA à la décision : bloquer les artefacts non conformes

Par mis à jour le

Améliorer cette page

Une provenance SLSA est une attestation signée qui dit où, à partir de quelle source et par quel builder un artefact a été construit. Elle ne protège rien tant que personne ne la vérifie : la sécurité vient de la décision qui refuse un artefact non conforme, en CI puis à l'admission Kubernetes. Cette leçon s'adresse à un DevOps qui produit déjà des attestations et veut qu'elles bloquent réellement.

Tout est mesuré le 1er octobre 2026 sur des artefacts publics : slsa-verifier 2.7.1 accepte l'image kyverno:v1.19.1 désignée par digest et la refuse par son tag (« the image is mutable »), une politique Kyverno 1.19.1 rejouée sur kind admet cette image et refuse une provenance d'un autre dépôt, et un pod statique passe à côté de l'admission sans être bloqué.

  • Interpréter ce qu'une provenance prouve, et quel niveau SLSA Build elle atteint.
  • Vérifier une provenance en CI avec slsa-verifier, gh attestation verify ou cosign verify-attestation, en contraignant source et builder.
  • Bloquer à l'admission Kubernetes une image sans provenance conforme.
  • Repérer la voie qui contourne l'admission : le pod statique.
  • Encadrer une exception break-glass, désactivée par défaut dans Kyverno.
  • Produire la preuve de SOCLE-REL-PRV-1, une provenance qui décrit builder, source et paramètres, et de SOCLE-REL-VER-1, sa vérification avant usage.

SLSA (Supply-chain Levels for Software Artifacts) décrit des niveaux de garantie. Sa version 1.2 les range en deux volets : Build, de L0 à L3, et Source, de L1 à L4. Une provenance porte sur le volet Build : elle nomme le builder, le dépôt source, la référence (tag, commit) et les paramètres du build, le tout signé.

Le niveau dépend de qui produit la provenance. Une attestation émise par le workflow du projet lui-même, comme actions/attest-build-provenance, atteint Build L2 : la plateforme est hébergée et la provenance signée, mais le builder est le workflow que le projet contrôle. Les générateurs isolés de slsa-github-generator visent Build L3, parce que la provenance est produite par un workflow réutilisable que le projet ne peut pas modifier.

Provenance mesuréeBuilder inscritNiveau visé
gh_2.102.0_linux_amd64.tar.gz (GitHub CLI)https://github.com/cli/cli/.github/workflows/deployment.yml@refs/heads/trunkBuild L2
kyverno:v1.19.1.../slsa-github-generator/.github/workflows/generator_container_slsa3.yml@refs/tags/v2.1.0Build L3

La première ligne le montre : le builder d'une attestation GitHub est le workflow du dépôt, qui pourrait être modifié par quiconque y pousse. La seconde est un workflow tiers, isolé. Cette différence change aussi l'identité à vérifier, et c'est le piège principal de la leçon.

La vérification s'applique à deux points de contrôle complémentaires. Le schéma les place sur le chemin de l'artefact, avec la voie qui échappe au second, le pod statique :

Chemin d'un artefact : le build produit l'image et sa provenance, un contrôle CI par slsa-verifier refuse ou laisse passer vers le tag de release du registre, puis l'admission Kyverno refuse ou crée le pod ; un pod statique, hors de l'API, atteint l'état de pod créé sans passer par l'admission.Buildimage et provenanceContrôle CIslsa-verifierRegistretag de releaseAdmissionKyvernoPod crééRefus et rapportPod statiquehors API
Source · SVG
Échap pour fermer, + et − pour zoomer, défilement pour se déplacer. 100 %
Point de contrôleObjectifOutils
Pipeline CIEmpêcher la promotion d'un artefact sans provenance valideslsa-verifier, gh, cosign
Admission KubernetesBloquer le déploiement d'une image non conformeKyverno, Sigstore Policy Controller

Le pipeline est le premier endroit où refuser un artefact, parce qu'il connaît le contexte : dépôt source, tag attendu, digest produit. Trois outils couvrent ce besoin selon la façon dont la provenance a été produite. Dans tous les cas, la vérification précède la promotion de l'image vers un tag de release : publier d'abord et constater ensuite ne protège personne.

slsa-verifier, du projet SLSA, vérifie les provenances produites par les générateurs slsa-github-generator. Il contrôle la signature, l'inscription dans Rekor, le builder, puis compare la source et le tag à ceux qu'on lui donne. Mesuré sur son propre binaire de release et sur l'image de Kyverno :

Fenêtre de terminal
slsa-verifier verify-artifact slsa-verifier-linux-amd64 \
--provenance-path slsa-verifier-linux-amd64.intoto.jsonl \
--source-uri github.com/slsa-framework/slsa-verifier --source-tag v2.7.1
# Verified build using builder ".../builder_go_slsa3.yml@refs/tags/v2.0.0" at commit ea584f45...
# PASSED: SLSA verification passed
slsa-verifier verify-image \
ghcr.io/kyverno/kyverno:v1.19.1@sha256:b31d8511ae5fd6010e2a01ea72ebae08eb82fa51d91af14a1d9fe989949b4edb \
--source-uri github.com/kyverno/kyverno --source-tag v1.19.1
# Verified build using builder ".../generator_container_slsa3.yml@refs/tags/v2.1.0" at commit 40ec788d...
# PASSED: SLSA verification passed

Les échecs sont aussi parlants que les succès, et ils sortent tous en code 1 :

Essai mesuréMessage
--source-uri github.com/attaquant/slsa-verifierexpected source 'attaquant/slsa-verifier', got 'slsa-framework/slsa-verifier'
--source-tag v2.7.0 au lieu de v2.7.1expected tag 'v2.7.0', got 'v2.7.1'
image désignée par :v1.19.1, sans digestthe image is mutable: 'ghcr.io/kyverno/kyverno:v1.19.1'

La dernière ligne compte : slsa-verifier verify-image refuse un tag. Un exemple qui vérifie "$IMAGE:$TAG" ne fonctionne donc pas, il faut passer le digest produit par le build.

.github/workflows/verify-provenance.yml
name: verify-provenance
on:
workflow_dispatch:
inputs:
digest:
description: "Digest de l'image à promouvoir (sha256:...)"
required: true
type: string
tag:
description: "Tag Git dont l'image doit provenir (v1.4.0)"
required: true
type: string
permissions: {}
jobs:
verify:
runs-on: ubuntu-24.04
permissions: {}
steps:
- name: Installer slsa-verifier v2.7.1, empreinte contrôlée
run: |
curl -sSfLo slsa-verifier https://github.com/slsa-framework/slsa-verifier/releases/download/v2.7.1/slsa-verifier-linux-amd64
echo "946dbec729094195e88ef78e1734324a27869f03e2c6bd2f61cbc06bd5350339 slsa-verifier" | sha256sum -c -
sudo install -m 0755 slsa-verifier /usr/local/bin/slsa-verifier
- name: Vérifier la provenance avant toute promotion
env:
IMAGE: ghcr.io/${{ github.repository }}@${{ inputs.digest }}
SOURCE: github.com/${{ github.repository }}
TAG: ${{ inputs.tag }}
run: |
slsa-verifier verify-image "${IMAGE}" \
--source-uri "${SOURCE}" \
--source-tag "${TAG}"

Le workflow GitHub passe actionlint 1.7.12, zizmor 1.30.0 et poutine 1.1.6 sans constat ; les entrées passent par env:, jamais directement dans le script. Le binaire est installé par téléchargement contrôlé par empreinte plutôt que par l'action slsa-verifier/actions/installer, que poutine signale comme provenant d'un éditeur non vérifié. Le job GitLab, qui vérifie une image amont avant de la recopier en interne, a été rejoué tel quel dans un conteneur alpine:3.22 : slsa-verifier: OK, puis PASSED. plumber n'a pas pu être lancé dessus, faute d'instance GitLab de test.

Les attestations de GitHub (actions/attest-build-provenance) se vérifient avec la CLI gh. Elle interroge l'API d'attestations de GitHub, pas le registre. Mesuré avec gh 2.100.0 sur l'archive de release de la CLI GitHub elle-même :

Fenêtre de terminal
gh attestation verify gh_2.102.0_linux_amd64.tar.gz --repo cli/cli \
--signer-workflow cli/cli/.github/workflows/deployment.yml
echo $?
# 0
gh attestation verify gh_2.102.0_linux_amd64.tar.gz --repo cli/cli \
--signer-workflow cli/cli/.github/workflows/autre.yml
# Error: verifying with issuer "sigstore.dev" (code 1)

Sans --signer-workflow, la vérification accepte n'importe quel workflow du dépôt, conséquence du niveau Build L2 décrit plus haut : l'option est donc indispensable, puisqu'elle restreint la provenance au seul workflow de release attendu. Une attestation absente répond HTTP 404 sur l'API, mesuré sur le binaire slsa-verifier, dont la provenance passe par le générateur et non par les attestations GitHub.

cosign vérifie aussi une provenance attachée à l'image, avec --type slsaprovenance. L'identité à contraindre est celle du signataire de la provenance, et c'est là que les exemples se trompent souvent : pour une image produite par slsa-github-generator, le signataire est le générateur, pas le workflow du projet.

Fenêtre de terminal
cosign verify-attestation --type slsaprovenance \
--certificate-identity-regexp '^https://github.com/slsa-framework/slsa-github-generator/.github/workflows/generator_container_slsa3.yml@refs/tags/v' \
--certificate-oidc-issuer https://token.actions.githubusercontent.com \
ghcr.io/kyverno/kyverno:v1.19.1@sha256:b31d8511ae5fd6010e2a01ea72ebae08eb82fa51d91af14a1d9fe989949b4edb
# Certificate subject: https://github.com/slsa-framework/slsa-github-generator/.github/workflows/generator_container_slsa3.yml@refs/tags/v2.1.0
# GitHub Workflow Repository: kyverno/kyverno
# GitHub Workflow Ref: refs/tags/v1.19.1

La sortie, mesurée avec Cosign v3.1.3, donne le sujet du certificat (le générateur) et le dépôt qui l'a appelé (kyverno/kyverno). La provenance décodée porte le predicateType https://slsa.dev/provenance/v0.2 et la source git+https://github.com/kyverno/kyverno@refs/tags/v1.19.1. Un motif d'identité trop large, comme .*, annule la vérification.

Le contrôle en CI ne couvre que les images passées par le pipeline officiel. Un kubectl apply manuel ou un pipeline parallèle le contournent. Le contrôleur d'admission ferme cette porte : il intercepte chaque requête de création de Pod avant qu'elle ne soit enregistrée dans etcd, et refuse celles dont l'image n'a pas de provenance valide. Il existe en revanche une voie qui ne passe pas par l'API, traitée dans « Un contrôleur d'admission peut-il être contourné ».

Kyverno 1.19 vérifie les attestations avec une ImageValidatingPolicy. La politique suivante a été rejouée sur un cluster kind jetable (Kubernetes 1.37) : elle exige une provenance signée par le générateur SLSA, puis contrôle dans son contenu que la source est un tag du dépôt attendu.

kyverno-slsa-policy.yaml
apiVersion: policies.kyverno.io/v1
kind: ImageValidatingPolicy
metadata:
name: verify-slsa-provenance
spec:
validationActions: [Deny]
webhookConfiguration:
timeoutSeconds: 30
matchConstraints:
resourceRules:
- apiGroups: [""]
apiVersions: ["v1"]
operations: ["CREATE", "UPDATE"]
resources: ["pods"]
matchImageReferences:
- glob: "ghcr.io/kyverno/*"
attestors:
- name: generateur
cosign:
keyless:
identities:
- issuer: "https://token.actions.githubusercontent.com"
subjectRegExp: "^https://github.com/slsa-framework/slsa-github-generator/.github/workflows/generator_container_slsa3.yml@refs/tags/v"
ctlog:
url: "https://rekor.sigstore.dev"
attestations:
- name: provenance
intoto:
type: https://slsa.dev/provenance/v0.2
validations:
- expression: >-
images.containers.map(image, verifyAttestationSignatures(image, attestations.provenance, [attestors.generateur])).all(e, e > 0)
message: "Aucune provenance SLSA signée par le générateur slsa-github-generator."
- expression: >-
images.containers.map(image, extractPayload(image, attestations.provenance).predicate.invocation.configSource.uri.startsWith('git+https://github.com/kyverno/kyverno@refs/tags/')).all(e, e)
message: "La provenance ne désigne pas un tag du dépôt attendu."

Quatre essais, faits avec la même image kyverno:v1.19.1 désignée par digest, montrent ce que chaque partie de la politique apporte :

Variante de la politiqueRésultat mesuré
Politique ci-dessuspod/kyverno-avec-provenance created
Source attendue github.com/attaquant/kyvernoRefus : La provenance ne désigne pas un tag du dépôt attendu.
Identité attendue ^https://github.com/kyverno/.*/\.github/workflows/Refus : Aucune provenance SLSA signée par le générateur slsa-github-generator.
Chemin extractPayload(...).invocation sans .predicateRefus : failed to evaluate policy: no such key: invocation

La troisième ligne est le piège classique : une politique qui attend le workflow du projet comme signataire refuse une provenance parfaitement valide, puisque c'est le générateur qui signe. La quatrième rappelle que extractPayload() renvoie la déclaration in-toto entière, dont la provenance est le champ predicate. Et, comme pour les signatures, une validation réduite à expression: "true" ne vérifie rien et bloque tout.

Passer directement en blocage sur un cluster existant casse les déploiements de toutes les équipes qui n'ont pas encore de provenance. Les quatre étapes ci-dessous font varier deux réglages seulement : validationActions, qui décide entre observer et refuser, et matchConditions, qui restreint le périmètre aux namespaces choisis.

  1. Mode Audit : observer sans bloquer.

    spec:
    validationActions: [Audit]

    Les violations s'accumulent dans les PolicyReport pendant une à deux semaines.

  2. Analyser les violations.

    Fenêtre de terminal
    kubectl get policyreport -A -o json | jq '.items[].results[] | select(.result == "fail")'
  3. Mode Deny sur staging, avec une matchCondition qui cible le namespace.

    spec:
    validationActions: [Deny]
    matchConditions:
    - name: staging-only
    expression: "object.metadata.namespace == 'staging'"
  4. Extension progressive à la production.

    spec:
    matchConditions:
    - name: staging-and-prod
    expression: "object.metadata.namespace in ['staging', 'production']"

Un contrôleur d'admission peut-il être contourné ?

Section intitulée « Un contrôleur d'admission peut-il être contourné ? »

Oui, par un mécanisme documenté de Kubernetes : le pod statique. Le kubelet de chaque nœud surveille un répertoire local, /etc/kubernetes/manifests par défaut, et démarre tout Pod qu'il y trouve sans consulter l'API Kubernetes. Aucune requête d'admission n'est émise, donc aucune policy Kyverno ni ValidatingAdmissionPolicy ne se prononce. La vérification de provenance n'est pas contournée par une astuce : elle n'est jamais appelée. Le chemin exact figure dans la configuration du kubelet, sous la clé staticPodPath.

Ce comportement n'est pas un défaut, c'est ce qui permet au plan de contrôle de démarrer. Sur un cluster installé avec kubeadm, etcd, kube-apiserver, kube-controller-manager et kube-scheduler sont eux-mêmes des pods statiques : exiger une validation par l'API server pour démarrer l'API server serait circulaire.

Pourquoi le Pod finit quand même par apparaître dans kubectl

Section intitulée « Pourquoi le Pod finit quand même par apparaître dans kubectl »

Une fois le conteneur démarré, le kubelet crée dans l'API un mirror pod, une copie en lecture seule destinée à la visibilité. Ce mirror pod, lui, passe par l'admission, et c'est là que les choses deviennent trompeuses : le verdict porte sur la copie, jamais sur le conteneur, qui tourne déjà. Le tableau donne les deux issues possibles pour un manifeste dont l'image viole la policy.

Moteur de policyVerdict sur le mirror podCe qu'affiche kubectl get podsConteneur sur le nœud
ValidatingAdmissionPolicy (Kubernetes 1.35, mesure antérieure)RefuséAucun Pod, le namespace paraît videRunning
Kyverno 1.19.1 (kind, Kubernetes 1.37, rejoué pour cette leçon)Acceptéstatique-verif-m06-control-plane, Running, source fileRunning

Les deux issues trompent l'opérateur, dans des directions opposées. Avec la policy native, le refus n'apparaît que dans les journaux du kubelet, sous la forme Failed creating a mirror pod, pendant que le conteneur sert du trafic. Avec Kyverno, le Pod s'affiche comme n'importe quel autre et seul le PolicyReport porte le verdict : mesuré, une ligne FAIL 1 pour statique-verif-m06-control-plane. Dans les deux cas, kubectl get pods ne décrit pas ce qui s'exécute réellement.

Comment repérer un Pod qui n'est pas passé par l'API ?

Section intitulée « Comment repérer un Pod qui n'est pas passé par l'API ? »

Le kubelet marque ces Pods d'une annotation, kubernetes.io/config.source, qui vaut file pour un pod statique et api pour un Pod ordinaire. Cette commande liste les Pods concernés sur tout le cluster :

Fenêtre de terminal
kubectl get pods -A \
-o custom-columns='NAMESPACE:.metadata.namespace,NOM:.metadata.name,SOURCE:.metadata.annotations.kubernetes\.io/config\.source,NOEUD:.spec.nodeName' \
| grep -v '<none>'

La sortie contient normalement les composants du plan de contrôle, et rien d'autre. Toute ligne file en dehors de kube-system, ou sur un nœud worker, mérite une inspection immédiate. Ce contrôle interroge l'API : il ne verra donc pas un pod statique dont le mirror pod a été refusé. La liste exhaustive de ce qui tourne se lit sur le nœud, avec crictl pods.

Même la meilleure policy a besoin d'un mécanisme d'exception pour les urgences. L'objectif est de permettre le contournement sans le normaliser : une exception étroite, tracée, approuvée et supprimée après usage.

Une PolicyException pour une ImageValidatingPolicy

Section intitulée « Une PolicyException pour une ImageValidatingPolicy »

Une PolicyException désigne la politique visée par policyRefs et le périmètre exempté par des matchConditions en CEL. Deux faits mesurés sur Kyverno 1.19.1 conditionnent tout le reste. Les exceptions sont désactivées par défaut : l'argument --enablePolicyException=false est posé par le chart, et kubectl apply prévient « PolicyException resources would not be processed until it is enabled ». Et Kyverno ne fait pas expirer une exception : une annotation de date est purement informative.

breakglass-inc-12345.yaml
apiVersion: policies.kyverno.io/v1
kind: PolicyException
metadata:
name: breakglass-inc-12345
namespace: default
labels:
exception-type: break-glass
incident-id: "INC-12345"
annotations:
expiry: "2026-10-02T12:00:00Z" # informatif : Kyverno ne supprime rien
reason: "Correctif urgent, provenance ajoutée après l'incident"
spec:
policyRefs:
- name: verify-slsa-provenance
kind: ImageValidatingPolicy
matchConditions:
- name: pods-hotfix-uniquement
expression: "object.metadata.namespace == 'default' && object.metadata.name.startsWith('hotfix-')"

Activées par features.policyExceptions.enabled=true et features.policyExceptions.namespace=default dans le chart Helm 3.9.1, les exceptions s'appliquent : mesuré, hotfix-api est créé, autre-api reste refusé, et la métrique de Kyverno compte la première avec result="skip". Restreindre namespace à un seul namespace d'exceptions évite qu'une équipe crée les siennes n'importe où.

Créer l'exception à la main en pleine astreinte produit un objet trop large et non tracé. Le workflow ci-dessous impose un formulaire, une environment protégée qui exige une approbation, une validation des entrées avant de les écrire dans le manifeste, et une notification au canal sécurité. L'étape d'accès au cluster dépend de votre plateforme et n'est pas montrée.

.github/workflows/breakglass.yml
name: breakglass
on:
workflow_dispatch:
inputs:
incident_id:
description: "Identifiant d'incident (INC-12345)"
required: true
type: string
prefixe_pod:
description: "Préfixe des pods exemptés (hotfix-)"
required: true
type: string
raison:
description: "Raison de l'exception"
required: true
type: string
permissions: {}
jobs:
exception:
runs-on: ubuntu-24.04
environment: production-breakglass # approbation obligatoire
permissions:
contents: read
steps:
# Étape d'accès au cluster propre à votre plateforme (non montrée)
- name: Créer la PolicyException
env:
INCIDENT_ID: ${{ inputs.incident_id }}
PREFIXE: ${{ inputs.prefixe_pod }}
RAISON: ${{ inputs.raison }}
ACTEUR: ${{ github.actor }}
run: |
[[ "${INCIDENT_ID}" =~ ^INC-[0-9]+$ ]] || { echo "incident_id invalide"; exit 1; }
[[ "${PREFIXE}" =~ ^[a-z0-9-]+-$ ]] || { echo "prefixe_pod invalide"; exit 1; }
cat > exception.yaml <<EOF
apiVersion: policies.kyverno.io/v1
kind: PolicyException
metadata:
name: breakglass-${INCIDENT_ID,,}
namespace: production
labels:
exception-type: break-glass
incident-id: "${INCIDENT_ID}"
annotations:
created-by: "${ACTEUR}"
reason: "${RAISON//\"/}"
spec:
policyRefs:
- name: verify-slsa-provenance
kind: ImageValidatingPolicy
matchConditions:
- name: prefixe-autorise
expression: "object.metadata.name.startsWith('${PREFIXE}')"
EOF
kubectl apply -f exception.yaml
- name: Prévenir l'équipe sécurité
uses: slackapi/slack-github-action@dcb1066f776dd043e64d0e8ba94ca15cc7e1875d # v4.0.0
with:
method: chat.postMessage
token: ${{ secrets.SLACK_BOT_TOKEN }}
payload: |
channel: ${{ secrets.SLACK_SECURITY_CHANNEL_ID }}
text: "Exception break-glass ${{ inputs.incident_id }} créée par ${{ github.actor }}"

Ce workflow passe actionlint, zizmor et poutine sans constat. Les entrées passent par env: et sont validées par expression régulière avant d'entrer dans le manifeste : l'ancienne version, qui injectait ${{ inputs.reason }} directement dans le script, offrait une injection de commande à quiconque pouvait déclencher le workflow. La notification suppose deux secrets, SLACK_BOT_TOKEN (portée chat:write) et SLACK_SECURITY_CHANNEL_ID.

Chaque décision de vérification doit laisser une trace exploitable, pour la conformité comme pour le diagnostic. Kyverno en produit deux : les PolicyReport, dans le cluster, et des métriques Prometheus. Les PolicyReport suivent le cycle de vie des ressources : un Pod supprimé emporte son rapport, d'où l'intérêt de les exporter vers un stockage externe, par exemple avec l'outil Policy Reporter du projet Kyverno.

Les noms de métriques ne sont pas ceux des anciennes ClusterPolicy. Mesurés sur le service kyverno-svc-metrics de Kyverno 1.19.1, ceux qui concernent une ImageValidatingPolicy sont les suivants ; la métrique kyverno_policy_exception_total, citée dans certains exemples, n'y figure pas :

MétriqueCe qu'elle compteAlerte si
kyverno_image_validating_policy_results_total{result="fail"}Refus par politique et namespacePic soudain
kyverno_image_validating_policy_results_total{result="skip"}Passages sous exceptionDifférent de zéro hors incident
kyverno_image_validating_policy_execution_duration_secondsDurée de vérificationProche du délai du webhook
kyverno_admission_review_duration_secondsTemps de réponse de l'admissionPlusieurs secondes

Trois requêtes suffisent à un tableau de bord utile : le taux de refus par politique, la répartition par résultat et par namespace, et le volume de passages sous exception. Cette dernière est la plus parlante en revue : si elle ne redescend jamais à zéro, les exceptions sont devenues permanentes et la politique ne protège plus rien.

Requêtes PromQL
# Refus par politique
sum by (policy_name) (rate(kyverno_image_validating_policy_results_total{result="fail"}[1h]))
# Résultats par namespace
sum by (resource_namespace, result) (rate(kyverno_image_validating_policy_results_total[24h]))
# Passages sous exception
sum(increase(kyverno_image_validating_policy_results_total{result="skip"}[24h]))

Une même politique ne se règle pas de la même façon selon l'environnement : le coût d'un blocage injustifié est nul en dev et élevé en production. Cette matrice donne un point de départ, à lire comme un contrat entre l'équipe plateforme et les équipes applicatives. La colonne Notification compte autant que la colonne Mode : un refus silencieux devient un ticket d'incident dans l'heure.

EnvironnementModeExceptionsNotification
devAuditToutes autoriséesJournal uniquement
stagingDenyVia PR relueCanal de déploiement
productionDeny strictBreak-glass et approbation sécuritéAstreinte et SIEM

Trois symptômes couvrent l'essentiel des appels après une mise en blocage. Le premier vient de l'artefact, le deuxième du réseau, le troisième de l'installation de Kyverno. Déterminez lequel avant de toucher à la politique.

Image refusée : attestation introuvable ou identité différente

Section intitulée « Image refusée : attestation introuvable ou identité différente »

Avant de soupçonner la politique, vérifiez ce que le registre expose pour le digest visé, et qui a signé l'attestation. cosign tree liste les attestations attachées ; cosign verify-attestation avec une identité fausse affiche l'identité réelle après got.

Fenêtre de terminal
cosign tree ghcr.io/kyverno/kyverno:v1.19.1@sha256:b31d8511ae5fd6010e2a01ea72ebae08eb82fa51d91af14a1d9fe989949b4edb
# └── Attestations for an image tag: ghcr.io/kyverno/kyverno:sha256-b31d8511....att

Causes fréquentes : le pipeline n'a pas produit de provenance, la politique attend le workflow du projet alors que le générateur signe, ou l'attestation porte sur un autre digest que l'image déployée.

La vérification interroge le registre et le journal de transparence pendant l'admission, sur le chemin critique de création du Pod. Le délai se règle dans la politique, sous webhookConfiguration.timeoutSeconds, et Kubernetes le plafonne à 30 secondes pour un webhook d'admission : une valeur de 60, encore vue dans des exemples, n'est pas acceptée.

spec:
webhookConfiguration:
timeoutSeconds: 30

Causes fréquentes : Rekor ou le registre lents, sortie du cluster vers Internet filtrée par un proxy.

Une politique qui laisse tout passer signale presque toujours que le webhook n'est plus enregistré, que le contrôleur d'admission ne répond pas, ou que les images ne correspondent pas au motif glob de matchImageReferences. Ces trois commandes vérifient les Pods de Kyverno, l'enregistrement du webhook, puis les journaux du contrôleur.

Fenêtre de terminal
kubectl get pods -n kyverno
kubectl get validatingwebhookconfigurations | grep kyverno
kubectl logs -n kyverno -l app.kubernetes.io/component=admission-controller --tail=100
  • Une provenance ne protège rien sans décision : on vérifie en CI et à l'admission.
  • SLSA 1.2 distingue Build L0-L3 et Source L1-L4 ; une attestation GitHub seule atteint Build L2, un générateur isolé vise Build L3.
  • slsa-verifier verify-image exige un digest : un tag est refusé comme mutable.
  • Le signataire d'une provenance slsa-github-generator est le générateur, pas le workflow du projet : la politique doit l'attendre.
  • Avec Kyverno, la validation appelle verifyAttestationSignatures(), et extractPayload() renvoie la déclaration in-toto entière (.predicate).
  • Un pod statique démarre sans admission : surveillez kubernetes.io/config.source: file hors de kube-system.
  • Les PolicyException sont désactivées par défaut et n'expirent jamais seules.
  • La preuve attendue par SOCLE-REL-PRV-1 est une provenance décrivant builder, source et paramètres ; SOCLE-REL-VER-1 attend sa vérification avant usage.

Vérifiez que l'essentiel de cette leçon 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

Ce projet vous est utile ?

Les guides, formations, quiz et labs restent gratuits. Si vous les utilisez régulièrement, vous pouvez contribuer à leur maintenance et à leur développement.

Devenir membre de soutien (nouvel onglet) Soutenir une fois (nouvel onglet) En savoir plus

Suivez aussi le projet sur LinkedIn