KubeCon India 2026 (Mumbai) — Day 2 Deep Dives

Zero Trust for Fintech — Secure Banking Infrastructure With Cilium

Day 2 · networking & the kernel · from the uploaded deck

Jun 19, 2026 · conferences · 24 min read · 5200 words advanced

Zero trust for fintech — secure banking infrastructure with Cilium.

conferences kubecon cilium zero-trust ebpf

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.

RegulationThreat modelCloud-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:

Cilium: enforcement in the kernel, not in sidecar proxies Your banking workloads (payment, ledger, api-gateway…) Cilium + Hubble + Tetragon eBPF programs (loaded into the Linux kernel) Linux kernel L3 → L7 (identity, IP, port, HTTP, Kafka, gRPC, DNS) · enforcement, not a per-pod proxy

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.
Why "no sidecar" matters for a bank. A sidecar mesh adds a proxy container to every pod — that's memory, CPU, and a second process to patch on every CVE, multiplied by hundreds of microservices. It also adds latency to every call, which a payment path notices. Cilium moves the L3/L4 enforcement (and much of L7) into the kernel datapath, so the common case has no extra proxy hop. You can still bring sidecars/Envoy for advanced L7 where you want them — but you're not forced to pay the tax everywhere.

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.

PillarThe questionCilium answer
IdentityWho am I?K8s resources (Pod, Node, Cluster, kube-apiserver) become a numeric security identity derived from labels.
Network policyWhat am I allowed to access?A firewall on identity, FQDN, DNS, and HTTP/L7 — one CiliumNetworkPolicy CRD.
EncryptionWho can read me?WireGuard / IPsec for the wire plus identity-based mTLS for the workload.
ObservabilityWhat just happened?Hubble — every flow, every drop, every L7 verb, captured from eBPF.
Runtime securityWhat'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:

Kubernetes objects → numeric security identity Pod identity from labels (app, tier, env) reschedule does not change it id:1234 → app=payment,env=production Node node's own identity, for host firewall reserved:host / reserved:remote-node Cluster in Cluster Mesh, each cluster has an ID cross-cluster policy, no leaked topology cluster: jakarta ↔ cluster: mumbai kube-apiserver reserved identity for traffic to/from the Kubernetes API server itself reserved:kube-apiserver

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=production stays true across the pod's churn.
  • Node. A node has its own identity, used for host firewall policy on the kubelet — reserved:host and reserved: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.
Why this is the unlock. Once "the payment service" is a stable identity rather than a set of ephemeral IPs, every other pillar becomes writable. Policy, encryption peering, audit attribution, and runtime rules all key off the same identity. The hard problem in cloud-native zero trust isn't enforcement — it's naming the thing you want to enforce against. Cilium's identity model is that naming layer.

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 typeControlsShape
Identity (L3/L4)Which identity a pod can reachtoEndpoints / fromEndpoints + toPorts
FQDNEgress allow-list — which external domainstoFQDNs with matchName / matchPattern
CIDRWhich IP range a pod can reach (off-cluster)toCIDRSet
HTTP / L7Which verb · path · headertoPortsrules.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.

payment service — the complete allowed graph (default-deny everywhere else) api gateway L7 HTTP :8080 payment app=payment (jakarta) kube-dns · TCP&UDP :53 (DNS) otel collector · :4317 via host kube-apiserver · :443 entity postgres (VM) · :5432 CIDR /32 3rd party (internet) · :443 FQDN notification svc · :8080 ClusterMesh (mumbai)

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
The lesson in this example. Six destinations, six different mechanisms — identity, host entity, reserved entity, CIDR, FQDN, cross-cluster — and one policy language for all of them. The payment pod can reach exactly these six and nothing else. If it's compromised, it can't scan the cluster, can't reach the ledger it doesn't use, can't exfiltrate to a random domain. That's lateral-movement containment expressed as data, reviewable in a PR.

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.
Two layers: wire encryption + workload identity proof Identity & cert management: SPIFFE · cert-manager · Istio · Vault Identity A (pods) cilium + eBPF Identity B (pods) cilium + eBPF mutual auth handshake (mTLS) IPsec / WireGuard — the encrypted wire

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, or kubectl appearing in a production pod is a strong signal. The first event above is exactly that: a bash spawned via kubectl exec as root.
  • File integrity. Alert on read or write of secrets paths and keystores. The second event — cat trying to open() the Postgres TLS key — is BLOCKed inline.
  • Syscall tracing. Capture ptrace, raw sockets, capability use. The gdb ptrace attempt is denied by a ptrace-deny policy.
  • 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.
Why pillar 5 completes the model. Pillars 1–4 assume the attacker is on the network trying to reach somewhere. Pillar 5 assumes the attacker is already inside a pod and tries to act locally — read a key, attach a debugger, dial out. Network policy can't see those; Tetragon can, and can kill them. "Network closes one door; runtime closes the loop."

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.

Collect verdicts → ship to logging → diff against defined policy Cilium agent (Hubble) policy-verdict events events.log fieldMask + allowList log shipper (Fluent Bit / Vector) logging system · retention ≥ 90 days query logging matched in 90d kubectl get cnp all policies diff the two lists defined but never matched candidates for removal check seasonal traffic first

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.log shaped by a fieldMask + 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

← prev: LLMs Behind Bars series home →
© cvam — written in plaintext, served warm