Dieser Artikel ist Teil einer Reihe.

Einleitung

Nachdem wir im letzten Teil der Serie den Todesstern erfolgreich zerstören konnten, begeben wir uns nun auf die dunkle Seite der Macht und schützen unseren Todesstern durch die Anwendung von Cilium-Netzwerkpolicies auf OSI-Schicht 3/4 und vor allem auf Schicht 7 vor den bösen Allianz-X-Wings, aber auch vor den eigenen Tie-Fightern, die ja auch mal Fehler machen können.

The empire strikes back!

Als erstes verbieten wir es Allianz-Raumschiffen, auf dem Todesstern zu landen. Dafür nutzen wir eine Policy, die auf OSI-Schicht 3 und 4 operiert.

Diagram showing valid transition from 'org=empire, class=tiefighter' to 'org=empire, class=deathstar' (green check) and invalid transition from 'org=alliance, class=xwing' (red X).

Wenden wir die entsprechende Policy also an:

$ kubectl create -f - <<EOF  
apiVersion: "cilium.io/v2"
kind: CiliumNetworkPolicy
metadata:
  name: "deathstar-policy"
spec:
  description: "L3-L4 policy to restrict deathstar access to empire ships only"
  endpointSelector:
    matchLabels:
      org: empire
      class: deathstar
  ingress:
  - fromEndpoints:
    - matchLabels:
        org: empire
    toPorts:
    - ports:
      - port: "80"
        protocol: TCP 
EOF

Hiermit verbieten wir aus Todesstern-Sicht eingehende Kommunikation aller Cilium-Endpunkte mit dem Label org=empire auf TCP-Port 80. Versuchen wir erneut, mit dem X-Wing auf dem Todesstern zu landen:

$ kubectl exec xwing -- curl -s -XPOST deathstar.default.svc.cluster.local/v1/request-landing

Der Aufruf «hängt» und läuft irgendwann in einen Timeout (oder wird von uns via ctrl+c abgebrochen). Unsere Regel greift also, super! Zur Sicherheit prüfen wir auch, ob die imperialen Tie-Fighter immer noch landen können:

$ kubectl exec tiefighter -- curl -s -XPOST deathstar.default.svc.cluster.local/v1/request-landing 
Ship landed

Jawohl, das können sie. Ziel erreicht! Oder… oder?! Moment, da war doch was. Der Todesstern hat eine böse Schwachstelle. Genau, den v1/exhaust-port-Endpunkt. Nun können feindliche X-Wings diesen zwar nicht mehr erreichen, aber vielleicht klaut eine böse Rebellin ja einen Tie-Fighter, oder ein Stormtrooper macht mal einen Fehler.

Simulieren wir das doch mal:

$ kubectl exec tiefighter -- curl -s -XPUT deathstar.default.svc.cluster.local/v1/exhaust-port                              
Panic: deathstar exploded
...

Oh je. Wir haben immer noch ein Problem! Bevor Darth Vader das jetzt mitbekommt, sollten wir es lieber schnell lösen. «Eigentlich» sollten Raumschiffe nur mit dem Lande-Endpunkt des Todessterns interagieren können. Derzeit ist das Prinzip aber «ganz oder gar nicht», durch die Anwendung der grobgranularen Policy auf OSI-Schicht 3 und 4. Unser Ziel können wir wie folgt beschreiben:

Diagram illustrating API interactions: HTTP POST to '/v1/request-landing' succeeds for 'org=empire' and 'class=tiefighter', HTTP PUT to '/v1/exhaust-port' fails for 'org=alliance' and 'class=xwing'. Namespace labeled 'default'.

Wir wollen hier feingranulare Zugriffsregeln auf einzelne HTTP-Verben für einzelnen API-Endpunkte festlegen. Dies wäre mit Kubernetes-Netzwerkpolicies definitiv nicht möglich, weil wir uns hier auf OSI-Schicht 7 befinden.

