Container Security

Von Boris Sander5. November 2025
Container Security

Container Security

„Docker macht Deployment einfach. Docker Security macht Deployment zum Alptraum."


Sinn & Zweck

Container haben die Softwarewelt revolutioniert: Packe alles in ein Image, schieb es in die Cloud, fertig. Das Problem? Du packst auch alle Vulnerabilities mit ein – und in der Cloud haben sie mehr Angriffsfläche als je zuvor.

Container Security bedeutet: Das gesamte Container-Lifecycle absichern – vom Base Image bis zum laufenden Container, von der Registry bis zum Orchestrator.


Das Container-Sicherheitsmodell

Isolation – aber nicht wirklich

Container teilen sich den Kernel mit dem Host. Das ist keine VM-Isolation, sondern Namespace-Separation. Ein Kernel-Exploit = Game Over für alle Container.

┌──────────────────────────────────────────────────┐
│                    Host Kernel                    │
├────────────┬────────────┬────────────────────────┤
│ Container A│ Container B│      Host Processes    │
│  (App 1)   │  (App 2)   │                        │
├────────────┴────────────┴────────────────────────┤
│              Shared Kernel Vulnerabilities        │
│                  (wenn einer bricht...)           │
└──────────────────────────────────────────────────┘

Die Angriffsfläche

EbeneRisiken
ImageVulnerable Base Images, Malware, Secrets
RegistryImage Tampering, Unauthorized Access
OrchestratorMisconfiguration, RBAC-Fehler
RuntimeContainer Escape, Resource Abuse
NetworkLateral Movement, Unencrypted Traffic

Image Security

Base Images – Das Fundament

Das Problem:

FROM ubuntu:latest  # 300 Pakete, 47 CVEs
RUN apt-get install -y everything-and-the-kitchen-sink

Die Lösung:

FROM gcr.io/distroless/static:nonroot  # 0 Pakete, 0 Shell, 0 CVEs
# oder
FROM alpine:3.18  # Minimal, aber mit Shell

Image Scanning

Tools:

ToolTypIntegration
TrivyOpen SourceCI/CD, Registry
GrypeOpen SourceCI/CD
ClairOpen SourceRegistry (Quay)
Snyk ContainerCommercialAlles
AnchoreOpen Source/EntCI/CD, Registry
Prisma CloudCommercialEnterprise

Beispiel: Trivy

# Image scannen
trivy image myapp:latest

# Nur HIGH und CRITICAL
trivy image --severity HIGH,CRITICAL myapp:latest

# In CI/CD blockieren
trivy image --exit-code 1 --severity CRITICAL myapp:latest

Dockerfile Best Practices

# Multi-stage Build
FROM node:18-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production

FROM gcr.io/distroless/nodejs18-debian11
COPY --from=builder /app/node_modules /app/node_modules
COPY . /app
WORKDIR /app

# Non-root User
USER nonroot:nonroot

# Read-only Filesystem
# (wird zur Runtime erzwungen)

CMD ["server.js"]

Secrets in Images

So nicht:

ENV API_KEY=sk-1234567890abcdef  # Für immer im Image
COPY .env /app/.env              # Auch schlecht

Besser:

# Secrets zur Runtime injizieren
# Via Environment Variables oder Secrets Management

Registry Security

Private Registries

Warum?

  • Kontrolle über Images
  • Vulnerability Scanning
  • Access Control
  • Audit Logging

Optionen:

  • Harbor (Open Source, empfohlen)
  • AWS ECR
  • Google Container Registry
  • Azure Container Registry
  • JFrog Artifactory

Image Signing & Verification

Cosign (Sigstore):

# Image signieren
cosign sign --key cosign.key myregistry.com/myapp:v1.0.0

# Signatur verifizieren
cosign verify --key cosign.pub myregistry.com/myapp:v1.0.0

Kubernetes Policy:

apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: require-signed-images
spec:
  rules:
    - name: verify-signature
      match:
        resources:
          kinds:
            - Pod
      verifyImages:
        - image: "myregistry.com/*"
          key: |-
            -----BEGIN PUBLIC KEY-----
            ...
            -----END PUBLIC KEY-----

Runtime Security

Container Hardening

Pod Security Standards (Kubernetes):

apiVersion: v1
kind: Pod
metadata:
  name: secure-pod
spec:
  securityContext:
    runAsNonRoot: true
    runAsUser: 1000
    runAsGroup: 3000
    fsGroup: 2000
    seccompProfile:
      type: RuntimeDefault
  containers:
    - name: app
      image: myapp:latest
      securityContext:
        allowPrivilegeEscalation: false
        readOnlyRootFilesystem: true
        capabilities:
          drop:
            - ALL
      resources:
        limits:
          memory: "128Mi"
          cpu: "500m"

Runtime Monitoring

Falco – Der Syscall-Wächter:

- rule: Terminal shell in container
  desc: Detect shell execution in container
  condition: >
    spawned_process and container and
    shell_procs and proc.tty != 0
  output: >
    Shell executed in container 
    (user=%user.name container=%container.name)
  priority: WARNING

