Sikring af Linux-containere: RunC-sårbarheder, Image-hærdning og Runtime-beskyttelse i 2026

Komplet guide til container-sikkerhed i 2026: Forstå runC-sårbarheder (CVE-2025-31133, CVE-2025-52565, CVE-2025-52881), hærd dine images, opsæt seccomp og AppArmor, implementer Falco-overvågning, og udforsk gVisor og Kata Containers.

Indledning: Container-sikkerhed i en verden af konstante trusler

Container-teknologi har fundamentalt ændret den måde, vi bygger, distribuerer og kører software på. Docker, Kubernetes og alt det drumherum er blevet rygraden i moderne infrastruktur — fra små startups til Fortune 500-virksomheder. Men med den massive udbredelse følger en tilsvarende massiv angrebsflade.

I 2025-2026 står vi over for en virkelighed, hvor 87 procent af alle container-images indeholder mindst én kendt sårbarhed. Lad det lige synke ind et øjeblik. Angribere udnytter aktivt svagheder i container-runtimes til at bryde ud af den isolation, som containere ellers forventes at levere.

De seneste runC-sårbarheder, der blev offentliggjort i 2025, har ærligt talt sendt rystelser gennem DevOps- og sikkerhedsmiljøerne. RunC er den underliggende container-runtime, som Docker, containerd og CRI-O alle afhænger af. Når der dukker kritiske fejl op i runC, påvirker det bogstaveligt talt millioner af kørende containere verden over. CVE-2025-31133, CVE-2025-52565 og CVE-2025-52881 viste alle, at en ondsindet container under visse omstændigheder kan bryde ud af sin isolation og kompromittere hele værtssystemet.

Denne guide dækker hele spektret — fra grundlæggende forståelse af Linux-containerisolering, over de nyeste runC-sårbarheder, til praktisk image-hærdning, runtime-beskyttelse med seccomp og AppArmor, overvågning med Falco, og alternative runtimes som gVisor og Kata Containers. Målet er at give dig en komplet, handlingsorienteret forståelse af container-sikkerhed i 2026.

Forståelse af container-isolation: Namespaces, cgroups og seccomp

For at forstå, hvorfor container-sikkerhed er så kompleks, er vi nødt til at forstå de underliggende Linux-mekanismer. I modsætning til virtuelle maskiner (VMs), som kører deres eget operativsystem med en separat kerne, deler containere værts-kernen. Denne deling er både containerens største styrke (effektivitet, hastighed) og dens største svaghed (potentiel isolation-svigt).

Linux Namespaces

Namespaces er den primære isoleringsmekanisme for containere. De partitionerer kernel-ressourcer, så én gruppe processer ser ét sæt ressourcer, mens en anden gruppe ser noget helt andet. Linux understøtter flere typer:

  • PID namespace — Isolerer proces-ID-rummet. Processer i en container kan ikke se processer i andre containere eller på værten.
  • Network namespace — Giver hver container sin egen netværksstak med egne interfaces, routing-tabeller og firewall-regler.
  • Mount namespace — Isolerer filsystem-mount-punkter, så containeren har sit eget filsystem-hierarki.
  • UTS namespace — Tillader containere at have deres eget hostname og domænenavn.
  • IPC namespace — Isolerer inter-process communication ressourcer (delt hukommelse, semaforer osv.).
  • User namespace — Kortlægger bruger- og gruppe-ID'er inde i containeren til andre ID'er på værten, hvilket muliggør "rootless" containere.
  • Cgroup namespace — Isolerer visningen af cgroup-hierarkiet.

Du kan inspicere en containers namespaces med denne kommando:

# Vis namespaces for en kørende container
docker inspect --format '{{.State.Pid}}' min-container
# Brug derefter resultatet (f.eks. PID 12345) til at se namespaces
ls -la /proc/12345/ns/

# Output eksempel:
# lrwxrwxrwx 1 root root 0 jan 15 10:00 cgroup -> 'cgroup:[4026532587]'
# lrwxrwxrwx 1 root root 0 jan 15 10:00 ipc -> 'ipc:[4026532511]'
# lrwxrwxrwx 1 root root 0 jan 15 10:00 mnt -> 'mnt:[4026532509]'
# lrwxrwxrwx 1 root root 0 jan 15 10:00 net -> 'net:[4026532514]'
# lrwxrwxrwx 1 root root 0 jan 15 10:00 pid -> 'pid:[4026532512]'
# lrwxrwxrwx 1 root root 0 jan 15 10:00 user -> 'user:[4026531837]'
# lrwxrwxrwx 1 root root 0 jan 15 10:00 uts -> 'uts:[4026532510]'

Control Groups (cgroups)

Cgroups kontrollerer og begrænser de ressourcer, en container kan bruge — CPU, hukommelse, disk I/O og netværksbåndbredde. Uden cgroups kunne en ondsindet container monopolisere værtens ressourcer og forårsage denial-of-service for alt andet, der kører på maskinen.

# Kør en container med ressource-begrænsninger
docker run -d \
  --name sikker-app \
  --memory="512m" \
  --memory-swap="512m" \
  --cpus="1.5" \
  --pids-limit=100 \
  --read-only \
  min-app:latest

# Verificer cgroup-begrænsninger
cat /sys/fs/cgroup/docker/$(docker inspect --format '{{.Id}}' sikker-app)/memory.max

Seccomp (Secure Computing Mode)