Fügen wir also nun eine zweite Policy mit Regeln der OSI-Schicht 7 hinzu und schauen, ob wir den verbotenen Schwachpunkt noch erreichen können:

$ kubectl create -f - <<EOF  
apiVersion: "cilium.io/v2"
kind: CiliumNetworkPolicy
metadata:
  name: "deathstar-policy-2"
spec:
  description: "L7 policy to restrict access to specific HTTP call"
  endpointSelector:
    matchLabels:
      org: empire
      class: deathstar
  ingress:
  - fromEndpoints:
    - matchLabels:
        org: empire
    toPorts:
    - ports:
      - port: "80"
        protocol: TCP
      rules:
        http:
        - method: "POST"
          path: "/v1/request-landing" 
EOF

Rufen wir nun den verbotenen Endpunkt erneut auf (Vorsicht, Falle!):

$ kubectl exec tiefighter -- curl -s -XPUT deathstar.default.svc.cluster.local/v1/exhaust-port
Panic: deathstar exploded
...

Oha! Der Todesstern explodiert immer noch. Das haben wir so nicht erwartet. Was ist hier passiert?

Nun, wir haben hier schmerzhaft gelernt, dass die NetworkPolicy und auch Ciliums entsprechende Derivate additiv sind. Sie führen Policies zusammen, wenn mehr als eine Policy für die gleiche Entität vorliegt.

Da wir in unserer Schicht-3/4-Policy festlegen, dass der gesamte eingehende TCP-Traffic auf Port 80 erlaubt ist, führt das abstrahiert zu folgender Rechnung:

all-tcp-port80-ingress-traffic + subset-of-tcp-port-80-ingress-traffic 
== immer-noch-all-tcp-port-80-ingress-traffic

Wenn also unsere Bestehende Schicht-4-Regel allen eingehenden Traffic auf TCP unter Port 80 erlaubt, die Schicht-7-Regel dies dann aber einschränkt, gilt wegen der generellen Additivität immer noch «alles ist erlaubt».

Auch hier gibt es etwas von Ratiopharm Cilium, die so genannten Deny Policies, die sich nicht additiv verhalten.

Nachdem wir nun aber einen generellen Policy-Fallstrick besser kennengelernt haben, fixen wir lieber erstmal schnell unser Problem, bevor der Imperator guckt. Zuerst löschen wir dafür die neue Policy:

$ kubectl delete CiliumNetworkPolicy deathstar-policy-2 
ciliumnetworkpolicy.cilium.io "deathstar-policy-2" deleted

Nun aktualisieren wir stattdessen unsere bestehende Policy, damit sie auch die entsprechenden Schicht-7-Regeln enthält:

$ kubectl apply -f - <<EOF  
apiVersion: "cilium.io/v2"
kind: CiliumNetworkPolicy
metadata:
  name: "deathstar-policy"
spec:
  description: "L7 policy to restrict access to specific HTTP call"
  endpointSelector:
    matchLabels:
      org: empire
      class: deathstar
  ingress:
  - fromEndpoints:
    - matchLabels:
        org: empire
    toPorts:
    - ports:
      - port: "80"
        protocol: TCP
      rules:
        http:
        - method: "POST"
          path: "/v1/request-landing" 
EOF

Versuchen wir nun, den Todesstern wieder zu zerstören:

$ kubectl exec tiefighter -- curl -s -XPUT deathstar.default.svc.cluster.local/v1/exhaust-port
Access denied

Wir erhalten wir eine Access Denied-Meldung. Sehr gut! Können die imperialen Tie-Fighter denn immer noch landen?

$ kubectl exec tiefighter -- curl -s -XPOST deathstar.default.svc.cluster.local/v1/request-landing
Ship landed

Ja. Auftrag erfüllt, wir dürfen unseren Kopf behalten. Der Imperator wird zufrieden sein. Bereiten wir noch alles für die große Präsentation vor - mit Hilfe der Service Map.

