Blogartikel

21.01.2020

DevOps

Kubernetes AppOps Security Teil 2: Network Policies einsetzen (2/2) – Fortgeschrittene Themen und Tipps

In einem Kubernetes-Cluster können standardmäßig alle (Nodes, Pods, Kubelet, etc.) miteinander kommunizieren. Gelingt es einem Angreifer eine Sicherheitslücke in einer der Anwendungen auszunutzen, kann er seinen Angriff dadurch leicht auf alle dahinter liegende Systeme im gleichen Cluster ausbauen. Durch das Kubernetes-Bordmittel Network Policies kann dies eingeschränkt werden.

Beim Deployment von Anwendungen auf managed Kubernetes-Clustern ist der Betrieb für die Sicherheit zuständig, richtig? Nicht ganz! Zwar abstrahiert Kubernetes von der Hardware, sein API bietet Entwicklern dennoch viele Möglichkeiten, die Sicherheit der darauf betriebenen Anwendungen gegenüber der Standardeinstellung zu verbessern. Dieser Artikel thematisiert die fortgeschrittenen Themen zu Network Policies, wie CNI, Test, Debugging, Einschränkungen, Alternativen und Fallstricke.

In einem Kubernetes-Cluster können standardmäßig alle (Nodes, Pods, Kubelet, etc.) miteinander kommunizieren. Gelingt es einem Angreifer eine Sicherheitslücke in einer der Anwendungen auszunutzen, kann er seinen Angriff dadurch leicht auf alle dahinter liegende Systeme im gleichen Cluster ausbauen. Durch das Kubernetes-Bordmittel Network Policies kann dies eingeschränkt werden. Der erste Teil dieser Artikelserie empfiehlt Whitelisting des ein- und ausgehenden Datenverkehrs. Wer dies selbst in einer definierten Umgebung ausprobieren will, findet vollständige Beispiele mit Anleitung im Repository „cloudogu/k8s-security-demos” bei GitHub.

Doch wie sieht dies in der Praxis aus: Welche Fallstricke gibt es beim Thema Network Policies? Wo hat der Mechanismus seine Grenzen? Gibt es Alternativen? Wie kann sichergestellt werden, dass die Network Policies so funktionieren, wie gedacht? Was tun im Fehlerfall? Auf diese fortgeschrittenen Themen gibt dieser Teil Antworten.

CNI Plugin Support

Wie im ersten Teil erwähnt, werden Network Policies zwar in Kubernetes spezifiziert, aber vom Container Networking Interface (CNI) Plugin durchgesetzt. Spezifikation und Implementierung können also abweichen. Beispielsweise

  • unterstützt das CNI-Plugin Flannel generell keine Network Policies,
  • wurde die API mehrfach erweitert (egress ab Kubernetes 1.8, namespaceSelector und podSelector gleichzeitig verwendbar ab 1.11), weshalb diese Features erst in neueren Versionen der CNI-Plugins implementiert sind und
  • bei einem systematischen Test verschiedener CNI-Plugins zeigte sich, dass WeaveNet viele Network Policies anders auslegt als Calico.

Daher empfiehlt es sich, ein CNI-Plugin gründlich zu evaluieren, bevor es eingesetzt wird. Hier kann der Kubernetes-Conformance-Test “Sonobuoy” hilfreich sein. Damit kann man testen, ob ein Kubernetes-Cluster in seiner aktuellen Konfiguration alle Features laut Spezifikation unterstützt. Dies lässt sich einfach für das Feature Network Policies durchführen.

Nutzer eines managedCluster haben keinen Einfluss auf das verwendete CNI-Plugin. Sie sind davon abhängig, ob der Anbieter das Feature Network Policies bereitstellt. Typischerweise muss das Feature außerdem für den Cluster explizit aktiviert werden, da sonst die Network Policies nicht durchgesetzt werden. Die großen Anbieter setzen häufig auf das CNI-Plugin Calico, beispielsweise Googles GKE (verfügbar seit März 2018), Amazons EKS (verfügbar seit Juni 2018) sowie Microsofts AKS (verfügbar seit Mai 2019).

Test und Debugging

Es ist also empfehlenswert, am laufenden Kubernetes-Cluster zu validieren, dass die Network Policies gemäß Spezifikation durchgesetzt werden. Derzeit hat sich noch kein Tool etabliert, mit dem automatisierte Tests ausführbar sind. Für manuelles Testen gibt es jedoch viele Möglichkeiten, die auch gut zum Debugging verwendet werden können.

Im einfachsten Fall öffnet der Nutzer eine Shell im Container per kubectl exec. Dies funktioniert natürlich nur, wenn das zugehörige Image eine Shell enthält. Sehr wahrscheinlich sind im Container allerdings nicht viele Werkzeuge enthalten, mit denen Netzwerkzugriffe durchführbar sind. Um die Angriffsfläche so klein wie möglich zu halten, ist es gute Praxis, so wenig Pakete wie möglich in Container Images für die Produktion zu installieren.

