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é.
Ce que vous allez apprendre
Section intitulée « Ce que vous allez apprendre »- 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 verifyoucosign 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.
Que prouve une provenance SLSA ?
Section intitulée « Que prouve une provenance SLSA ? »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ée | Builder inscrit | Niveau visé |
|---|---|---|
gh_2.102.0_linux_amd64.tar.gz (GitHub CLI) | https://github.com/cli/cli/.github/workflows/deployment.yml@refs/heads/trunk | Build L2 |
kyverno:v1.19.1 | .../slsa-github-generator/.github/workflows/generator_container_slsa3.yml@refs/tags/v2.1.0 | Build 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.
Quand vérifier la provenance ?
Section intitulée « Quand vérifier la provenance ? »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 :
| Point de contrôle | Objectif | Outils |
|---|---|---|
| Pipeline CI | Empêcher la promotion d'un artefact sans provenance valide | slsa-verifier, gh, cosign |
| Admission Kubernetes | Bloquer le déploiement d'une image non conforme | Kyverno, Sigstore Policy Controller |
Vérification en pipeline CI
Section intitulée « Vérification en pipeline CI »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.
Avec slsa-verifier
Section intitulée « Avec slsa-verifier »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 :
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 passedLes échecs sont aussi parlants que les succès, et ils sortent tous en code 1 :
| Essai mesuré | Message |
|---|---|
--source-uri github.com/attaquant/slsa-verifier | expected source 'attaquant/slsa-verifier', got 'slsa-framework/slsa-verifier' |
--source-tag v2.7.0 au lieu de v2.7.1 | expected tag 'v2.7.0', got 'v2.7.1' |
image désignée par :v1.19.1, sans digest | the 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.
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}"verifier-provenance-amont: stage: verify image: alpine:3.22@sha256:5291449c3df73caf6ed85e649dec1b9e818b39a5d8c871e97afc13e9cd5e8fa8 variables: IMAGE_AMONT: "ghcr.io/kyverno/kyverno:v1.19.1@sha256:b31d8511ae5fd6010e2a01ea72ebae08eb82fa51d91af14a1d9fe989949b4edb" before_script: - apk add --no-cache curl - 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 - - install -m 0755 slsa-verifier /usr/local/bin/slsa-verifier script: - slsa-verifier verify-image "$IMAGE_AMONT" --source-uri github.com/kyverno/kyverno --source-tag v1.19.1Le 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.
Avec gh attestation verify
Section intitulée « Avec gh attestation verify »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 :
gh attestation verify gh_2.102.0_linux_amd64.tar.gz --repo cli/cli \ --signer-workflow cli/cli/.github/workflows/deployment.ymlecho $?# 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.
Avec cosign verify-attestation
Section intitulée « Avec cosign verify-attestation »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.
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.1La 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.
Vérification au déploiement Kubernetes
Section intitulée « Vérification au déploiement Kubernetes »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 ImageValidatingPolicy
Section intitulée « Kyverno ImageValidatingPolicy »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.
apiVersion: policies.kyverno.io/v1kind: ImageValidatingPolicymetadata: name: verify-slsa-provenancespec: 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 politique | Résultat mesuré |
|---|---|
| Politique ci-dessus | pod/kyverno-avec-provenance created |
Source attendue github.com/attaquant/kyverno | Refus : 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 .predicate | Refus : 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.
Déploiement progressif de la policy
Section intitulée « Déploiement progressif de la policy »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.
-
Mode Audit : observer sans bloquer.
spec:validationActions: [Audit]Les violations s'accumulent dans les
PolicyReportpendant une à deux semaines. -
Analyser les violations.
Fenêtre de terminal kubectl get policyreport -A -o json | jq '.items[].results[] | select(.result == "fail")' -
Mode Deny sur staging, avec une
matchConditionqui cible le namespace.spec:validationActions: [Deny]matchConditions:- name: staging-onlyexpression: "object.metadata.namespace == 'staging'" -
Extension progressive à la production.
spec:matchConditions:- name: staging-and-prodexpression: "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 policy | Verdict sur le mirror pod | Ce qu'affiche kubectl get pods | Conteneur sur le nœud |
|---|---|---|---|
ValidatingAdmissionPolicy (Kubernetes 1.35, mesure antérieure) | Refusé | Aucun Pod, le namespace paraît vide | Running |
| Kyverno 1.19.1 (kind, Kubernetes 1.37, rejoué pour cette leçon) | Accepté | statique-verif-m06-control-plane, Running, source file | Running |
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 :
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.
Gestion des exceptions (break-glass)
Section intitulée « Gestion des exceptions (break-glass) »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.
apiVersion: policies.kyverno.io/v1kind: PolicyExceptionmetadata: 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ù.
Workflow d'exception automatisé
Section intitulée « Workflow d'exception automatisé »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.
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.
Enregistrement et audit des décisions
Section intitulée « Enregistrement et audit des décisions »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.
Métriques de vérification
Section intitulée « Métriques de vérification »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étrique | Ce qu'elle compte | Alerte si |
|---|---|---|
kyverno_image_validating_policy_results_total{result="fail"} | Refus par politique et namespace | Pic soudain |
kyverno_image_validating_policy_results_total{result="skip"} | Passages sous exception | Différent de zéro hors incident |
kyverno_image_validating_policy_execution_duration_seconds | Durée de vérification | Proche du délai du webhook |
kyverno_admission_review_duration_seconds | Temps de réponse de l'admission | Plusieurs secondes |
Tableau de bord des décisions
Section intitulée « Tableau de bord des décisions »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.
# Refus par politiquesum by (policy_name) (rate(kyverno_image_validating_policy_results_total{result="fail"}[1h]))
# Résultats par namespacesum by (resource_namespace, result) (rate(kyverno_image_validating_policy_results_total[24h]))
# Passages sous exceptionsum(increase(kyverno_image_validating_policy_results_total{result="skip"}[24h]))Matrice de décision par environnement
Section intitulée « Matrice de décision par environnement »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.
| Environnement | Mode | Exceptions | Notification |
|---|---|---|---|
| dev | Audit | Toutes autorisées | Journal uniquement |
| staging | Deny | Via PR relue | Canal de déploiement |
| production | Deny strict | Break-glass et approbation sécurité | Astreinte et SIEM |
Dépannage courant
Section intitulée « Dépannage courant »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.
cosign tree ghcr.io/kyverno/kyverno:v1.19.1@sha256:b31d8511ae5fd6010e2a01ea72ebae08eb82fa51d91af14a1d9fe989949b4edb# └── Attestations for an image tag: ghcr.io/kyverno/kyverno:sha256-b31d8511....attCauses 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.
Délai de vérification dépassé
Section intitulée « Délai de vérification dépassé »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: 30Causes fréquentes : Rekor ou le registre lents, sortie du cluster vers Internet filtrée par un proxy.
Politique sans effet
Section intitulée « Politique sans effet »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.
kubectl get pods -n kyvernokubectl get validatingwebhookconfigurations | grep kyvernokubectl logs -n kyverno -l app.kubernetes.io/component=admission-controller --tail=100À retenir
Section intitulée « À retenir »- 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-imageexige un digest : un tag est refusé comme mutable.- Le signataire d'une provenance
slsa-github-generatorest le générateur, pas le workflow du projet : la politique doit l'attendre. - Avec Kyverno, la validation appelle
verifyAttestationSignatures(), etextractPayload()renvoie la déclaration in-toto entière (.predicate). - Un pod statique démarre sans admission : surveillez
kubernetes.io/config.source: filehors dekube-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.
Contrôle de connaissances
Section intitulée « Contrôle de connaissances »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
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
Vérification
(0/0)Score par domaine
Un domaine regroupe plusieurs guides et reçoit plusieurs questions : son score est plus fiable qu'un score par thème, qui porte souvent sur une seule question.
Bilan
Points forts
Aucun sujet réussi à 80 % sur au moins deux questions pour l’instant.
Points faibles
Aucun sujet sous le seuil de réussite.
À faire ensuite
Des indices pour retenter votre chance ?
Continuer le cours : Attaques via les gestionnaires de paquets en CI/CD
Nouveau quiz complet avec des questions aléatoires
Retravailler uniquement les questions ratées
Retour à la liste des certifications