Cilium Series Part 16: CiliumNetworkPolicy Hands-On Lab
This article was last updated on: May 17, 2026 am
Series Articles
Introduction
Today we dive into Cilium security topics, walking through a detailed CiliumNetworkPolicy hands-on lab based on Cilium’s official Star Wars demo.
Scenario
You are part of the Empire’s platform engineering team, responsible for developing the Death Star API and deploying it to the Imperial Galactic Kubernetes Service (IGKS). You’ve deployed the service, but you need to ensure that only the Empire’s TIE fighters can make landing requests to the Death Star API via HTTP POST, and cannot use PUT on any other API paths (such as the exhaust port path. 😂😂😂 In the Star Wars series, the Death Star’s weakness is the exhaust port — it’s been blown up multiple times by proton torpedoes fired through the exhaust port…). It’s not that any TIE fighter pilot would intentionally put something in the exhaust port, but you never know — your team wants to use Cilium’s network policy support as a safeguard in case a TIE fighter pilot has a momentary lapse in judgment. You really don’t want Darth Vader to lose confidence in your ability to secure the Death Star service. You would find his lack of faith… disturbing.
Your goal is to create a CiliumNetworkPolicy resource that restricts access to the Death Star service so that TIE fighters can only make HTTP-based landing requests.
Prerequisites
First, you need a Kubernetes cluster with Cilium installed. The cluster created previously will suffice.
You also need to deploy a Death Star application, including service definitions, service backend pods, and TIE fighter client pods that access the service using internal-only cluster communication. The Cilium project has a Death Star demo application manifest example that you can use. You can install the manifest into the cluster’s default namespace using the following:
1 | |
│ 📝Notes
│
│ xwing: X-wing Starfighter. The Empire’s arch-nemesis: the Rebel Alliance’s primary fighter.
How did that get in there? No worries, we can make sure our network policy denies the X-wing access to the full Death Star service.
The Death Star service has been created with only a cluster-internal IP address, so only starships running within the cluster’s private network can access it:
1 | |
Additionally, a Cilium endpoint has been created for each new pod:
1 | |
Cilium has created endpoints corresponding to the Death Star backend pods as well as the X-wing and TIE fighter pods.
Note: The two deathstar-* endpoints share the same Identity ID. As discussed previously, they share the same Cilium Identity because they have the same set of security-relevant labels. The Cilium Agent uses identity IDs for endpoints matching relevant network policies to facilitate efficient key-value lookups by eBPF programs running in the network datapath.
Back to the task at hand! There are no network policies yet, so nothing should prevent the X-wing or TIE fighter from accessing the cluster-internal Death Star service via its fully qualified domain name (FQDN), and then having kube-proxy or Cilium forward the HTTP-based landing request to one of the Death Star backend pods.
Time to issue some landing requests from the TIE fighter and X-wing:
1 | |
The Death Star service is up and running. Now it’s time to implement network policies to restrict which pods can access the Death Star service.
Hands-On Lab
Empire Ingress Allow Policy
The simplest way to ensure the X-wing pod cannot access the Death Star service endpoints in this cluster is to write a label-based L3 policy that leverages the different labels used by the pods. An L3 policy restricts access to all network ports on the endpoint. If you want to restrict access to a specific port number, you can write a label-based L4 policy.
If you inspect the xwing pod, you’ll find it has the label org=alliance, while the tiefighter pod has the label org=empire:
1 | |
1 | |
An L4 network policy referencing TCP port 80 that only allows pods labeled org=empire will block the xwing pod from accessing the Death Star service endpoints. We can use the <networkpolicy.io> policy editor to craft this policy.
First, edit the central service-map element to configure the policy name and endpointSelector, adding the org=empire and class=deathstar labels to ensure the policy applies only to pods serving as Death Star service endpoints.

Then, create a new policy in the interactive service-map UI and add an “In Namespace” Ingress policy rule that uses org=empire as the pod selector expression and 80|TCP as the value for the “To Ports” input field.

