Control pod egress (and the FQDN limitation)
Egress control limits what your pods can call out to, which is how you contain data exfiltration and a compromised pod's reach. On Syself Autopilot you write egress with Cilium policy at Layers 3 and 4: address, port, and workload identity. There is one limitation to plan around. FQDN and DNS-based rules are rejected the moment you apply them, so you name destinations by address and identity, not by hostname.
Egress is the outbound half of segmentation. Start from Segment with network policies for the two network layers and the default-deny baseline; this page covers what leaves the cluster and the FQDN gate in full.
Why FQDN and DNS rules are rejected here
Rules that inspect a request need a userspace proxy on the node, and the node's datapath denies the proxy's return path today. A rule you apply would then silently break the traffic it is meant to permit, and a broken DNS rule can cut off name resolution for other workloads on the same node. A built-in admission policy (syself-restrict-l7-proxy-policies) rejects these rules at apply time with a readable error, so a bad rule never ships to break traffic quietly.
| Rule family | Status |
|---|---|
Application-layer rules (rules.http, rules.kafka, rules.l7proto, rules.l7) | Rejected permanently in a CiliumClusterwideNetworkPolicy; rejected for now in a namespaced CiliumNetworkPolicy |
DNS rules (toPorts[].rules.dns, and toFQDNs, which needs one) | Rejected for now in both kinds |
| Layer 3 and Layer 4 rules (identity, IP ranges, entities, ports, protocols) | Work today, entirely in the kernel |
The application-layer restriction is permanent in a clusterwide policy by design: it applies to every namespace at once, so one wrong request-level match can change how a workload another team owns behaves. In a namespaced policy the same rules lift once the datapath return path works and is verified. The DNS-rule restriction lifts once every node in the fleet runs the fixed node image, not in the release that first ships it, since a policy is cluster-scoped while the fix lands per node. Networking and Cilium explains the datapath and DNS gates under the hood; Control admission covers the admission policy itself.
Egress rules that work today
Write egress on identity, address, and port. This policy lets a workload reach only its database and DNS, and nothing else outbound:
apiVersion: cilium.io/v2
kind: CiliumNetworkPolicy
metadata:
name: app-egress
namespace: team-web
spec:
endpointSelector:
matchLabels:
app: web
egress:
# to the database pods, by identity
- toEndpoints:
- matchLabels:
app: db
toPorts:
- ports:
- {port: "5432", protocol: TCP}
# to a specific external API, by CIDR
- toCIDR:
- "203.0.113.10/32"
toPorts:
- ports:
- {port: "443", protocol: TCP}
# DNS to CoreDNS (an L4 allow to kube-dns, not a DNS-content rule)
- toEndpoints:
- matchLabels:
k8s-app: kube-dns
io.kubernetes.pod.namespace: kube-system
toPorts:
- ports:
- {port: "53", protocol: UDP}
- {port: "53", protocol: TCP}
The DNS entry here opens port 53 and nothing more. It carries no rules.dns block, so it does not put the pod's lookups through Cilium's DNS proxy and none of the FQDN restrictions apply to it. Allowing DNS this way works today.
A default-deny egress baseline
Flip a namespace's pods from "reach anything outbound" to "reach only what you allow" with a policy that selects every pod and lists only the egress you permit. With endpointSelector: {} and an egress list, anything not listed is denied. Always include the port-53 allow to CoreDNS, or the pods cannot resolve names and every outbound connection fails at the lookup.
When two rules overlap, the result is deny. There is no priority field, so you cannot write a broad deny and then allow one thing above it. Write each exception as its own selector.
When you need to name a destination by hostname
Allow-listing an external service by hostname, or restricting by URL path, is not something egress policy does here today. Two options work around this:
- Pin the destination by IP where it is stable, using
toCIDRas above, or route the workload through an egress gateway so the far end sees one stable source address. The egress gateway works today. - Use a service mesh for request-level HTTP control between your own services (allow only
GET /api, per-path rules). Istio is supported on the platform; see Handle unreliable networks with Istio .
Revisit FQDN policy when the datapath return-path limitation lifts. Until then, address and identity rules are the supported egress controls.
FQDN policy, for when the gate lifts
toFQDNs allows outbound traffic to a named domain like api.github.com instead of an IP range. Cilium reads the pod's own DNS answers, watches which addresses a name resolves to, and permits exactly those. The pattern below is the one to use once the DNS-rule gate lifts, so you can plan for it. Applying it today is rejected.
The policy needs two rules, and both are required. The first names the allowed destination with toFQDNs. The second allows DNS with a rules.dns block on port 53, and that second rule is what puts the pod's lookups through Cilium's DNS proxy so Cilium ever sees an answer. Without it, Cilium never learns the address behind api.github.com, the toFQDNs rule matches nothing, and every connection to that name is dropped. Deleting the second rule to make the example shorter breaks it completely.
apiVersion: cilium.io/v2
kind: CiliumNetworkPolicy
metadata:
name: egress-github-only
namespace: team-a
spec:
endpointSelector:
matchLabels: {app: ci-runner}
egress:
- toFQDNs:
- matchName: "api.github.com"
toPorts:
- ports: [{port: "443", protocol: TCP}]
- toEndpoints:
- matchLabels:
k8s:io.kubernetes.pod.namespace: kube-system
toPorts:
- ports: [{port: "53", protocol: UDP}]
rules: {dns: [{matchPattern: "*"}]}
Only the name lookup passes through the DNS proxy. The connection to the resolved address that follows goes straight from the pod to that address on the eBPF fast path, enforced at Layer 3 and Layer 4 like any other connection.
Related
- Segment with network policies : the two network layers and the ingress-side baseline
- Networking and Cilium : the datapath and DNS gates behind the rejection
- Control admission : the admission policy that rejects these rules
- Handle unreliable networks with Istio : request-level HTTP control through a service mesh
Segment with network policies
How Cilium enforces pod-to-pod traffic policy by label identity, and how to set up a default-deny baseline per namespace using standard NetworkPolicy objects.
Encrypt pod traffic with WireGuard
Enable WireGuard transparent encryption in Cilium so cross-node pod traffic is encrypted at the kernel level with no changes to your applications or workloads.