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-onlyRoot-Dateisysteme undno-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.jsonverifizieren.
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
- 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.
- Überprivilegierte Container (
--privileged): Das Flag--privilegeddeaktiviert 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. - 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:
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:
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/shexistiert schlicht nicht). - Keine Paketmanager: Das Nachladen von Schadsoftware via
curl,wgetoderaptschlä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
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
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:
"default": [{"type": "reject"}]: Grundsätzlich wird jeder Download von Images aus unbekannten Quellen kompromisslos verweigert."quay.io/mein-unternehmen": Eigene Unternehmens-Images werden nur dann akzeptiert, wenn sie mit dem hinterlegten öffentlichen Schlüssel/etc/pki/containers/cosign.pubsigniert wurden.- Versucht ein Angreifer oder ein Administrator versehentlich, ein unsigniertes Image auszuführen, blockiert Podman den Vorgang unmittelbar:
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.