Now, the service map in the policy editor should show that only ingress from pods labeled org=empire in the same namespace as the Death Star service can access TCP port 80 on the corresponding Death Star endpoints.
The read-only YAML from the policy editor should look similar to this:
1 | |
Note that this L4 policy specifically restricts ingress access to the deathstar-* pods serving as service endpoints, not to the Death Star service itself.
If you want to restrict a pod’s egress access to a limited number of services, you can create an egress policy for the client pods, referencing the allowed services by name in the egress policy’s toServices attribute. In our case, this would mean writing egress policies with different toServices information for the xwing and tiefighter pods. That’s possible, but this time it’s easier to achieve our goal using a single ingress policy that only allows Empire units to access the Death Star API while denying access to everyone else.
As for whether you should write an ingress or egress policy, it depends on your intent. Do you want to control who a pod is allowed to send traffic to? If so, egress is likely the policy you want to write. If you want to control which pods can initiate communication with a specific service or endpoint, then an ingress policy is most likely the simplest way to achieve that intent.
You can download the L4 policy from the policy editor UI to a file named allow-empire-in-namespace.yaml and apply it to the cluster:
1 | |
Now with this policy in place, the X-wing can no longer access the landing request API:
1 | |
The curl command issued from the xwing pod will fail with an error.
But the same command issued from the tiefighter pod still succeeds:
1 | |
Success! The X-wing pod can no longer access the Death Star API, but all other pods labeled org=empire can still access the full API, including the troublesome exhaust port:
1 | |
Oops! But we can fix this with an L7 HTTP policy to further restrict access, so that the exhaust port API endpoint is only accessible to Imperial maintenance droids, not rookie pilots who can’t tell a landing bay from an exhaust port. We can address the design flaw of the API exhaust port endpoint in the next development sprint. But for now, let’s use the CiliumNetworkPolicy custom resource definition to restrict access so this doesn’t happen again.
Adding L7 HTTP Path-Specific Allow Policy
Let’s extend the Empire access policy to explicitly include rules for both the landing path and the exhaust port path.
Now, these rules will match on both org and class labels.
1 | |
Save this policy update to the file allow-empire-in-namespace.yaml and apply it to the cluster:
1 | |
Now, the TIE fighter will receive an HTTP 403 Forbidden message instead of being able to access the exhaust port, because the Cilium Agent runs an embedded HTTP proxy on the node where the Death Star backend pods are running.
1 | |
X-wing fighters attempting to access the Death Star API are still denied by the L4 policy, which drops packets and causes a connection timeout rather than returning an HTTP Forbidden status message.
We have successfully restricted access to the Death Star API so that TIE fighters can make landing requests without being able to access the exhaust port. We’ve also blocked any X-wing fighters in the cluster from accessing the Death Star API. Lord Vader will be pleased.
Note: The behavioral difference between L3/L4 policies and L7 policies in handling dropped packets is expected, as different implementations are used. For L3/L4 policies, eBPF programs running in the Linux network datapath drop the packets — they are essentially swallowed by a black hole in the network. L7 policies, on the other hand, run an embedded HTTP proxy that acts like an HTTP server, rejecting requests and providing an HTTP status response to the client explaining the reason for the denial. Regardless of which implementation is used, you can use Hubble to inspect network flows and track whether packets are being dropped at the Death Star endpoint’s ingress. We will cover this in a later chapter.
Summary
Setting aside the Star Wars whimsy, this hands-on lab demonstrates an approach to writing CiliumNetworkPolicy that helps secure access between pods running within a cluster. You can use CiliumNetworkPolicy to establish reasonable restrictions based on expected workload behavior (encoded as label metadata), rather than implicitly trusting that pods have full access to all services exposed by peer pods in the cluster. Restricting network access in this way prevents issues caused by misconfigured or vulnerable applications interacting with services in unexpected ways.