kloak

module
v0.2.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 8, 2026 License: AGPL-3.0

README

Kloak Logo

Kloak

Secure Your Secrets, Agentless
Kubernetes eBPF HTTPS interceptor. Transparent secret injection without application changes or sidecars.

CI Coverage Go Version Kubernetes License


Kloak transparently intercepts outbound TLS traffic in Kubernetes using eBPF uprobes, replacing placeholders with real secrets in-kernel by patching the encrypted TLS record on its way out of the pod. Applications never handle actual credentials, and no sidecars or code changes are required.

Features

  • No code changes -- No SDK, no library, no application modifications. Mount a secret, make HTTPS requests, and Kloak handles the rest.
  • Secret isolation -- Applications only see random shadow values (kl::…, same length as the real value). The real value is never written into the pod's memory; it is XOR-patched into the AES-GCM ciphertext in-kernel.
  • Zero overhead -- eBPF uprobes operate in kernel space with negligible latency impact. No userspace proxy or sidecar in the data path.
  • Kubernetes native -- Works with standard Kubernetes Secrets. Enable with a single label.
  • Per-pod secret binding -- A pod can only use the secrets it references in its spec (Secret volume, secretKeyRef, envFrom). A placeholder sent by any other pod, even one that learned it, goes out unchanged.
  • Host and IP filtering -- Secrets annotated with getkloak.io/hosts are only sent to specific destination hostnames or IP addresses, preventing exfiltration to unauthorized servers.
  • Port-based filtering -- Secrets annotated with getkloak.io/port are restricted to connections on a specific destination port.
  • Broad runtime support -- Hooks into OpenSSL, BoringSSL, and Go's native crypto/tls. Works with Python, Node.js, Bun, Go, Rust, Ruby, PHP, curl, and any OpenSSL-linked runtime.

Quick Start

Prerequisites

  • Kubernetes cluster (1.28+) with Linux kernel 5.17+ (6.6+ attaches the tc patch program via TCX; older kernels fall back to a clsact qdisc + cls_bpf filter, see controller.ebpf.tcAttachMode)
  • Helm 3.12+
  • kubectl configured with cluster access

Install with Helm

helm repo add kloak https://chart.getkloak.io
helm repo update

helm install kloak kloak/kloak -n kloak-system --create-namespace

kubectl get pods -n kloak-system

Labels and Annotations

Key Type Scope Description
getkloak.io/enabled=true Label Secret, Namespace, Pod Enables Kloak for the target resource. On namespaces and pods, controls webhook scope. On secrets, triggers shadow secret creation.
getkloak.io/hosts=host Annotation Secret Restricts which destination host or IP address a secret can be sent to (single value only)
getkloak.io/port=443 Annotation Secret Restricts which destination port a secret can be sent to
getkloak.io/managed=true Label Secret Marks shadow secrets created by Kloak (do not set manually)

Try the Demo

# Full demo: creates a Lima VM with K3s, deploys Kloak and sample apps
./examples/setup-demo.sh

export KUBECONFIG=/tmp/kloak-k3s.yaml

# View logs (should show real secrets in httpbin.org responses)
kubectl logs -f -l app=demo-python -n kloak-demo -c demo-app

# Cleanup
./examples/destroy-demo.sh

Architecture

Kloak consists of a control plane (controller + webhook) and an eBPF data plane that runs entirely in kernel space.

graph TD
    subgraph Control_Plane [Control Plane]
        C[Kloak Controller - DaemonSet]
        W[Mutating Webhook - Deployment]
    end

    subgraph Data_Plane [Data Plane - eBPF in Kernel]
        UP[TLS Uprobes]
        HX[H Extract]
        XOR[XOR Path]
        KP[DNS Kprobe/Kretprobe]
        TP[Connect/Close Tracepoints]
        PROC[Process Tracepoints]
        TCP[tcp_sendmsg Kprobe]
        TC[TC Patch - host-side veth]
        GHASH[TC GHASH Update]
    end

    subgraph App [Application]
        P[Pod with Shadow Secret]
    end

    C -->|Creates shadow secrets and syncs BPF maps| UP
    C -->|Attaches probes to container processes| UP
    W -->|Rewrites secret references to shadow secrets| P
    P -->|TLS write with kl:: placeholder| UP
    UP -->|Tail call: libcrypto-hook processes| HX
    UP -->|Tail call| XOR
    HX -->|Tail call| XOR
    XOR -->|Stores xor_pending| TCP
    TCP -->|Stores tc_pending| TC
    TC -->|Tail call| GHASH
    GHASH -->|Patched ciphertext + tag| Internet
    KP -->|Populates dns_ip_map| UP
    TP -->|Populates conn_ip_map / last_verified_fd| UP
    PROC -->|Tracks process lifecycle| C

Components

