Container-Sicherheit ist ein eigenes Thema, weil ein Container per Definition Prozesse auf einem gemeinsamen Kernel ausführt. Die Isolation ist gut, aber nicht so stark wie bei einer virtuellen Maschine. Wer Container produktiv einsetzt, sollte wissen, welche Grenzen der Kernel zieht – und wie man sie enger stellt.
Was Container isolieren – und was nicht
Isoliert (mit eigenen Namensraeumen / namespaces):
Prozesse eigene PID-Nummerierung
Netzwerk eigene Schnittstellen, Routing-Tabellen, Firewall
Einhängepunkte eigene Sicht auf das Dateisystem
Benutzer/Gruppen eigene UID/GID-Zuordnung
Rechnername eigener Hostname
Zwischenprozess eigene IPC-Puffer
NICHT isoliert:
Der Kernel selbst
-> Eine Luecke im Kernel ist aus jedem Container heraus nutzbar.
-> Ein ungepatchter Host-Kernel ist die groesste Gefahr
im gesamten Containerbetrieb.
Hardware und Geraete
-> Wer /dev/* durchreicht, gibt viel Kontrolle ab.
/dev/mem, /dev/sda oder der Docker-Socket sind praktisch
gleichbedeutend mit Root auf dem Host.
Ressourcen ohne explizite Begrenzung
-> Ohne Speichergrenze kann ein Container den Host
in den Speichermangel treiben.
Die zehn wichtigsten Maßnahmen
- Der Host-Kernel wird aktuell gehalten. Das ist wirksamer als jede Containerkonfiguration. Ein Kernel-Ausbruch betrifft alle Container gleichzeitig.
- Nicht als Root im Container arbeiten. Im Dockerfile einen Benutzer anlegen und mit
USERumschalten. Wer als Root im Container läuft, hätte bei einem Ausbruch die höchsten Rechte.FROM debian:bookworm-slim RUN groupadd -r app && useradd -r -g app -d /srv/app -s /usr/sbin/nologin app WORKDIR /srv/app COPY --chown=app:app . . USER app CMD ["/srv/app/server"] - Dateisystem nur lesen.
--read-onlyverhindert, dass ein Angreifer Schadsoftware ablegt. Schreibpfade einzeln freigeben.docker run -d \ --read-only \ --tmpfs /tmp:rw,noexec,nosuid,size=64m \ --tmpfs /run:rw,noexec,nosuid,size=16m \ --mount type=volume,src=app-daten,dst=/srv/app/daten \ myapp:1.0 - Fähigkeiten (Capabilities) entziehen. Ein Standard-Root-Container hat erhebliche Kernel-Rechte. Alle entfernen und nur die nötigen zurückgeben.
docker run -d \ --cap-drop ALL \ --cap-add NET_BIND_SERVICE \ --security-opt no-new-privileges \ myapp:1.0 # Besonders gefaehrlich: SYS_ADMIN (fast gleichbedeutend mit Root), # SYS_PTRACE, SYS_MODULE, NET_ADMIN ohne Not, und --privileged. # --privileged hebt praktisch JEDE Isolation auf. - Namensräume des Hosts nicht teilen.
--pid=host,--network=hostoder--ipc=hostsind Sonderfälle mit gutem Grund – nicht der Standard. - Ressourcen begrenzen. Speicher- und CPU-Grenzen verhindern, dass ein Container den Host lahmlegt.
docker run -d \ --memory 512m --memory-swap 512m \ --cpus 1.5 --pids-limit 200 \ myapp:1.0 # --pids-limit schuetzt vor einer Prozesslawine # (Fork-Bombe) und sollte IMMER gesetzt werden. - Signatur und Herkunft der Images prüfen. Nur Images aus vertrauenswürdigen Quellen und mit festgeschriebener Version per Hash – nicht
:latest.docker pull myapp:1.0 docker inspect myapp:1.0 --format '{{.RepoDigests}}' # Immer per Digest pinnen - der Tag kann sich aendern! docker run -d myapp@sha256:abc123... # Image auf bekannte Schwaechen pruefen trivy image myapp:1.0 grype myapp:1.0 # In die Pipeline einbauen: Abbruch bei kritischen Funden. # Fuer Docker Swarm und Kubernetes zusaetzlich: cosign sign myapp:1.0 cosign verify myapp:1.0 docker trust inspect --pretty myapp:1.0 - Keine Geheimnisse im Image. Weder im Dockerfile noch in Umgebungsvariablen (
docker inspectzeigt sie jedem mit Zugriff). Geheimnisse zur Laufzeit einhängen.# NICHT: ENV DB_PASS=geheim -> dauerhaft im Image! # NICHT: -e DB_PASS=geheim -> sichtbar in "docker inspect"! # Richtig: Datei einhaengen, nur zur Laufzeit vorhanden docker run -d \ --mount type=bind,src=/srv/secrets/db_pass,dst=/run/secrets/db_pass,ro \ myapp:1.0 # Docker Swarm: docker secret create # Kubernetes: Secret als Volume einhaengen # Neuer: --mount type=tmpfs fuer Geheimnisse, die nur im Speicher liegen - Den Docker-Socket schützen. Wer
/var/run/docker.sockeinhängt, kann Container erstellen, die als Root alles dürfen. Das ist praktisch gleichbedeutend mit uneingeschränktem Root-Zugriff auf den Host.# NIEMALS so: # -v /var/run/docker.sock:/var/run/docker.sock # Wenn es sich nicht vermeiden laesst: # - Socket-Proxy mit eingeschraenktem Befehlsumfang # - Rootless-Docker # - Podman mit dediziertem Socket und Rechtepruefung - Logging und Erkennung. Ohne Sichtbarkeit lässt sich ein Vorfall weder erkennen noch aufklären.
docker logs -f mein-container journalctl -u docker -f # Den Ereignispuffer vergroessern, sonst sind Protokolle # nach wenigen Minuten weg: # /etc/docker/daemon.json: # { "log-driver": "json-file", # "log-opts": {"max-size": "50m", "max-file": "5"} } # Laufzeitpruefung auf Systemebene (Falco, Tetragon): # Meldet ungewoehnliche Vorgaenge IM Container - # etwa einen Shell-Start in einem Dienstcontainer, der nie # eine Shell starten sollte. Das ist der wirkungsvollste # einzelne Punkt in der Erkennung.
Images klein und nachvollziehbar halten
# 1. Schlankes Ausgangsimage
FROM debian:bookworm-slim # statt debian:latest
# oder distroless für Produktion - keine Shell, keine Paketverwaltung:
# FROM gcr.io/distroless/static-debian12
# 2. Mehrstufiger Aufbau: Werkzeuge rein, Artefakt raus
FROM golang:1.23 AS bauen
WORKDIR /src
COPY . .
RUN CGO_ENABLED=0 go build -o /out/server
FROM gcr.io/distroless/static-debian12
COPY --from=bauen /out/server /server
USER nonroot:nonroot
ENTRYPOINT ["/server"]
# Ergebnis: kein Compiler, kein Paketmanager, keine Shell im
# fertigen Image. Ein Angreifer findet kaum Werkzeuge.
# 3. Kein "latest", keine unnoetigen Pakete
# 4. .dockerignore nutzen - verhindert, dass .git, .env,
# Schluessel oder Testdaten ins Image gelangen
# .git
# .env
# *.pem
# *.key
# node_modules
# tests/
# 5. Pruefen, was drin ist
docker run --rm -it --entrypoint sh mein-image -c 'ls -la /; which sh; which curl'
docker history --no-trunc mein-image | head -20
dive mein-image # Schichten interaktiv ansehen
Prüfliste für den Betrieb
- Läuft der Container als nicht-privilegierter Benutzer?
- Ist das Dateisystem schreibgeschützt, mit einzelnen Ausnahmen?
- Sind alle Fähigkeiten entfernt und nur die nötigen ergänzt?
- Ist
no-new-privilegesgesetzt (verhindert Rechteerhöhung durch setuid-Programme)? - Sind Speicher, CPU und Prozessanzahl begrenzt?
- Ist das Image per Digest festgeschrieben und auf Schwachstellen geprüft?
- Sind Geheimnisse zur Laufzeit eingehängt und nicht im Image?
- Ist der Docker-Socket nirgends eingehängt?
- Werden Protokolle aufbewahrt und auf ungewöhnliche Vorgänge geprüft?
- Sind die Container-Ports nur dort veröffentlicht, wo es nötig ist – und Datenbanken nie auf allen Schnittstellen?
Verwandte Grundlagen: Container-Technologie, Linux-Fähigkeiten, seccomp, Docker-Netzwerke.