Skip to content
Edouard Topin's Blog
Kubernetes security in production / Series 02/04

Network policies and Cilium: building a defensible default-deny

The NetworkPolicy API ships with Kubernetes; enforcing it is the CNI's job. What Cilium adds, what stays standard, and how to reach default-deny by watching real flows before blocking any.

Edouard Topin
23 min read
Abstract editorial illustration of a mesh of network flows where only a few paths remain open.

An auditor opens the network chapter on your production cluster and asks exactly one question: the evidence of what is denied, not the list of what is allowed. On vks-fret-01, the three namespaces webshop-web, webshop-app and webshop-db share the cluster with four other teams, no NetworkPolicy exists, and the database answers any pod that knocks. Nobody knows which flows actually exist — and that is not operational sloppiness, it is a non-capability the Kubernetes project publishes about itself.

This article separates three planes that half the third-party content blends together: the upstream networking.k8s.io/v1 API and the list of ten things it does not do, what Cilium adds on top of it in cilium.io/v2 together with its published limits, and what VKS actually ships. Out of those comes a four-phase cutover path whose first phase is an observation phase. The Nordwind Logistics setting is a working hypothesis: no lab was run for this article, and anything that depends on an instance is flagged as such.

Upstream API networking.k8s.io/v1Cilium CRD cilium.io/v2No lab was run

TL;DR

  • The decision: default-deny is obtained by selection without any allow rule, never by a deny keyword — the upstream API publishes that it has no explicit deny. And an object applied on a cluster with no plugin implementing it filters nothing, with no error and no warning.
  • The trade-off that costs: switching CNI to get explicit deny and flow logging is a choice paid for in portability — Cilium and Antrea policies are CRDs outside the Kubernetes tree, and on VKS the CNI is declared at cluster creation time. Before getting there, check what the CNI already in place documents: on vks-fret-01, Antrea already covers both points.
  • Monday morning: one negative test in a throwaway namespace. Apply a default-deny-all, verify that a flow is genuinely cut, delete the object. A successful kubectl apply proves nothing.

Version contract

This series runs on a Kubernetes 1.35 floor and a 1.36 target. Two reasons for the floor: the 1.34 branch leaves support on 2026-10-27, before this article is published, and the VKS Cilium add-on publicly requires “VKS 3.6 or later, and VKr 1.35 or later”. The Cilium documentation branch cited here is v1.20; the Antrea anchor is 2.6.1, the version shipped in VKr 1.36.1.

One drift worth knowing: the official kubernetes/sig-release repository publishes a 1.37 target date of 26 August 2026, which is earlier than this publication. In practice the three supported branches by the time you read this will be 1.35, 1.36 and 1.37 — but no 1.37 feature page was loaded for this article, and nothing below describes a 1.37 capability.

Mechanism apiVersion Published state Version
NetworkPolicy networking.k8s.io/v1 stable — (the page carries no FEATURE STATE banner)
NetworkPolicyendPort field networking.k8s.io/v1 stable v1.25
AdminNetworkPolicy / BaselineAdminNetworkPolicy policy.networking.k8s.io/v1alpha1 alpha, out-of-tree, classified “Previous” by their own project merged into ClusterNetworkPolicy
ClusterNetworkPolicy policy.networking.k8s.io/v1alpha2 alpha, out-of-tree, “Active (under development)”
CiliumNetworkPolicy / CiliumClusterwideNetworkPolicy cilium.io/v2 Cilium CRD, outside the Kubernetes tree docs v1.20
Antrea ClusterNetworkPolicy / NetworkPolicy crd.antrea.io/v1beta1 Antrea CRD, outside the Kubernetes tree Antrea 2.6.1

Two mechanisms are not covered here because they are behind us. PodSecurityPolicy was deprecated in v1.21 and removed in v1.25: it does not exist in this series’ floor, and the RBAC article treats it in the past tense. And AdminNetworkPolicy is not where the project is heading — see below.

Plane A — the upstream API, and the ten things it publishes it cannot do