Seccomp filtrerer de systemkald, en container kan foretage. Linux-kernen har over 300 systemkald, men en typisk container bruger kun en brøkdel af dem. Docker anvender som standard en seccomp-profil, der blokerer ca. 44 potentielt farlige systemkald — herunder mount, reboot, kexec_load og ptrace.

# Se Dockers standard seccomp-profil
docker run --rm -it alpine cat /proc/self/status | grep Seccomp
# Output: Seccomp:  2  (2 = filter mode aktiv)

# Kør en container UDEN seccomp (farligt — kun til test!)
docker run --rm -it --security-opt seccomp=unconfined alpine sh

Hvorfor containere ikke er VMs

Det her er virkelig vigtigt at forstå: containerens isolation er software-baseret, ikke hardware-baseret. En VM kører med sin egen kerne og er isoleret af hypervisoren, der udnytter hardware-virtualiseringsudvidelser (Intel VT-x, AMD-V). En container deler derimod kernen med værten.

Det betyder i praksis, at:

  • En kernel-sårbarhed kan potentielt kompromittere alle containere på værten
  • En fejl i container-runtimen (som runC) kan tillade container escape
  • Forkert konfigurerede containere (f.eks. med --privileged flaget) kan have næsten fuld adgang til værten
  • Den delte kerneoverflade giver en større angrebsflade sammenlignet med VMs

Denne fundamentale arkitektoniske forskel er netop grunden til, at container-sikkerhed kræver en lagdelt tilgang — defense in depth — hvor flere sikkerhedsmekanismer kompenserer for hinanden.

RunC-sårbarhederne i 2025: En dybdegående analyse

RunC er referenceimplementeringen af OCI (Open Container Initiative) runtime-specifikationen og ligger under Docker, containerd, CRI-O og Podman. I 2025 blev tre alvorlige sårbarheder offentliggjort, som alle handlede om, hvordan runC håndterer filsystem-operationer under container-opsætning.

Lad os gennemgå dem én for én.

CVE-2025-31133: Masked Path Abuse — /dev/null-symlink-angrebet

Denne sårbarhed udnyttede den måde, runC implementerede "masked paths" — stier i containeren, der skulle gøres utilgængelige ved at blive overskrevet med /dev/null. OCI-specifikationen kræver, at visse følsomme stier (som /proc/kcore eller /proc/sched_debug) maskeres for at forhindre informationslækage.

Problemet? En angriber inde i containeren kunne erstatte filen på den maskerede sti med et symlink, inden runC nåede at montere /dev/null ovenpå. Da runC fulgte symlinket og monterede /dev/null på symlinkets destination, kunne resultatet være, at vigtige filer på værtssystemet blev overskrevet. Temmelig ubehageligt.

# Konceptuelt angreb (forenklet illustration)
# 1. Angriber i container erstatter en masked path med et symlink:
#    /proc/sched_debug -> /host-path/etc/crontab
#
# 2. RunC forsøger at maskere stien ved at montere /dev/null:
#    mount --bind /dev/null /proc/sched_debug
#
# 3. Da /proc/sched_debug nu er et symlink, følger mount operationen
#    symlinket og monterer /dev/null over målfilen på værten
#
# Resultat: Kritiske værts-filer bliver utilgængelige eller overskrevet

Angrebskrav: Angriberen skulle kunne oprette filer og symlinks inde i containeren og have timing til at placere symlinket i det korte vindue mellem containerens opstart og runC's maskeringsproces.

CVE-2025-52565: /dev/console Mount Exploitation — Race condition med symlinks

Denne sårbarhed involverede en race condition (TOCTOU — Time of Check, Time of Use) i den måde, runC monterede /dev/console inde i containeren. Under container-opstart opretter runC en pseudo-terminal (PTY) og monterer den som /dev/console.

Angrebet udnyttede det tidsmæssige vindue mellem, hvornår runC verificerede destinationsstien, og hvornår den faktisk udførte monteringen. I det korte vindue kunne en koordineret proces inde i containeren erstatte /dev/console med et symlink, der pegede på en vilkårlig fil på værtens filsystem.

# Race condition illustration:
#
# Tidspunkt T1: runC verificerer /dev/console → det er en normal fil ✓
# Tidspunkt T2: Angriber erstatter /dev/console med symlink → /host/etc/shadow
# Tidspunkt T3: runC monterer PTY over /dev/console (følger symlink!)
#
# Resultat: PTY-enheden monteres over /etc/shadow på værten
#
# For at udnytte dette kræves præcis timing, typisk via:
# - Høj-hastighed symlink-udskiftningsløkke inde i containeren
# - Brug af inotify til at detektere runC's filsystem-operationer

Konsekvens: Denne sårbarhed kunne føre til vilkårlig filskrivning på værtssystemet — noget der potentielt kunne bruges til privilege escalation eller fuld kompromittering af værten.

CVE-2025-52881: Procfs Write Redirection — Omdirigering af skrivninger til følsomme filer

Den tredje sårbarhed handlede om, hvordan runC håndterede skrivninger til procfs (process filesystem). Under visse omstændigheder kunne en angriber omdirigere skrivninger, der var beregnet til procfs-filer inde i containeren, til vilkårlige filer på værtens filsystem.

Procfs (/proc) indeholder en masse filer, der bruges til at konfigurere processer og kerneparametre. RunC skriver til visse procfs-filer under container-opsætning (f.eks. for at konfigurere cgroups eller oom_score_adj). Ved at manipulere filsystem-opstillingen kunne en angriber narre runC til at følge et symlink og skrive til en fil på værten i stedet.