Component Description
Controller (DaemonSet) Watches Secrets labeled getkloak.io/enabled=true, creates shadow secrets with random kl::… placeholders of the same length as each real value, syncs real values into eBPF maps, and attaches TLS uprobes to container processes via cgroup discovery.
Webhook (Deployment) Mutating admission webhook that intercepts Pod creation. Rewrites Secret volume mounts and environment variable references (secretKeyRef, envFrom) to point to shadow secrets. Evaluates enablement through pod labels or namespace labels. Rejects pods if the shadow secret has not been created yet (fail-closed). Two webhook entries ensure only kloak-enabled namespaces and pods are affected; non-kloak workloads are never impacted, even when the webhook is down.
TLS Uprobes Attach to SSL_write / SSL_write_ex (OpenSSL/BoringSSL), crypto/tls.(*Conn).Write (Go native), and SSL_write at a pre-computed file offset in symbol-stripped Bun binaries. Intercept outbound TLS writes and scan the plaintext for kl:: placeholders. The plaintext buffer is only read, never modified.
H Extract Recovers the connection's AES-GCM GHASH key H, which is needed to fix the authentication tag after patching. For processes whose libcrypto exposes EVP_CipherInit_ex, a uprobe captures it at cipher init. Otherwise the tcp_sendmsg kprobe walks the library's TLS structs to it. BoringSSL keeps only the AES key schedule, so H = AES_K(0) is recomputed in-kernel.
XOR Path + TC Patch Used for every runtime. Checks the destination against the secret's host/IP/port policy, then computes shadow XOR real for each matched placeholder. This bridges through the tcp_sendmsg kprobe to a tc program on the host side of the pod's veth pair, which XOR-patches the AES-GCM ciphertext in the outgoing packet and recomputes the GHASH tag via a tail call to tc_ghash_update. The real secret is never written to user-space memory, and patching outside the pod's network namespace keeps the patched ciphertext out of reach of in-pod packet capture.
DNS Kprobe Kprobe/kretprobe on udp_recvmsg captures DNS responses system-wide. Parses A/AAAA records for watched hostnames and populates dns_ip_map (IP to hostname) with TTL tracking.
Connect/Close Tracepoints Hooks sys_enter/exit_connect to track TCP connections (fd to destination IP in conn_ip_map). When the destination matches a DNS-verified hostname, caches the fd in last_verified_fd. Hooks sys_enter_close to clean up stale entries.
Process Tracepoints Hooks sched_process_exec and sched_process_exit to track container process lifecycle for uprobe attachment and cleanup.

Supported Runtimes

Runtime TLS Library Hook Point
Python, Rust, Ruby, PHP, curl OpenSSL (libssl.so) SSL_write / SSL_write_ex uprobe
Node.js OpenSSL (statically linked into node) SSL_write uprobe on the main executable
Apps linked against BoringSSL BoringSSL (libssl.so) SSL_write uprobe
Bun (incl. single-executable apps) BoringSSL (statically linked, symbols stripped) SSL_write uprobe at a per-version file offset (tools/bun-offsets/)
Go crypto/tls (native) crypto/tls.(*Conn).Write uprobe

Secrets are patched into AES-128-GCM and AES-256-GCM records (TLS 1.2 and 1.3). Other ciphers (e.g. ChaCha20-Poly1305) and some libraries (GnuTLS, Go built with GOEXPERIMENT=boringcrypto) are not supported yet.

DNS-Verified Trust Chain

Secrets with getkloak.io/hosts annotations are only rewritten when the destination is verified through the full DNS resolution chain. This prevents secret exfiltration to unauthorized servers, even if an application is compromised.

sequenceDiagram
    participant App as Application
    participant DNS as DNS Server
    participant KP as eBPF kprobe
    participant TP as eBPF tracepoint
    participant UP as eBPF uprobe
    participant XOR as XOR Path
    participant TC as tc patch (host veth)
    participant Srv as api.stripe.com

    Note over App,Srv: 1. DNS Resolution
    App->>DNS: resolve api.stripe.com
    DNS-->>App: A 52.55.108.115
    KP->>KP: dns_ip_map[52.55.108.115] = api.stripe.com

    Note over App,Srv: 2. TCP Connect
    App->>Srv: connect(fd=7, 52.55.108.115:443)
    TP->>TP: conn_ip_map[tgid,fd=7] = 52.55.108.115
    TP->>TP: IP in dns_ip_map -> mark fd as verified

    Note over App,Srv: 3. TLS Write (Allowed)
    App->>UP: SSL_write with kl::a1b2c3d4
    UP->>UP: resolve_host -> api.stripe.com
    UP->>XOR: Tail call
    XOR->>XOR: allowed_host matches -> stage XOR delta
    App->>TC: Encrypted record leaves the pod
    TC->>TC: XOR-patch ciphertext, recompute GCM tag
    TC->>Srv: Real secret decrypted only by api.stripe.com

    Note over App,Srv: 4. TLS Write (Blocked)
    App->>UP: SSL_write to evil.com with kl::a1b2c3d4
    UP->>UP: resolve_host -> evil.com
    UP->>XOR: Tail call
    XOR->>XOR: allowed_host mismatch -> nothing staged
    App->>Srv: Placeholder sent as-is