“Network policies are off by default” is wrong in both directions, and that is the first correction to make. The NetworkPolicy resource belongs to the networking.k8s.io group and exists in any conformant cluster: there is nothing to switch on at the API level. What is missing is the enforcer, and the upstream page writes it as a prerequisite:

“Network policies are implemented by the network plugin. To use network policies, you must be using a networking solution which supports NetworkPolicy. Creating a NetworkPolicy resource without a controller that implements it will have no effect.”

The operational translation is blunt: a kubectl apply of a deny policy on a cluster whose CNI does not implement it returns networkpolicy.networking.k8s.io/default-deny-all created. No error, no warning, not one packet filtered. The default behaviour of a pod really is wide open — “By default, a pod is non-isolated for ingress; all inbound connections are allowed” — but through absence of selection, not through configuration. A pod only becomes ingress-isolated once a policy selects it and carries Ingress in policyTypes.

Three independent conditions, therefore three distinct failure modes: the object always exists, the plugin may not be there, and the pod is only isolated if it is selected.

The list the project publishes about itself

Section What you can’t do with network policies (at least, not yet), ten entries, reproduced in full:

  • “Forcing internal cluster traffic to go through a common gateway (this might be best served with a service mesh or other proxy).”
  • “Anything TLS related (use a service mesh or ingress controller for this).”
  • “Node specific policies (you can use CIDR notation for these, but you cannot target nodes by their Kubernetes identities specifically).”
  • “Targeting of services by name (you can, however, target pods or namespaces by their labels, which is often a viable workaround).”
  • “Creation or management of ‘Policy requests’ that are fulfilled by a third party.”
  • “Default policies which are applied to all namespaces or pods (there are some third party Kubernetes distributions and projects which can do this).”
  • “Advanced policy querying and reachability tooling.”
  • “The ability to log network security events (for example connections that are blocked or accepted).”
  • “The ability to explicitly deny policies (currently the model for NetworkPolicies are deny by default, with only the ability to add allow rules).”
  • “The ability to prevent loopback or incoming host traffic (Pods cannot currently block localhost access, nor do they have the ability to block access from their resident node).”

Three of those ten are not details, they are project constraints that decide the shape of the cutover plan. No explicit deny: you cannot write “block webshop-db except from webshop-app”. No logging: the observation phase does not exist on this plane — you cannot know which flows you are about to break before you break them. No default policy across all namespaces: you need one object per namespace, applied by hand or by GitOps.

The reader who arrived looking for “how do I turn on default-deny” therefore leaves with the opposite, and far more useful, information: the upstream API on its own gives them nothing to prepare the cutover with. That is not an opinion, it is written in its own documentation.

The upstream default-deny, exactly as published

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: default-deny-all
spec:
  podSelector: {}
  policyTypes:
  - Ingress
  - Egress

Three things to read in those nine lines. podSelector: {} selects every pod in the namespace, not in the cluster — there is no cluster scope in the upstream API. There is no ingress and no egress clause at all: the denial comes from the absence of a rule, not from a keyword. And policyTypes carries both directions; dropping Egress from it changes the behaviour completely, and that is the most common mistake.

The page also publishes the warning that is worth an incident on its own: “A default deny-all egress policy also blocks DNS traffic. If your workloads need DNS resolution, you must add a separate NetworkPolicy that allows egress to your cluster’s DNS service.” DNS is therefore the first flow to reopen, and it is reopened before the deny is applied.

For the rest of the upstream surface: the immutable kubernetes.io/metadata.name label that Kubernetes stamps on every namespace is the published workaround for the “Targeting of services by name” entry; the endPort field has been stable since v1.25, with endPort greater than or equal to port and both ports numeric; and the SCTP note (“You must be using a CNI plugin that supports SCTP protocol NetworkPolicies”) carries no version banner on the page that was loaded, so I do not attribute one to it.

The out-of-tree API: the trail the project left behind

