Watch Workload Identity in the Kernel (Not the Sidecar)
Watch Workload Identity in the Kernel (Not the Sidecar)
I grew up professionally in the early (early) 2000s building, securing, maintaining, and quite often troubleshooting networks. To date myself: my first network ran fully routable internet addresses, a full Class-C per building. When the decision came down to “implement a firewall,” the line got drawn. Outside vs inside. Outside, bad. Inside, good. Right? It was that straightforward, right up until the trojans, worms, and bots showed up. My all-time favorite was the ARP flooding attack we tracked to a developer’s laptop after a long weekend of “migration work.”
We trusted everything inside until Layer 2 loops burned us badly enough to move to a Layer 3 core and distribution architecture. The data center got stratified into well-defined VLANs and ACLs, with physical devices added for transport segmentation and DMZs. On the hosts themselves, we traded valuable CPU for a pile of agents to sniff, monitor, and watch our admins do their jobs. The vision was the inverted donut: instead of one big moat, we’d dig lots of little moats around classes of services. It worked. But we never reached the isolation we actually wanted, the kind that lets you control exactly what each compute resource can talk to without rewriting ACLs and firewall rules every week. And we were only ever controlling our own software anyway, never the malware that would inevitably land on the box.
Every one of those defenses shared a blind spot. Each one trusted what the host reported about itself, and we paid for that trust in CPU and constant rule churn. Inside was never a real trust boundary. We just kept relearning that the hard way.
Fast forward to 2025. The world starts building with genAI agents, and we make the same call we always make: speed over security, in the name of innovation. No brakes, no helmet. And it’s fine, supposedly, because your agents are making MCP tool calls, API requests, and service-to-service connections that nobody sees. No proxy intercepts them. No session broker records them. Your SIEM gets the log after the target system already processed the request, if it logs at all. Machine-to-machine identity is the fastest-growing surface in your cluster, and you have close to zero real-time visibility into it.
So here is where two-plus decades of digging moats finally lands: stop trusting what the host says about itself. Move the observation point below the application, read identity off the wire at the kernel, with no agent to feed and nothing for the workload to fake.
workload-ebpf-reflector is an attempt to fix that one layer down, where lying is harder. It is an eBPF DaemonSet that reads SPIFFE identity off TLS handshakes at the kernel, evaluates policy, and can drop untrusted connections before the application ever decides anything. No SDK, no sidecar in the data path, no payload decryption.
The trick: read the cert at the kernel, not the header
Most identity tooling trusts what the application reports. An app says “this request came from spiffe://prod/payments/processor," and your logs believe it. But the app is exactly the thing you cannot trust if it is compromised or impersonated.
The Reflector moves the observation point. When a TLS handshake begins, a uprobe on the X.509 parse path (d2i_X509) fires inside the kernel. It pulls the SPIFFE URI out of the certificate SAN, hands it to an in-process OPA policy engine, and if the trust domain is not on the allow list, it inserts a deny entry for TC to drop the connection.
TLS handshake begins
-> d2i_X509 uprobe fires (kernel)
-> SPIFFE URI extracted from cert SAN
-> OPA evaluates: "evil.corp" not in trusted_domains
-> POLICY_VIOLATION logged
-> deny map entry inserted for TC drop
The application never decides. The kernel decides.
That last line is the whole thesis. Identity is observed before the workload sees the cert, so the decision does not depend on the workload being honest.
How it fits together
The DaemonSet does the kernel work. A separate sidecar, reflector-map, keeps a live session map you can query over plain HTTP. The two are decoupled on purpose: the session map runs on any cluster (OpenShift, Talos, vanilla Kubernetes) and does not care how the events were produced.

workload eBPF reflector architecture
The lab loop is short:
make lab-demo
curl localhost:9101/sessions | jq # every observed connection
curl localhost:9101/stats | jq # active / closed / stale
The part that is actually new .. per identity firewall?
Plenty of tools log connections. The piece worth pointing at is the attestation endpoint, which turns the kernel observation into something other systems can ask about:
curl 'http://reflector-map:9101/attest?pid=1234&src=10.0.0.5:54321&dst=10.0.0.10:8200'
# -> {"spiffe_id":"spiffe://prod/ns/payments/sa/processor","confidence":"kernel"}
The confidence field is the point. kernel means the SPIFFE ID was pulled off the cert at the handshake, before the app touched it. jwt-only means nobody observed it at the wire and you are trusting a header. That distinction lets an external system, a Vault secrets engine, an OPA external-data call, a SIEM enrichment, ask a question it normally cannot: was this identity seen at the kernel, or merely claimed? That is a policy information point built out of ground truth instead of self-report.
It also extracts more than SPIFFE from the same handshake: JWT subjects, MCP tool names from JSON-RPC tools/call, OTLP signals. One handshake, correlated into a single identity record that no individual broker in the path can assemble on its own.
What is proven and what is not
This is where I want to be precise, because “wire speed enforcement” is the kind of phrase that gets ahead of the evidence.

That last row matters. On a single-node cluster, pod-to-pod traffic rides the CNI bridge and never crosses eth0, so the TC hook does not fire. The drop path exists and the deny map gets populated, but proving enforcement on physical cross-node traffic is the next milestone (unblocked now that I added a 2nd Talos node to my home lab). Observe and evaluate are real now. Enforce is real in the lab path and pending validation where it counts.
Also worth saying plainly: the session map API has no auth in v1. Deploy it behind a network policy. There are no published releases yet either, so “shipped” here means the crawl and walk sprints are complete, not that there is a tagged artifact to pin.
What is next
- Multi-node enforcement: stand up two physical nodes and confirm POLICY_VIOLATION plus TC drop fires on traffic crossing the real NIC.
- Kafka wire visibility: the TLS hooks already capture Kafka plaintext, so a parser could pull client ID and topic and bind them to the SPIFFE ID from the same handshake. That is an identity record no Kafka broker can produce.
- Behavioral fingerprinting in production: the profile infrastructure exists (rolling windows, z-score deviation, destination-set novelty). The open question is whether 30-plus days of real traffic keeps the baseline stable enough to flag the unknown-bad without drowning you in false positives. The policy gate handles known-bad. Fingerprinting is the bet on catching the compromised credential and the agent that went rogue.
Try it
Repo: github.com/raygj/workload-ebpf-reflector
Run make lab-demo, hit /sessions, and watch identity show up that nothing else in your stack was recording. If you are running SPIFFE workloads and want kernel-level ground truth next to your policy enforcement point, open an issue.
This is one of a handful of small tools I’ve been building over the last few months, all poking at the same question: what does security look like when it has to move at the speed of the things it’s defending. The reflector is the part that watches. More to come.
메타데이터
- post_id
- c0c2c3f4e8f9
- slug
- watch-workload-identity-in-the-kernel-not-the-sidecar-c0c2c3f4e8f9
- url
- https://medium.com/@ray.jim/watch-workload-identity-in-the-kernel-not-the-sidecar-c0c2c3f4e8f9
- canonical_url
- https://medium.com/@ray.jim/watch-workload-identity-in-the-kernel-not-the-sidecar-c0c2c3f4e8f9
- author_url
- https://medium.com/@ray.jim
- status
- ok
- fetched_at
- 2026-07-09 15:12:33