Schauen wir uns die Regeln mit Blick auf die bösen Allianz-X-Wings an, sieht das wie folgt aus:

Kubernetes network diagram showing 'xwing' connecting to 'deathstar' over TCP port 80 and HTTP path '/v1/exhaust-port', with traffic flow and metadata details.
Service-Map aus Sicht des X-Wings

Die Ansicht für den Tie-Fighter dagegen zeigt, dass wir nur Landeanfragen stellen dürfen:

Network flow visualization showing traffic between components 'xwing', 'tiefighter', and 'deathstar', with flows to port 80 dropped due to policy denial.
Service-Map und Eventstream aus Sicht des Tie-Fighters

Im obigen Bild sehen wir im unteren Bereich auch den Livestream der Events im Namespace. Dort kann der Imperator dann schön sehen, dass der X-Wing zwar versucht, auf dem Todesstern zu landen, dies aber nun durch unsere Policy verboten wurde. Und da klingt auch schon der Imperial March durch die Gänge…

Fazit

Ich hoffe, ich konnte euch mit dieser Artikelreihe darlegen, wie ihr bei euch lokal erste Schritte mit Cilium unternehmen könnt, und was Cilium euch bieten kann. Wir haben gelernt, was Cilium von Kubernetes unterscheidet und wo sich beide ähnlich verhalten, was Netzwerkpolicies und deren Cilium-Derivate sind und wie wir sie anwenden können, samt erster Fallstricke. (Ich entschuldige mich hiermit bei frustrierten Leserinnen und Lesern, die mein «Lass sie erst reinfallen und zeig ihnen dann die Lösung»-Vorgehen nicht gut finden. 😜 )

Nun waren das natürlich nur erste Schritte. Cilium ist eine sehr mächtige Erweiterung und Abstraktion über ein schon an sich sehr kompliziertes und mächtiges Werkzeug, Kubernetes. Daher zum Schluss noch ein gut gemeinter Rat: Wenn ihr überlegt, sowohl Kubernetes als auch Cilium einzusetzen, fragt euch bitte immer zuerst, ob ihr diese Komplexität wirklich braucht, oder ob ihr eure Ziele nicht auch einfacher erreichen könnt. Vielleicht «löst» Cilium für euch Probleme, die erst durch den Einsatz von Kubernetes für viel zu kleine Systemlandschaften überhaupt geschaffen wurden.

That said: Wenn ihr natürlich wirklich eine Kubernetes-Umgebung benötigt, ist Cilium ein sehr interessanter Aufsatz, der durch eBPF-Magie (die eines eigenen Artikels bedürfte) mehr Flexibilität, Observability und Sicherheit bei einfacher Nutzbarkeit erlaubt.

Wenn ihr Interesse daran habt, eure Kenntnisse in Cilium weiter zu vertiefen, beherzigt die goldene Regel: «Lernen kommt von machen!»

Mit der hier von uns aufgesetzten, lokalen Umgebung habt ihr grundsätzlich alles an der Hand, um Cilium weiter zu erforschen.

Wenn ihr dafür etwas Inspiration braucht, kein Problem. Schaut euch beispielsweise die Limits der Kubernetes NetworkPolicy an und vergleicht sie mit eurem neu gewonnenen Wissen von Ciliums Fähigkeiten. Probiert TLS-, Entity- oder die nicht-additiven Deny Policies aus. Schaut euch gern auch den policy audit mode an, das Dry-run-Feature für Netzwerkpolicies, um erstmal «kontrolliert zu schauen, was die Policy tut», bevor ihr sie wirklich scharf schaltet. Oder modelliert eure Policies im Online-Editor und erweitert den Einblick in den Cluster mittels des Grafana-Stacks.

Am Wichtigsten bei all dem: Experimentiert. Wendet es an. Es kann nichts schlimmes kaputt gehen. Außer Todessterne, und das ist eigentlich gar nicht so schlecht. Viel Spaß!