Was Falco erkennt:

  • Shell in Container gestartet
  • Verdächtige Prozesse
  • Filesystem-Änderungen
  • Netzwerk-Anomalien
  • Privilegierte Operationen

Container Escape Prevention

Gefährliche Konfigurationen:

# NIEMALS in Production:
securityContext:
  privileged: true           # Container hat Root auf Host
  
hostNetwork: true            # Sieht alle Host-Netzwerk-Traffic
hostPID: true                # Sieht alle Host-Prozesse
hostIPC: true                # Zugriff auf Host IPC

volumes:
  - hostPath:
      path: /                 # Host-Root gemountet

Kubernetes Security

RBAC – Role-Based Access Control

# Least Privilege Service Account
apiVersion: v1
kind: ServiceAccount
metadata:
  name: myapp-sa
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: myapp-role
rules:
  - apiGroups: [""]
    resources: ["configmaps"]
    verbs: ["get", "list"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: myapp-binding
subjects:
  - kind: ServiceAccount
    name: myapp-sa
roleRef:
  kind: Role
  name: myapp-role

Network Policies

# Default: Deny all
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: default-deny-all
spec:
  podSelector: {}
  policyTypes:
    - Ingress
    - Egress
---
# Explizit erlauben
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-frontend-to-backend
spec:
  podSelector:
    matchLabels:
      app: backend
  ingress:
    - from:
        - podSelector:
            matchLabels:
              app: frontend
      ports:
        - protocol: TCP
          port: 8080

Admission Controllers

OPA Gatekeeper / Kyverno:

# Kyverno: Keine privilegierten Container
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: disallow-privileged
spec:
  validationFailureAction: enforce
  rules:
    - name: deny-privileged
      match:
        resources:
          kinds:
            - Pod
      validate:
        message: "Privileged containers are not allowed"
        pattern:
          spec:
            containers:
              - securityContext:
                  privileged: "!true"

Secrets Management in Containern

Das Anti-Pattern

# So nicht:
env:
  - name: DB_PASSWORD
    value: "SuperSecret123"  # Im YAML, im etcd, überall

Kubernetes Secrets (Basis)

# Etwas besser:
apiVersion: v1
kind: Secret
metadata:
  name: db-credentials
type: Opaque
data:
  password: U3VwZXJTZWNyZXQxMjM=  # base64 != encryption

External Secrets Operator

# Best Practice: Secrets aus Vault
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
  name: db-credentials
spec:
  secretStoreRef:
    name: vault-backend
  target:
    name: db-credentials
  data:
    - secretKey: password
      remoteRef:
        key: database/creds
        property: password

Risiken & Grenzen

Shared Kernel

Ein Kernel-Exploit betrifft alle Container. Grsecurity, SELinux, AppArmor helfen, aber sind nicht perfekt.

Supply Chain

Dein Base Image hat Dependencies, die Dependencies haben, die... CVE-Party.

Komplexität

Kubernetes + Istio + OPA + Falco + Vault + ... = 
  "Niemand versteht mehr, was passiert"

Day-2 Operations

Images bauen ist einfach. Images aktuell halten ist ein Vollzeitjob.

Fehlkonfiguration

Die meisten Container-Breaches kommen von:

  • Öffentlichen Dashboards
  • Default Credentials
  • Misconfigured RBAC
  • Offenen etcd-Ports

Vorteile

Immutability

Container sind immutable. Ein kompromittierter Container kann einfach gelöscht und neu deployed werden.

Isolation (wenn richtig gemacht)

Mit guten Policies, Namespaces und Network Segmentation bieten Container solide Isolation.

Standardisierung

Einheitliche Security-Policies über alle Container hinweg.

Reproducibility

Das Image ist das Image ist das Image. Was getestet wurde, wird deployed.

Schnelle Updates

Vulnerable Image? Neues Image bauen, deployen, fertig. (Theoretisch.)


Best Practices Checkliste

Build Time

  • Minimale Base Images (Distroless, Alpine)
  • Multi-stage Builds
  • Keine Secrets im Image
  • Image Scanning in CI/CD
  • Fixed Image Tags (nicht :latest)
  • Image Signing

Runtime

  • Non-root User
  • Read-only Root Filesystem
  • Keine Privileged Container
  • Dropped Capabilities
  • Resource Limits
  • Seccomp/AppArmor Profile

Orchestration

  • RBAC mit Least Privilege
  • Network Policies (Default Deny)
  • Pod Security Standards
  • Admission Controllers
  • Secrets aus externem Vault
  • Audit Logging aktiviert

Monitoring

  • Falco oder äquivalent
  • Container Logs zentral
  • Anomaly Detection
  • Runtime Vulnerability Scanning

Fazit

Container Security ist keine einzelne Maßnahme, sondern ein Ökosystem aus Tools, Policies und Prozessen. Die gute Nachricht: Mit modernen Tools lässt sich viel automatisieren. Die schlechte Nachricht: Die Komplexität ist real, und die Angriffsfläche wächst mit jedem neuen Container.

Container isolieren nicht von Dummheit. Sie verpacken sie nur hübscher.


Weiterführende Ressourcen: