Network Policies mit Project Calico auf einem Rancher Kubernetes Cluster

Eine NetworkPolicy ist eine Spezifikation die definiert, wie eine Gruppe von Pods miteinander und mit anderen Netzwerk Endpunkten kommunizieren dürfen. Dabei werden z.B. Labels verwendet um Pods zu selektieren und dann Regeln definiert, die beschreiben was für Netzwerkverkehr für die selektierten Pods erlaubt ist. In diesem Blogpost zeige ich euch, wie in einem Rancher Kubernetes Cluster und beim Calico Network Provider Network Policies eingesetzt werden können.

Rancher Project Network Isolation

Rancher verwendet als Container Network Interface standardmässig Canal. Ein Cluster Administrator kann damit ein Project Network Isolation aktivieren bei dem Pods nur mit anderen Pods aus dem gleichen Rancher Projekt kommunizieren können. Rancher erstellt im Hintergrund dann automatisch NetworkPolicies in jedem Namespace. Eine davon erlaubt ingress Traffic von allen anderen Namespaces aus dem gleichen Projekt, sowie ingress Traffic vom Rancher System Projekt.

---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: np-default
  namespace: mynamespace
spec:
  ingress:
  - from:
    - namespaceSelector:
        matchLabels:
          field.cattle.io/projectId: p-b6jcw
    - namespaceSelector:
        matchLabels:
          field.cattle.io/projectId: p-mcb29
  podSelector: {}
  policyTypes:
  - Ingress

Rancher verwendet das Label field.cattle.io/projectId, um die Zugehörigkeit zu einem Projekt zu markieren. Es wird automatisch durch Rancher verwaltet.

Diese Funktionalität steht aber nur zur Verfügung, wenn Canal als Network Provider gewählt wird. Das Project Network Isolation Feature ist bei Calico nicht auswählbar – obschon Canal eigentlich Calico für Network Policy Enforcement verwendet. Im folgenden, will ich eine Variante aufzeigen, wie Network Policies selbst implementiert werden können. Dabei sollen folgende Ziele erreicht werden:

  • Komplette Netzwerk-Isolation als Standardeinstellung. Sowohl ingress wie auch egress Traffic soll vollständig unterbunden werden, sofern es nicht explizit erlaubt wird.

Viele gute Beispiele und Erklärungen zu Kubernetes Network Policies sind im Github Projekt von ahmetb zu finden.

Project Calico NetworkPolicy

Nebst den Kubernetes Network Policies kennt Calico auch Calico Network Policies, die etwas mehr Funktionalität gegenüber den Kubernetes Network Policies bieten. Wir verwenden hier Calico’s NetworkPolicy. Dabei steht uns insbesondere der Typ GlobalNetworkPolicy zur Verfügung, mit dem wir Regeln für den ganzen Kubernetes Cluster definieren können.

Default Deny Rules

Für die komplette Netzwerk-Isolation benötigen wir zuerst eine default-deny-all und default-deny-all-egress GlobalNetworkPolicy:

---
# Deny all ingress by default
apiVersion: crd.projectcalico.org/v1
kind: GlobalNetworkPolicy
metadata:
  name: default.default-deny-all
spec:
  selector:
  types:
  - Ingress
  ingress:

---
# Deny all egress expect tcp/53 & udp/53 for dns service
apiVersion: crd.projectcalico.org/v1
kind: GlobalNetworkPolicy
metadata:
  name: default.default-deny-all-egress
spec:
  selector:
  types:
  - Egress
  egress:
  # Except DNS
  - action: Allow
    protocol: TCP
    destination:
      ports:
      - 53
  - action: Allow
    protocol: UDP
    destination:
      ports:
      - 53

Damit die Cluster interne DNS Auflösung (für Pod und Service Auflösung) noch funktioniert, müssen wir Port 53 tcp/udp für egress Traffic erlauben.

Rancher Komponenten

Damit kubectl exec und kubectl logs funktionieren, müssen wir dem Rancher Agent erlauben, zu kommunizieren. Dieser läuft mit den ServiceAccount cattle:

---
# Otherwise kubectl exec / log not possible
# need connection to kubelet on nodes
# rancher tunnels connections from rancher trough cattle agents
# https://erkanerol.github.io/post/how-kubectl-exec-works/
apiVersion: crd.projectcalico.org/v1
kind: GlobalNetworkPolicy
metadata:
  name: default.allow-egress-cattle
spec:
  selector:
  types:
  - Egress
  egress:
  - action: Allow
    source:
      serviceAccounts: 
        names:
        - cattle

Ähnliches können wir für Rancher Cluster & Project Monitoring sowie Logging machen. Die Rancher Monitoring Komponenten laufen mit dem ServiceAccount cluster-monitoring bzw project-monitoring. Das Rancher Logging läuft mit dem ServieAccount rancher-logging-fluentd.

