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: nginx
und 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.