SIG Network’s network-policy-api.sigs.k8s.io subgroup classifies its own APIs: ClusterNetworkPolicy as Active (“undergoing active development”), AdminNetworkPolicy and BaselineAdminNetworkPolicy as Previous (“have evolved to the next stage and are no longer actively developed”), DeveloperNetworkPolicy as Future. A subgroup post dated 9 October 2025, signed by Nadia Pinaeva — a project post, not product documentation — publishes the merge: both v1alpha1 APIs become a single v1alpha2.ClusterNetworkPolicy with a tier field, and the published evaluation order is tier: Admin, then the upstream NetworkPolicy API, then tier: Baseline.

A 2025 piece presenting AdminNetworkPolicy as the future of network policy therefore describes a superseded state. That leaves the platform landing: the Implementations page of the same project gives ClusterNetworkPolicy a “Standard support in Calico v3.32”, and VKr 1.36.1 ships Calico 3.31.5. On this series’ reference platform, that API is therefore out of reach of the Calico that is shipped — a version gap, not a product defect.

Plane B — what Cilium adds, and what it does not replicate

First fact to put down, because it answers “what do I lose”: Cilium supports four policy resources, and the first one on the list is the upstream resource — NetworkPolicy, Kubernetes ClusterNetworkPolicy, CiliumNetworkPolicy, CiliumClusterwideNetworkPolicy. The CRD’s positioning is published as such: “The CiliumNetworkPolicy is very similar to the standard NetworkPolicy. The purpose is to provide the functionality which is not yet supported in NetworkPolicy.” The project also publishes its own warning about stacking them: mixing several policy types makes the resulting allowed traffic hard to reason about.

Identity changes the reasoning. In Cilium, “security is based on the identity of a pod, which is derived through labels. This identity can be shared between pods.” Reserved entities are the piece most directly useful to an egress cutover: host (“includes the local host”), remote-node, cluster (“the logical group of all network endpoints inside of the local cluster”), world (“Allowing to world is identical to allowing to CIDR 0.0.0.0/0”) and above all kube-apiserver, which “represents both deployments of the kube-apiserver: within the cluster and outside of the cluster”. That last one answers the upstream “Targeting of services by name” entry for this one case and this case only: on the upstream API, allowing egress to the API server forces you to write an ipBlock with a VIP address, which moves.

The limit is published on the same page and must travel with it: “The kube-apiserver entity may not work for ingress traffic in some Kubernetes distributions, such as Azure AKS and GCP GKE.” VKS is not named in that sentence — I am not adding it and I am inferring nothing from it; it is yours to check on your platform.

The three enforcement modes

The published definitions, word for word: default — “endpoints have unrestricted network access until selected by policy. Upon being selected by a policy, the endpoint permits only allowed traffic”; always — “policy enforcement is enabled on all endpoints even if no rules select specific endpoints”; never — “policy enforcement is disabled on all endpoints, even if rules do select specific endpoints”. The setting goes through the policyEnforcementMode Helm value or the enable-policy flag.

The always mode is the only clean answer to the upstream “Default policies which are applied to all namespaces or pods” entry. It is also the most dangerous one to switch on in a production cluster: it isolates everything, including what has never been selected, and therefore never observed.

Deny policies: the precedence and its limit

apiVersion: "cilium.io/v2"
kind: CiliumClusterwideNetworkPolicy
metadata:
  name: "external-lockdown"
spec:
  endpointSelector: {}
  ingressDeny:
  - fromEntities:
    - "world"
  ingress:
  - fromEntities:
    - "all"

The precedence is published: “Deny policies take precedence over allow policies, regardless of whether they are a Cilium Network Policy, a Clusterwide Cilium Network Policy or even a Kubernetes Network Policy.” And the limit, published on the same page, never separates from it: “Deny policies do not support: policy enforcement at L7, i.e., specifically denying an URL and toFQDNs, i.e., specifically denying traffic to a specific domain name.” Put plainly: you cannot write a deny rule against a domain name.

L7 does not cut — it answers