Ein einfaches Mittel, um solche minimalen Container zu debuggen, ist das Ausführen temporärer Container, welche die notwendigen Tools enthalten. Zum Thema Netzwerk bietet sich hier das Image nicolaka/netshoot an, das viele Netzwerk Utilities (wie curl, ifconfig, nmap, ngrep, socat, tcpdump, etc.) enthält. Technisch kann ein Container in der gleichen Umgebung wie ein anderer gestartet werden, indem beide in denselben Linux-Namespaces (PID, Network, etc.) ausgeführt werden. Bei Docker ist dies für den Network-Namespace mittels docker run --net container:<container_name> nicolaka/netshoot einfach möglich. Bei Kubernetes ist es herausfordernder. Hier abstrahieren Pods vom Zugriff auf die Container. Alle Container innerhalb desselben Pods teilen sich die Linux-Namespaces. Mit kubectl 1.22 ist es noch nicht ohne weiteres möglich, kurzlebige neue Container in einem Pod zu starten. Perspektivisch wird der „kubectl debug” Command jedoch den Start von kurzlebigen Containern für das Debugging erlauben. Bis dahin gibt es folgende Möglichkeiten (siehe Listing 1 für konkrete Beispiele):

  • Temporäre Pods können mit den gleichen Labels wie die der eigentliche Pod gestartet werden. Dies ist einfach durchzuführen und beeinträchtigt den eigentlichen Pod nicht. Allerdings befinden sich die Pods dann nicht im gleichen Linux-Namespace. Für viele Network Policy Testfälle reicht dies jedoch aus.
  • Einem Deployment können explizit weitere Container (oft auch Sidecar genannt) hinzugefügt werden. Die Umsetzung per YAML ist allerding umständlich und erfordert ein Neuanlegen des Pods. Daher ist sie für die Produktion nur bedingt geeignet. Der neue Container muss nach dem Debugging wieder manuell entfernt werden, was einen weiteren Neustart bedingt.
  • Existiert Zugriff auf die Nodes, können direkt über die Container Runtime weitere Container im Namespace des gewünschten Containers gestartet werden. Für Docker wäre das der oben angegebene docker run Befehl.
  • Besteht kein Zugriff auf die Nodes, kann ein Zugriff auf die Container Runtime über einen weiteren Pod erfolgen. Wer Docker als Container Runtime verwendet, kann über einen Mount des Docker-Socket weitere Container starten. Es gibt auch ein 3rd-Party-Tool, welches dies automatisiert. Dies ist jedoch nur möglich, wenn keine PodSecurityPolicy die Ausführung von Containern mit dem User root und das mounten des Docker-Socket verhindert.
  • Außerdem kann ein temporärer Pod im Network Namespace des Hosts, also dem des Kubernetes Node, starten. Von hier kann beispielsweise auf alle Interfaces des Nodes zugegriffen, sowie Pakete von innerhalb des Clusters, aber außerhalb des Pod-Netzwerks versendet werden. Auch dies ist nur möglich, wenn es nicht durch eine PodSecurityPolicy verhindert wird.
# Temporären Pod mit bestimmten Labels starten
$ kubectl run --generator=run-pod/v1 tmp-shell --rm -i --tty \
  --labels "app=a" -n team-a --image nicolaka/netshoot  -- /bin/bash

# Temporären Pod im Network Namespace des Hosts (Kubernetes node) starten
$ kubectl run tmp-shell --generator=run-pod/v1 --rm -i --tty \
    --overrides='{"spec": {"hostNetwork": true}}' -n team-a --image nicolaka/netshoot -- /bin/bash

# Debug Container in Deployment einfügen und in einem zugehörigen Pod eine Shell öffnen
$ kubectl patch deployment app-a -n team-a -p "$(cat <<EOF
spec:
  template:
  spec:
    containers:
    - image: nicolaka/netshoot
      name: netshoot
      args:
      - sleep
      - '9999999999999999999'
EOF
)"
$ kubectl exec -ti $(kubectl get pod -l app=a -o jsonpath="{.items[0].metadata.name}") \
  -c netshoot bash

Listing 1: Netshoot Container in Kubernetes starten

Fallstricke und Tipps bei der Verwendung von Network Policies