# Eksempel på procfs-filer, som runC interagerer med:
# /proc/[pid]/oom_score_adj  — Justerer OOM killer prioritet
# /proc/[pid]/attr/current   — SELinux sikkerhedskontekst
#
# Angrebsvektor (konceptuelt):
# 1. Angriber opretter symlink: /proc/self/oom_score_adj -> /etc/passwd
# 2. RunC skriver den konfigurerede OOM-score til filen
# 3. Skrivningen rammer /etc/passwd i stedet for procfs
#
# Selvom indholdet der skrives er begrænset, kan selv
# delvis korrumpering af systemfiler have alvorlige konsekvenser

Patchede versioner og hvad du bør gøre

Alle tre sårbarheder blev rettet i følgende runC-versioner:

RunC-version Status Anbefaling
1.2.8 Patched (stabil 1.2.x-gren) Opdater hvis du kører 1.2.x
1.3.3 Patched (stabil 1.3.x-gren) Anbefalet for de fleste produktionsmiljøer
1.4.0-rc.3 Patched (release candidate) Til early adopters og test-miljøer

For Docker- og Kubernetes-brugere er det vigtigt at forstå afhængighedskæden:

# Tjek din runC-version
runc --version
# Forventet output: runc version 1.3.3 (eller nyere)

# For Docker-brugere:
docker info | grep -i runc
# Se efter: runc version 1.3.3+

# For Kubernetes-brugere (med containerd):
crictl info | jq '.config.containerd.runtimes.runc'

# Opdater runC på Debian/Ubuntu:
sudo apt update && sudo apt install runc

# Opdater Docker Engine (som inkluderer ny runC):
sudo apt update && sudo apt install docker-ce docker-ce-cli containerd.io

Vigtig pointe: Disse sårbarheder kræver alle, at angriberen allerede har kodeeksekvering inde i en container. Det er ikke remote exploits. Men i en multi-tenant Kubernetes-klynge, hvor du kører upålidelig kode, eller i CI/CD-pipelines, hvor build-jobs kører i containere — ja, så er denne angrebsvektor yderst relevant.

Hærdning af container-images

Den mest grundlæggende container-sikkerhedsforanstaltning er at minimere angrebsfladen i dine container-images. Simpelt sagt: jo færre pakker, filer og kendte sårbarheder, jo færre muligheder har en angriber.

Minimale base-images

Valget af base-image er afgørende — og det er ærligt talt et af de steder, hvor mange teams kan gøre den største forskel med mindst mulig indsats. Her er en sammenligning:

Base Image Størrelse Pakker Typiske CVE'er
ubuntu:24.04 ~78 MB ~90+ Mange
debian:bookworm-slim ~52 MB ~50+ Moderate
alpine:3.21 ~7 MB ~15
gcr.io/distroless/static ~2 MB Ingen pakkemanager Minimale
scratch 0 MB Ingen Ingen

Multi-stage builds

Multi-stage builds er en af de mest effektive teknikker til at minimere image-størrelse og angrebsflade. Idéen er enkel: brug ét image til at bygge din applikation og et andet, minimalt image til at køre den.

# Eksempel: Multi-stage Dockerfile for en Go-applikation
# Stage 1: Build
FROM golang:1.23-alpine AS builder
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -ldflags="-s -w" -o /app/server .

# Stage 2: Runtime (minimalt image)
FROM gcr.io/distroless/static-debian12:nonroot
COPY --from=builder /app/server /server
USER nonroot:nonroot
EXPOSE 8080
ENTRYPOINT ["/server"]
# Eksempel: Multi-stage Dockerfile for en Python-applikation
# Stage 1: Build med alle udviklerværktøjer
FROM python:3.13-slim AS builder
WORKDIR /app
RUN python -m venv /opt/venv
ENV PATH="/opt/venv/bin:$PATH"
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

# Stage 2: Runtime
FROM python:3.13-slim
RUN groupadd -r appuser && useradd -r -g appuser appuser
COPY --from=builder /opt/venv /opt/venv
COPY . /app
WORKDIR /app
ENV PATH="/opt/venv/bin:$PATH"
USER appuser
CMD ["python", "app.py"]

Scanning med Trivy og Grype

Automatiseret sårbarhedsscanning bør være en integreret del af din CI/CD-pipeline. Ingen diskussion. To fremragende open source-værktøjer er Trivy (fra Aqua Security) og Grype (fra Anchore).

# Installation af Trivy
curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/install.sh | sh -s -- -b /usr/local/bin

# Scan et container-image for sårbarheder
trivy image --severity HIGH,CRITICAL min-app:latest

# Scan med output i tabelformat og ignorer unfixed
trivy image --severity HIGH,CRITICAL --ignore-unfixed min-app:latest

# Scan en Dockerfile for fejlkonfigurationer
trivy config --severity HIGH,CRITICAL Dockerfile

# Scan et lokalt filsystem (f.eks. i CI/CD)
trivy fs --severity HIGH,CRITICAL --scanners vuln,misconfig .

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

# Scan med Grype
grype min-app:latest --only-fixed --fail-on high

Det smarte er at integrere scanning i din CI/CD-pipeline, så buildet fejler automatisk, hvis der dukker kritiske sårbarheder op:

