Cilium Network Policies

Overview

Cilium’s network policies limit what traffic can come into pods (Ingress) and leave pods (Egress). Without policies, all traffic is allowed. After adding at least one policy for a namespace or pod, the principle “everything that is not explicitly allowed” is prohibited.

Where to configure: Network policies in the cluster networks section or Administration → Custom resources. Policies can be created via the GUI or import manifest.

Screenshot: network policy status

Network policy status


Important: how policies work

Default behavior

SituationResult
There are no network policies in namespaceAll traffic is allowed (incoming and outgoing)
Namespace has at least one policyOnly traffic that is explicitly described in the rules is allowed. Everything else is blocked

Note: Common error: Added the “allow Ingress” policy and forgot to allow DNS. The pods stopped resolving names - the application “broke”. Always add all the rules you need in one policy or in multiple policies.

What is endpointSelector

endpointSelector defines to which pods the policy applies:

  • endpointSelector: {} — to all pods in namespace (empty selector = all pods)
  • endpointSelector: { matchLabels: { app: my-app } } - only for pods with the label app=my-app

A policy with endpointSelector: {} and rules ingress/egress specifies what traffic is allowed for the selected pods. Anything that is not included in the rules is blocked.


Cluster and namespace policies

TypeApplicationWhen to use
ClusterwideCiliumNetworkPolicyTo the entire clusterGeneral rules (eg DNS access for everyone)
CiliumNetworkPolicyTo one namespaceApplication Specific Isolation

In the cluster-wide and namespaced policy lists you can delete one policy or several at once. How to select objects and confirm deletion: Bulk deletion.

Recommendation to get started: Use namespace policies (CiliumNetworkPolicy). They are simpler and do not affect the entire cluster.

Screenshot: cluster network policy editor

Cluster network policy editor

Screenshot: namespace network policies editor

Namespace network policy editor


Key concepts

Ingress (incoming traffic)

Who can connect to your pods:

  • Outside the cluster - users, external services, Ingress Controller
  • Inside the cluster - pods from other namespaces
  • Inside the namespace - pods from the same namespace

Egress (outgoing traffic)

Where can your pods connect from:

  • Outside the cluster - external APIs, Internet (via IP or FQDN)
  • Inside the cluster — Kubernetes DNS, KubeAPI, other pods and services
  • Inside the namespace - pods and services in the same namespace

matchLabels

Selector by pod labels. Example:

matchLabels:
  app.kubernetes.io/name: my-backend
  io.kubernetes.pod.namespace: my-app

The rule only applies to pods that have all of the specified labels. View pod labels: kubectl get pod <name> -n <ns> --show-labels.


Step-by-step setup: web application with Ingress

Typical scenario: an application in the namespace my-app, accessible from the outside via Ingress.

Step 1: Determine what traffic you need

DirectionWho/whereWhy
IngressBy Ingress ControllerUsers log in via browser
EgressTO Kubernetes DNSResolve names (Service, external domains)
Egress (optional)To KubeAPIOnly necessary for operators, controllers

Step 2: Find out the Ingress Controller labels

kubectl get pods -n ingress -l app.kubernetes.io/name=shturval-ingress-controller --show-labels

Typically: app.kubernetes.io/name: shturval-ingress-controller, io.kubernetes.pod.namespace: ingress.

Step 3: Create a policy

One policy with all the rules you need:

apiVersion: cilium.io/v2
kind: CiliumNetworkPolicy
metadata:
  name: my-app-policy
  namespace: my-app
spec:
  description: "Web app: Ingress from controller + DNS"
  endpointSelector: {}
  ingress:
    - fromEndpoints:
        - matchLabels:
            app.kubernetes.io/name: shturval-ingress-controller
            io.kubernetes.pod.namespace: ingress
  egress:
    - toEndpoints:
        - matchLabels:
            io.kubernetes.pod.namespace: kube-system
            k8s-app: kube-dns
      toPorts:
        - ports:
            - port: "53"
              protocol: UDP
            - port: "53"
              protocol: TCP
            - port: "9153"
              protocol: TCP
    - toEndpoints:
        - matchLabels:
            io.kubernetes.pod.namespace: kube-system
            app.kubernetes.io/name: shturval-caching-dns
  toPorts:
        - ports:
            - port: "53"
              protocol: UDP
            - port: "9253"
              protocol: TCP

