Veröffentlicht am

Supply Chain Security: Container-Images mit SLSA und Sigstore

Teilen:
Authors

Kubernetes Supply Chain Security: Container-Images mit SLSA und Sigstore absichern

TL;DR

  • SLSA definiert Sicherheits-Level fuer den Build-Prozess, Sigstore liefert die Tools zur kryptografischen Signierung
  • Cosign signiert Container-Images keyless ueber OIDC -- kein langlebiges Schluesselmanagement noetig
  • Rekor speichert Signaturen in einem oeffentlichen Transparency Log, Fulcio stellt kurzlebige Zertifikate aus
  • Admission Controller (Kyverno oder Gatekeeper) blocken unsignierte Images vor dem Deployment
  • Die Integration in bestehende CI/CD-Pipelines (GitHub Actions, GitLab CI) erfordert typischerweise 20-30 Zeilen Pipeline-Config

Das Problem

Die meisten Kubernetes-Cluster lassen jedes Container-Image zu, solange es pullbar ist. Es gibt keine Garantie, dass das Image tatsaechlich aus der eigenen CI/CD-Pipeline stammt, nicht nachtraeglich veraendert wurde und nur verifizierte Abhaengigkeiten enthaelt.

Angriffe wie SolarWinds (kompromittierter Build-Prozess), Codecov (manipuliertes CI-Skript) oder die xz-utils Backdoor (infiltrierter Maintainer) zeigen: Die Software-Lieferkette ist ein attraktives Angriffsziel. Ein einziger kompromittierter Build kann tausende Deployments betreffen.

Die Frage ist nicht ob, sondern wann ein Angriff auf die eigene Supply Chain versucht wird. SLSA und Sigstore bieten einen pragmatischen Schutz, der sich in bestehende Workflows integrieren laesst.

SLSA: Was es ist und was es definiert

SLSA (Supply-chain Levels for Software Artifacts, gesprochen "Salsa") ist ein Framework von Google, das Sicherheitsanforderungen fuer den Software-Build-Prozess in vier Level einteilt:

SLSA LevelAnforderungBedeutung
Level 0Keine AnforderungenStatus quo bei den meisten Projekten
Level 1Dokumentierter Build-ProzessBuild ist nachvollziehbar, Provenienz wird erzeugt
Level 2Gehosteter Build-ServiceBuild laeuft auf einer Plattform, nicht lokal
Level 3Gehaerteter Build-ProzessBuild ist isoliert, nicht vom Entwickler manipulierbar

SLSA Level 1 ist fuer die meisten Teams in wenigen Stunden erreichbar. Level 2 hat man oft schon, wenn CI/CD in GitHub Actions oder GitLab CI laeuft. Level 3 erfordert gehaertete, nicht manipulierbare Build-Umgebungen -- das ist der Sprung, der echten Aufwand bedeutet.

Das Kernkonzept von SLSA ist die Provenienz (Provenance): Ein maschinenlesbares Dokument, das beschreibt, welcher Quellcode mit welchem Build-System zu welchem Artefakt gebaut wurde. Diese Provenienz wird kryptografisch signiert und kann spaeter verifiziert werden.

Sigstore: Die Tool-Suite

Sigstore besteht aus drei Komponenten:

Cosign: Signiert und verifiziert Container-Images und andere OCI-Artefakte. Unterstuetzt sowohl Key-basierte als auch Keyless-Signierung ueber OIDC.

Rekor: Ein Transparency Log, das Signaturen unveraenderlich speichert. Aehnlich wie Certificate Transparency fuer TLS-Zertifikate. Jeder kann pruefen, ob eine Signatur existiert.

Fulcio: Eine Certificate Authority, die kurzlebige X.509-Zertifikate auf Basis von OIDC-Identitaeten ausstellt. Der Entwickler authentifiziert sich ueber Google, GitHub oder einen anderen OIDC-Provider. Fulcio stellt ein Zertifikat mit einer Lebensdauer von 10 Minuten aus. Damit wird signiert, danach verfaellt das Zertifikat. Kein langfristiges Key-Management noetig.

Container-Images mit Cosign signieren

Die Keyless-Signierung ist der empfohlene Weg fuer CI/CD-Pipelines:

# Cosign installieren
curl -sSfL https://github.com/sigstore/cosign/releases/latest/download/cosign-linux-amd64 -o /usr/local/bin/cosign
chmod +x /usr/local/bin/cosign

# Image bauen und pushen
docker build -t registry.example.com/my-app:v1.2.0 .
docker push registry.example.com/my-app:v1.2.0

# Image keyless signieren (in CI/CD mit OIDC-Token)
COSIGN_EXPERIMENTAL=1 cosign sign registry.example.com/my-app:v1.2.0

# SLSA-Provenienz als Attestation anhaengen
cosign attest --predicate provenance.json \
  --type slsaprovenance \
  registry.example.com/my-app:v1.2.0

# Signatur verifizieren
cosign verify \
  --certificate-identity "https://github.com/my-org/my-repo/.github/workflows/build.yml@refs/heads/main" \
  --certificate-oidc-issuer "https://token.actions.githubusercontent.com" \
  registry.example.com/my-app:v1.2.0

# Attestation verifizieren
cosign verify-attestation \
  --type slsaprovenance \
  --certificate-identity "https://github.com/my-org/my-repo/.github/workflows/build.yml@refs/heads/main" \
  --certificate-oidc-issuer "https://token.actions.githubusercontent.com" \
  registry.example.com/my-app:v1.2.0

In GitHub Actions sieht die Integration so aus (vereinfacht):

# .github/workflows/build-sign.yml
name: Build and Sign
on:
  push:
    tags: ['v*']

permissions:
  contents: read
  packages: write
  id-token: write  # Noetig fuer Keyless Signing

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v4

    - uses: sigstore/cosign-installer@v3

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

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

    - name: Sign Image
      run: |
        cosign sign --yes \
          ghcr.io/${{ github.repository }}@${{ steps.build.outputs.digest }}

    - name: Generate and Attach SLSA Provenance
      run: |
        cosign attest --yes \
          --predicate <(slsa-provenance generate) \
          --type slsaprovenance \
          ghcr.io/${{ github.repository }}@${{ steps.build.outputs.digest }}

Die id-token: write Permission ist der Schluessel. Damit kann der Workflow ein OIDC-Token von GitHub anfordern, das Fulcio zur Ausstellung eines kurzlebigen Zertifikats nutzt. Kein einziger Key muss als Secret hinterlegt werden.

Deployment-Verifizierung mit Admission Controllern

Die Signierung allein reicht nicht. Man muss auch erzwingen, dass nur signierte Images deployed werden. Dafuer gibt es Policy Engines als Kubernetes Admission Controller.

Kyverno ist hier meine Empfehlung, weil die Policies als native Kubernetes-Ressourcen geschrieben werden (kein Rego wie bei Gatekeeper):

apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: verify-image-signatures
spec:
  validationFailureAction: Enforce
  background: false
  rules:
  - name: verify-cosign-signature
    match:
      any:
      - resources:
          kinds:
          - Pod
    verifyImages:
    - imageReferences:
      - "ghcr.io/my-org/*"
      - "registry.example.com/*"
      attestors:
      - entries:
        - keyless:
            subject: "https://github.com/my-org/*"
            issuer: "https://token.actions.githubusercontent.com"
            rekor:
              url: https://rekor.sigstore.dev
      attestations:
      - type: slsaprovenance
        attestors:
        - entries:
          - keyless:
              subject: "https://github.com/my-org/*"
              issuer: "https://token.actions.githubusercontent.com"
        conditions:
        - all:
          - key: "{{ builder.id }}"
            operator: Equals
            value: "https://github.com/my-org/my-repo/*"

Diese Policy erzwingt zwei Dinge:

  1. Jedes Image aus ghcr.io/my-org/* oder registry.example.com/* muss eine gueltige Cosign-Signatur haben
  2. Die Signatur muss von einer GitHub Actions Workflow aus der eigenen Organisation stammen (Keyless ueber OIDC verifiziert)

Ein kubectl apply mit einem unsignierten Image wird sofort abgelehnt:

Error from server: admission webhook "mutate.kyverno.svc-fail" denied the request:
resource Pod/default/my-app was blocked due to the following policies:
  verify-image-signatures:
    verify-cosign-signature: 'image verification failed for ghcr.io/my-org/my-app:latest:
      signature not found'

SBOM: Software Bill of Materials

Ergaenzend zu Signaturen und Provenienz ist ein SBOM (Software Bill of Materials) sinnvoll. Es listet alle Abhaengigkeiten eines Container-Images auf -- Betriebssystem-Pakete, Bibliotheken, Sprach-Pakete.

Tools wie Syft generieren SBOMs, die man mit Cosign als Attestation an das Image anhaengen kann:

# SBOM generieren
syft registry.example.com/my-app:v1.2.0 -o spdx-json > sbom.spdx.json

# SBOM als Attestation anhaengen
cosign attest --predicate sbom.spdx.json \
  --type spdxjson \
  registry.example.com/my-app:v1.2.0

# SBOM auf bekannte CVEs pruefen
grype sbom:sbom.spdx.json

Damit hat man eine verifizierbare Aufstellung aller Komponenten im Image. Bei einem neuen CVE kann man sofort pruefen, welche Images betroffen sind.

Vergleich der Policy Engines

FeatureKyvernoOPA GatekeeperKubewarden
Policy-SpracheYAML (nativ K8s)RegoWasm (Go, Rust, etc.)
Image-VerifikationEingebautUeber External DataUeber Policies
Cosign-IntegrationNativeManuellNative
LernkurveNiedrigHoch (Rego)Mittel
Mutation SupportJaBegrenztJa
CommunityGross, CNCF IncubatingGross, CNCF GraduatedKleiner, CNCF Sandbox

Fuer die reine Image-Signatur-Verifizierung ist Kyverno der schnellste Weg. Wer bereits Gatekeeper im Cluster hat, kann die Cosign-Verifizierung ueber External Data Provider einbinden, aber der Aufwand ist hoeher.

Schrittweise Einfuehrung

Die Einfuehrung sollte nicht als Big Bang passieren. Ein pragmatischer Fahrplan:

Woche 1-2: Cosign in einer CI/CD-Pipeline fuer eine nicht-kritische Anwendung einbauen. Image signieren, Signatur verifizieren. Gefuehl fuer die Tools bekommen.

Woche 3-4: Kyverno oder Gatekeeper auf einem Test-Cluster installieren. Policy im Audit-Modus (nicht Enforce) aktivieren. Schauen, welche Images durchfallen wuerden.

Woche 5-6: SLSA-Provenienz in der Pipeline generieren und als Attestation anhaengen. SBOM-Generierung einrichten.

Woche 7-8: Policy auf Enforce umstellen fuer den Test-Cluster. Alle relevanten Images muessen jetzt signiert sein.

Woche 9-12: Ausrollen auf weitere Pipelines und Cluster. Monitoring und Alerting einrichten fuer abgelehnte Deployments. Incident-Response-Prozess fuer kompromittierte Signaturen definieren.

Der Audit-Modus in Woche 3-4 ist entscheidend. Er zeigt, welche Third-Party-Images keine Signaturen haben und wie man damit umgeht (eigene Signatur draufsetzen, alternative Images finden oder Ausnahmen definieren).

Haeufige Stolperfallen

Tag-basierte Referenzen: my-app:latest oder my-app:v1 sind mutable Tags. Die Signatur bezieht sich auf den Digest, nicht den Tag. Wenn jemand ein neues Image unter demselben Tag pusht, ist die alte Signatur ungueltig. Loesung: Immer Digest-basierte Referenzen verwenden (my-app@sha256:abc123...).

Third-Party-Images: Nicht alle Upstream-Images sind signiert. Man muss entscheiden: Eigene Signatur draufsetzen (nach Pruefung), Ausnahme-Policy definieren oder auf signierte Alternativen wechseln. Chainguard Images bieten hier eine gute Quelle fuer signierte Base Images.

Keyless in Air-Gapped Environments: Keyless Signing braucht Zugang zu Fulcio und Rekor. In Air-Gapped-Umgebungen muss man Sigstore selbst hosten oder auf klassische Key-basierte Signierung zurueckfallen.

Registry-Kompatibilitaet: Cosign speichert Signaturen als OCI-Artefakte neben dem Image. Nicht alle Registries unterstuetzen das (aeltere Harbor-Versionen, manche On-Premise-Registries). Vorher testen.

Compliance-Relevanz

Supply Chain Security ist nicht nur technische Hygiene. NIS2 fordert explizit Massnahmen zur Absicherung der Software-Lieferkette. Der Cyber Resilience Act (CRA) wird SBOM-Pflichten bringen. ISO 27001 Annex A.14 adressiert Sicherheit in Entwicklungsprozessen.

SLSA-Provenienz und Sigstore-Signaturen liefern den kryptografischen Nachweis, den Auditoren sehen wollen: Wer hat wann was gebaut, ob es veraendert wurde und ob es fuer das Deployment autorisiert war.

Fuer eine breitere Perspektive auf Kubernetes Security empfehle ich den Beitrag zu Kubernetes Security Hardening. Wer automatisierte Security Scans in der Pipeline etablieren moechte, findet Details unter Security Scanning in Kubernetes. Und fuer Runtime Security lohnt sich ein Blick auf Kubernetes Runtime Security.

Zusammenfassung

Supply Chain Security in Kubernetes laesst sich mit SLSA und Sigstore pragmatisch umsetzen. Die Keyless-Signierung ueber OIDC eliminiert das groesste Hindernis (Key-Management), die Integration in gaengige CI/CD-Systeme ist unkompliziert, und Admission Controller erzwingen die Verifizierung zur Deployment-Zeit.

Der wichtigste Schritt ist der erste: Ein Image in der Pipeline signieren und die Signatur verifizieren. Alles Weitere baut darauf auf.

Wenn Sie Unterstuetzung bei der Absicherung Ihrer Kubernetes Supply Chain brauchen oder eine Bestandsaufnahme Ihrer aktuellen Sicherheitslage wuenschen, sprechen Sie uns an unter /kontakt.

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