Wenn mit der Kubernetes API kommuniziert werden muss (z.B. von NGINX-Ingress-Controller), benötigt auch dies eine egress Rules. Der Zugriff darauf ist aber etwas speziell. Im Kubernetes-Cluster ist die Kubernetes API über den Service Namen kubernetes.default.svc erreichbar. Dieser löst auf eine interne Cluster IP auf. Aufgrund von diversen NAT’s muss die Network Policy aber nicht auf die ClusterIP matchen, sondern auf die effektiven public IP’s der Kubernetes API. Im Fall von Rancher sind das die jeweilen NodeIP’s auf Port 6443. Rancher deployed einen lokalen NGINX, der den Kubernetes API Traffic auf die Master Nodes verteilt. Die NetworkPolicy sieht wie folgt aus:

---
# For Operator who need access to the kubernetes api
apiVersion: crd.projectcalico.org/v1
kind: GlobalNetworkPolicy
metadata:
  name: default.allow-egress-kubeapi
spec:
  selector: all()
  types:
  - Egress
  egress:
  - action: Allow
    protocol: TCP
    destination:
      nets:
      - X.X.X.X/32 # nodeips or cidr
      ports:
      - 6443

Wenn hier jemand einen besseren Weg gefunden hat, darf diese Person mich gerne kontaktieren.

Ingress Controller

In einem Kubernetes Cluster wird häufig ein Ingress Controller für L7 Traffic verwendet (z.B. NGINX bei Rancher). Damit dieser erreichbar ist und insbesondere auch Verbindungen zu seinen backend Services (die effektive Applikation) aufbauen kann, erstellen wir die folgenden GlobalNetworkPolicies:

---
# Allow nginx ingress to access its backend services
apiVersion: crd.projectcalico.org/v1
kind: GlobalNetworkPolicy
metadata:
  name: default.allow-ingress-nginx
spec:
  selector: network-policy/ingress == 'nginx'
  types:
  - Ingress
  ingress:
  - action: Allow
    protocol: TCP
    source:
      namespaceSelector: ingress == 'nginx'
    destination:
      ports:
      - 80
      - 443

---
# Allow nginx to access backend services which allow ingress to be nginx
# Make sure your ingress has label `ingress:nginx`
apiVersion: crd.projectcalico.org/v1
kind: GlobalNetworkPolicy
metadata:
  name: default.allow-egress-nginx
spec:
  selector: ingress == 'nginx'
  types:
  - Egress
  egress:
  - action: Allow
    protocol: TCP
    destination:
      selector: network-policy/ingress == 'nginx'
      ports:
      - 80
      - 443

Der Namespace in dem der Ingress Controller läuft, benötigt dann ein Label ingress: nginxund alle Namespaces, in dem backend Services laufen, benötigen ein Label network-policy/ingress: nginx. Wenn die backend Services nicht auf Port 80/443 laufen, muss die Policy natürlich noch angepasst werden. Wir verwenden hier ebenfalls die GlobalNetworkPolicy, damit nicht in jedem Namespace eine NetworkPolicy für den Zugriff von NGINX erstellt werden muss.

Applikation Traffic im Cluster

Um weiteren Netzwerkverkehr innerhalb des Cluster zu steuern, verwenden wir ebenfalls Labels auf den Namespaces. Der Namespace eines Services, der konsumiert werden soll, bekommt z.B. ein Label network-policy/role_redis. Der Namespace, der einen Service konsumieren will, bekommt z.B. ein Label network-policy/allow_redis. Die NetworkPolicies (jetzt nicht mehr global) sehen dann wie folgt aus:

---
# Allow ingress to role redis in allowed workloads
apiVersion: crd.projectcalico.org/v1
kind: NetworkPolicy
metadata:
  name: default.allow-role-redis
spec:
  selector: has(network-policy/role_redis)
  types:
  - Ingress
  ingress:
  - action: Allow
    protocol: TCP
    source:
      selector: has(network-policy/allow_redis)
    destination:
      ports:
      - 6379
---
# Allow egress for workload allowed to access role redis
apiVersion: crd.projectcalico.org/v1
kind: NetworkPolicy
metadata:
  name: default.allow-egress-to-role-redis
spec:
  selector: has(network-policy/allow_redis)
  types:
  - Egress
  egress:
  - action: Allow
    protocol: TCP
    destination:
      selector: has(network-policy/role_redis)
      ports:
      - 6379

Die allow-role-redis Policy wird im Namespace von Redis erstellt. Die allow-egress-to-role-redis wird im Namespace des konsumierenden Service erstellt.

Kommentare sind geschlossen.