What this policy does:

  • endpointSelector: {} - applies to all serves in my-app
  • ingress - allows incoming traffic only from Ingress Controller
  • egress - allows outgoing traffic to kube-dns and shturval-caching-dns (DNS)

Step 4: Check

# Application responds
curl -H "Host: app.example.com" http://<INGRESS_IP>/

# Pods in Running
kubectl get pods -n my-app

If the application has stopped working, see Troubleshooting.


Typical scenarios

Scenario 1: Frontend + Backend in one namespace

Frontend receives traffic from Ingress and goes to Backend under the name Service.

Need to allow:

  • Ingress: from Ingress Controller
  • Egress: DNS, traffic to Backend (by labels or toEndpoints: [{}] inside namespace)

Scenario 2: Backend calls external API

Backend calls https://api.external.com.

Need to allow:

  • Egress: DNS (required for FQDN)
  • Egress: to CIDR or FQDN of external API (by IP or domain)

Scenario 3: Backend for internal pods only

The backend should not be accessible from the outside, only from other pods in the cluster.

Need to allow:

  • Ingress: from specific namespaces or subin (by label)
  • Egress: DNS, if necessary - to other services

Scenario 4: Operator or Controller

Pods that need access to KubeAPI (for example, operators).

Need to allow:

  • Egress: toEntities: [kube-apiserver]
  • Egress: DNS (if needed)

Examples with explanations

Access to KubeAPI server

For pods that need access to the Kubernetes API (operators, controllers):

apiVersion: cilium.io/v2
kind: CiliumNetworkPolicy
metadata:
  name: allow-kube-apiserver
  namespace: operators
spec:
  description: "For endpoints in this namespace, allow egress to kube-apiserver"
  endpointSelector: {}
  egress:
    - toEntities:
        -kube-apiserver

toEntities: [kube-apiserver] is a special Cilium selector for the cluster API server.


Deny policy (deny-all)

Blocks all traffic in the namespace. Use carefully only if complete isolation is desired.

Screenshot: deny-all in the editor

Denying policy deny-all

apiVersion: "cilium.io/v2"
kind: CiliumNetworkPolicy
metadata:
  name: deny-all
  namespace: yournamespacename
spec:
  description: "Deny all traffic in this namespace"
  endpointSelector: {}
  ingress: [{}]
  egress: [{}]

ingress: [{}] and egress: [{}] - empty rules mean “do not allow anything”.


Outgoing traffic to KubeDNS and NodeLocalDNS

Mandatory for pods that are accessed by name (Service, FQDN):

apiVersion: cilium.io/v2
kind: CiliumNetworkPolicy
metadata:
  name: allow-dns
  namespace: yournamespacename
spec:
  description: "For endpoints in this namespace, allow egress to kube-dns and shturval-caching-dns"
  endpointSelector: {}
  egress:
    - toEndpoints:
        - matchLabels:
            io.kubernetes.pod.namespace: kube-system
            k8s-app: kube-dns
      toPorts:
        - ports:
            - port: "53"
              protocol: UDP
            - port: "53"
              protocol: TCP
            - port: "9153"
              protocol: TCP
    - toEndpoints:
        - matchLabels:
            io.kubernetes.pod.namespace: kube-system
            app.kubernetes.io/name: shturval-caching-dns
      toPorts:
        - ports:
            - port: "53"
              protocol: UDP
            - port: "9253"
              protocol: TCP

Port 9153 - CoreDNS metrics; 9253 - shturval-caching-dns. Without them, DNS may be unstable.


Outgoing traffic to a specific application

Allow pods in the namespace to go only to a specific backend:

Screenshot: allow-egress in the editor

