System Architecture
kguardian is designed as a distributed system: four core components (Controller, Broker, CLI, UI) plus an audit-mode evaluator (deployed by default) and an optional AI assistant (the LLM Bridge, running tools and policy/seccomp generation in-process) work together to observe, analyze, and secure your Kubernetes workloads.Components
Controller
Language: Rust + eBPF (C)Deployment: DaemonSet (one per node)Purpose: Observes kernel-level events
Broker
Language: Rust + Actix-webDeployment: Deployment with PostgreSQLPurpose: Stores and serves telemetry data
CLI
Language: GoDeployment: kubectl pluginPurpose: Generates security policies
UI
Language: React + TypeScriptDeployment: Web applicationPurpose: Visualizes network topology
Evaluator
Deployment: Deployment (on by default; disable with
evaluator.enabled=false)Purpose: Replays observed flows against policies and records would-deny verdictsAI Assistant (Optional)
Language: TypeScriptDeployment: LLM Bridge (opt-in via
ai.enabled)Purpose: Natural-language queries and conversational policy generation; optionally serves the same tools to external MCP clients at /mcpData Flow
1. eBPF Monitoring
The Controller attaches eBPF programs to kernel hooks on each node — fentry probes on
tcp_v4_connect, tcp_set_state, udp_sendmsg, and tcp_retransmit_skb, a kprobe/kretprobe pair on inet_csk_accept, the raw_syscalls/sys_enter tracepoint, and tp_btf/cgroup_mkdir (so a container is captured from cgroup creation, before its pod is registered). When pods make network connections or execute syscalls, eBPF programs capture:- Source/destination IPs and ports
- Protocol (TCP/UDP)
- Syscall names and arguments
- Container network namespace inode
- Timestamp
eBPF runs in the kernel with low overhead, avoiding the need for sidecar proxies or agent injection.
2. Event Enrichment
The Controller’s userspace component:
- Receives events from eBPF via BPF ring buffers
- Queries containerd to map network namespace inodes to container IDs
- Matches containers to Kubernetes pods via the API server
- Enriches events with pod name, namespace, labels, and owner references
3. Data Transmission
Enriched events are sent to the Broker via HTTP POST:
- Network traffic →
/pod/traffic(batched via/pod/traffic/batch) - Pod metadata →
/pod/spec - Syscall data →
/pod/syscalls - Service mappings →
/svc/spec - Pod deletions →
/pod/mark_dead
4. Storage & Indexing
The Broker stores all telemetry in PostgreSQL with indexes on:
- Pod IP address
- Pod name + namespace
- Timestamp (for time-range queries)
- Traffic type (INGRESS/EGRESS)
5. Policy Generation
The CLI queries the Broker API:
- Fetches all traffic for a specific pod
- Identifies unique source/destination IPs
- Reads the peer identity the Broker stamped on each row at ingest; falls back to
/pod/ip/{ip}?at=for unresolved rows (how peers are attributed) - Groups traffic by protocol and port
- Deduplicates rules
- Generates K8s/Cilium NetworkPolicy YAML
6. Visualization
The UI fetches data from the Broker and renders:
- An interactive graph of pod-to-pod communication (React Flow)
- Tables of traffic details with filtering
- Live view (polled, ~5s) — the UI re-fetches from the Broker on a short interval
Technology Stack
Controller (Rust + eBPF)
Why Rust?
Why Rust?
- Memory safety without garbage collection (critical for low-level system programming)
- Performance on par with C/C++ for userspace eBPF handling
- libbpf-rs bindings provide excellent eBPF ergonomics
- Tokio async runtime for efficient event processing
eBPF Programs
eBPF Programs
- Written in C, compiled with
clang -target bpf - Use
libbpf-cargoto generate Rust skeleton bindings (.skel.rs) - Attach fentry probes to
tcp_v4_connect,tcp_set_state,udp_sendmsg, andtcp_retransmit_skb; a kprobe/kretprobe pair toinet_csk_accept; theraw_syscalls/sys_entertracepoint; andtp_btf/cgroup_mkdirfor startup syscall capture - Use BPF maps for kernel ↔ userspace communication
Kubernetes Integration
Kubernetes Integration
- kube-rs: Rust Kubernetes client for pod watching
- Watches pods on the current node via field selector
- Filters by excluded namespaces (kube-system, kguardian, etc.)
- Handles pod lifecycle events (create, update, delete)
Broker (Rust + Actix-web + PostgreSQL)
API Framework
API Framework
- Actix-web: High-performance HTTP server (thousands of req/sec)
- RESTful API with JSON payloads
- Async request handling with Tokio
- Diesel ORM for type-safe SQL queries
Data Schema
Data Schema
Tables:
pod_details: Pod metadata (name, namespace, IP, labels, spec)svc_details: Service metadata (name, namespace, cluster IP, selectors)pod_traffic: Network connections (src/dst IP, port, protocol, type) plus the peer identity resolved at ingest (peer_kind,peer_namespace,peer_name,peer_uid,peer_workload_kind,peer_workload_name,peer_resolved_at)pod_syscalls: Syscall observations (pod, syscall names, count, architecture)audit_verdicts: Evaluator would-deny verdicts for observed flowsinstall_info: Installation metadata
Data Retention
Data Retention
audit_verdictsare pruned automatically:broker.audit.retention.days(default 30), withintervalSecondsandbatchSizecontrolling the pruning loop.pod_trafficrows of departed pods (dead, no longer in the pod table, or an earlier pod with the same name) are pruned afterbroker.traffic.retention.days(default 14), with the sameintervalSecondsandbatchSizeknobs. Rows of running pods are kept however old: each flow is recorded once, when first seen, and a running pod never reports it again, so pruning them would drop rules from its next generated policy. The exception is a running pod’s row that a newer row supersedes (same direction, protocol, port, decision and in-cluster peer workload), which also goes afterdays. Rows whose peer has no identity (external or unresolved) can be bounded with the opt-inbroker.traffic.retention.maxRowsPerPod.pod_syscallsis retained indefinitely.- The image inventory (
images,workload_containers) is sized by what runs: running pods refresh their rows continuously, and a digest no running pod has reported forbroker.imageInventory.retention.days(default 30) is pruned, followed by images nothing references, with the sameintervalSecondsandbatchSizeknobs. A digest counts as running while a pod refreshed it withinbroker.imageInventory.runningWindowSeconds(default 900) or while the pod that last reported it is live; every live pod is re-posted every 60 s whether or not it is Ready, so crash-looping workloads stay visible. The container must also be running (or crash-looping) in its latest report: a completed init container is listed asranAsInitand a digest that never started aswaiting, neither counted as running. - Vulnerability and SBOM data from the supplychain component (
vuln_sources,image_vulnerabilities,image_sbom_components,supplychain_image_links, and the per-CVE summaryvuln_cve_summaryrebuilt each retention pass) is keyed by image digest, never by pod. A payload is deleted once no inventory digest it applies to has had a workload container seen forbroker.supplychain.retention.days(default 30) and none is running; a payload nothing in the inventory matches is kept forunlinkedGraceHours(default 24). Incomplete paged SBOMs expire aftersbomPageTtlSeconds. See Vulnerability endpoints. - The cluster-wide
GET /pod/trafficendpoint is capped to protect the broker: 5000 rows by default, up to 20000 via?limit=.
CLI (Go)
Why Go?
Why Go?
- kubectl plugin ecosystem: Natural fit for K8s tooling
- client-go: Official Kubernetes Go client
- cobra: Industry-standard CLI framework
- Cross-platform binaries without dependencies
Policy Generation Logic
Policy Generation Logic
Network policies:
- Query
/pod/traffic/{pod}from Broker - Use the row’s stored
peer_*identity; for rows without one, query/pod/ip/{ip}?at=<time_stamp>or/svc/ip/{ip} - Build
NetworkPolicyPeerwithpodSelector+namespaceSelector - Group by direction (ingress/egress) and port/protocol
- Deduplicate rules via JSON marshaling comparison
- Generate YAML with
sigs.k8s.io/yaml
Seccomp Generation Logic
Seccomp Generation Logic
- Query
/pod/syscalls/{pod}from Broker - Extract unique syscall names from aggregated data
- Group by architecture (x86_64, arm64, etc.)
- Generate JSON with
defaultAction: SCMP_ACT_ERRNO - Add
syscallsarray withaction: SCMP_ACT_ALLOW
UI (React + TypeScript)
Frontend Stack
Frontend Stack
- React 19: Modern UI framework
- TypeScript: Type safety for complex state
- Vite: Fast build tooling
- TailwindCSS 4: Utility-first styling
- React Flow: Interactive network graph rendering
Visualization
Visualization
- Pods rendered as collapsible nodes (expand to show containers)
- Edges show traffic direction with arrows
- Namespace-based grouping with color coding
- Interactive filtering by namespace, pod, traffic type
Data Display
Data Display
- Traffic table with sorting and pagination
- Pod details panel with labels and metadata
- Live view (polled, ~5s) via the Broker API
- Dark mode support (matches Cilium Hubble aesthetic)
Deployment Architecture
Standard Deployment (Helm)
/mcp, see Connect an MCP Client) are both routes on port 8080.
High Availability
For production, configure:- Broker replicas: 2-3 for load balancing
- PostgreSQL: Use managed service (RDS, Cloud SQL) or PostgreSQL operator with replication
- Controller: Automatically HA via DaemonSet (one per node)
- UI: 2+ replicas behind ingress/load balancer
Security Considerations
Controller Privileges
Controller Privileges
The Controller requires:
- Privileged container with CAP_BPF added (to load eBPF programs)
- hostNetwork: true (to access the node network namespace)
Data Access
Data Access
- The Broker API is unauthenticated by default, with opt-in scoped bearer tokens via
broker.auth:read(frontend proxy, llm-bridge, CLI),ingest(controller),supplychain(supply-chain writes) and optionaladmin. Every route declares its scope./healthand/metricsstay open - The chart can also manage a NetworkPolicy restricting Broker access
- For production, additionally consider:
- Ingress with authentication (OAuth2 proxy, etc.)
- mTLS between components
Data Sensitivity
Data Sensitivity
The PostgreSQL database contains:
- Pod names, namespaces, labels
- IP addresses and ports of communication
- Syscall activity
Performance Characteristics
The chart ships with these default resource requests and limits:For measured numbers from a real deployment, see the reference deployment in the project README.
Scalability
- Controllers: Scale linearly with node count (DaemonSet)
- Broker: Stateless, can add replicas behind a load balancer
- PostgreSQL: Vertical scaling or read replicas for high query loads
- CLI: No scaling concerns (client-side)
- UI: Horizontal scaling behind ingress
- Sharding data by namespace
- Time-windowed queries
- Data aggregation/rollup
Deep Dive: eBPF Monitoring
Learn how eBPF programs capture kernel events
Controller Implementation
Explore the Controller source code
Broker API Reference
View all API endpoints and schemas
Troubleshooting
Diagnose common architecture-related issues