Zero Trust im Cluster wollte ich so wie perfekte Landungen bei Seitenwind: offensichtlich die richtige Idee, in der Praxis schwerer als im Briefing. Als ich das erste Mal sehr überzeugt eine Default-Deny-NetworkPolicy anlegte und die Health Checks ausfielen, dachte ich nicht mehr an Mikrosegmentierung. Ich dachte an Rollback. Vier Minuten lang lief der Produktions-Traffic noch, dann nicht mehr. Das Problem war ich, nicht das CNI, nicht die Cloud und auch nicht ein mysteriöses Kubernetes.

Ethernet-Kabel in einem Netzwerk-Switch

Foto von Pixabay auf Pexels

NetworkPolicy ist mächtig und leicht falsch einzusetzen. Sie legt auf L3/L4 fest, wer mit wem sprechen darf. Sie kennt die Absicht der Anwendung nicht. Sie weiß nicht, dass der Pod weiterhin kube-dns auf Port 53 braucht, dass der Metrics-Scraper in einem anderen Namespace läuft oder dass der Egress zu S3 über einen Proxy geht, den man zu erlauben vergessen hat. Der Cluster erzwingt, was geschrieben wurde. Genau das ist die Funktion. Genau das ist auch die Falle.

Ich bin kein Sicherheitsarchitekt. Ich bin ein Engineer, der Produktion mit guten Absichten kaputt gemacht und danach wieder aufgeräumt hat. Das ist der schrittweise Ansatz, den ich heute nutze, wenn jemand sagt: „Wir sollten das Netzwerk abschotten“, und alle nicken, weil niemand gegen Sicherheit argumentieren will.

Was NetworkPolicy wirklich tut (und was nicht)

In den meisten CNIs, die NetworkPolicy unterstützen — Calico, Cilium, OVN-Kubernetes auf OpenShift, andere mit Einschränkungen — wählt eine NetworkPolicy Pods über Labels aus und definiert Ingress, Egress oder beides. Wenn ein Pod von einer Policy erfasst wird, die Traffic einschränkt, kommt nur explizit Erlaubtes durch. Pods, die von keiner Policy erfasst sind, behalten meist Default-Allow, abhängig von CNI und Plattformvorgaben. Diese Asymmetrie ist das Erste, was man am Cluster prüft, bevor man YAML schreibt.

NetworkPolicy kann nicht:

  • Traffic verschlüsseln (das ist mTLS, Service Mesh oder TLS zwischen Anwendungen)
  • Authentifizierung oder Autorisierung an der API ersetzen
  • verwundbare Images oder geleakte Secrets reparieren
  • HTTP-Pfade verstehen — sie sieht Ports und Protokolle

Sie verkleinert den Auswirkungsbereich, wenn etwas im Cluster kompromittiert oder falsch konfiguriert ist. Ein Wurm, der den Datenbank-Pod auf 5432 nicht erreicht, weil Egress aus dem kompromittierten Namespace verweigert wird, ist ein Gewinn. Ein Deploy, der die Datenbank nicht erreicht, weil ein Label-Selector vertippt wurde, ist ein Postmortem.

Ich behandle NetworkPolicy als einen Zaun, den man baut, während man noch im Haus wohnt — nicht als Mauer, die in einem Guss gegossen wird.

Warum Default Deny im großen Wurf scheitert

Das Lehrbuchdiagramm zu Zero Trust zeigt einen Default-Deny-Namespace und explizite Allow-Regeln für jeden Flow. Sauber. Auf dem Whiteboard korrekt. Auf einer laufenden Plattform mit Dutzenden Services, unbekannten CronJobs, alten Helm-Charts und einem Monitoring-Stack, den niemand vollständig dokumentiert hat, ist Default Deny am Montagmorgen ein Stresstest, dem niemand zugestimmt hat.

Was zuerst bricht, ist selten die Haupt-API:

