Veröffentlicht am

Container Images signieren: Sigstore und Kyverno

Teilen:
Authors

TL;DR

  • Supply Chain Attacks sind real und treffen Unternehmen jeder Groesse -- manipulierte Container Images sind einer der haeufigsten Angriffsvektoren.
  • Cosign (Sigstore) signiert Images kryptografisch nach dem Build. Kyverno oder OPA Gatekeeper pruefen die Signatur vor dem Deployment.
  • Startet im Audit-Modus, um zu sehen, welche Images nicht signiert sind, bevor ihr auf Enforce umstellt.
  • Key Management ist der schwierigste Teil -- nutzt ein KMS (HashiCorp Vault, AWS KMS, Azure Key Vault) statt lokaler Schluessel.
  • Ergaenzt Image Verification mit Runtime Security (Falco) fuer Defense in Depth.

Das Problem: Vertrauen in Container Images

Jedes docker pull oder kubectl apply laeuft auf dem gleichen Vertrauensmodell: man nimmt an, dass das Image in der Registry das ist, was man erwartet. In der Praxis ist diese Annahme fragil.

Angriffsvektoren gibt es viele. Kompromittierte CI/CD-Pipelines, manipulierte Base Images, gekaperte Registry-Accounts, Typosquatting auf Docker Hub. Der SolarWinds-Angriff 2020 hat gezeigt, dass selbst grosse Unternehmen anfaellig fuer Supply Chain Attacks sind. Und seit dem Log4Shell-Vorfall wissen wir, dass eine einzige verwundbare Dependency ganze Branchen lahmlegen kann.

Die Loesung: Images kryptografisch signieren und die Signatur vor dem Deployment pruefen. Kubernetes startet nur Images, die verifiziert sind. Alles andere wird abgelehnt.

Die Vertrauenskette: Vom Build bis zur Runtime

Die Absicherung besteht aus vier Stufen:

StufeKomponenteToolAufgabe
1. BuildCI/CD PipelineGitHub Actions, GitLab CIImage bauen und testen
2. SignImage SigningCosign (Sigstore)Kryptografische Signatur erzeugen
3. StoreContainer RegistryHarbor, GHCR, ECRSigniertes Image + Signatur speichern
4. VerifyAdmission ControllerKyverno, OPA GatekeeperSignatur beim Deployment pruefen

Optional kommt als fuenfte Stufe Runtime Security dazu (Falco, Tetragon), die das Verhalten laufender Container ueberwacht. Aber die ersten vier Stufen sind die Basis.

Cosign: Images signieren

Cosign ist Teil des Sigstore-Projekts und hat sich als Standard fuer Container Image Signing etabliert. Es ist einfach, gut dokumentiert und integriert sich in jede CI/CD-Pipeline.

Schluesselpaar erzeugen:

# Einmalig: Schluesselpaar generieren
cosign generate-key-pair

# Ergebnis: cosign.key (privat) und cosign.pub (oeffentlich)
# Den privaten Schluessel in ein KMS verschieben!

Fuer Produktion sollte der private Schluessel nie auf einer lokalen Maschine liegen. Stattdessen KMS verwenden:

# AWS KMS
cosign generate-key-pair --kms awskms:///arn:aws:kms:eu-central-1:123456789:key/abcd-1234

# Azure Key Vault
cosign generate-key-pair --kms azurekms://mykeyvault.vault.azure.net/keys/cosign-key

# HashiCorp Vault
cosign generate-key-pair --kms hashivault://cosign-key

Image signieren nach dem Build:

# Image bauen und taggen
docker build -t registry.example.com/api-server:v2.1.0 .

# Image pushen
docker push registry.example.com/api-server:v2.1.0

# Image signieren (mit lokalem Key)
cosign sign --key cosign.key registry.example.com/api-server:v2.1.0

# Image signieren (mit KMS Key)
cosign sign --key awskms:///arn:aws:kms:eu-central-1:123456789:key/abcd-1234 \
  registry.example.com/api-server:v2.1.0

Signatur manuell verifizieren:

cosign verify --key cosign.pub registry.example.com/api-server:v2.1.0

Wenn die Signatur gueltig ist, gibt Cosign die Payload als JSON aus. Wenn nicht, schlaegt der Befehl fehl. Genau dieses Verhalten bildet Kyverno im Cluster automatisiert ab.

CI/CD-Integration: Automatisch signieren

Die Signierung gehoert in die Pipeline, nicht in den manuellen Workflow. Hier ein GitHub Actions Beispiel:

name: Build, Push & Sign

on:
  push:
    tags: ['v*']

jobs:
  build-sign:
    runs-on: ubuntu-latest
    permissions:
      contents: read
      id-token: write  # Fuer Keyless Signing
      packages: write

    steps:
    - uses: actions/checkout@v4

    - name: Login to Registry
      uses: docker/login-action@v3
      with:
        registry: ghcr.io
        username: ${{ github.actor }}
        password: ${{ secrets.GITHUB_TOKEN }}

    - name: Build and Push
      uses: docker/build-push-action@v5
      with:
        push: true
        tags: ghcr.io/${{ github.repository }}:${{ github.ref_name }}

    - name: Install Cosign
      uses: sigstore/cosign-installer@v3

    - name: Sign Image
      env:
        COSIGN_KEY: ${{ secrets.COSIGN_PRIVATE_KEY }}
        COSIGN_PASSWORD: ${{ secrets.COSIGN_PASSWORD }}
      run: |
        cosign sign --key env://COSIGN_KEY \
          ghcr.io/${{ github.repository }}:${{ github.ref_name }}