The L7Rules structure carries two protocols on the page that was loaded, HTTP and DNS. The behaviour is the most counter-intuitive fact of plane B: “violation of layer 7 rules does not result in packet drops. Instead, if possible, an application protocol specific access denied message is crafted and returned, e.g. an HTTP 403 access denied”. The architectural cost is published too: “Layer 7 policies will proxy traffic through a node-local Envoy instance, which will either be deployed as a DaemonSet or embedded in the agent pod.” An L7 policy is therefore not a network filter, it is application access control served by a proxy — one more component in the data path, with a lifecycle of its own.

On the domain-name side, “By default, Cilium uses an in-agent DNS proxy for DNS policy enforcement”, the standalone proxy being announced as alpha. The published manifest combines two egress clauses: a toEndpoints towards the DNS service carrying rules: dns, and a toFQDNs by matchName or matchPattern. The dependency deserves to be spelled out: toFQDNs relies on the responses the proxy sees. Allowing port 53 at L4 without the rules: dns clause lets resolution happen but does not feed the table of allowed IPs — the page does not publish that chain as such, it is a reconstruction to verify on your platform.

Host Policies, and the lockout warning

“Host policies take the form of a CiliumClusterwideNetworkPolicy with a Node Selector instead of an Endpoint Selector”, enabled with --set hostFirewall.enabled=true. This is the catch-up on the upstream “The ability to prevent loopback or incoming host traffic” entry, and it has a price: host L7 DNS policies are published as beta, and adding L7 DNS rules to a host policy sends all of the node’s DNS queries through the proxy — unavailable while the agent restarts, which can leave the node unusable, kubelet included.

Two reservations I state out loud

The Caveats page of Cilium v1.20 writes: “Cilium Network Policies do not perfectly replicate the functionality of Kubernetes Network Policies.” It points to a comparison table whose target page, loaded twice, contains neither the announced section nor the table. So I publish the existence of the reservation and not its detail: a list of gaps is not something you invent.

Second hole, a divergence between two official pages loaded on the same day. The Cilium v1.20 docs say ClusterNetworkPolicy support is “available starting with Cilium 1.20”, flag --enable-k8s-cluster-network-policy=true. The Implementations page of network-policy-api.sigs.k8s.io does not list Cilium for that API — it files it under AdminNetworkPolicy with a “tracking issue” note. I quote both and I do not arbitrate: a CNI project page documents what the CNI implements, a conformance page documents what has been verified as conformant; both can be true at once.

One gap, though, is published and purely operational: “By default, ipBlock rules in NetworkPolicy do not match intra-cluster IPs (such as Pod or Node IPs)”, behaviour changeable via --policy-cidr-match-mode (pods or nodes). Direct consequence for a CNI migration: an upstream NetworkPolicy written on a CNI that does match ipBlock against pod IPs, then replayed unchanged on Cilium, changes meaning without changing text.

Plane C — what VKS actually ships

This is the distinction the article has to make explicitly, because it decides the order of work: on VKS, Cilium and Antrea are not at the same rank.

The VKS Components page in the 9.1 tree writes: “VKS clusters support the following Container Network Interface (CNI) options: Antrea (default) and Calico”, with the implementation detail “Antrea uses Open vSwitch. Calico uses Linux IP tables.” The VKS Cluster Networking page in the 9.0 tree says the same. Cilium is named on neither page, checked on both trees.

Cilium does exist, though: the VKS 3.6 release notes give it as a “validated Standard Package”, and a dedicated procedure page publishes its prerequisites — “Verify that your environment is running VKS 3.6 or later, and VKr 1.35 or later”, plus an add-on repository “v3.6.0+20260320 or later” installed on the Supervisor. Package version recorded: 1.19.1+vmware.1-vks.1, with four capabilities listed by Broadcom, among them “Kubernetes NetworkPolicy (L3/L4 Support)” and “CiliumNetworkPolicy (L7 Support)”. Hubble Relay and Hubble UI are deployment options of the package, the UI requiring Relay. Inbound ports to open: 2379-2380/TCP, 8472/UDP, 4240/TCP, 4244/TCP.