How Host Verification Works

  1. DNS capture -- A kprobe on udp_recvmsg intercepts DNS responses on the node. For hostnames listed in getkloak.io/hosts, resolved IPs are stored in dns_ip_map with TTL tracking.

  2. Connection tracking -- Tracepoints on sys_enter/exit_connect record each TCP connection's fd-to-destination-IP mapping in conn_ip_map. If the destination IP exists in dns_ip_map, the fd is marked as verified.

  3. Host resolution -- At SSL_write time, resolve_host() chains through verified fd, conn_ip_map, and dns_ip_map to determine the hostname of the current TLS connection.

  4. Secret filtering -- The XOR path compares the resolved hostname (and IP/port, if set) against the secret's policy. Match: a patch is staged for the tc program. Mismatch: nothing is staged and the placeholder is sent as-is, keeping the secret safe.

  5. TTL enforcement -- DNS entries include a TTL. Expired entries are skipped on lookup, requiring re-verification through fresh DNS responses.

How It Works

1. Label and Annotate Your Secrets

Add getkloak.io/enabled=true as a label to enable Kloak. Use annotations for host and port filtering. Kloak generates a shadow secret whose values are random kl::… placeholders of exactly the same length as the originals.

apiVersion: v1
kind: Secret
metadata:
  name: api-credentials
  labels:
    getkloak.io/enabled: "true"
  annotations:
    getkloak.io/hosts: "api.stripe.com"
    getkloak.io/port: "443"
data:
  api-key: c2stbGl2ZS14eXoxMjM=  # sk-live-xyz123

2. Deploy Your Application

The webhook automatically rewrites the pod's secret references (volume mounts, secretKeyRef, envFrom) to use the shadow secret. Your application sees only kl::… placeholders and never handles real credentials.

# What the application reads from the mounted secret (same length as the real value):
kl::q7XmB2rTa9

Only the pods that reference a secret this way can use it. The controller binds each pod's containers to the secrets in its spec, and the data plane refuses to rewrite a placeholder for any other pod: the placeholder goes out unchanged and the controller logs a secret binding violation. Apps that read a shadow secret through the Kubernetes API instead of mounting it are refused too. Set controller.ebpf.secretBinding: audit to let such rewrites through while logging each one, to find these workloads before enforcing.

3. Automatic In-Kernel Rewrite

When the application makes an outbound HTTPS request, the eBPF uprobe reads the TLS write, verifies the destination through the DNS trust chain, and stages a patch. After the TLS library encrypts the record, a tc program on the host side of the pod's veth XOR-patches the ciphertext so that it decrypts to the real secret, and recomputes the AES-GCM tag. The real value never exists in the pod's memory.

# What the application writes (and what is in its memory):
Authorization: Bearer kl::q7XmB2rTa9

# What api.stripe.com decrypts (after the in-kernel ciphertext patch):
Authorization: Bearer sk-live-xyz123

Development

make build          # Build the kloak binary
make test           # Run all tests
make docker-build   # Build Docker image

eBPF development requires Linux. On macOS, Kloak uses Lima VMs:

make lima-start      # Start Lima VM
make generate-ebpf   # Generate eBPF Go bindings
make test-linux      # Run tests in Linux VM
make lima-shell      # Shell into VM

License

AGPL-3.0

Directories

Path Synopsis
cmd
ebpftest command
kloak command
pkg
controller
Package controller provides the Kubernetes controller for watching pods and managing eBPF program attachments.
Package controller provides the Kubernetes controller for watching pods and managing eBPF program attachments.
logging
Package logging provides a centralized zap logger setup for the kloak project.
Package logging provides a centralized zap logger setup for the kloak project.
secrets
Package secrets defines the boundary between kloak's control plane (which decides which secrets exist and what they should be rewritten to) and its eBPF data plane (which carries out the rewrite in-kernel).
Package secrets defines the boundary between kloak's control plane (which decides which secrets exist and what they should be rewritten to) and its eBPF data plane (which carries out the rewrite in-kernel).
secrets/k8s
Package k8s implements secrets.Source over a Kubernetes informer cache.
Package k8s implements secrets.Source over a Kubernetes informer cache.
secrets/yaml
Package yaml is one concrete `secrets.Source` implementation: it reads a kloak-native YAML file and emits the canonical `[]secrets.Secret` slice the data plane consumes.
Package yaml is one concrete `secrets.Source` implementation: it reads a kloak-native YAML file and emits the canonical `[]secrets.Secret` slice the data plane consumes.
webhook
Package webhook provides the mutating admission webhook handler for rewriting secret volumes to point to shadow secrets.
Package webhook provides the mutating admission webhook handler for rewriting secret volumes to point to shadow secrets.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL