Zum Inhalt

Container-Sicherheit & Image-Supply-Chain: Härtung & Signierung

Erstellt: 10.11.2026 Aktualisiert: 10.11.2026

Management Summary: Die Bereitstellung von Anwendungen in Containern bietet keine prinzipielle Sicherheitsgarantie. Container teilen sich denselben Linux-Betriebssystem-Kernel wie der Host: Manipulierte Basis-Images, veraltete Bibliotheken mit kritischen CVEs, überprivilegierte Root-Container und ungeschützte Dateisysteme öffnen Angreifern direkte Ausbruchspfade zum Host-System. Ein belastbares Sicherheitskonzept verlangt eine kompromisslose Defense-in-Depth-Strategie über den gesamten Lebenszyklus: Von minimalistischen Multi-Stage- und Distroless-Builds über automatisierte Schwachstellen-Scans (Trivy, Grype) und Software-Stücklisten (SBOM via Syft) bis hin zur kryptografischen Signierung mit Cosign und der Durchsetzung strikter Host-Policies (policy.json), die unautorisierte Images vor der Ausführung verwerfen. Dieser Leitfaden führt dich durch die Container-Bedrohungsmatrix, Laufzeit-Härtung, Build-Pipelines, Scanning, Signierung und Policy-Enforcement.

  • Container-Bedrohungsmatrix


    Shared-Kernel-Risiken, Container-Escapes, Kernel-Exploits (Dirty Pipe) und die Gefahren gemounteter Docker-Sockets.

  • Laufzeit-Härtung (Runtime)


    Maximale Isolation durch erzwungenes Privilege-Dropping, --cap-drop=ALL, --read-only Root-Dateisysteme und no-new-privileges.

  • Minimalistische Base-Images


    Angriffsflächen radikal schrumpfen durch Multi-Stage-Containerfiles, statische Binärdateien und Distroless- bzw. Scratch-Images.

  • CVE-Scanning (Trivy & Grype)


    Statische Code- und Image-Analyse in CI/CD-Pipelines: Schwachstellen automatisiert vor dem Deployment blockieren.

  • SBOM (Software Bill of Materials)


    Vollständige Transparenz aller Komponenten und Abhängigkeiten mit standardisierten SBOM-Formaten (CycloneDX, SPDX).

  • Kryptografische Signatur (Cosign)


    Images kryptografisch via Sigstore/Cosign signieren und auf Host-Ebene über /etc/containers/policy.json verifizieren.


1. Container-Bedrohungsmatrix & Ausbruchsvektoren (Runtime Threat Modeling)

Ein weit verbreiteter Irrglaube lautet: „Container isolieren wie virtuelle Maschinen.“ Architektonisch ist das Gegenteil der Fall:

  • Virtuelle Maschinen (Hypervisor Ring -1): Jede VM besitzt ihren eigenen, vollständig gekapselten Betriebssystem-Kernel. Ein Kernel-Panic oder Exploit in einer VM betrifft andere VMs in der Regel nicht.
  • Linux-Container (Shared Kernel Ring 0): Container sind lediglich normale Linux-Prozesse, die durch Kernel-Features (Namespaces für Sichtbarkeit, cgroups für Ressourcen-Limits und LSM-Module für Zugriffskontrolle) isoliert werden. Alle Container teilen sich denselben Linux-Kernel mit dem Host!
%%{init: {'flowchart': {'wrappingWidth': 350}}}%%
flowchart TB
    subgraph Developer_Stage["1. Build- & Entwicklungsphase"]
        Code["Quellcode & Abhängigkeiten<br/>(Git / npm / pip)"] ==> Build["Multi-Stage Build<br/>(Distroless / Scratch Base)"]
        Build --> Scan["Automatisierter Scan<br/>(Trivy / Syft SBOM)"]
    end

    subgraph Registry_Stage["2. Signierung & Registry"]
        Sign["Kryptografische Signatur<br/>(cosign sign mit Sigstore)"] ==> Registry["OCI Registry<br/>(Quay.io / Harbor / OCI)"]
    end

    subgraph Host_Runtime["3. Host-Laufzeitumgebung"]
        Policy["Host Policy Engine<br/>(/etc/containers/policy.json)"] ==> Run["Gehärtete Podman Runtime<br/>(Rootless / Read-Only / Cap-Drop)"]
    end

    Developer_Stage ==> Registry_Stage
    Registry_Stage ==> Host_Runtime

Die drei gefährlichsten Ausbruchsvektoren

  1. Kernel-Schwachstellen (z. B. Dirty Pipe CVE-2022-0847): Wenn ein Container Zugriff auf Systemaufrufe hat, die eine ungepatchte Schwachstelle im Host-Kernel triggern, kann der Prozess aus dem Namespace ausbrechen und beliebige Host-Dateien überschreiben.
  2. Überprivilegierte Container (--privileged): Das Flag --privileged deaktiviert nahezu alle Sicherheitsmechanismen des Kernels. Es übergibt alle Host-Devices (/dev/) an den Container und verleiht ihm volle Kernel-Capabilities (CAP_SYS_ADMIN). Ein Ausbruch erfordert hier lediglich das Mounten der Host-Festplatte aus dem Container heraus.
  3. Gemountete Docker-/Podman-Sockets (/var/run/docker.sock): Wird der Socket des Container-Daemons in einen Container gemountet (z. B. für Build-Tools oder Monitoring), erhält der Container die uneingeschränkte Kontrolle über den gesamten Host, da er über die API neue Container mit beliebigen Host-Root-Mounts starten kann.

2. Kompromisslose Laufzeit-Härtung (Defense-in-Depth)

Um das Risiko von Container-Breakouts zu minimieren, muss die Laufzeitumgebung nach dem Prinzip der minimalen Rechte (Least Privilege) konfiguriert werden.

1. Radikales Privilege-Dropping: Verbot von Root

Ein Container darf niemals als root (UID 0) ausgeführt werden. Auch wenn Rootless-Podman UID 0 auf eine unprivilegierte SubUID abbildet, vergrößert root innerhalb des Namespaces die Angriffsfläche unnötig.

Definiere in jedem Containerfile oder Startbefehl einen expliziten unprivilegierten Benutzer:

# Start mit expliziter unprivilegierter UID (z. B. 10001)
podman run --user 10001:10001 ...

2. Capabilities strikt entziehen: --cap-drop=ALL

Der Linux-Kernel unterteilt Superuser-Rechte in 40+ POSIX-Capabilities. Container erhalten standardmäßig ~14 Capabilities (z. B. CAP_NET_RAW für Ping, CAP_CHOWN). Für reine Anwendungsdienste (Webserver, APIs, Datenbanken) sind die meisten davon überflüssig.

Enterprise-Standardregel: Entziehe prinzipiell alle Capabilities und füge nur exakt jene hinzu, die für den Betrieb unverzichtbar sind:

# Beispiel: Nur Binden an Port < 1024 erlauben, alles andere sperren
podman run --rm \
  --cap-drop=ALL \
  --cap-add=NET_BIND_SERVICE \
  docker.io/library/nginx:alpine

3. Unterbindung von Rechten-Eskalation: no-new-privileges

Verhindert, dass Prozesse innerhalb des Containers über setuid- oder setgid-Binaries (z. B. manipulierte sudo- oder su-Programme) höhere Rechte erlangen:

podman run --security-opt=no-new-privileges:true ...

4. Schreibgeschütztes Root-Dateisystem (--read-only)

Gelingt einem Angreifer die Code-Ausführung (z. B. über eine Remote-Code-Execution-Lücke in einer Web-App), versucht er typischerweise, Schadcode nach /tmp oder /usr/bin herunterzuladen und auszuführen.

Ein schreibgeschütztes Dateisystem macht dies unmöglich:

# Root-Dateisystem schreibgeschützt mounten, flüchtige Pfade via tmpfs bereitstellen
podman run --rm \
  --read-only \
  --tmpfs /tmp:rw,noexec,nosuid,size=64m \
  --tmpfs /var/run:rw,noexec,nosuid,size=16m \
  docker.io/library/nginx:alpine

Hinweis: Das Flag noexec auf dem tmpfs-Mount verhindert zusätzlich, dass Binärdateien aus /tmp heraus ausgeführt werden können!


3. Sichere Container-Builds: Multi-Stage, Distroless & Scratch

Monolithische Basis-Images (wie ubuntu:latest oder debian:bookworm) sind für den Produktionsbetrieb ein erhebliches Sicherheitsrisiko: Sie bringen Paketmanager (apt), Shells (/bin/bash, /bin/sh), Python, Perl und hunderte Standardwerkzeuge mit. Jeder gefundene Angriffsvektor kann von Angreifern mit den bordeigenen Hilfsmitteln (Living off the Land) ausgeweitet werden.

1. Das Multi-Stage-Build-Muster

Multi-Stage-Builds trennen die Entwicklungs- und Kompilierwerkzeuge strikt vom finalen Auslieferungs-Image:

%%{init: {'flowchart': {'wrappingWidth': 350}}}%%
flowchart TB
    subgraph BuildStage["1. Build-Stage (Schwergewichtig)"]
        Compiler["golang:1.22-alpine<br/>(Compiler, Git, Header)"]
        Source["Quellcode kompilieren<br/>(CGO_ENABLED=0 go build)"]
        Compiler --> Source
    end

    subgraph FinalStage["2. Produktions-Stage (Minimalistisch)"]
        Artifact["Kompilierte Binärdatei<br/>(Rein statisches Binary)"]
        Distroless["gcr.io/distroless/static-debian12<br/>(Keine Shell, kein apt, keine Tools)"]
        Artifact --> Distroless
    end

    Source ==>|"COPY --from=builder"| Artifact

2. Vollständiges Produktions-Containerfile: Go-Microservice

Erstelle Containerfile:

# Containerfile
# ==============================================================================
# Enterprise Multi-Stage Build: Maximale Sicherheit & minimale Angriffsfläche
# ==============================================================================

# STAGE 1: Builder
FROM docker.io/library/golang:1.22-alpine AS builder

WORKDIR /src

# Abhängigkeiten vorab cachen
COPY go.mod go.sum ./
RUN go mod download

COPY . .

# Vollständig statisch kompilieren (ohne glibc-Abhängigkeiten)
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build \
    -ldflags="-w -s" \
    -o /bin/app .

# ------------------------------------------------------------------------------
# STAGE 2: Minimalistische Produktions-Runtime (Distroless)
# Enthält ausschließlich: CA-Zertifikate, /etc/passwd für "nonroot" und tzdata
FROM gcr.io/distroless/static-debian12:nonroot

# Unprivilegierter Standardbenutzer von Distroless (UID 65532)
USER nonroot:nonroot

# Kopiere ausschließlich das kompilierte Binary aus der Build-Stage
COPY --from=builder --chown=nonroot:nonroot /bin/app /app

# Exponiere Port
EXPOSE 8080

# Starte Anwendung direkt ohne Shell-Wrapper
ENTRYPOINT ["/app"]

Warum Distroless und Scratch überlegen sind:

  • Keine Shell vorhanden: Ein Angreifer kann selbst bei erfolgreicher RCE-Lücke keine interaktive Shell spawnen (/bin/sh existiert schlicht nicht).
  • Keine Paketmanager: Das Nachladen von Schadsoftware via curl, wget oder apt schlägt fehl.
  • Minimaler CVE-Fußabdruck: Wo keine Pakete installiert sind, können keine bekannten Sicherheitslücken gemeldet werden.

4. Automatisierte Schwachstellen-Scans mit Trivy & Grype

Bevor ein Container-Image in eine Registry gepusht oder auf einem Produktionsserver gestartet wird, muss es einem automatisierten Schwachstellen-Audit unterzogen werden.

1. Installation von Trivy

Trivy (entwickelt von Aqua Security) ist der De-facto-Standard für statische Image-Sicherheitsanalysen:

# Installation unter Debian / Ubuntu
sudo apt-get install wget apt-transport-https gnupg lsb-release -y
wget -qO - https://aquasecurity.github.io/trivy-repo/deb/public.key | sudo gpg --dearmor -o /usr/share/keyrings/trivy.gpg
echo "deb [signed-by=/usr/share/keyrings/trivy.gpg] https://aquasecurity.github.io/trivy-repo/deb $(lsb_release -sc) main" | sudo tee /etc/apt/sources.list.d/trivy.list
sudo apt-get update && sudo apt-get install trivy -y

2. Image-Scan durchführen

# Vollständigen Scan eines lokalen oder Remote-Images durchführen
trivy image docker.io/library/nginx:alpine

3. CI/CD-Pipeline-Integration: Strikte Blockade bei kritischen Lücken

In automatisierten Build-Pipelines soll der Build fehlschlagen, wenn ungepatchte Schwachstellen mit Schweregrad HIGH oder CRITICAL entdeckt werden:

# Bricht mit Exit-Code 1 ab, wenn kritische oder hohe CVEs gefunden werden
trivy image \
  --exit-code 1 \
  --severity HIGH,CRITICAL \
  --ignore-unfixed \
  mein-image:latest

Tipp: Der Parameter --ignore-unfixed blendet Schwachstellen aus, für die vom Upstream-Hersteller noch kein Sicherheits-Patch veröffentlicht wurde. Das verhindert Pipeline-Blockaden bei nicht behebbaren Findings.

4. False Positives verwalten mit .trivyignore

Wenn ein Fund nachweislich nicht ausnutzbar ist (z. B. weil die betroffene Funktion im Code gar nicht aufgerufen wird), kann er dokumentiert in .trivyignore hinterlegt werden:

# .trivyignore
# Begründung: Betrifft nur CLI-Tool, das im Produktions-Container nicht aufgerufen wird
CVE-2023-12345

5. Software Bill of Materials (SBOM) mit Syft

Eine Software Bill of Materials (SBOM) ist das digitale Inhaltsverzeichnis aller Software-Komponenten, Lizenzen und Abhängigkeiten eines Containers. Regulatorische Vorgaben (wie die europäische NIS2-Richtlinie oder US Executive Order 14028) fordern zunehmend lückenlose SBOMs für betriebskritische Systeme.

1. Installation von Syft

Syft (von Anchore) erzeugt maschinenlesbare SBOMs in standardisierten Formaten (CycloneDX und SPDX):

# Installation via offiziellem Installationsskript
curl -sSfL https://raw.githubusercontent.com/anchore/syft/main/install.sh | sudo sh -s -- -b /usr/local/bin

2. SBOM im CycloneDX-Format generieren

# Erzeuge eine standardisierte CycloneDX-JSON-Stückliste
syft docker.io/library/nginx:alpine -o cyclonedx-json=nginx-sbom.json

# Anzeigen der erkannten Pakete im Terminal
syft docker.io/library/nginx:alpine

3. Vulnerability-Matching auf Basis der SBOM mit Grype

Ein entscheidender Vorteil von SBOMs: Man muss das Container-Image nicht jedes Mal neu scannen. Das Werkzeug Grype kann Schwachstellen direkt anhand der generierten sbom.json ermitteln:

# Installiere Grype
curl -sSfL https://raw.githubusercontent.com/anchore/grype/main/install.sh | sudo sh -s -- -b /usr/local/bin

