Prasta Maha and Herbert Sianturi — Lead and Senior DevOps Engineers at Krom Bank Indonesia — gave the closing-day, real-bank answer to a question Day 2 kept circling: how do you actually build zero trust on Kubernetes when you're regulated, multi-tenant, and rescheduling pods every minute? Their answer is Cilium: an eBPF dataplane where policy follows the workload's identity, not its IP. The talk walks the five pillars a bank needs — identity (labels become a numeric security identity), network policy (one CRD spanning L3/L4 and L7, default-deny), encryption (WireGuard/IPsec for the wire plus identity-based mTLS for the workload), observability (Hubble flow logs that serve SRE and auditor from the same data), and runtime security (Tetragon catching what bypasses the network). The thesis in one line: "Zero Trust is a practice, not a product — Cilium gives you the pieces."
This is the bank-floor companion to two other Day-2 talks: Zero Trust for Agents (WSO2) framed identity/policy for AI workloads, and LLMs Behind Bars (CodeRabbit) handled execution isolation. This one grounds the same principles in PCI-DSS, RBI/MAS/OJK-style regulation, and a payment service that actually has to move money. It also picks up the eBPF/Cilium thread from the Day-1 Lean Observability talk.
"Never trust, always verify" — what it means on a cluster
The speakers anchored on the canonical definition — NIST SP 800-207, Zero Trust Architecture (2020) — and its one-line slogan: "Never trust, always verify." That sounds like a poster. The useful part is the three concrete properties it decomposes into, and what each one forbids:
- No implicit trust. Network location does not grant access. Being inside the cluster, inside the VPC, or on the same node means nothing. The old "hard shell, soft centre" model — firewall at the perimeter, flat trust inside — is exactly what zero trust deletes.
- Always authenticate. Every request, every session, every time. Not "authenticate once at the edge then trust the bearer for an hour."
- Continuous verification. Trust is re-evaluated, not granted once. A workload that was allowed a minute ago is re-checked, because identity, policy, and posture can all change.
The reason this matters more in a bank than almost anywhere else is that the blast radius of "implicit trust inside the perimeter" is a regulated, money-moving system. If a single compromised pod can reach the ledger database because they happen to share a subnet, that's not a bug — that's the whole architecture failing the audit.
Three forces pushing every bank toward zero trust
The talk's most grounding slide named the three pressures that make this non-optional for a fintech specifically. They map cleanly onto regulation, threats, and the platform's own behaviour.
| Regulation | Threat model | Cloud-native reality |
|---|---|---|
| PCI-DSS v4.0 demands segmentation, encryption, and logging. Local rules — OJK (Indonesia), RBI (India), MAS (Singapore), PSD2 (EU). ISO 27001 and SOC 2 access-control expectations on top. | Lateral movement after a single compromised pod. Supply-chain risk in container images and CI. Insider threat from privileged operators with cluster access. | Pods are rescheduled across nodes every minute. Hundreds of microservices on multi-tenant clusters. Static IP allow-lists do not scale or stay correct — the IP that was the payment service at 09:00 is something else by 09:05. |
That last column is the technical heart of the whole talk. Traditional firewalls — and even classic Kubernetes NetworkPolicy written against CIDRs — assume IP addresses are stable identifiers of who a workload is. In a cluster they are not. An IP is a temporary lease on a pod that may live three minutes. Writing security policy against IPs in this world is writing policy against noise. You need a stable name for "the payment service" that survives reschedules. That stable name is identity, and producing it cheaply is what Cilium is for.
Cilium in one diagram — eBPF, not sidecars
Cilium is a CNCF graduated project (since 2023) that provides networking, security, and observability for Kubernetes, built on eBPF. The stack the speakers drew is worth internalising because it explains every property that follows:
Fig 1 — the Cilium stack. Because enforcement lives in eBPF in the kernel, there's no sidecar tax per pod.
Three properties fall out of that picture, and they're exactly the three the speakers highlighted:
- It's a graduated CNCF project. Not a science experiment — it's the default CNI for many managed Kubernetes offerings and is run in production at scale. For a bank's procurement and risk teams, "graduated" is a meaningful signal.
- It spans L3 → L7. One system understands identity, IP, port, and application protocols — HTTP, Kafka, gRPC, DNS. You don't bolt a separate L7 firewall onto an L3 network policy engine; it's one policy language.
- Enforcement is in the kernel, not sidecar proxies. This is the differentiator from a sidecar service mesh. No Envoy injected next to every pod, no doubling of pod count, no per-hop proxy latency for the common case. The eBPF programs run in the kernel's networking path directly.
How Cilium implements zero trust — the five pillars
The spine of the talk: five questions a workload must be able to answer, each mapped to a Cilium capability. Hold this table; the rest of the article is just each row in detail.
| Pillar | The question | Cilium answer |
|---|---|---|
| Identity | Who am I? | K8s resources (Pod, Node, Cluster, kube-apiserver) become a numeric security identity derived from labels. |
| Network policy | What am I allowed to access? | A firewall on identity, FQDN, DNS, and HTTP/L7 — one CiliumNetworkPolicy CRD. |
| Encryption | Who can read me? | WireGuard / IPsec for the wire plus identity-based mTLS for the workload. |
| Observability | What just happened? | Hubble — every flow, every drop, every L7 verb, captured from eBPF. |
| Runtime security | What's actually running inside the pod? | Tetragon — process, file, and syscall events, with inline enforcement. |
Pillar 1 — Cilium resource as identity
The foundational move: Cilium translates Kubernetes objects into a numeric security identity, so policy follows the label, not the IP. When a pod is created, Cilium reads its labels, derives a security identity, and from then on every packet that pod sends is tagged (in the datapath) with that identity number. Policy is written against identities; the kernel enforces against the tag. Four kinds of identity matter:
Fig 2 — the four identity scopes. The pod case is the one that solves the "IP churns every minute" problem.
- Pod. Every pod derives identity from labels (
app,tier,env). A reschedule onto a different node, with a different IP, does not change the identity — same labels, same identity number. This is the whole point:id:1234 → app=payment,env=productionstays true across the pod's churn. - Node. A node has its own identity, used for host firewall policy on the kubelet —
reserved:hostandreserved:remote-node. Lets you write policy about the node itself, not just pods. - Cluster. In Cluster Mesh, each cluster carries an ID, so you can write cross-cluster policy (
cluster: jakarta ↔ cluster: mumbai) without leaking or hard-coding the other cluster's network topology. - kube-apiserver. A reserved identity for traffic to and from the Kubernetes API server itself — so "may talk to the API server" is a first-class, nameable rule.
Pillar 2 — the firewall: one policy language, default-deny
Cilium's CiliumNetworkPolicy (CNP) is one CRD that expresses L3, L4, and L7 rules. The speakers stated the operating principle plainly: by default, block all; allow only defined rules. Default-deny is the posture; every allowed flow is an explicit, reviewable rule. Four rule types cover the bank's needs:
| Rule type | Controls | Shape |
|---|---|---|
| Identity (L3/L4) | Which identity a pod can reach | toEndpoints / fromEndpoints + toPorts |
| FQDN | Egress allow-list — which external domains | toFQDNs with matchName / matchPattern |
| CIDR | Which IP range a pod can reach (off-cluster) | toCIDRSet |
| HTTP / L7 | Which verb · path · header | toPorts → rules.http |
The most banking-relevant idea here is the L7 rule. Classic network policy can say "api-gateway may reach payment on 8080." Cilium can say "api-gateway may call only GET /balance and POST /transfer on payment, and nothing else." That's the difference between opening a port and opening an API. For a payment service, that distinction is the control PCI auditors actually want.
A worked example — the payment service's allowed world
The speakers built up a single concrete graph: a payment service (app=payment, cluster jakarta), reached by an api-gateway over L7 HTTP, and itself allowed to reach exactly six destinations — and nothing else. Everything below is one CNP, shown rule by rule.
Fig 3 — the payment service's six allowed egress destinations, each a different rule type. Everything not drawn is denied.
Ingress — api-gateway → payment, L7 HTTP only. The gateway may reach payment on 8080, but only for two specific routes:
ingress:
# API gateway -> payment, L7 HTTP only
- fromEndpoints:
- matchLabels:
io.kubernetes.pod.namespace: ingress-gateway
app: api-gateway
toPorts:
- ports:
- port: "8080"
protocol: TCP
rules:
http:
- method: "GET"
path: "/balance"
- method: "POST"
path: "/transfer"
Egress — DNS first, and it's mandatory. A subtle gotcha the speakers flagged: FQDN policies can't work unless the pod is allowed to resolve names. DNS must be explicitly permitted, or every toFQDNs rule below silently fails to resolve:
egress:
# DNS — REQUIRED, otherwise toFQDNs below can't resolve
- toEndpoints:
- matchLabels:
io.kubernetes.pod.namespace: kube-system
k8s-app: kube-dns
toPorts:
- ports:
- port: "53"
protocol: ANY
rules:
dns:
- matchPattern: "*"
Routing DNS through Cilium's L7 DNS proxy is also what makes FQDN policy trustworthy — Cilium learns the resolved IPs from the actual queries, so the allow-list tracks real DNS answers rather than a stale hard-coded set.
Egress — the node-local OpenTelemetry collector (a DaemonSet on the host). Reached via the host entity, since the collector runs on the node:
egress:
# Node-local OpenTelemetry collector (DaemonSet)
- toEntities:
- host
toPorts:
- ports:
- port: "4317"
protocol: TCP
Egress — the Kubernetes API server, via its reserved entity (no IP guessing):
egress:
# Kubernetes API server
- toEntities:
- kube-apiserver
toPorts:
- ports:
- port: "443"
protocol: TCP
Egress — Postgres on a VM / managed service, off-cluster, pinned by CIDR to specific /32s:
egress:
# CIDR postgres (VM / managed service)
- toCIDRSet:
- cidr: 10.123.123.10/32
- cidr: 10.123.123.20/32
toPorts:
- ports:
- port: "5432"
protocol: TCP
Egress — a third-party API over the internet, pinned by FQDN (this is why DNS above was mandatory):
egress:
# FQDN 3rd party via internet
- toFQDNs:
- matchName: "api.abc123.com"
- matchPattern: "*.cncf.io"
toPorts:
- ports:
- port: "443"
protocol: TCP
Egress — a notification service in another cluster, over Cluster Mesh. Note the cross-cluster label — the policy names the remote cluster by identity, not by reaching into its topology:
egress:
# Cross-cluster via ClusterMesh
- toEndpoints:
- matchLabels:
io.cilium.k8s.policy.cluster: mumbai
io.kubernetes.pod.namespace: notif
app: notif
toPorts:
- ports:
- port: "8080"
protocol: TCP
Pillar 3 — encrypt the wire AND the workload
The speakers were precise here: encryption has two distinct jobs, and a bank needs both.
- Encrypt the wire — transparent transport encryption. WireGuard or IPsec, configured once at the dataplane, encrypts all pod-to-pod traffic on the node-to-node path. No app changes. This is the "data in transit is encrypted" checkbox PCI wants, applied to everything automatically.
- Encrypt the workload — identity-based mTLS. Mutual TLS proves who the peer is at the workload level, using the Cilium identity, with certificates managed by an external system — SPIFFE, cert-manager, Istio, or Vault. This is the "always authenticate" property made cryptographic.
Fig 4 — use both: WireGuard/IPsec for the wire, mTLS for workload identity proof.
The mTLS authentication is requested in policy itself — a small but important detail, because it means "this flow must be mutually authenticated" is part of the same reviewable CNP, not a separate config:
ingress:
- fromEndpoints:
- matchLabels:
app: service-a
authentication:
mode: "required"
toPorts:
- ports:
- port: "8080"
protocol: TCP
The takeaway the speakers stressed: use both. WireGuard/IPsec answers "can a network tap read this?" (no). mTLS answers "is the peer actually who its identity claims?" (proven). Either alone leaves a gap a bank can't accept.
Pillar 4 — Hubble: observability as audit evidence
Hubble shows every flow, every drop, every L7 verb — captured from eBPF, in the kernel. The framing that made it land for a banking audience: the same flow data serves the SRE debugging an outage and the auditor proving a control. One pipeline, two consumers.
$ hubble observe --namespace banking --verdict DROPPED Jun 14 09:12:03 DROP pay-svc → ledger-db:5432 TCP policy-denied Jun 14 09:12:04 DROP web-fe → 198.51.100.7:80 TCP policy-denied Jun 14 09:12:06 DROP fraud-svc → ledger-db:5432 TCP no L7 match Jun 14 09:12:08 DROP dev-pod → ledger-db:5432 TCP identity not allowed
Read those four drops as an auditor would: a service that shouldn't touch the ledger was denied; a frontend trying to reach a random internet host was denied; a call that hit the right port but the wrong L7 route was denied; and a dev pod with the wrong identity was denied. Every one is a control working, with a timestamp, source, destination, and reason. The speakers broke the value into three:
- Forensic replay. Every dropped packet — source, destination, and reason — for any incident window. When something goes wrong, you can reconstruct exactly what was attempted and what the policy did.
- Continuous evidence. Export to Prometheus, OpenTelemetry, or a SIEM. The same data feeds SRE dashboards and the audit trail — you're not maintaining two separate systems that have to agree.
- Live service map. The Hubble UI renders the live dependency graph — which the speakers called out as invaluable for scope-of-PCI reviews, where you must prove which services are in the cardholder-data environment and which aren't.
Pillar 5 — Tetragon: closing the loop inside the pod
The honest admission that makes this talk complete: network policy stops bad packets; it does nothing about bad behaviour inside the pod. If an attacker already has code execution in a pod, a network rule doesn't see them read a secret file or spawn a shell. That's Tetragon's job — eBPF-based runtime observability and enforcement at the process/syscall level.
$ tetra getevents -o compact [exec] payment-7d9 bash -c "..." parent: kubectl exec user: root [file] ledger-pg open() /etc/ssl/pg.key binary: cat policy: secret-files BLOCK [net] web-3kf2 connect 185.220.101.42:443 binary: /tmp/.x policy: tor-exit SIGKILL [cap] dev-pod ptrace(PEEKTEXT) pid=1421 binary: gdb policy: ptrace-deny
What Tetragon observes — and can enforce on — inside the pod:
- Process execution. Detect every
exec— a shell,curl, orkubectlappearing in a production pod is a strong signal. The first event above is exactly that: abashspawned viakubectl execas root. - File integrity. Alert on read or write of secrets paths and keystores. The second event —
cattrying toopen()the Postgres TLS key — is BLOCKed inline. - Syscall tracing. Capture
ptrace, raw sockets, capability use. Thegdbptraceattempt is denied by aptrace-denypolicy. - Process-aware network. Attribute every flow to a specific binary and PID — so the connect to a Tor exit node is tied to
/tmp/.x, not just "some pod," and gets SIGKILLed. - Inline enforcement. Tetragon can SIGKILL or override syscalls when a policy matches — enforcement in the kernel, not an after-the-fact alert.
Operationalising it — evaluating and pruning policy
A detail that separated this from a product demo: the speakers showed how Krom Bank keeps the policy set honest over time, using Hubble's own verdict events as the feedback loop. Two pipelines run side by side.
Fig 5 — policy hygiene: ship verdicts, then diff "policies actually matched" against "policies defined" to find dead rules.
- Left pipeline — capture and retain. The Cilium agent emits policy-verdict events via Hubble. They land in an
events.logshaped by afieldMask+allowList(keep what audit needs, drop the noise), get picked up by a log shipper (Fluent Bit / Vector), and stored in a logging system with ≥ 90-day retention — a common regulatory floor. - Right pipeline — diff to prune. Query the logging system for policies that actually matched in the last 90 days; list all policies in the cluster with
kubectl get cnp; diff the two. Rules that are defined but never matched are candidates for removal — with the explicit caveat: check seasonal traffic first (a rule unused in 90 days might still be needed for quarter-end or a yearly batch).
This is the part teams usually skip and regret: zero-trust policy sets only stay tight if you actively garbage-collect them. A default-deny posture full of stale "allow" exceptions slowly rots back toward implicit trust. Using the audit log as the source of truth for "is this rule still earning its place" is the discipline that keeps the model real.
Key takeaways
The speakers closed on five, under the banner "Zero Trust is a practice, not a product. Cilium gives you the pieces."
- Identity, not IP. Kubernetes labels become identity; policy travels with the workload across every reschedule.
- Policy at every layer. Identity, FQDN, DNS, and L7 in one CRD, one control plane — default-deny, explicit allows.
- Encrypt wire AND workload. WireGuard/IPsec for transport; mTLS for mutual identity proof. Both, not either.
- Observability is evidence. Hubble flow logs serve the SRE and the auditor from the same data.
- Runtime closes the loop. Tetragon catches what bypasses the network — in real time, in the kernel.
FAQ
Why not just use standard Kubernetes NetworkPolicy?
Standard NetworkPolicy is L3/L4 only and, critically, it has no concept of stable identity for off-cluster or L7 cases — and many implementations key heavily off pod selectors/IPs. It can't express "only GET /balance and POST /transfer," can't do FQDN egress allow-lists, can't span clusters, and gives you no audit trail of verdicts. Cilium's CNP is a superset that adds L7, FQDN, CIDR, Cluster Mesh, mTLS requests, and Hubble observability — the things a regulated bank actually needs.
Does running enforcement in eBPF instead of sidecars cost L7 features?
Not the ones in this talk. Cilium does L7 (HTTP, DNS, Kafka, gRPC) in the kernel datapath / its own proxy where needed, without injecting an Envoy into every pod. You keep L7 policy and visibility while avoiding the per-pod sidecar overhead. For advanced mesh features you can still layer Envoy/Istio selectively — but you're not forced to pay the sidecar tax on every microservice.
How does this relate to the other Day-2 zero-trust talks?
Three layers of the same idea. Zero Trust for Agents (WSO2) covered identity and policy for AI workloads conceptually; LLMs Behind Bars (CodeRabbit) covered execution isolation of untrusted code. This talk is the network-and-runtime dataplane that enforces zero trust for all workloads — agents included — on a real regulated cluster.
Why is the DNS egress rule called out as "REQUIRED"?
Because FQDN policies (toFQDNs) work by Cilium watching the pod's DNS queries to learn which IPs a domain currently resolves to. If the pod can't reach kube-dns, it can't resolve names, so every toFQDNs rule fails silently — the pod just can't connect, and it looks like a mysterious outage. Allowing DNS through Cilium's DNS proxy is what makes the FQDN allow-list both functional and trustworthy.
References & extra reads
- NIST SP 800-207 — Zero Trust Architecture (2020) · the "never trust, always verify" source
- Cilium documentation · CiliumNetworkPolicy, identity, Cluster Mesh, encryption
- Hubble · flow observability for Cilium
- Tetragon · eBPF runtime security & enforcement
- Zero Trust for Autonomous Agents · the identity/policy companion talk (WSO2)
- LLMs Behind Bars — Sandboxes at Scale · the execution-isolation companion (CodeRabbit)
- The Lean Observability Stack · Day-1 eBPF/mesh telemetry companion
- Official Day 2 schedule (June 19) · session source