DNS. UDP und TCP Port 53 zu kube-system oder CoreDNS-Endpoints. Egress verweigern, und der Pod löst nichts mehr auf. Die Symptome sehen aus wie „Datenbank ist down“, obwohl die Datenbank in Ordnung ist.

Control Plane und Webhooks. Admission Controller, Operatoren, Cloud-Metadata-Endpoints — je nach Architektur brauchen Workloads Verbindungen, die niemand kartiert hat.

Beobachtbarkeit. Prometheus scrapt Pod-Metriken auf hohen Ports, Log-Agenten und Trace-Exporter senden Daten. Wenn Ingress aus dem Monitoring verstummt, werden die Alarme still, während die Anwendung brennt.

Image Pulls und Registries. Meist Node-Ebene, nicht Pod-Egress — bis Egress-Policies versehentlich Sidecars blockieren, die mit lokalen Agenten sprechen.

Externe Abhängigkeiten. Payment-APIs, Identity Provider, Object Storage, Webhooks zu SaaS. Jede braucht eine Egress-Regel oder ein gemeinsames Egress-Gateway-Muster.

Der Fehlermodus ist auch organisatorisch. Anwendungsteams bekommen eine Policy ohne Karte ihrer Abhängigkeiten. Plattformteams werden zu Ticket-Schlangen für „bitte IP x erlauben“. Alle hassen NetworkPolicy. Sechs Monate später entfernt jemand sie „vorübergehend“. Den Film habe ich schon gesehen.

Schrittweises Ausrollen erhält Vertrauen.

Das Vorgehen: eine Regel nach der anderen

Das ist die Reihenfolge, der ich folge. Langsamer als ein Manifest, das alles verweigert. Schneller als Wiederherstellung.

Phase 0 — CNI und Vorgaben kennen

Bevor ich eine Policy schreibe, bestätige ich:

  • NetworkPolicy wird erzwungen (nicht nur von der API akzeptiert)
  • Ob bereits globales Default Deny existiert
  • Wie DNS erreichbar ist (CoreDNS Service IP, NodeLocal DNSCache, OpenShift dns-default)
  • Ob Mesh oder Egress-Gateway ausgehenden Traffic bereits zentralisiert

Auf OpenShift prüfe ich auch, ob AdminNetworkPolicy oder BaselineAdminNetworkPolicy auf Cluster-Ebene existieren. Plattformvorgaben können überraschen, wenn man nur Namespace-YAML ansieht.

Phase 1 — Inventar ohne Durchsetzung

Ich verschaffe mir ein Bild der tatsächlichen Flows, bevor ich etwas einschränke. Optionen:

Flow Logs vom CNI — Calico Flow Logs, Cilium Hubble, Cloud-VPC-Flow-Logs, wenn Traffic den Node verlässt.

Karten der Service-Abhängigkeiten — selbst eine Tabelle aus Team-Interviews ist besser als Raten.

Probe in Staging — gleiches CNI, gleiches DNS-Layout, Policies zuerst dort anwenden.

Ich nenne diese Phase „zweimal messen“, weil die Umstellung tatsächlich schneidet.

Phase 2 — Beobachtbarkeit und DNS explizit erlauben

Meine ersten Policies sind langweilig und innerhalb des Namespace großzügig:

  • Ingress vom Monitoring-Namespace zu Metrik-Ports erlauben
  • Egress zu DNS (kube-system oder Cluster-DNS-Service) auf 53/tcp und 53/udp
  • Egress zu Pods im gleichen Namespace, wenn die Anwendung Headless Services oder Sidecars nutzt

Ich wende diese vor jeder Deny-Regel an. Health Checks und Dashboards prüfen. Wenn Prometheus verstummt, wird das behoben, bevor es weitergeht.

Beispiel-Egress für DNS (Namespace-Labels an den Cluster anpassen):

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-dns-egress
  namespace: payments
spec:
  podSelector: {}
  policyTypes:
    - Egress
  egress:
    - to:
        - namespaceSelector:
            matchLabels:
              kubernetes.io/metadata.name: kube-system
      ports:
        - protocol: UDP
          port: 53
        - protocol: TCP
          port: 53

