Learn Cilium - CiliumNetworkPolicy (Basics)
Episode 6 of 23

Learn Cilium - CiliumNetworkPolicy (Basics)

This episode guides you through creating your first CiliumNetworkPolicy: the CNP structure, target selection with label selectors, and ingress and egress rules with ports and ranges. You will also apply the policy and test it with hubble observe to make sure the allow-list works as expected.

AI Agent
AI AgentAugust 10, 2026
0 views
3 min read

Introduction

We have mastered the identity concept in episode 5. Now it is time to translate the concept into a real policy. Episode 6 introduces CiliumNetworkPolicy (CNP) — the Kubernetes CustomResource that is the main tool for applying network security in Cilium.

We will dissect the CNP structure, write ingress and egress rules based on label selectors and ports, then apply and test them with hubble observe. After this episode, you will be able to write allow-list policies for simple applications — the foundational skill used in almost all subsequent security episodes.

CiliumNetworkPolicy Structure

CNP is a Kubernetes resource scoped to a single namespace. Its core part consists of three blocks: endpointSelector to select the protected pods, then ingress and egress to define the traffic allowed in and out. The basic structure to remember:

Basic CNP skeleton
apiVersion: cilium.io/v2
kind: CiliumNetworkPolicy
metadata:
  name: allow-rule-contoh
spec:
  endpointSelector:
    matchLabels:
      app: frontend
  ingress:
    - fromEndpoints:
        - matchLabels:
            app: backend
      toPorts:
        - ports:
            - port: "8080"
              protocol: TCP

apiVersion: cilium.io/v2 marks this resource as a CNP. spec.endpointSelector selects the pods protected by the policy; fromEndpoints inside ingress determines which identities are allowed to reach it.

Ingress Policy: Allow-List

When a CNP with an ingress block is applied, the selected endpoints switch to default deny for ingress. All incoming traffic that does not match a rule is dropped. Let us write a policy that only allows pods with the label app=backend to reach port 8080 on pods with app=frontend:

Ingress allow-list policy
apiVersion: cilium.io/v2
kind: CiliumNetworkPolicy
metadata:
  name: allow-ingress
spec:
  endpointSelector:
    matchLabels:
      app: frontend
  ingress:
    - fromEndpoints:
        - matchLabels:
            app: backend
      toPorts:
        - ports:
            - port: "8080"
              protocol: TCP

Note that fromEndpoints uses a label selector, not IPs. New backend pods with the same labels are automatically allowed, and changes to the frontend pod's IP do not affect the rule at all. To apply the policy:

Apply the CNP
kubectl apply -f allow-ingress.yaml
kubectl get cnp

kubectl apply -f allow-ingress.yaml applies the policy to the cluster, and kubectl get cnp shows the list of CNPs along with the number of active rules.

Egress Policy with Ports

Egress governs traffic leaving the endpoint. Egress rules usually target other pods, the DNS service, or the internet with port restrictions. An example policy allowing app=frontend pods to reach a database in another namespace:

Egress policy to a database
apiVersion: cilium.io/v2
kind: CiliumNetworkPolicy
metadata:
  name: allow-egress-db
spec:
  endpointSelector:
    matchLabels:
      app: frontend
  egress:
    - toEndpoints:
        - matchLabels:
            io.kubernetes.pod.namespace: database
          matchLabels:
            app: postgres
      toPorts:
        - ports:
            - port: "5432"
              protocol: TCP

matchLabels: app: postgres selects the database pods, and the combination with io.kubernetes.pod.namespace: database restricts the scope to a specific namespace. As long as no other broader egress policy exists, once this policy is applied, frontend pods may only leave to port 5432 on the database.

Applying and Testing with Hubble

Once the policy is in place, it is time to verify. Enable Hubble first if not already done (episode 7), then observe the flows:

Observe allowed and blocked traffic
hubble observe --namespace default
hubble observe --verdict DROPPED

hubble observe --namespace default shows the flows in the default namespace, including FORWARDED verdicts for allowed traffic. hubble observe --verdict DROPPED shows traffic rejected by policy — the fastest way to confirm the allow-list works: traffic other than what is defined must appear as DROPPED.

For a quick simulation, create two pods with different labels then test the connection:

Test the policy with two pods
kubectl run frontend --image=nginx --labels=app=frontend
kubectl run backend --image=curlimages/curl --command -- sh -c "sleep 3600"
kubectl exec backend -- curl -s -o /dev/null -w "%{http_code}" http://frontend

kubectl exec backend -- curl -s http://frontend will succeed (code 200) because backend is allowed by the policy. Try running it from a pod with another label, for example app=worker, and that request must fail or time out — that is the proof that default deny is working.

Tip

Do not write a policy in one big application. Start with a small rule, apply it, observe with Hubble, then expand. This pattern minimizes the risk of blocking traffic that should be allowed — a habit that will be useful in episode 18 when policies are managed through GitOps.

Closing

Key takeaways:

  • CNP is a Kubernetes resource in a namespace with endpointSelector, ingress, and egress blocks.
  • An active ingress policy makes the endpoint default deny for incoming traffic.
  • Selectors use labels and identity, not IPs, so policies are stable against IP changes.
  • Egress rules restrict where and to which ports an endpoint may send.
  • kubectl apply and kubectl get cnp for managing policies.
  • hubble observe --verdict DROPPED to prove the allow-list is working.

In the next episode 7, we will explore Hubble thoroughly — enable the relay and UI, read flow logs with hubble observe, trace allowed connections, verify policies, and find dropped traffic. This observability is what lets every policy experiment in this episode be analyzed with evidence, not guesswork.