Debugging in Produktion: Chaos Engineering mit eBPF

Seite 3: Alles im Griff? – Nicht ohne Validierung!

Inhaltsverzeichnis

Die IT-Umgebung läuft, Observability-Tools sind installiert – damit sollte die Arbeit abgeschlossen sein. Nicht ganz, denn um sicherzugehen, dass im Fehlerfall auch tatsächlich alle Metriken gesammelt oder bösartige Dienste sofort erkannt werden, fehlt noch eine Validierung der Konfiguration.

Im Umfeld von Observability und Service Level Objectives (SLOs) lassen sich etwaige Produktionsausfälle mit den Praktiken des Chaos Engineering simulieren. Das funktioniert auch mit einigen der vorgestellten Tools. Eine Validierung von Coroot etwa ermöglichen CPU-Stresstests und Chaos-Experimente mit dem Netzwerkverkehr, um erhöhte Werte im GUI zu prüfen, wenn der CPU-Verbrauch auf den Nodes steigt oder Container untereinander mehr Traffic generieren. Der eBPF-Exporter für Prometheus lässt mit den gleichen Experimenten testen. Ergänzend empfehlen sich hier noch Experimente mit verlangsamender Disk-IO.

Sicherheitsvorfälle hingegen sind schwieriger zu simulieren. Hierzu bietet sich an, bestimmte Tracing-Policies und Events zu triggern, die dann in Cilium Tetragon oder Tracee auftauchen sollten. Beispiele sind etwa der Versuch von Privilege Escalations oder verdächtige Zugriffe auf Dateien (/etc/password zum Beispiel).

Aktive Malware wie Bitcoin-Miner in Kubernetes-Clustern oder CI/CD-Pipelines sind deutlich schwieriger zu erkennen. Dazu bedarf es zusätzlicher Muster-Heuristiken, die sich mithilfe von trainierten Machine-Learning-Modellen besser erkennen lassen. Wie sind beispielsweise Rootkits im System zu entdecken? Um Cilium Tetragon und Tracee zu testen, kann man beispielsweise ein Rootkit installieren und beobachten, was passiert. Solche Experimente sollten jedoch nicht in Produktionsumgebungen erfolgen. Stattdessen empfiehlt sich eine vom Netzwerk abgeschottete virtuelle Maschine (VM) mit einem möglichst aktuellen Linux wie Ubuntu 23. Diese VM reicht für erste Tests und sollte anschließend auch wieder vollständig gelöscht werden. Das Diamorphine Rootkit lässt sich wie in Listing 7 zu sehen als Kernel-Modul laden. Es versteckt sich selbst und überschreibt bestimmte Syscalls, um Dateien zu verstecken und um Root-Rechte zu erlangen. Es ist daher nicht zu erkennen, dass ein Rootkit im Hintergrund läuft (siehe Abbildung 7).

Listing 7: Diamorphine Rootkit

# Docker https://docs.docker.com/engine/install/ubuntu/

# Kernel header, build tools
apt -y install linux-headers-`uname -r` make clang gcc

# Diamorphine Rootkit laden
git clone https://github.com/m0nad/Diamorphine
cd Diamorphine
make

insmod diamorphine.ko
lsmod | grep dia

# Tracee erkennt überschriebene Syscalls, die auf ein Rootkit hindeuten

docker run \
  --name tracee --rm -it \
  --pid=host --cgroupns=host --privileged \
  -v /etc/os-release:/etc/os-release-host:ro \
  -v /sys/kernel/security:/sys/kernel/security:ro \
  -v /boot/config-`uname -r`:/boot/config-`uname -r`:ro \
  -e LIBBPFGO_OSRELEASE_FILE=/etc/os-release-host \
  -e TRACEE_EBPF_ONLY=1 \
  aquasec/tracee:0.17.0 \
  --events hooked_syscalls

# Rootkit entladen https://github.com/m0nad/Diamorphine#uninstall
# Übung: Wenn das Rootkit wieder sichtbar ist, Tracee noch einmal laufen lassen und die Ergebnisse vergleichen.

kill -63 0
rmmod diamorphine

Tracee hat überschriebene Syscalls erkannt: kill, getdents, getdents64 (für Directory-Listings). Der Angreifer hat seinen Namen allerdings versteckt (Abb. 7).

Chaos-Experimente hängen stark vom Anwendungsfall ab. Im Wesentlichen geht es darum, etwas professionell kaputt zu machen und zu beobachten, wie sich Systeme und Anwendungen im Fehlerfall verhalten. Zum Erstellen der Experimente haben sich verschiedene Tools und Frameworks für Chaos Engineering entwickelt, die sich mittels Weboberfläche und CLI bedienen lassen. Ebenso sollen Experimente unter Umständen in zeitlichen Abständen wiederholt werden, um bestimmte Fehlerszenarien zu simulieren. Dazu kommen Schedules zum Einsatz.