The choice is made at cluster creation time:

apiVersion: addons.kubernetes.vmware.com/v1alpha1
kind: AddonInstall
metadata:
  name: REPLACE_WITH_CLUSTER_NAME-cilium
  namespace: REPLACE_WITH_CLUSTER_NAMESPACE
spec:
  addonRef:
    name: cilium
  clusters:
  - constraints:
      expression: "cluster.cniRefName() == 'cilium' && version_in_range(cluster.spec.topology.version, '>=1.35.0')"
    selector:
      matchLabels:
        cluster.x-k8s.io/cluster-name: REPLACE_WITH_CLUSTER_NAME
  stopMatchingBehavior: Retain

The CNI is then declared in spec.topology.variables of the Cluster resource, via bootstrapAddons.cniRef. No loaded page describes a switch from Antrea to Cilium on an existing cluster. That is why the running example introduces a second cluster, vks-fret-lab-01, rather than an in-place migration — and not a coexistence of two CNIs in the same cluster, which is described nowhere.

Two more published constraints. A known issue: “The VKS Istio add-on is currently incompatible with clusters provisioned using the Cilium CNI; installation will fail as a result of a known limitation in the current Cilium add-on version.” And a version gap that governs the whole reading: the VKS package ships Cilium 1.19.1 while the citable documentation branch is v1.20. Any capability explicitly dated to 1.20 — ClusterNetworkPolicy support, precisely — is therefore out of reach of the shipped package. The rule: on VKS, read the Cilium docs for the version of the package, not the ones on the stable branch.

Antrea already answers two upstream non-capabilities

This is the most counter-intuitive point of this plane. Antrea publishes two CRDs under apiVersion: crd.antrea.io/v1beta1kind: ClusterNetworkPolicy (cluster scope) and kind: NetworkPolicy (namespace scope) — with four rule actions: "Allow", "Drop", "Reject", "Pass". And logging: when enableLogging is true, “the first packet of any traffic flow that matches this rule will be logged to a file (/var/log/antrea/networkpolicy/np.log) on the Node on which the rule is enforced”. Six tiers are published with their priorities — Emergency 50, SecurityOps 100, NetworkOps 150, Platform 200, Application 250, Baseline 253 — and policies in the baseline tier have lower precedence than the upstream NetworkPolicy objects written by developers.

So: on vks-fret-01, with the default CNI and without installing anything, two of the ten published non-capabilities of the upstream API already have a documented answer — explicit deny and logging. A VKS operator who believes they must move to Cilium in order to get an observation phase is solving a problem they do not have.

What do I lose if I change CNI

No page publishes this breakdown: it is an assembly of six official pages, to be read with its three caveats.

Capability Upstream API networking.k8s.io/v1 Antrea crd.antrea.io/v1beta1 (2.6.1) Cilium cilium.io/v2 (docs v1.20)
explicit deny published as absent Drop / Reject actions ingressDeny / egressDeny, published precedence
flow logging published as absent enableLoggingnp.log on the node Hubble + Policy Audit Mode
cluster scope published as absent kind: ClusterNetworkPolicy CiliumClusterwideNetworkPolicy
priorities / tiers no six tiers, priorities 50→253 absent from the loaded page; deny precedence stands in for ordering
targeting by service name published as absent absent from the loaded page partially — kube-apiserver entity
targeting by domain name no absent from the loaded page toFQDNs (not in deny)
L7 HTTP published as absent absent from the loaded page Envoy proxy, 403 response rather than a drop
host / node policy published as absent absent from the loaded page Host Policies, hostFirewall.enabled
port ranges endPort, stable v1.25 yes yes, except DNS rules
ClusterNetworkPolicy v1alpha2 not applicable (out-of-tree) listed under ANP v1alpha1 unresolved divergence