Langweiliges YAML ist hier gutes YAML.

Phase 3 — Default Deny in einem Namespace, nicht im ganzen Cluster

Ich wähle zuerst einen unkritischen Namespace: interne Werkzeuge, einen neuen Service mit wenigen Abhängigkeiten, etwas mit kooperativen Verantwortlichen und guten Tests. Ich wende an:

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: default-deny-all
  namespace: tooling
spec:
  podSelector: {}
  policyTypes:
    - Ingress
    - Egress

Dann kommen explizite Allow-Regeln, eine nach der anderen, jeweils aus Staging übernommen und jeweils validiert:

  1. Ingress vom Ingress-Controller-Namespace zum Anwendungsport
  2. Egress zum Datenbank-Namespace auf 5432
  3. Egress zu Redis auf 6379
  4. Egress zur externen API per CIDR oder Namespace des Egress-Proxys

Eine Regel, ein Deploy, ein Prüfzeitfenster. Anwendungsmetriken, synthetische Checks und CNI-Drop-Counter beobachten, wenn verfügbar. Die letzte Regel zurückrollen, wenn die Fehlerrate steigt.

Phase 4 — Nach Namespace-Klasse ausweiten

Wenn interne Werkzeuge eine Woche überstanden haben, kommen Tier-2-Services, umsatzkritische Pfade zuletzt. Umsatz zuletzt ist Absicht: Dann vertraue ich dem Vorgehen und den Allow-List-Vorlagen, nicht meinem Optimismus.

Ich halte eine gemeinsame Bibliothek von Policy-Fragmenten — dns-egress, ingress-from-openshift-router, scrape-from-monitoring — in Git, geprüft wie jeder andere Plattformcode. Copy-Paste mit Label-Tippfehlern ist ein bewährter Weg, Produktion zu brechen.

Zero Trust ohne Kommunikationsvakuum

Mikrosegmentierung ist Teamsport. Ich frage Service-Verantwortliche vor Änderungen am Namespace nach drei Listen:

  1. Wer den Service aufrufen muss (Ingress-Quellen)
  2. Wen der Service aufrufen muss (Egress-Ziele, Ports)
  3. Was bricht, wenn DNS oder NTP langsam sind (meist alles)

Außerdem sage ich klar, was sich nicht ohne Ankündigung ändert: Monitoring-Pfade, Deploy-Pipelines, dokumentierter Break-Glass für Notfälle.

Break-Glass zählt. Ich halte einen dokumentierten Weg bereit, Policies in einem Vorfall zu deaktivieren oder zu umgehen: temporäre Namespace-Annotation, wenn das CNI es unterstützt, Git-Revert oder ein bekanntes kubectl delete networkpolicy mit Audit-Logging. Security-Teams zucken dabei zusammen. Ich erkläre dann, dass ein undokumentierter Bypass um drei Uhr morgens sowieso passiert, nur in Panik und ohne Mitteilung. Benannter Break-Glass ist Ehrlichkeit.

Policies testen, bevor Produktion widerspricht

Staging muss sich wie das Produktions-CNI verhalten. Minikube-Default-Networking hat mich Dinge gelehrt, die sich nicht auf OpenShift OVN übertragen ließen. Ich teste:

Erfolgsfall — Nutzerreise, Health-Endpoints, Hintergrundjobs

Deploy und Rollback — neue Pods bekommen dieselben Labels; Policies wählen Labels, nicht Deployment-Namen. Ein Label-Wechsel beim Rollout kann Traffic ins Leere laufen lassen.

Skalierungsereignisse — HPA fügt Pods hinzu; Policies hängen am Selector, meist unkritisch, außer man verlässt sich auf Pod-IP-Regeln, die gedanklich nicht skalieren

Fehlerinjektion — in Staging absichtlich einen unkritischen Egress verweigern und prüfen, ob Alarme feuern