Alternativ unterstuetzt Cosign auch "Keyless Signing" ueber OIDC-Identitaeten (Fulcio + Rekor). Dabei wird kein langlebiger Schluessel benoetigt -- stattdessen wird die Identitaet des CI-Systems kryptografisch bestaetigt. Das ist eleganter, erfordert aber Vertrauen in die Sigstore-Infrastruktur.

Kyverno: Signatur im Cluster pruefen

Kyverno ist ein Policy-as-Code Engine fuer Kubernetes. Es laeuft als Admission Controller und prueft Pods, bevor sie gestartet werden. Fuer Image Verification braucht man eine ClusterPolicy:

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

Wichtig: Startet immer mit validationFailureAction: Audit. Im Audit-Modus loggt Kyverno Verstoesse, blockiert aber nichts. So seht ihr, welche Images nicht signiert sind, ohne den laufenden Betrieb zu stoeren.

# Policy-Verstoesse im Audit-Modus pruefen
kubectl get policyreport -A

# Details zu einem spezifischen Report
kubectl describe policyreport -n production

Sobald alle relevanten Images signiert sind, wechselt auf Enforce:

spec:
  validationFailureAction: Enforce

Ab jetzt werden unsignierte Images abgelehnt. Der Scheduler startet den Pod nicht, und im Event-Log des Pods erscheint eine klare Fehlermeldung.

Ausnahmen definieren

Nicht jedes Image kann signiert werden. System-Images von Kubernetes selbst, Third-Party-Operator-Images oder temporaere Debug-Container brauchen Ausnahmen:

apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: verify-image-signatures
spec:
  validationFailureAction: Enforce
  rules:
  - name: check-image-signature
    match:
      any:
      - resources:
          kinds:
          - Pod
    exclude:
      any:
      - resources:
          namespaces:
          - kube-system
          - kyverno
      - resources:
          selector:
            matchLabels:
              skip-verification: "true"
    verifyImages:
    - imageReferences:
      - "ghcr.io/myorg/*"
      - "registry.example.com/*"
      attestors:
      - count: 1
        entries:
        - keys:
            publicKeys: |-
              -----BEGIN PUBLIC KEY-----
              MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE...
              -----END PUBLIC KEY-----

Das exclude Feld nimmt System-Namespaces und explizit gelabelte Pods aus. Haltet die Ausnahmeliste so kurz wie moeglich und prueft sie regelmaessig.

Harbor als Registry mit integrierter Sicherheit

Harbor ist eine Open-Source Container Registry, die Signaturen nativ unterstuetzt und zusaetzlich Vulnerability Scanning mitbringt:

# Harbor per Helm installieren
helm repo add harbor https://helm.goharbor.io
helm repo update

helm install harbor harbor/harbor \
  --namespace harbor \
  --create-namespace \
  --set expose.type=ingress \
  --set expose.ingress.hosts.core=registry.example.com \
  --set expose.tls.certSource=secret \
  --set expose.tls.secret.secretName=harbor-tls \
  --set persistence.persistentVolumeClaim.registry.size=100Gi \
  --set trivy.enabled=true

Harbor mit aktiviertem Trivy scannt jedes gepushte Image automatisch auf bekannte CVEs. In Kombination mit Cosign-Signaturen hat man zwei Sicherheitsebenen: Integritaet (Signatur) und Schwachstellen (Scan).

Vergleich: Kyverno vs. OPA Gatekeeper

Beide Tools koennen Image Verification, aber mit unterschiedlichen Ansaetzen:

KriteriumKyvernoOPA Gatekeeper
Policy-SpracheYAML (nativ)Rego (eigene Sprache)
LernkurveNiedrigHoch
Image VerificationBuilt-in (verifyImages)Custom Constraint Template
Mutation SupportJaBegrenzt
CommunityGross, aktivGross, aelter
Resource OverheadMittelMittel-Hoch

Fuer Teams, die schnell starten wollen und YAML-Erfahrung haben, ist Kyverno die bessere Wahl. OPA Gatekeeper lohnt sich, wenn man bereits Rego-Policies aus anderen Kontexten mitbringt oder sehr komplexe Policy-Logik braucht.

Mehr zu Policy-Enforcement und Security-Automatisierung unter Kubernetes Security Automation.

Runtime Security als zweite Verteidigungslinie

Image Verification schuetzt vor dem Deployment. Aber was, wenn ein signiertes Image eine Zero-Day-Luecke hat? Oder ein Angreifer es schafft, Code innerhalb eines laufenden Containers zu injizieren?

Hier kommt Falco ins Spiel. Falco ueberwacht Syscalls und erkennt verdaechtiges Verhalten:

# Falco per Helm installieren
helm repo add falcosecurity https://falcosecurity.github.io/charts
helm repo update

helm install falco falcosecurity/falco \
  --namespace falco \
  --create-namespace \
  --set tty=true \
  --set falcosidekick.enabled=true \
  --set falcosidekick.config.slack.webhookurl="https://hooks.slack.com/..."

Falco erkennt unter anderem:

  • Shell-Zugriff in einem Container (kubectl exec)
  • Unerwartete Netzwerkverbindungen
  • Schreibzugriffe auf /etc oder /bin
  • Privilegierte Container-Starts

Das ist Defense in Depth: Cosign/Kyverno pruefen die Integritaet vor dem Start, Falco ueberwacht das Verhalten danach. Wer tiefer einsteigen will, findet unter Kubernetes Runtime Security weiterfuehrende Patterns.

Rollout-Strategie: Von Audit zu Enforce

Der Wechsel von "nichts pruefen" zu "alles blockieren" muss schrittweise passieren. Hier ein bewaehrter Ablauf:

Woche 1-2: Bestandsaufnahme

  • Alle verwendeten Images und deren Quellen auflisten
  • Eigene Images vs. Third-Party Images kategorisieren
  • Key Management Strategie festlegen

Woche 3-4: Tooling aufsetzen

  • Cosign in CI/CD integrieren
  • Kyverno im Audit-Modus deployen
  • Harbor oder bestehende Registry konfigurieren

Woche 5-6: Signierung starten

  • Alle eigenen Images in der Pipeline signieren
  • Policy Reports auswerten, Luecken identifizieren
  • Ausnahmeliste fuer Third-Party Images definieren

Woche 7-8: Enforce in Staging

  • Kyverno auf Enforce in Staging umstellen
  • Fehlerhafte Deployments untersuchen und fixen
  • Runbooks fuer Troubleshooting schreiben

Woche 9-10: Enforce in Produktion

  • Schrittweise pro Namespace auf Enforce umstellen
  • Monitoring und Alerting fuer Policy-Verstoesse einrichten
  • Team-Schulung zu neuen Deployment-Anforderungen

Woche 11-12: Haertung

  • Runtime Security (Falco) aktivieren
  • Regelmaessige Key-Rotation planen
  • Audit-Prozess fuer Ausnahmeliste etablieren

Compliance-Aspekte

Image Verification ist nicht nur technisch sinnvoll, sondern auch fuer Compliance relevant:

  • BSI C5 / IT-Grundschutz: Fordert Integritaetsschutz fuer Software-Komponenten. Signierte Images sind ein direkter Nachweis.
  • ISO 27001: Kontrolle A.14.2.7 (Security Testing) und A.12.5.1 (Installation von Software) werden durch automatisierte Verifikation abgedeckt.
  • DSGVO Art. 32: Technische Massnahmen zur Sicherheit der Verarbeitung. Image Verification schuetzt die Integritaet der Verarbeitungssysteme.

Fuer eine umfassende Compliance-Strategie lohnt sich der Blick auf Kubernetes Compliance Automation und DSGVO-Compliance fuer Kubernetes.

Troubleshooting

Pod wird abgelehnt (ImagePullBackOff + Policy Violation):

# Events pruefen
kubectl describe pod <pod-name> -n <namespace>

# Kyverno Policy Reports pruefen
kubectl get clusterpolicyreport -o yaml | grep -A 10 "result: fail"

# Signatur manuell verifizieren
cosign verify --key cosign.pub <image-reference>

Haeufige Ursachen:

  • Image wurde ohne Signatur gepusht (Pipeline-Fehler)
  • Falscher Public Key in der Kyverno Policy
  • Image-Tag wurde ueberschrieben (Signatur passt nicht mehr zum Digest)
  • Registry-Konnektivitaetsproblem (Kyverno kann Signatur nicht abrufen)

Best Practice: Verwendet immer Image Digests statt Tags fuer signierte Images. Tags koennen ueberschrieben werden, Digests nicht:

image: registry.example.com/api-server@sha256:abc123...

Fazit

Container Image Verification ist keine optionale Haertungsmassnahme mehr -- sie ist Baseline Security. Die Toolchain (Cosign + Kyverno + Harbor) ist ausgereift, Open Source und in wenigen Wochen produktionsreif.

Der wichtigste Rat: Startet im Audit-Modus. Bekommt ein Bild davon, was in eurem Cluster laeuft. Dann signiert eure eigenen Images. Und erst wenn alles sauber ist, schaltet auf Enforce.

Weitergehende Themen wie Secrets Management und Encryption Strategies findet ihr unter Kubernetes Secrets Management und Kubernetes Encryption Strategies.

Wenn ihr Unterstuetzung beim Aufbau einer sicheren Container Supply Chain braucht, meldet euch unter /kontakt. Wir helfen von der Architektur bis zur Umsetzung.

Kubernetes-Beratung gesucht?

Wir helfen deutschen Unternehmen bei der Kubernetes-Implementierung, Migration und Optimierung. DSGVO-konform und praxiserprobt.

📖 Verwandte Artikel

Weitere interessante Beiträge zu ähnlichen Themen