The “absent from the loaded page” cells are absences from the page consulted, not product absences. The Antrea and Cilium columns describe CRDs outside the Kubernetes tree: a policy written in one is not portable to the other, and the only portable row is the upstream API row. Finally, the Cilium column describes documentation branch v1.20 while the VKS package ships 1.19.1.

The four-phase path

The principle that orders them fits on one line: you only write a deny after observing what it is going to cut. None of these phases has been executed; what follows is a work plan, not an operations report.

Phase 0 — verify that the plugin enforces anything at all. On VKS, the CNI is read on the Cluster resource, not on the pods:

kubectl get cluster REPLACE_WITH_CLUSTER_NAME \
  -n REPLACE_WITH_CLUSTER_NAMESPACE \
  -o jsonpath='{.spec.topology.variables}'

kubectl get daemonset -A

What one expects to find there — an Antrea DaemonSet on vks-fret-01, a Cilium DaemonSet and an AddonInstall object on vks-fret-lab-01 — remains an expectation, not a reading. The only check that proves anything is the negative test: a default-deny-all in a throwaway namespace, a flow that was supposed to be cut and is, then deletion of the object.

Phase 1 — observe, before any deny. This is the phase the upstream API does not allow, and the tooling depends on the plane. On Cilium, Policy Audit Mode “configures Cilium to allow all traffic while logging all connections that would otherwise be dropped by network policies” and “supports auditing network policies implemented at networks layers 3 and 4” — with the published warning that it is “not recommended for production deployment”, hence its confinement to the lab cluster. It is enabled with --policy-audit-mode=true at daemon scope, or more narrowly with cilium-dbg endpoint config "$ENDPOINT" PolicyAuditMode=Enabled, and the verdicts are read like this:

hubble observe flows -t policy-verdict --last 1

The published verdicts come in three shapes — policy-verdict:none AUDITED, policy-verdict:L3-L4 ALLOWED, policy-verdict:none DENIED — from which source and destination labels, direction and port can be derived, and therefore the rule to write. On Antrea, a broad Allow policy with enableLogging: true writes the first packet of every flow into np.log on the node; aggregating that file towards a central sink is described by no loaded page. And on the upstream API alone, there is no phase 1: that is a result, not a gap to paper over with a workaround.

Phase 2 — allow, DNS first. The order runs against intuition: allow policies are written and applied before the deny, since an allow policy on its own changes nothing as long as no policy selects the pod in the matching direction.

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-egress-dns
  namespace: webshop-app
spec:
  podSelector: {}
  policyTypes:
  - Egress
  egress:
  - to:
    - namespaceSelector:
        matchLabels:
          kubernetes.io/metadata.name: kube-system
      podSelector:
        matchLabels:
          k8s-app: REPLACE_WITH_DNS_POD_LABEL
    ports:
    - protocol: UDP
      port: 53
    - protocol: TCP
      port: 53

REPLACE_WITH_DNS_POD_LABEL is not a value to guess at: the label on the DNS service pods depends on the distribution, no loaded Broadcom page publishes it, and it is read off your own cluster. The kubernetes.io/metadata.name namespace label, on the other hand, is guaranteed by Kubernetes. Then come the application flows, one per relationship actually seen in phase 1.

Phase 3 — deny, by selection, one namespace at a time. The manifest is the one from the upstream page, applied namespace by namespace. Suggested order: webshop-db first — fewest outbound dependencies, most value — then webshop-app, then webshop-web, which carries ingress traffic from the ingress controller and breaks most visibly. Between each namespace, three checks: probes stay green, DNS resolution answers from a pod in the namespace, and the nominal application flow passes end to end.

Phase 4 — harden, and only where it is reversible. Three hardening moves, least to most risky: the explicit deny of one specific flow (Cilium ingressDeny/egressDeny, Antrea Drop), the cluster-scoped policy (one mistake hits the entire cluster), and Cilium’s always mode (isolates every endpoint, including those never selected). In the running example, none of the three applies to production: phases 0 to 3 on vks-fret-01 with Antrea, phase 4 on vks-fret-lab-01 with Cilium, and a written decision before any generalisation.