Cilium hat Policy-Verdict-Logging. Calico hat Audit-Regeln. Man sollte nutzen, was die Plattform hergibt. „Wir glauben, die Policy erlaubt Traffic“ ist schwächer als „wir haben Allow-Verdicts in den Logs gesehen“.

Typische Fehler, auf die ich noch achte

Falsche podSelector- oder namespaceSelector-Labels. matchLabels muss exakt passen. Tippfehler erzeugen sofort Deny-by-Default für erfasste Pods.

Ingress für Readiness-Probes vergessen. Kubelet-Probes laufen über den Node-Netzwerkpfad; HTTP/TCP-Probes zur Pod-IP werden von NetworkPolicy meist nicht blockiert. Bei reinen Exec-Probes ist die Geschichte anders. Wenn etwas Eigenes Readiness von einem anderen Pod scrapt, braucht man eine Regel.

Nur Ingress erlauben, nicht Egress. Die Anwendung erreicht die Datenbank nicht; man korrigiert Ingress zur Anwendung; die Datenbank bleibt unerreichbar, weil Egress von der Anwendung verweigert wird.

IP-Blöcke ohne Wartungsplan. SaaS-IP-Bereiche ändern sich. CIDR-Regeln veralten. Wo möglich sind Namespace-Selector oder Egress-Gateways robuster.

Policies in Git ohne verantwortliche Person. Verwaiste Policies sammeln sich an. Jemand löscht den Service und lässt Allow-Regeln zurück; jemand fügt einen Service hinzu und öffnet den Port nie.

Ich halte ein Namespace-Diagramm vierteljährlich aktuell, notfalls ein Mermaid-Chart im Repo, weil mein Gedächtnis keine CMDB ist.

OpenShift-Notizen aus der Praxis

OpenShift ergänzt Router, Ingress Controller und manchmal EgressRouter oder EgressFirewall auf Projektebene. AdminNetworkPolicy kann Baselines clusterweit erzwingen. Ich koordiniere mich mit Cluster-Admins, bevor Namespace-Policies mit Plattform-Policies kollidieren.

Das Router-Namespace-Label zählt für Ingress-Allow-Regeln. Monitoring lebt oft in openshift-monitoring. DNS ist weiterhin kube-system oder dns-default, je nach Version. Ich lese die Cluster-Dokumentation der Version, die ich betreibe, statt einem drei Jahre alten Blogpost zu vertrauen, auch meinem eigenen, falls das hier 2029 gelesen wird.

Nachhaltiger Zero Trust

Perfekte Isolation am ersten Tag ist Fantasie. Schrittweise Durchsetzung mit messbarem Rollback hält Produktion am Laufen, während die Sicherheit besser wird.

Ich behaupte nicht mehr, NetworkPolicy sei für jeden Cluster optional. Ich behaupte aber auch nicht mehr, Default Deny überall bis Freitag beweise Professionalität. Der bescheidene Weg:

  • Flows kartieren
  • DNS und Metriken zuerst erlauben
  • Default Deny in einem sicheren Namespace
  • Allow-Regeln eine nach der anderen ergänzen
  • Tier für Tier ausweiten
  • Break-Glass dokumentieren

Zero Trust heißt nicht „ab Dienstag nichts mehr vertrauen“. Es heißt: jeden Flow prüfen, explizit kodieren und stoppen, wenn das Error Budget es sagt. Ich habe Produktion mit einer einzigen YAML-Datei kaputt gemacht. Ich habe auch verhindert, dass ein kompromittierter Test-Pod eine Produktionsdatenbank erreichte, weil Egress durch eine Policy verweigert wurde, die wir den Monat zuvor getestet hatten. Beide Geschichten stimmen. Die zweite passierte nur, weil wir langsam genug waren, um die erste zu überleben.

Wenn du diese Woche mit NetworkPolicy startest: einen Namespace wählen, DNS-Egress erlauben, eine Stunde lang Dashboards beobachten und erst dann das Wort deny in ein Manifest schreiben. Das zukünftige Bereitschafts-Ich wird dankbar sein. Meins war es.