Générer de la provenance SLSA ne suffit pas. Sans vérification systématique, ces attestations restent des documents ignorés. Le vrai gain de sécurité vient de la mise en application : bloquer automatiquement tout artefact qui ne respecte pas vos exigences de provenance.
Ce guide montre comment implémenter une chaîne de vérification complète : d’abord en CI (avant publication), puis au déploiement (admission Kubernetes), avec une gestion rigoureuse des exceptions.
Quand vérifier la provenance ?
Section intitulée « Quand vérifier la provenance ? »La vérification de provenance s’applique à deux points de contrôle complémentaires :
┌─────────────────────────────────────────────────────────────────────────┐│ Pipeline CI/CD ││ ┌──────────┐ ┌──────────┐ ┌────────────────┐ ┌────────────┐ ││ │ Build │───>│ Generate │───>│ Push image + │───>│ Verify │ ││ │ artifact │ │provenance│ │ attestation │ │ before tag │ ││ └──────────┘ └──────────┘ └────────────────┘ │ "release" │ ││ └────────────┘ │└────────────────────────────────────────────┬────────────────────────────┘ │ ▼┌─────────────────────────────────────────────────────────────────────────┐│ Kubernetes cluster ││ ┌──────────────────────────────────────────────────────────────────┐ ││ │ Admission Controller (Kyverno / Gatekeeper) │ ││ │ ┌─────────────┐ │ ││ │ │ Pod create │──> Verify provenance ──> PASS ──> Allow deploy │ ││ │ │ request │ └── FAIL ──> Block + log │ ││ │ └─────────────┘ │ ││ └──────────────────────────────────────────────────────────────────┘ │└─────────────────────────────────────────────────────────────────────────┘| Point de contrôle | Objectif | Outils |
|---|---|---|
| CI pipeline | Empêcher la publication d’artefacts sans provenance valide | slsa-verifier, cosign |
| Admission Kubernetes | Bloquer le déploiement d’images non conformes | Kyverno, Gatekeeper, Connaisseur |
Vérification en pipeline CI
Section intitulée « Vérification en pipeline CI »Avec slsa-verifier
Section intitulée « Avec slsa-verifier »L’outil slsa-verifier du SLSA Framework vérifie cryptographiquement qu’une provenance provient bien d’un builder SLSA connu.
name: Verify and Release
on: push: tags: ['v*']
jobs: verify-provenance: runs-on: ubuntu-latest permissions: contents: read id-token: write
steps: - uses: actions/checkout@v4
- name: Install slsa-verifier uses: slsa-framework/slsa-verifier/actions/installer@v2.7.0
- name: Verify container image provenance env: IMAGE: ghcr.io/${{ github.repository }}:${{ github.ref_name }} run: | slsa-verifier verify-image "$IMAGE" \ --source-uri "github.com/${{ github.repository }}" \ --source-tag "${{ github.ref_name }}" \ --builder-id "https://github.com/slsa-framework/slsa-github-generator/.github/workflows/generator_container_slsa3.yml@refs/tags/v2.1.0"
- name: Promote to release tag if: success() run: | # Tag l'image comme release uniquement après vérification crane tag "$IMAGE" "release-${{ github.ref_name }}"verify-provenance: stage: verify image: alpine:3.20 before_script: - apk add --no-cache curl jq - curl -sSL https://github.com/slsa-framework/slsa-verifier/releases/download/v2.7.0/slsa-verifier-linux-amd64 -o /usr/local/bin/slsa-verifier - chmod +x /usr/local/bin/slsa-verifier script: - | slsa-verifier verify-image "$CI_REGISTRY_IMAGE:$CI_COMMIT_TAG" \ --source-uri "github.com/$CI_PROJECT_PATH" \ --builder-id "https://github.com/slsa-framework/slsa-github-generator/.github/workflows/generator_container_slsa3.yml@refs/tags/v2.1.0" rules: - if: $CI_COMMIT_TAGAvec cosign verify-attestation
Section intitulée « Avec cosign verify-attestation »Pour les attestations signées avec Sigstore (keyless), utilisez cosign :
# Vérifier qu'une image possède une attestation SLSA signée par GitHub Actionscosign verify-attestation \ ghcr.io/mon-org/mon-app:v1.0.0 \ --type slsaprovenance \ --certificate-identity-regexp "^https://github.com/mon-org/mon-app/.github/workflows/build.yml@.*" \ --certificate-oidc-issuer "https://token.actions.githubusercontent.com"
# Extraire et inspecter le contenu de la provenancecosign verify-attestation \ ghcr.io/mon-org/mon-app:v1.0.0 \ --type slsaprovenance \ --certificate-identity-regexp "^https://github.com/mon-org/mon-app/.*" \ --certificate-oidc-issuer "https://token.actions.githubusercontent.com" \ | jq -r '.payload' | base64 -d | jq '.predicate'Vérification au déploiement Kubernetes
Section intitulée « Vérification au déploiement Kubernetes »Kyverno ClusterPolicy
Section intitulée « Kyverno ClusterPolicy »Kyverno permet de définir des policies déclaratives pour vérifier les images à l’admission :
apiVersion: kyverno.io/v1kind: ClusterPolicymetadata: name: verify-slsa-provenance annotations: policies.kyverno.io/title: "Verify SLSA Provenance" policies.kyverno.io/category: "Supply Chain Security" policies.kyverno.io/severity: high policies.kyverno.io/description: >- Vérifie que les images de conteneurs possèdent une attestation SLSA valide signée par un workflow GitHub Actions autorisé.spec: validationFailureAction: Enforce # Bloque réellement background: true webhookTimeoutSeconds: 30 rules: - name: verify-slsa-keyless match: any: - resources: kinds: - Pod namespaces: - production - staging verifyImages: - imageReferences: - "ghcr.io/mon-org/*" attestations: - type: https://slsa.dev/provenance/v1 predicateType: https://slsa.dev/provenance/v1 attestors: - entries: - keyless: subject: "https://github.com/mon-org/*/.github/workflows/*" issuer: "https://token.actions.githubusercontent.com" rekor: url: https://rekor.sigstore.dev conditions: - all: # Vérifie que le build provient du bon repository - key: "{{ invocation.configSource.uri }}" operator: Equals value: "git+https://github.com/mon-org/*"Déploiement progressif de la policy
Section intitulée « Déploiement progressif de la policy »-
Mode Audit : Observez sans bloquer
spec:validationFailureAction: AuditCollectez les violations dans les PolicyReports pendant 1-2 semaines.
-
Analysez les violations
Fenêtre de terminal kubectl get policyreport -A -o json | jq '.items[].results[] | select(.result == "fail")' -
Mode Enforce sur staging
spec:validationFailureAction: Enforcerules:- match:any:- resources:namespaces: ["staging"] -
Extension progressive à production
spec:rules:- match:any:- resources:namespaces: ["staging", "production"]
Gestion des exceptions (break-glass)
Section intitulée « Gestion des exceptions (break-glass) »Même la meilleure policy nécessite un mécanisme d’exception pour les urgences. L’objectif : permettre le contournement sans le normaliser.
Stratégie break-glass avec Kyverno
Section intitulée « Stratégie break-glass avec Kyverno »apiVersion: kyverno.io/v1kind: PolicyExceptionmetadata: name: emergency-deploy-incident-12345 namespace: kyverno labels: exception-type: break-glass created-by: alice@example.com incident-id: "12345" annotations: expiry: "2026-02-10T12:00:00Z" # Expire dans 24h reason: "Hotfix critique CVE-2026-XXXX, provenance sera ajoutée post-incident" approved-by: "security-team@example.com"spec: exceptions: - policyName: verify-slsa-provenance ruleNames: - verify-slsa-keyless match: any: - resources: kinds: - Pod namespaces: - production names: - "hotfix-*" # Limite aux pods hotfixWorkflow d’exception automatisé
Section intitulée « Workflow d’exception automatisé »name: Break-Glass Request
on: workflow_dispatch: inputs: namespace: description: 'Target namespace' required: true type: choice options: [staging, production] pod_pattern: description: 'Pod name pattern (e.g., hotfix-*)' required: true duration_hours: description: 'Exception duration (hours)' required: true default: '4' type: choice options: ['1', '4', '12', '24'] reason: description: 'Reason for exception' required: true incident_id: description: 'Incident/ticket ID' required: true
jobs: create-exception: runs-on: ubuntu-latest environment: production-breakglass # Requires approval steps: - uses: actions/checkout@v4
- name: Calculate expiry id: expiry run: | EXPIRY=$(date -u -d "+${{ inputs.duration_hours }} hours" +%Y-%m-%dT%H:%M:%SZ) echo "expiry=$EXPIRY" >> $GITHUB_OUTPUT
- name: Create PolicyException run: | cat <<EOF | kubectl apply -f - apiVersion: kyverno.io/v1 kind: PolicyException metadata: name: breakglass-${{ inputs.incident_id }} namespace: kyverno labels: exception-type: break-glass created-by: ${{ github.actor }} incident-id: "${{ inputs.incident_id }}" annotations: expiry: "${{ steps.expiry.outputs.expiry }}" reason: "${{ inputs.reason }}" workflow-run: "${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" spec: exceptions: - policyName: verify-slsa-provenance ruleNames: ["verify-slsa-keyless"] match: any: - resources: kinds: [Pod] namespaces: ["${{ inputs.namespace }}"] names: ["${{ inputs.pod_pattern }}"] EOF
- name: Notify Security Channel uses: slackapi/slack-github-action@v2.0.0 with: channel-id: 'security-alerts' slack-message: | :warning: *BREAK-GLASS EXCEPTION CREATED* • Namespace: `${{ inputs.namespace }}` • Pattern: `${{ inputs.pod_pattern }}` • Duration: ${{ inputs.duration_hours }}h • Reason: ${{ inputs.reason }} • Incident: ${{ inputs.incident_id }} • Created by: ${{ github.actor }} • Expires: ${{ steps.expiry.outputs.expiry }}Enregistrement et audit des décisions
Section intitulée « Enregistrement et audit des décisions »Chaque décision de vérification doit être tracée pour la conformité et le debugging.
Collecte des PolicyReports
Section intitulée « Collecte des PolicyReports »apiVersion: batch/v1kind: CronJobmetadata: name: export-policy-reports namespace: kyvernospec: schedule: "0 * * * *" # Toutes les heures jobTemplate: spec: template: spec: containers: - name: export image: bitnami/kubectl:latest command: - /bin/sh - -c - | kubectl get policyreport -A -o json | \ curl -X POST -H "Content-Type: application/json" \ -d @- https://siem.example.com/api/kyverno-reports restartPolicy: OnFailureMétriques de vérification
Section intitulée « Métriques de vérification »Exposez des métriques Prometheus pour monitorer la santé de vos policies :
apiVersion: monitoring.coreos.com/v1kind: ServiceMonitormetadata: name: kyverno-metrics namespace: monitoringspec: selector: matchLabels: app.kubernetes.io/name: kyverno endpoints: - port: metrics path: /metrics interval: 30sMétriques clés à surveiller :
| Métrique | Description | Alerte si |
|---|---|---|
kyverno_policy_results_total{result="fail"} | Violations bloquées | Spike soudain |
kyverno_policy_execution_duration_seconds | Latence de vérification | > 5s |
kyverno_admission_review_duration_seconds | Temps de réponse admission | > 10s |
Dashboard de décisions
Section intitulée « Dashboard de décisions »# Taux de violations par policysum by (policy) (rate(kyverno_policy_results_total{result="fail"}[1h]))
# Ratio pass/fail par namespacesum by (namespace, result) (rate(kyverno_policy_results_total[24h]))
# Exceptions break-glass activescount(kyverno_policy_exception_total{type="break-glass"})Matrice de décision par environnement
Section intitulée « Matrice de décision par environnement »| Environnement | Mode | Exceptions | Notification |
|---|---|---|---|
| dev | Audit | Toutes autorisées | Log uniquement |
| staging | Enforce | Via PR reviewée | Slack #deploy |
| production | Enforce strict | Break-glass + approbation SecOps | PagerDuty + SIEM |
Dépannage courant
Section intitulée « Dépannage courant »Image rejected: attestation not found
Section intitulée « Image rejected: attestation not found »# Vérifiez que l'attestation existecosign tree ghcr.io/mon-org/mon-app:v1.0.0
# Listez les attestations attachéescrane manifest ghcr.io/mon-org/mon-app:v1.0.0 | jq '.manifests[] | select(.annotations["org.opencontainers.image.ref.name"] | contains("att"))'Causes fréquentes :
- Le pipeline de build n’a pas généré de provenance
- L’attestation est sur un tag différent (
:v1.0.0vs:sha-abc123) - Le registry ne supporte pas les referrers OCI
Verification timeout
Section intitulée « Verification timeout »# Augmenter le timeout dans la policyspec: webhookTimeoutSeconds: 60 # Défaut: 10Causes fréquentes :
- Rekor (transparency log) temporairement lent
- Réseau cluster → Internet filtré
- Registry externe lent
Policy not enforced
Section intitulée « Policy not enforced »# Vérifier que Kyverno est opérationnelkubectl get pods -n kyverno
# Vérifier les webhookskubectl get validatingwebhookconfigurations | grep kyverno
# Logs du controllerkubectl logs -n kyverno -l app.kubernetes.io/component=admission-controller --tail=100À retenir
Section intitulée « À retenir »Double vérification
Vérifiez en CI (avant publication) et à l’admission (avant déploiement). Une seule vérification peut être contournée.
Policy as Code
Les policies Kyverno sont versionnées en Git. Changements = PR + review.
Break-glass encadré
Exceptions possibles mais : durée limitée, scope minimal, traçabilité complète.
Observabilité
Chaque décision (pass/fail/exception) doit être loggée et monitorable.