Die Lernkurve von Network Policies ist nicht gerade flach. Daher lauern hier auch einige Fallstricke, die teilweise schon erwähnt wurden. Der folgende Abschnitt fasst diese zusammen und gibt weitere Tipps.

  • Network Policies werden vom CNI-Plugin durchgesetzt. Spezifikation und Implementierung können demnach abweichen. Also ist es empfehlenswert, am laufenden Kubernetes-Cluster zu validieren, dass die Network Policies auch gemäß Spezifikation durchgesetzt werden.
  • Wenn ein CNI-Plugin verwendet wird, dass keine Network Policies unterstützt, werden vorhandene Network Policies nicht durchgesetzt. Dies kann dann zu einem falschen Gefühl der Sicherheit führen, wie es auch im Kubernetes Security Audit angemahnt wird.
  • Für das Selektieren von Namespaces müssen den Namespaces Labels hinzugefügt werden.
  • Beim Whitelisting des ingress besteht die Gefahr, Monitoring Tools, Ingress Controller und DNS zu vergessen.
  • Beim Whitelisting des egress kann leicht die Verbindung zum Kubernetes API Server vergessen werden. Außerdem ist egress in der Namespace API generell erst ab Kubernetes Version 1.8 verfügbar.
  • Die gleichzeitige Verwendung von namespaceSelector und podSelector ist erst ab Kubernetes Version 1.11 möglich.
  • Nach Änderung der Network Policies ist ein Neustart aller potentiell betroffenen Pods empfehlenswert. Beispielsweise kann Prometheus nach Änderung von Network Policies teilweise zunächst noch weiter Metriken abholen. Der Fehler zeigt sich erst nach einem Neustart. Bei Traefik bleibt die Verbindung zum API Server nach Änderung der Network Policy noch offen, beim Neustart tritt aber direkt ein Fehler auf. Bei lang laufenden Anwendungen kann es überraschend sein, dass diese beim nächsten Neustart ohne naheliegende Änderung nicht mehr wie erwartet funktionieren. Daher ist ein Neustart direkt nach dem Ändern der Network Policies ratsam, beispielsweise mittels kubectl rollout restart deployment (seit kubectl 1.15 verfügbar).

Einschränkungen und Alternativen/Erweiterungen

In dieser Artikelserie werden Möglichkeiten aufgezeigt, wie Network Policies zur Absicherung eines Kubernetes-Clusters beitragen können. Trotzdem gibt es einige Anforderungen, die nicht mit Network Policies realisierbar sind. Beispielsweise

  • gibt es keine Möglichkeit Cluster-weite Policies durchzusetzen,
  • egress auf Ebene von Domain Names zuzulassen oder
  • auf ISO/OSI-Ebene 7 (wie HTTP oder gRPC) zu filtern.

Für die Realisierung dieser Anforderungen bieten sich zwei Möglichkeiten: Die Nutzung proprietärer Erweiterungen der CNI-Plugins (Cilium und Calico bieten beispielsweise die oben genannten Möglichkeiten) oder den Einsatz eines Service-Meshs wie Istio oder Linkerd.

Die Hürde für die Verwendung proprietärer Erweiterungen der CNI-Plugins ist relativ gering. Diese stehen als Custom Resource Definition, also als Erweiterung der Kubernetes-API zur Verfügung und können einfach in gewohnter YAML-Syntax auf den Cluster angewendet werden. Vorsicht ist allerdings beim Mischen mit Standard Network Policies geboten. Cilium rät beispielsweise hiervon ab. Durch die Verwendung von proprietären Network Policies findet außerdem eine engere Kopplung mit dem jeweiligen CNI-Plugin statt. Dieses kann dann schwerer ausgetauscht werden, falls beispielsweise später bessere Performance oder andere Erweiterungen (wie Verschlüsselung) gewünscht werden.

Ein Service Mesh bietet neben Policies auf ISO/OSI-Schicht 7 viele weitere Funktionen wie Resiliency Patterns, Ende-zu-Ende Verschlüsselung und Observability, die über den Kontext dieses Artikels hinausgehen. Die Verwendung eines Service Meshs erhöht allerdings die Gesamtkomplexität der Infrastruktur enorm. Wer also nur auf HTTP-Ebene filtern will, kommt mit den proprietären Features eines CNI-Plugins mit weniger Aufwand zum Ziel. Wer hingegen ohnehin ein Service Mesh betreibt, kann durch die Kombination von Kubernetes Network Policies und Service Mesh noch weitere Absicherung erreichen.

Fazit

Die Lernkurve des Kubernetes-Bordmittel Network Policies ist nicht flach. Dafür ist ihr Nutzen für die Sicherheit von auf Kubernetes-Clustern laufenden Anwendungen hoch. Der erste Artikel in dieser Serie zeigt einen pragmatischen Nutzungsweg, wie sich der Aufwand in Grenzen halten sollte. Dieser zweite Teil trägt zur Begrenzung des Aufwands bei, indem Fallstricke umgangen und Tipps zum Debugging gegeben werden. Er zeigt auf, dass es generell empfehlenswert ist, Network Policies im Cluster zu testen. Wer darüber nachdenkt, ein Service Mesh einzuführen, verschwendet mit Network Policies nicht seine Zeit, denn dessen Vorteile können ergänzend eingesetzt werden.

Tags