Neben Chaos Monkey von Netflix haben sich in der Cloud-Native-Community die Open-Source-Projekte Litmus und Chaos Mesh etabliert. Beide Tools kommen mit einer Vielzahl an Chaos-Experimenten, um unter anderem die CPU zu stressen, Disk-IO zu simulieren, TCP-/HTTP-Antworten zu stören oder DNS-Antworten mit falschen Antworten auszustatten. Das DNS-Chaos-Experiment in Chaos Mesh verwendet beispielsweise CoreDNS in Kubernetes, um überschriebene DNS-Records zurückzuliefern.

Ein weiterer, bei Entwicklern beliebter Vertreter im Bereich Chaos Engineering ist das Chaos Toolkit, das sich durch ein leichtgewichtiges CLI in Python und seine Extensions auszeichnet. Chaos Toolkit lässt sich beispielsweise als Proxy in CI/CD-Pipelines einbetten, um zu testen, wie Programme auf unerwartete HTTP-Requests und -Antworten reagieren. Memory-Leaks lassen sich so unter Umständen direkt finden.

Was wäre, wenn sich auf Kernelebene ein eBPF-Programm laden ließe, das mittels Maps vom Kernel ausgehend mit dem User-Space kommuniziert? So ließen sich DNS- oder HTTP-Chaos-Experimente überall betreiben, sowohl in Kubernetes als auch in Linux-VMs. Mit dem Projekt XPress DNS existiert dazu bereits ein Proof-of-Concept, der sich in Richtung der Upstream-Projekte Chaos Mesh oder Chaos Toolkit weiterentwickeln ließe. Eine andere Idee ist, ein Rootkit zu simulieren, das Syscalls überschreibt, und dann kontinuierlich die eigene Umgebung zu testen, ob Alarme getriggert werden. Hierfür ließe sich etwa das Diamorphine-Rootkit anpassen, sodass es lediglich noch Syscalls überschreibt und kontinuierlich "nach Hause telefonieren" möchte, aber keine weiteren bösartigen Aktionen ausführt.

Wer eigene eBPF-Programme entwickelt, sollte diese auch mit CI/CD-Pipelines testen. Dies gestaltet sich in der Regel schwierig, da die Programme als Root in einen laufenden Kernel geladen und wieder entladen werden wollen. Zum Entwickeln empfehlen sich Bibliotheken (Go, Rust, C++), die eine geeignete Build-Umgebung mitbringen, die sich in CI/CD integrieren lässt. Der einfachste Weg ist es, eine Linux-VM mit Kernel-Headern, Compilern und weiteren Build-Tools zu provisionieren, inklusive eines selbstgehosteten CI/CD-Runners, der sicherstellt, dass die Jobs isoliert mit Root-Rechten laufen können und sich Kernel-Lade- und Entladetests durchführen lassen. Ein Beispiel für die Provisionierung mit Ansible findet sich im eBPF-Workshop des Autors.

Der eBPF Verifier, der die eBPF-Programme in den Kernel lädt und dabei auf Sicherheit und Qualität prüft, ist derzeit nur in einem laufenden Kernel verfügbar. Es gibt aber Bestrebungen, eine Stand-alone-Variante zu entwickeln, die Continuous Integration erleichtern könnte. Ein Beispiel dafür ist das eBPF-Verifier-Harness-Projekt von Trail of Bits. Ebenso sind Unit-Tests, Laufzeit-Tests und Security-Scanning notwendig. Auch in diesen Bereichen ist in der näheren Zukunft mit Verbesserungen zu rechnen.

Ein weiteres Risiko liegt in der Komplexität von eBPF. Beim Einsatz der Tools muss man den Anbietern blind vertrauen, und riskiert gegebenenfalls eine "Operation am offenen Herzen" – mit Root-Rechten. Darüber hinaus benötigt die eigene IT-Umgebung ein Review erlaubter Operationen, um beispielsweise zu definieren, welche Personengruppen eBPF-Programme laden, privilegierte Container starten oder andere sicherheitsrelevante Aktionen durchführen dürfen. Beim Monitoring geladener eBPF-Programme stellt sich zudem die klassische Frage: "Wer überwacht die Wächter?" – und welches Programm macht was? Denn ein Packet-Loss beispielsweise könnte auf ein geladenes Programm im XDP-Scope zurückgehen, das Pakete verwirft – etwa ein Überbleibsel eines SRE-Produktionstests. Den Überblick über geladene eBPF-Programme behält man am einfachsten mit dem Befehl bpftool prog list.