# Eksempel: GitHub Actions workflow
name: Container Security Scan
on:
  push:
    branches: [main]
  pull_request:

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

      - name: Byg Docker image
        run: docker build -t min-app:${{ github.sha }} .

      - name: Kør Trivy sårbarhedsscanning
        uses: aquasecurity/trivy-action@master
        with:
          image-ref: 'min-app:${{ github.sha }}'
          format: 'sarif'
          output: 'trivy-results.sarif'
          severity: 'CRITICAL,HIGH'
          exit-code: '1'

      - name: Upload scanningsresultater
        uses: github/codeql-action/upload-sarif@v3
        if: always()
        with:
          sarif_file: 'trivy-results.sarif'

Supply chain-sikkerhed og signerede images

I kølvandet på SolarWinds og lignende supply chain-angreb er det blevet kritisk vigtigt at verificere, at dine container-images faktisk er autentiske og umodificerede. Cosign (fra Sigstore-projektet) er det førende værktøj til opgaven:

# Installer Cosign
go install github.com/sigstore/cosign/v2/cmd/cosign@latest

# Generer et nøglepar
cosign generate-key-pair

# Signer et image
cosign sign --key cosign.key min-registry.dk/min-app:v1.2.3

# Verificer et signeret image
cosign verify --key cosign.pub min-registry.dk/min-app:v1.2.3

# Keyless signing med OIDC (anbefalet for CI/CD)
cosign sign min-registry.dk/min-app:v1.2.3
# Dette bruger din identitetsudbyder (GitHub, Google, osv.) automatisk

Runtime-sikkerhed med seccomp og AppArmor

Selv med hærdede images har du brug for runtime-beskyttelse. Seccomp og AppArmor er to komplementære mekanismer, der giver dig præcis kontrol over, hvad en container kan gøre, mens den kører. Tænk på det som endnu et lag i din forsvarsmodel.

Tilpassede seccomp-profiler

Dockers standard seccomp-profil er en fin start, men en tilpasset profil, der er skræddersyet til netop din applikation, er langt mere sikker. Princippet er simpelt nok: tillad kun de systemkald, din applikation faktisk har brug for, og bloker alt andet.

// Eksempel: Tilpasset seccomp-profil (custom-seccomp.json)
// Denne profil tillader kun de nødvendige syscalls for en webserver
{
  "defaultAction": "SCMP_ACT_ERRNO",
  "defaultErrnoRet": 1,
  "archMap": [
    {
      "architecture": "SCMP_ARCH_X86_64",
      "subArchitectures": [
        "SCMP_ARCH_X86",
        "SCMP_ARCH_X32"
      ]
    }
  ],
  "syscalls": [
    {
      "names": [
        "accept4", "bind", "clone", "close", "connect",
        "epoll_create1", "epoll_ctl", "epoll_wait",
        "exit", "exit_group", "fstat", "futex",
        "getpid", "getsockopt", "listen", "mmap",
        "mprotect", "munmap", "openat", "read",
        "recvfrom", "rt_sigaction", "rt_sigprocmask",
        "sendto", "setsockopt", "socket", "write",
        "writev", "brk", "fcntl", "getrandom",
        "nanosleep", "clock_gettime", "sigaltstack",
        "set_tid_address", "set_robust_list",
        "prlimit64", "sched_getaffinity",
        "arch_prctl", "access", "execve",
        "readlinkat", "newfstatat", "getdents64",
        "lseek", "pipe2", "dup3"
      ],
      "action": "SCMP_ACT_ALLOW"
    }
  ]
}
# Anvend den tilpassede seccomp-profil
docker run -d \
  --name webserver \
  --security-opt seccomp=custom-seccomp.json \
  min-webserver:latest

# Tip: Brug strace til at identificere nødvendige syscalls
# Kør først uden seccomp og observer:
strace -c -f -p $(docker inspect --format '{{.State.Pid}}' webserver)

# Eller brug Dockers --security-opt seccomp=unconfined med audit log:
# (kræver Linux audit framework)
docker run --security-opt seccomp=unconfined \
  --security-opt apparmor=unconfined \
  min-app:latest

AppArmor container-profiler

AppArmor er et Mandatory Access Control (MAC) system, der begrænser programmers adgang til filer, netværk og systemressourcer. Docker bruger som standard en AppArmor-profil kaldet docker-default, men du kan (og bør ofte) oprette mere restriktive profiler:

# Fil: /etc/apparmor.d/docker-webserver
#include 

