September 2026
M D M D F S S
 123456
78910111213
14151617181920
21222324252627
282930  

Ein docker pull prüft nicht, wer ein Image gebaut hat. Er prüft, ob die Registry unter dem angegebenen Namen und Tag etwas ausliefert, das sich herunterladen lässt. Kubernetes verhält sich beim Deployment genauso: das Kubelet zieht das Image, das im Pod-Spec steht, unabhängig davon, ob es aus deiner eigenen Pipeline stammt oder von jemandem, der denselben Namen und Tag in eine erreichbare Registry gepusht hat. In den letzten beiden Artikeln dieser Serie ging es um cosign zum Signieren von Images und um Kyverno als Admission Controller für Policy-Regeln. Heute führe ich beides zusammen: eine ClusterPolicy, die unsignierte Images blockiert, bevor der Pod überhaupt startet.

Warum Signieren allein nichts erzwingt

cosign sign hinterlegt eine Signatur zum Image-Digest in der Registry, entweder mit einem privaten Schlüssel oder keyless über ein von Fulcio ausgestelltes Kurzzeitzertifikat. Das ist die Herkunftsangabe. Nur ruft nichts im normalen Kubernetes-Deploy-Pfad von sich aus cosign verify auf. Wer Schreibzugriff auf deine Registry oder deinen Namespace bekommt, kann ein unsigniertes oder falsch signiertes Image pushen, und kubectl apply nimmt es ohne Rückfrage an. Die Signatur existiert, geprüft wird sie an der entscheidenden Stelle nicht: beim Start des Pods.

Kyverno verifyImages als Durchsetzungspunkt

Kyverno klinkt sich als Admission Controller genau in diesen Moment ein. Eine verifyImages-Regel prüft bei jedem Pod, ob die referenzierten Images eine gültige Signatur tragen, bevor die Admission durchgeht. Läuft die Policy im Enforce-Modus, schlägt der Request fehl, wenn die Signatur fehlt oder nicht zum hinterlegten Schlüssel passt.

apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: verify-image-signatures
spec:
validationFailureAction: Enforce
background: false
rules:
- name: verify-signature
match:
any:
- resources:
kinds:
- Pod
verifyImages:
- imageReferences:
- "registry.example.com/apps/*"
attestors:
- count: 1
entries:
- keys:
publicKeys: |-
-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE...
-----END PUBLIC KEY-----

background: false ist hier kein Stilfehler. verifyImages arbeitet nur zum Admission-Zeitpunkt, ein Background-Scan über bereits laufende Ressourcen ergibt für Signaturprüfung keinen Sinn, weil Kyverno dafür das Image erneut gegen die Registry auflösen müsste.

Wer eine solche Policy zum ersten Mal produktiv einführt, sollte nicht sofort mit validationFailureAction: Enforce starten. Im Audit-Modus protokolliert Kyverno jeden Verstoß, ohne den Request zu blockieren, dadurch siehst du zuerst, wie viele bereits laufende oder frisch deployte Workloads überhaupt unsignierte Images verwenden. Erst wenn diese Liste auf null oder auf bewusst ausgenommene Ausnahmen heruntergeht, macht der Wechsel auf Enforce Sinn. Ohne diesen Zwischenschritt reißt eine frisch aktivierte Policy im schlimmsten Fall bestehende Deployments ab, die niemand vorher auf Signaturpflicht geprüft hat.

Keyless verifizieren, ohne Schlüssel zu verwalten

Ein statischer Public Key bedeutet auch eine statische Schlüsselverwaltung: wo liegt der private Schlüssel, wer darf ihn benutzen, was passiert bei Rotation. Keyless Signing mit cosign umgeht das über kurzlebige Zertifikate von Fulcio, gebunden an eine OIDC-Identität, zum Beispiel den Workflow einer CI-Pipeline. Die Kyverno-Policy prüft dann nicht mehr gegen einen Schlüssel, sondern gegen Issuer und Subject dieser Identität.

      verifyImages:
        - imageReferences:
            - "registry.example.com/apps/*"
          attestors:
            - count: 1
              entries:
                - keyless:
                    subject: "https://github.com/meine-org/meine-pipeline/.github/workflows/build.yml@refs/heads/main"
                    issuer: "https://token.actions.githubusercontent.com"

Damit lässt sich ein Image nur dann als vertrauenswürdig durchwinken, wenn es exakt dieser Workflow auf exakt diesem Branch signiert hat. Ein Fork oder ein anderer Branch erzeugt eine andere Subject-Angabe und fällt bei der Prüfung durch.

Signieren und Durchsetzung testen

Der Ablauf in der Pipeline bleibt derselbe wie im cosign-Artikel: Image bauen, pushen, signieren.

# mit statischem Schlüssel
cosign sign --key cosign.key registry.example.com/apps/backend:1.4.0
# keyless über OIDC, ohne interaktive Rückfrage
cosign sign --yes registry.example.com/apps/backend:1.4.0
# lokale Verifikation vor dem Deploy
cosign verify --key cosign.pub registry.example.com/apps/backend:1.4.0

Versuchst du danach, ein unsigniertes Image in einen Namespace mit aktiver Policy zu deployen, bricht der Request am Admission Webhook ab, nicht erst beim Pod-Status:

$ kubectl run test --image=registry.example.com/apps/backend:unsigned
Error from server: admission webhook "validate.kyverno.svc-fail" denied the request:
resource Pod/default/test was blocked due to the following policies
verify-image-signatures:
verify-signature: 'image verification failed for registry.example.com/apps/backend:unsigned:
.attestors[0].entries[0]: no matching signatures found'

Die genaue Formulierung der Fehlermeldung hängt von der Kyverno-Version ab, das Prinzip nicht: kein gültiger Nachweis, kein laufender Pod.

Was die Signatur nicht beweist

Eine Signatur beantwortet die Frage nach der Herkunft, nicht die Frage nach dem Inhalt. Sie sagt: dieses Image kommt exakt von diesem Schlüssel oder exakt von dieser CI-Identität. Sie sagt nichts darüber, ob im Image eine veraltete Bibliothek mit bekannten Schwachstellen steckt. Eine kompromittierte Pipeline signiert ein kompromittiertes Image genauso bereitwillig wie ein sauberes, die Signatur bleibt in beiden Fällen technisch gültig. Kyverno verifyImages ersetzt deshalb kein Scanning in der Pipeline, es ist eine zusätzliche, orthogonale Kontrolle: Herkunft erzwingen und Inhalt scannen sind zwei getrennte Fragen mit zwei getrennten Werkzeugen.

Wer den Bogen von der einzelnen Policy zur unternehmensweiten Lieferketten-Perspektive schlagen will, wird auf digital-business.blog fündig, dort ordne ich solche Bausteine regelmäßig in den größeren Zusammenhang ein.

Hinterlasse einen Kommentar

Diese Seite verwendet Akismet, um Spam zu reduzieren. Erfahre, wie deine Kommentardaten verarbeitet werden..