Rollback is asymmetric, and that is what justifies this order. Deleting a test object or an allow policy costs nothing; a kubectl delete networkpolicy default-deny-all -n <ns> is immediate and granular; undoing a cluster-scoped policy or always mode is global. And the CNI change has no documented rollback: the CNI is frozen into the topology at creation, so rolling back the Cilium plane is not a reverse switch, it is abandoning the lab cluster and keeping production on Antrea.

Pitfalls & watch-outs

Counting objects is not auditing. An audit that counts NetworkPolicy objects measures intent, not filtering — “Creating a NetworkPolicy resource without a controller that implements it will have no effect”. That is the first sentence to quote in an audit response, before any coverage table.

Probes: the opposite pitfall to the expected one. The API publishes that “the only allowed connections into the pod are those from the pod’s node and those allowed by the ingress list”, and that pods cannot block access from their resident node. Since kubelet probes come from the node, the pitfall is not that they break: it is that you believe they will break, write a useless rule, and then pin some other incident on the wrong cause. Exact behaviour depends on the plugin and on the probe mode — to be verified on your platform, this article guarantees nothing here.

Egress forgotten in policyTypes. A default-deny written with policyTypes: [Ingress] alone gives a feeling of coverage and leaves the whole egress path open, which is exactly the route an exfiltration takes. Conversely, adding Egress without having reopened DNS first cuts the application immediately. Both mistakes come from the same line.

Looking for a connection refused where there is a 403. An L7 rule violation produces no drop but an application-level response. An operator digging through incident traces with the wrong symptom will find nothing.

Service mesh and TLS are out of scope — and that is published. Two entries of the list explicitly point elsewhere (“Anything TLS related (use a service mesh or ingress controller for this)”). On VKS, the combination has a cost: the Istio add-on is published as incompatible with clusters provisioned with Cilium. A reader planning mesh and Cilium has a decision to make before writing their first policy.

One last word on what this article does not contain: no numbers. No flow volumes, no latency, no resource consumption, no rule counts. None were found on any documentation page that was loaded, and the field of eBPF measurements is saturated with promotional figures. The absence is a result, not a shortfall.

Conclusion

A defensible default-deny does not rest on a product, it rests on an order of operations and on published sentences — including the ones describing what the mechanism does not do. The logical sequel is the admission plane: every flow denied here is a flow the next article, Supply chain security: Sigstore, SBOM, admission control, no longer has to qualify. And for the platform network underneath the cluster, the detour through Networking in VCF 9: from vDS to pod remains the prerequisite.

Three conditions, three failures

The API always exists, the plugin may not be there, and the pod is only isolated if it is selected. A successful apply covers the first one only.

Look at your CNI before replacing it

Antrea, the VKS default, already documents explicit deny and flow logging. Cilium is a catalogue package, chosen at cluster creation, with no documented rollback.

RBAC and network are two planes

The identities set up by the RBAC article become selectors here. RBAC authorises API calls, NetworkPolicy authorises packets.

Get the next one by email

New articles and series, sent when they are published. No other mail.

One click to unsubscribe, any time.

Back to blog
Share

Related articles

  1. 18 min read

    Kubernetes RBAC: the foundations, and the pitfalls that survive an audit

    Every one of these pitfalls is published on kubernetes.io. What is missing is the ordering — and the path that leads from a vSphere Namespace straight to cluster-admin.

  2. 17 min read

    Runtime security: Falco and Tetragon, and how to actually choose

    Falco and Tetragon both collect through eBPF. What separates them lies elsewhere: event scope, rule model, and above all what each one can actually prevent.

  3. 17 min read

    Supply chain security: Sigstore, SBOM, admission control

    Kubernetes verifies no image signature on its own. Signing with Sigstore, inventorying with an SBOM, refusing at admission — and what each of those verbs actually covers.

Follow along

New articles, thoughts, and updates.