TT Lab
Get started
Learn Learning paths Courses

CCA — Cilium Certified Associate

Building Up From Default Deny to L7

Continue in TT Lab

Goal

Actually apply default deny and selector-based allows with the standard NetworkPolicy, and write as manifests the L7, FQDN, and explicit deny features that are possible only with CiliumNetworkPolicy.

Why it matters

A successful apply does not guarantee that a network policy works as intended. Three things in particular quietly deceive people.

First, the independence of directions. If you set only an ingress policy and think "it is isolated," egress is still open to the entire internet. Most data exfiltration paths are on the egress side.

Second, a selector typo fails quietly, not as an error but as "selects nothing." After applying, you must check how many endpoints are actually in an enforced state.

Third, toFQDNs never matches without a DNS rule. This is the most common configuration mistake in Cilium policies, and it comes up repeatedly on the exam.

Actually apply the standard NetworkPolicy, and write the CiliumNetworkPolicy as files under /root/cca-policy/. This is because the Cilium CRDs are not installed in the lab cluster, and the skill the exam demands is also writing manifests correctly.

Steps

  1. Create the namespaces cca-shop and cca-mon, and put the label tier=monitoring on cca-mon.
  2. In the namespace cca-shop, deploy the Deployments frontend and backend with 1 replica each. The Pod labels are app=frontend and app=backend respectively, and the image is nginx:1.27-alpine. Then create the Service backend on port 8080.
  3. In the namespace cca-shop, create the NetworkPolicy default-deny. The podSelector is empty, policyTypes lists both Ingress and Egress, and you put no allow rules at all.
  4. In the same namespace, create the NetworkPolicy allow-frontend-to-backend. The podSelector is app=backend, policyTypes is just Ingress, and the ingress rule allows only sources whose podSelector is app=frontend, only on port 8080 / protocol TCP.
  5. In the same namespace, create the NetworkPolicy allow-monitoring. The podSelector is app=backend, the from entry of the ingress rule has just one namespaceSelector that selects the namespace with tier=monitoring, and the port is 9090.
  6. Write a CiliumNetworkPolicy in /root/cca-policy/cnp-l7.yaml. The apiVersion is cilium.io/v2, metadata.namespace is cca-shop, and the endpointSelector is app: backend. The ingress rule has fromEndpoints of app: frontend, and under port 8080/TCP of toPorts, it has two entries in rules.http. The first is method GET / path /api/v1/orders, and the second is method POST / path /api/v1/orders.
  7. Write a CiliumNetworkPolicy in /root/cca-policy/cnp-fqdn.yaml. The endpointSelector is app: frontend, and there are exactly two egress rules. The first opens port 53/UDP to the target whose toEndpoints is k8s-app: kube-dns and puts matchPattern: "*" in rules.dns. The second has a toFQDNs matchName of api.pgprovider.example and opens port 443/TCP.
  8. In /root/cca-policy/cnp-deny.yaml, write a CiliumNetworkPolicy named block-metadata. The endpointSelector is empty; in the egress rule, allow cidr: 0.0.0.0/0 with toCIDRSet but put 169.254.169.254/32 in except, and in addition state 169.254.169.254/32 explicitly in the toCIDR of egressDeny.

Notes

Create the application and monitoring namespaces

Create the namespaces cca-shop and cca-mon, and put the label tier=monitoring on cca-mon.

A namespaceSelector can only see a namespace's labels. You cannot select by name, so attach the label beforehand.

Deploy the frontend and backend

In the namespace cca-shop, deploy the Deployments frontend and backend with 1 replica each. The Pod labels are app=frontend and app=backend respectively, and the image is nginx:1.27-alpine. Then create the Service backend on port 8080.

Policies select their targets by label. The app label in the Pod template has to be exact for all the later steps to fit together.

Create a default deny in both directions

In the namespace cca-shop, create the NetworkPolicy default-deny. The podSelector is empty, policyTypes lists both Ingress and Egress, and you put no allow rules at all.

The directions are independent. If you list only one, the opposite direction is still fully open. Do not put in any allow rules.

Open just one path with a Pod selector

In the same namespace, create the NetworkPolicy allow-frontend-to-backend. The podSelector is app=backend, policyTypes is just Ingress, and the ingress rule allows only sources whose podSelector is app=frontend, only on port 8080 / protocol TCP.

The policy's podSelector selects the receiving side, and the podSelector in from selects the sending side. In this step you allow only within the same namespace, so do not put a namespaceSelector alongside it.

Open another namespace with a namespace selector

In the same namespace, create the NetworkPolicy allow-monitoring. The podSelector is app=backend, the from entry of the ingress rule has just one namespaceSelector that selects the namespace with tier=monitoring, and the port is 9090.

Use the namespace label you attached in step 1. The port you need to open is the metrics port, not the application port.

Restrict by HTTP method and path

Write a CiliumNetworkPolicy in /root/cca-policy/cnp-l7.yaml. The apiVersion is cilium.io/v2, metadata.namespace is cca-shop, and the endpointSelector is app: backend. The ingress rule has fromEndpoints of app: frontend, and under port 8080/TCP of toPorts, it has two entries in rules.http. The first is method GET / path /api/v1/orders, and the second is method POST / path /api/v1/orders.

This is territory the standard NetworkPolicy cannot reach. Put rules.http under toPorts and list the methods and paths. A request that does not match comes back as a 403, not as a block.

Allow an external API by domain name

Write a CiliumNetworkPolicy in /root/cca-policy/cnp-fqdn.yaml. The endpointSelector is app: frontend, and there are exactly two egress rules. The first opens port 53/UDP to the target whose toEndpoints is k8s-app: kube-dns and puts matchPattern: "*" in rules.dns. The second has a toFQDNs matchName of api.pgprovider.example and opens port 443/TCP.

toFQDNs alone never works. The IP is registered only after the DNS proxy learns the response, so a rule that allows DNS queries and makes them observable must always come as its pair.

Seal the metadata endpoint

In /root/cca-policy/cnp-deny.yaml, write a CiliumNetworkPolicy named block-metadata. The endpointSelector is empty; in the egress rule, allow cidr: 0.0.0.0/0 with toCIDRSet but put 169.254.169.254/32 in except, and in addition state 169.254.169.254/32 explicitly in the toCIDR of egressDeny.

Use both methods: carving an exception into the allow rule, and laying down an extra layer of explicit deny. Deny beats allow, so the two layers do not contradict each other.