Outgoing traffic to the application

apiVersion: cilium.io/v2
kind: CiliumNetworkPolicy
metadata:
  name: allow-egress
  namespace: frontend
spec:
  description: "Allow egress to backend app only"
  endpointSelector: {}
  egress:
    - toEndpoints:
        - matchLabels:
            io.kubernetes.pod.namespace: backend
            app.kubernetes.io/name: api
      toPorts:
        - ports:
            - port: "8080"
              protocol: TCP

Replace backend, api, 8080 with real namespace, application name and port. Don’t forget to add a DNS rule if the backend is specified by the name Service.


Incoming traffic from the Ingress controller

Required for applications accessible externally via Ingress:

Screenshot: allow-ingress in the editor

Incoming traffic from Ingress

apiVersion: cilium.io/v2
kind: CiliumNetworkPolicy
metadata:
  name: allow-ingress
  namespace: my-app
spec:
  description: "Allow ingress from ingress-controller"
  endpointSelector: {}
  ingress:
    - fromEndpoints:
        - matchLabels:
            app.kubernetes.io/name: shturval-ingress-controller
            io.kubernetes.pod.namespace: ingress

Without this rule, requests from users will not reach the application pods.


Traffic inside the namespace

Allow pods in namespace to communicate with each other:

Screenshot: allow-in-namespace in the editor

Traffic inside namespace

apiVersion: cilium.io/v2
kind: CiliumNetworkPolicy
metadata:
  name: allow-in-namespace
  namespace: my-app
spec:
  description: "Allow all traffic within namespace"
  endpointSelector: {}
  ingress:
    - fromEndpoints: [{}]
  egress:
    - toEndpoints: [{}]

fromEndpoints: [{}] and toEndpoints: [{}] - “any pods in the same namespace.”


Complete example: application with Ingress, DNS and internal API

Web application my-app in the namespace my-app: external access via Ingress, internally runs in Redis under the name Service.

apiVersion: cilium.io/v2
kind: CiliumNetworkPolicy
metadata:
  name: my-app-full
  namespace: my-app
spec:
  description: "Web app: Ingress + DNS + Redis in same namespace"
  endpointSelector: {}
  ingress:
    - fromEndpoints:
        - matchLabels:
            app.kubernetes.io/name: shturval-ingress-controller
            io.kubernetes.pod.namespace: ingress
  egress:
    # DNS - required
    - toEndpoints:
        - matchLabels:
            io.kubernetes.pod.namespace: kube-system
            k8s-app: kube-dns
      toPorts:
        - ports:
            - port: "53"
              protocol: UDP
            - port: "53"
              protocol: TCP
            - port: "9153"
              protocol: TCP
    - toEndpoints:
        - matchLabels:
            io.kubernetes.pod.namespace: kube-system
            app.kubernetes.io/name: shturval-caching-dns
      toPorts:
        - ports:
            - port: "53"
              protocol: UDP
            - port: "9253"
              protocol: TCP
    # Redis in the same namespace
    - toEndpoints:
        - matchLabels:
            app: redis
      toPorts:
        - ports:
            - port: "6379"
              protocol: TCP

Common mistakes

ErrorWhat’s going onSolution
Forgot to resolve DNSPods do not resolve names, application does not workAdd egress to kube-dns and shturval-caching-dns
Forgot to allow IngressUsers cannot log in via browserAdd ingress from shturval-ingress-controller
Only one DNS port specifiedResolve may be unstableAdd ports 53 (UDP/TCP), 9153, 9253
Incorrect labels in matchLabelsThe rule doesn’t workCheck kubectl get pod ... --show-labels
Apply deny-all without allowing rulesEverything is blockedFirst add all the necessary permissions, then, if necessary, deny-all

Check after setup

  1. Pods started: kubectl get pods -n <namespace>
  2. The application responds: curl or browser by URL
  3. Application logs: kubectl logs -n <namespace> <pod>
  4. Cilium: for problems - Troubleshooting

More information about approaches to configuration can be found in Reference.