profile docker-webserver flags=(attach_disconnected,mediate_deleted) {
  #include 
  #include 

  # Netværksadgang
  network inet tcp,
  network inet udp,
  network inet6 tcp,
  network inet6 udp,

  # Tillad læsning af applikationsfiler
  /app/** r,
  /app/server ix,

  # Tillad skrivning til log-mappe
  /var/log/app/ rw,
  /var/log/app/** rw,

  # Tillad adgang til tmp
  /tmp/ rw,
  /tmp/** rw,

  # Bloker adgang til følsomme stier
  deny /etc/shadow r,
  deny /etc/passwd w,
  deny /proc/*/mem rw,
  deny /sys/firmware/** rwx,
  deny /proc/sysrq-trigger rw,
  deny /proc/kcore r,

  # Bloker mount operationer
  deny mount,
  deny umount,

  # Bloker raw socket adgang
  deny network raw,
  deny network packet,

  # Tillad nødvendige proc-filer
  /proc/self/fd/ r,
  /proc/self/fd/** rw,
  /proc/sys/net/** r,
}
# Indlæs AppArmor-profilen
sudo apparmor_parser -r /etc/apparmor.d/docker-webserver

# Kør containeren med den tilpassede profil
docker run -d \
  --name webserver \
  --security-opt apparmor=docker-webserver \
  min-webserver:latest

# Verificer at profilen er aktiv
docker inspect --format '{{.HostConfig.SecurityOpt}}' webserver
# Output: [apparmor=docker-webserver]

# Se AppArmor-status
sudo aa-status | grep docker

Kombineret sikkerhedskonfiguration

For maksimal runtime-sikkerhed bør du kombinere flere mekanismer. Her er et eksempel, der samler det hele — det kan virke som mange flag, men hvert enkelt har sin berettigelse:

# Den mest sikre måde at køre en container på
docker run -d \
  --name sikker-webserver \
  --read-only \
  --tmpfs /tmp:rw,noexec,nosuid,size=64m \
  --security-opt seccomp=custom-seccomp.json \
  --security-opt apparmor=docker-webserver \
  --security-opt no-new-privileges:true \
  --cap-drop ALL \
  --cap-add NET_BIND_SERVICE \
  --memory="256m" \
  --memory-swap="256m" \
  --cpus="0.5" \
  --pids-limit=64 \
  --user 1000:1000 \
  --network min-isolerede-netværk \
  -p 8080:8080 \
  min-webserver:latest

Lad os gennemgå, hvad hver parameter gør:

  • --read-only — Gør containerens filsystem skrivebeskyttet
  • --tmpfs /tmp — Opretter en midlertidig skrivbar tmpfs (uden exec-rettigheder)
  • --security-opt no-new-privileges:true — Forhindrer privilege escalation via setuid/setgid
  • --cap-drop ALL — Fjerner alle Linux capabilities
  • --cap-add NET_BIND_SERVICE — Tilføjer kun den specifikke capability, der er nødvendig
  • --pids-limit=64 — Begrænser antallet af processer (forhindrer fork bombs)
  • --user 1000:1000 — Kører som ikke-root bruger

Runtime-overvågning med Falco

Mens seccomp og AppArmor forhindrer uønskede handlinger, giver Falco (fra CNCF) dig mulighed for at detektere og alarmere om mistænkelig container-aktivitet i realtid. Det er lidt som at have et alarmsystem i dit hus — selvom du har låst alle døre og vinduer, vil du stadig gerne vide, hvis nogen prøver at bryde ind.

Falco bruger enten en kernel-modul eller en eBPF-driver til at observere systemkald, og det er overraskende nemt at komme i gang med.

Installation af Falco med eBPF

# Installation via Helm (anbefalet til Kubernetes)
helm repo add falcosecurity https://falcosecurity.github.io/charts
helm repo update

helm install falco falcosecurity/falco \
  --namespace falco \
  --create-namespace \
  --set driver.kind=modern_ebpf \
  --set falcosidekick.enabled=true \
  --set falcosidekick.config.slack.webhookurl="https://hooks.slack.com/services/DIN/WEBHOOK/URL"

# Installation direkte på en Linux-vært (Debian/Ubuntu)
curl -fsSL https://falco.org/repo/falcosecurity-packages.asc | \
  sudo gpg --dearmor -o /usr/share/keyrings/falco-archive-keyring.gpg

echo "deb [signed-by=/usr/share/keyrings/falco-archive-keyring.gpg] https://download.falco.org/packages/deb stable main" | \
  sudo tee /etc/apt/sources.list.d/falcosecurity.list

sudo apt update
sudo apt install -y falco

# Aktiver modern eBPF-driver (anbefalet, kræver kernel >= 5.8)
sudo falcoctl driver install --type modern_ebpf

# Start Falco
sudo systemctl enable --now falco-modern-bpf.service

Tilpassede Falco-regler til container-sikkerhed

Falcos styrke ligger i dets regelbaserede system. Du kan definere præcis, hvad der udgør "mistænkelig aktivitet" i dit specifikke miljø. Her er et sæt regler, som vi har haft god erfaring med i praksis:

# Fil: /etc/falco/rules.d/custom-container-rules.yaml

# Regel 1: Detekter shell-eksekvering i containere
- rule: Shell spawned in container
  desc: >
    En shell-proces (bash, sh, zsh osv.) er blevet startet i en container.
    Dette kan indikere et angreb eller uautoriseret debugging.
  condition: >
    spawned_process
    and container
    and proc.name in (bash, sh, zsh, dash, ash, csh, ksh, fish)
    and not container.image.repository in (
      "tilladt-debug-image",
      "min-ci-runner"
    )
  output: >
    Shell startet i container
    (bruger=%user.name kommando=%proc.cmdline container=%container.name
    image=%container.image.repository:%container.image.tag
    pod=%k8s.pod.name namespace=%k8s.ns.name)
  priority: WARNING
  tags: [container, shell, mitre_execution]

# Regel 2: Detekter forsøg på at læse følsomme filer
- rule: Sensitive file read in container
  desc: >
    En proces i en container forsøger at læse følsomme filer
    som /etc/shadow, private nøgler eller cloud credentials.
  condition: >
    open_read
    and container
    and (
      fd.name startswith /etc/shadow or
      fd.name startswith /etc/gshadow or
      fd.name contains "/.ssh/id_" or
      fd.name contains "/.aws/credentials" or
      fd.name contains "/.kube/config" or
      fd.name endswith ".pem" or
      fd.name endswith ".key"
    )
  output: >
    Følsom fil læst i container
    (fil=%fd.name bruger=%user.name proces=%proc.name
    container=%container.name image=%container.image.repository)
  priority: CRITICAL
  tags: [container, credential_access, mitre_credential_access]

# Regel 3: Detekter container escape-forsøg
- rule: Container escape attempt via mount
  desc: >
    En proces i en container forsøger at montere et filsystem,
    hvilket kan indikere et container escape-forsøg.
  condition: >
    evt.type in (mount, umount2)
    and container
    and not proc.name in (mount, umount)
  output: >
    Mount-forsøg detekteret i container
    (kommando=%proc.cmdline container=%container.name
    image=%container.image.repository bruger=%user.name)
  priority: CRITICAL
  tags: [container, escape, mitre_privilege_escalation]

# Regel 4: Detekter kryptomining i containere
- rule: Crypto mining activity in container
  desc: >
    Detekterer forbindelser til kendte mining-pools
    eller processer med mining-relaterede navne.
  condition: >
    container
    and (
      (spawned_process and proc.name in (xmrig, minerd, minergate, cpuminer))
      or
      (evt.type = connect and fd.sip.name contains "mining" or
       fd.sport in (3333, 4444, 5555, 8333, 9999, 14444))
    )
  output: >
    Mulig kryptomining detekteret i container
    (proces=%proc.name container=%container.name
    image=%container.image.repository forbindelse=%fd.name)
  priority: CRITICAL
  tags: [container, cryptomining, mitre_resource_hijacking]

# Regel 5: Detekter ændringer i /etc inde i containere
- rule: Modification of etc directory in container
  desc: >
    Ændringer i /etc kan indikere persistence-forsøg
    eller konfigurationsmanipulation.
  condition: >
    open_write
    and container
    and fd.name startswith /etc/
    and not proc.name in (sed, tee)
    and not container.image.repository in ("tilladt-config-image")
  output: >
    Fil i /etc ændret i container
    (fil=%fd.name proces=%proc.name container=%container.name
    image=%container.image.repository)
  priority: WARNING
  tags: [container, persistence, mitre_persistence]
# Genindlæs Falco-regler uden genstart
sudo kill -SIGHUP $(pidof falco)

# Test reglerne ved at starte en shell i en container
docker exec -it min-container bash
# → Falco genererer en alarm

# Se Falco-logfiler
sudo journalctl -u falco-modern-bpf.service -f

# Kør Falco i forgrunden med verbose output (debugging)
sudo falco -r /etc/falco/falco_rules.yaml \
  -r /etc/falco/rules.d/custom-container-rules.yaml \
  --verbose

Integration med Falcosidekick

Falcosidekick er en companion-tjeneste, der videresender Falco-alarmer til mere end 50 forskellige destinationer — Slack, PagerDuty, Elasticsearch, Prometheus, AWS SNS og mange flere. Det gør det muligt at integrere container-sikkerhedsovervågning direkte i dine eksisterende incident response-workflows, hvilket virkelig er der, magien sker:

# Falcosidekick konfiguration (values.yaml for Helm)
falcosidekick:
  enabled: true
  config:
    slack:
      webhookurl: "https://hooks.slack.com/services/DIN/WEBHOOK"
      minimumpriority: "warning"
      messageformat: "Falco Alert: {{ .Output }}"
    prometheus:
      enabled: true
    elasticsearch:
      hostport: "https://elasticsearch.intern.dk:9200"
      index: "falco-alerts"
      minimumpriority: "notice"

Alternative container-runtimes: gVisor, Kata Containers og rootless

Når standard container-isolation simpelthen ikke er nok — f.eks. ved kørsel af upålidelig kode, i multi-tenant-miljøer eller under strenge sikkerhedskrav — kan alternative container-runtimes give et ekstra lag af beskyttelse.

gVisor: En bruger-rum kerne

gVisor (udviklet af Google) implementerer en bruger-rum kerne kaldet Sentry, der opfanger og håndterer systemkald fra containeren. I stedet for at containerens processer taler direkte med værtens kerne, går alle systemkald gennem Sentry, som implementerer en begrænset delmængde af Linux-kernens systemkald i et sandboxed miljø.

Det er en ret elegant løsning, skal det siges.

# Installation af gVisor (runsc)
curl -fsSL https://gvisor.dev/archive.key | sudo gpg --dearmor -o /usr/share/keyrings/gvisor-archive-keyring.gpg
echo "deb [arch=amd64 signed-by=/usr/share/keyrings/gvisor-archive-keyring.gpg] https://storage.googleapis.com/gvisor/releases release main" | \
  sudo tee /etc/apt/sources.list.d/gvisor.list
sudo apt update && sudo apt install -y runsc

# Konfigurer Docker til at bruge gVisor
# Tilføj til /etc/docker/daemon.json:
{
  "runtimes": {
    "runsc": {
      "path": "/usr/bin/runsc"
    }
  }
}

# Genstart Docker
sudo systemctl restart docker

# Kør en container med gVisor
docker run --runtime=runsc -d --name sikker-sandbox min-app:latest

# Verificer at gVisor er aktiv
docker exec sikker-sandbox dmesg | head -5
# Output: [    0.000000] Starting gVisor...

Fordele ved gVisor:

  • Dramatisk reduceret kernel-angrebsflade — kun ~200 af ~300+ systemkald er implementeret
  • Skrevet i Go med memory safety (ingen buffer overflows)
  • Nem integration med Docker og Kubernetes
  • Ingen speciel hardware kræves

Ulemper:

  • Ydelses-overhead, især for I/O-intensive workloads (10-30% langsommere)
  • Ikke alle applikationer er kompatible (specialiserede syscalls mangler)
  • Debugging kan være mere udfordrende

Kata Containers: Lette virtuelle maskiner

Kata Containers kombinerer fordelene ved containere (hastighed, OCI-kompatibilitet) med sikkerheden fra virtuelle maskiner. Hver container — eller pod — kører i sin egen letvægts-VM med en dedikeret kerne, isoleret af hardware-virtualisering. Det er på en måde det bedste fra begge verdener.

# Installation af Kata Containers
sudo apt install -y kata-containers

# Konfigurer Docker til at bruge Kata
# Tilføj til /etc/docker/daemon.json:
{
  "runtimes": {
    "kata": {
      "path": "/usr/bin/kata-runtime"
    }
  }
}

sudo systemctl restart docker

# Kør en container med Kata
docker run --runtime=kata -d --name vm-isoleret min-app:latest

# Verificer at Kata kører (du kan se den separate kerne)
docker exec vm-isoleret uname -r
# Output viser Kata's gæste-kerne, ikke værtens kerne

# For Kubernetes: Brug RuntimeClass
# Opret RuntimeClass:
cat <<'YAML' | kubectl apply -f -
apiVersion: node.k8s.io/v1
kind: RuntimeClass
metadata:
  name: kata
handler: kata
YAML

# Brug den i en Pod:
cat <<'YAML' | kubectl apply -f -
apiVersion: v1
kind: Pod
metadata:
  name: sikker-pod
spec:
  runtimeClassName: kata
  containers:
  - name: app
    image: min-app:latest
YAML

Fordele ved Kata Containers:

  • Hardware-baseret isolation — ægte VM-sikkerhed
  • Separat kerne pr. container — kernel-sårbarheder påvirker kun den ene container
  • OCI-kompatibel — fungerer med Docker og Kubernetes
  • Ideel til multi-tenant-miljøer

Ulemper:

  • Højere ressourceforbrug pr. container (separat kerne og VM-overhead)
  • Kræver hardware-virtualiseringsunderstøttelse
  • Langsamere opstartstid end standard containere
  • Mere kompleks netværkskonfiguration

Rootless Docker og Podman

Rootless containere kører hele container-runtimen — inklusive dæmonen — som en uprivilegeret bruger. Det eliminerer en hel kategori af angreb, da selv en succesfuld container escape kun giver adgang til den uprivilegerede brugers rettigheder. Det er nok den nemmeste sikkerhedsopgradering, du kan lave.

# Opsæt rootless Docker
dockerd-rootless-setuptool.sh install

# Sæt miljøvariabler (tilføj til ~/.bashrc)
export PATH=/usr/bin:$PATH
export DOCKER_HOST=unix://$XDG_RUNTIME_DIR/docker.sock

# Kør containere som rootless
docker run -d --name rootless-app min-app:latest

# Alternativ: Brug Podman (rootless som standard)
# Installation af Podman
sudo apt install -y podman

# Podman kører rootless som standard - ingen dæmon nødvendig
podman run -d --name sikker-app min-app:latest

# Podman understøtter også Docker Compose-formatet
podman compose up -d

# Verificer at containeren kører rootless
podman top sikker-app user huser
# Output viser bruger-mapping mellem container og vært

Hvornår skal du bruge hvad?

Det er et spørgsmål, vi hører ret ofte. Her er en oversigt, der forhåbentlig gør valget lidt nemmere:

Scenarie Anbefalet Runtime Begrundelse
Standard webapplikationer Rootless Docker/Podman + seccomp God balance mellem sikkerhed og ydeevne
CI/CD build-jobs gVisor Kører vilkårlig kode med reduceret angrebsflade
Multi-tenant SaaS Kata Containers Hardware-isolation mellem kunder
Kørsel af upålidelig kode gVisor eller Kata Maksimal isolation fra værtssystemet
Høj-ydeevne workloads Standard runC + hærdning Laveste overhead, hærdning via seccomp/AppArmor
Udvikler-workstations Podman rootless Ingen root-krav, ingen dæmon, Docker-kompatibel

Best practices-tjekliste for container-sikkerhed

Okay, vi har dækket en masse grund. Her er en samlet tjekliste, der samler alle anbefalingerne fra denne guide. Brug den som reference, når du opbygger og drifter container-baserede systemer.

Image-sikkerhed

  • Brug minimale base-images — Alpine, Distroless eller scratch. Undgå store general-purpose images som ubuntu, medmindre du virkelig har brug for dem.
  • Anvend multi-stage builds — Adskil bygge- og runtime-miljøer for at minimere angrebsfladen.
  • Pin image-versioner — Brug altid specifikke tags eller SHA256-digests. Aldrig :latest i produktion.
  • Scan images automatisk — Integrer Trivy eller Grype i CI/CD-pipelinen, og lad buildet fejle ved kritiske CVE'er.
  • Signer container-images — Brug Cosign til at signere og verificere images i hele supply chain.
  • Undgå hemmeligheder i images — Brug Docker secrets, Kubernetes secrets eller en ekstern vault i stedet.
  • Anvend .dockerignore — Udeluk unødvendige filer fra build-konteksten.

Runtime-konfiguration

  • Kør aldrig som root — Brug USER-direktivet i Dockerfile og --user flaget ved kørsel.
  • Drop alle capabilities — Brug --cap-drop ALL og tilføj kun de specifikke, nødvendige capabilities.
  • Aktiver no-new-privileges — Forhindrer privilege escalation via setuid/setgid binærer.
  • Brug read-only filsystem — Monter containerens root-filsystem som read-only med --read-only.
  • Begræns ressourcer — Sæt grænser for hukommelse, CPU og PID-antal med cgroup-begrænsninger.
  • Anvend tilpassede seccomp-profiler — Tillad kun de systemkald, din applikation faktisk bruger.
  • Konfigurer AppArmor-profiler — Begræns filadgang og netværksoperationer med MAC-politikker.
  • Brug aldrig --privileged — Det deaktiverer de fleste sikkerhedsmekanismer og giver næsten fuld værtsadgang. Bare lad være.

Netværkssikkerhed

  • Isoler containernetværk — Opret dedikerede Docker-netværk for hver applikationsgruppe.
  • Begræns udgående trafik — Brug netværkspolitikker (Kubernetes NetworkPolicy) til at begrænse egress.
  • Kryptér kommunikation — Brug mTLS (mutual TLS) for kommunikation mellem containere.
  • Publicer ikke unødvendige porte — Bind kun de porte, der faktisk er nødvendige.

Orkestrering og drift

  • Hold runC opdateret — Sørg for at køre en patched version (minimum 1.2.8, 1.3.3 eller 1.4.0-rc.3).
  • Opdater regelmæssigt — Følg sikkerhedsopdateringer for Docker, containerd og Kubernetes.
  • Aktiver audit-logging — Brug Falco eller lignende værktøjer til at overvåge container-aktivitet.
  • Implementer admission controllers — Brug OPA Gatekeeper eller Kyverno i Kubernetes til at håndhæve sikkerhedspolitikker.
  • Brug Pod Security Standards — Konfigurer Pod Security Admission med "restricted" profilen.
  • Overvej alternative runtimes — Brug gVisor eller Kata Containers til højrisiko-workloads.
  • Implementer image-politikker — Tillad kun images fra godkendte registries og med gyldige signaturer.

Kubernetes-specifik sikkerhed

# Eksempel: Pod Security Standards (restricted)
apiVersion: v1
kind: Namespace
metadata:
  name: sikker-namespace
  labels:
    pod-security.kubernetes.io/enforce: restricted
    pod-security.kubernetes.io/enforce-version: latest
    pod-security.kubernetes.io/audit: restricted
    pod-security.kubernetes.io/warn: restricted
---
# Eksempel: Kyverno-politik der forbyder privilegerede containere
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: forbyd-privilegerede-containere
spec:
  validationFailureAction: Enforce
  rules:
  - name: tjek-privilegeret
    match:
      any:
      - resources:
          kinds:
          - Pod
    validate:
      message: "Privilegerede containere er ikke tilladt."
      pattern:
        spec:
          containers:
          - securityContext:
              privileged: "false"
---
# Eksempel: NetworkPolicy der begrænser trafik
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: begræns-intern-trafik
  namespace: sikker-namespace
spec:
  podSelector:
    matchLabels:
      app: webserver
  policyTypes:
  - Ingress
  - Egress
  ingress:
  - from:
    - podSelector:
        matchLabels:
          app: frontend
    ports:
    - protocol: TCP
      port: 8080
  egress:
  - to:
    - podSelector:
        matchLabels:
          app: database
    ports:
    - protocol: TCP
      port: 5432
  - to:  # Tillad DNS-opslag
    - namespaceSelector: {}
    ports:
    - protocol: UDP
      port: 53

Konklusion: Container-sikkerhed er en rejse, ikke en destination

Container-sikkerhed er ikke noget, du løser én gang og så kan glemme alt om. Det er en kontinuerlig proces, der kræver opmærksomhed på flere niveauer — fra de images du bygger, over runtime-konfigurationen, til den overvågning du har i produktion.

De runC-sårbarheder, vi har gennemgået (CVE-2025-31133, CVE-2025-52565 og CVE-2025-52881), illustrerer en vigtig pointe: selv den mest fundamentale komponent i container-stakken kan have kritiske fejl. Patch management er ikke valgfrit. Sørg for, at din infrastruktur kører patchede versioner af runC, og hav processer på plads til hurtigt at reagere på fremtidige sikkerhedsopdateringer.

Men patching alene er ikke nok. Du har brug for en defense-in-depth-strategi:

  1. Minimer angrebsfladen — Brug minimale base-images, multi-stage builds og automatiseret sårbarhedsscanning.
  2. Begræns privilegier — Kør aldrig som root, drop alle capabilities, og anvend seccomp og AppArmor.
  3. Overvåg runtime-adfærd — Implementer Falco med tilpassede regler for at detektere mistænkelig aktivitet i realtid.
  4. Overvej stærkere isolation — For følsomme workloads bør du evaluere gVisor eller Kata Containers.
  5. Håndhæv politikker — Brug Kubernetes admission controllers, Pod Security Standards og netværkspolitikker.

Container-teknologien fortsætter med at udvikle sig, og det samme gør truslerne. WebAssembly (WASM) begynder at dukke op som et potentielt alternativ til containere i visse scenarier, og nye sandboxing-teknologier baseret på eBPF er under aktiv udvikling. Men uanset hvilken teknologi du ender med at bruge, forbliver de grundlæggende principper de samme: minimer angrebsfladen, begræns privilegier, overvåg adfærd, og patch hurtigt.

Ved at anvende de teknikker og værktøjer, vi har gennemgået i denne guide, er du godt rustet til at beskytte dine containeriserede applikationer — både mod de trusler, vi ser i 2026, og dem der uundgåeligt vil komme i fremtiden.

Om Forfatteren Editorial Team

Our team of expert writers and editors.