# Scanne die vorab erzeugte SBOM auf neu bekannt gewordene CVEs
grype sbom:nginx-sbom.json

6. Kryptografische Signierung & Verifikation mit Cosign (Sigstore)

Selbst ein perfekt gescanntes und gehärtetes Image bietet keinen Schutz, wenn auf dem Produktionsserver manipulierte Fremd-Images aus unsicheren Quellen ausgeführt werden können.

Mit Cosign (Teil des Open-Source-Projekts Sigstore) werden OCI-Images kryptografisch signiert. Die Signatur wird als OCI-Artefakt direkt in der Container-Registry neben dem Image gespeichert.

1. Installation von Cosign

# Installation unter Linux (amd64)
COSIGN_VERSION=$(curl -s https://api.github.com/repos/sigstore/cosign/releases/latest | grep 'tag_name' | cut -d\" -f4)
curl -LO "https://github.com/sigstore/cosign/releases/download/${COSIGN_VERSION}/cosign-linux-amd64"
sudo install -m 0755 cosign-linux-amd64 /usr/local/bin/cosign
rm -f cosign-linux-amd64

2. Schlüsselpaar generieren

# Erzeuge privates und öffentliches Schlüsselpaar
cosign generate-key-pair
Befehl erzeugt: * cosign.key: Der private Schlüssel (passwortgeschützt, verbleibt in der CI/CD-Pipeline). * cosign.pub: Der öffentliche Schlüssel (wird auf den Ziel-Servern hinterlegt).

3. Container-Image signieren

Nach dem Push des Images in die Registry:

# Image anhand seines eindeutigen Digests signieren
cosign sign --key cosign.key quay.io/mein-unternehmen/web-app:latest

4. Signatur auf dem Zielsystem verifizieren

# Überprüfe die Signatur vor dem Deployment
cosign verify --key cosign.pub quay.io/mein-unternehmen/web-app:latest
Gibt Cosign die JSON-Payload der Signatur aus, ist die Authentizität und Unverfälschtheit des Images mathematisch bewiesen!


7. Enterprise Host Policy Enforcement (/etc/containers/policy.json)

Der finale und wirksamste Schutzmechanismus: Podman kann auf Betriebssystem-Ebene angewiesen werden, ausschließlich kryptografisch verifizierte Images auszuführen.

Die Policy wird in /etc/containers/policy.json definiert.

Produktions-Konfiguration: Strikte Signatur-Erzwingung

Erstelle /etc/containers/policy.json:

{
  "default": [
    {
      "type": "reject"
    }
  ],
  "transports": {
    "docker": {
      "quay.io/mein-unternehmen": [
        {
          "type": "sigstoreSigned",
          "keyPath": "/etc/pki/containers/cosign.pub",
          "signedIdentity": {
            "type": "matchRepository"
          }
        }
      ],
      "docker.io/library": [
        {
          "type": "insecureAccept"
        }
      ]
    }
  }
}

Wie diese Policy schützt:

  1. "default": [{"type": "reject"}]: Grundsätzlich wird jeder Download von Images aus unbekannten Quellen kompromisslos verweigert.
  2. "quay.io/mein-unternehmen": Eigene Unternehmens-Images werden nur dann akzeptiert, wenn sie mit dem hinterlegten öffentlichen Schlüssel /etc/pki/containers/cosign.pub signiert wurden.
  3. Versucht ein Angreifer oder ein Administrator versehentlich, ein unsigniertes Image auszuführen, blockiert Podman den Vorgang unmittelbar:
    Error: Source image rejected: Signature verification failed
    

Mit der Kombination aus Multi-Stage Distroless-Builds, automatisierter Schwachstellen-Triage (Trivy), SBOM-Transparenz (Syft), Signierung (Cosign) und hardwarenaher Podman Policy-Erzwingung ist die Container-Supply-Chain auf höchstem Enterprise-Niveau gegen Manipulation und Zero-Day-Ausbrüche abgesichert.