spinningfactory/kloak

GitHub: spinningfactory/kloak

基于 eBPF 的 Kubernetes 原生密钥保护工具,在内核层面透明替换 TLS 流量中的占位符为真实凭据,实现零信任密钥隔离。

Stars: 133 | Forks: 2

Kloak Logo

Kloak

无代理保护您的机密
Kubernetes eBPF HTTPS 拦截器。无需更改应用程序或使用 sidecar,即可实现透明的密钥注入。

[![CI](https://static.pigsec.cn/wp-content/uploads/repos/2026/04/4659410262163308.svg)](https://github.com/spinningfactory/kloak/actions/workflows/ci.yml) ![Coverage](https://img.shields.io/badge/Coverage-77.1%25-brightgreen) [![Go 版本](https://img.shields.io/badge/Go-1.26+-00ADD8?logo=go)](https://go.dev/) [![Kubernetes](https://img.shields.io/badge/Kubernetes-1.28+-326CE5?logo=kubernetes)](https://kubernetes.io/) [![许可证](https://img.shields.io/badge/License-AGPL--3.0-blue.svg)](LICENSE)
Kloak 使用 eBPF uprobes 在 Kubernetes 中透明地拦截出站 TLS 流量,并在加密之前于内核级别将散列占位符替换为真实的密钥。应用程序永远不会接触到实际的凭据,并且不需要 sidecar 或更改代码。 ## 功能 - **无需更改代码** -- 无需 SDK,无需库,无需修改应用程序。挂载一个 secret,发出 HTTPS 请求,剩下的交由 Kloak 处理。 - **密钥隔离** -- 应用程序只能看到散列的影子值(`kloak:`)。真实的密钥仅存在于 eBPF maps 中,并在 TLS 写入时于内核中注入。 - **零开销** -- eBPF uprobes 在内核空间运行,延迟影响可忽略不计。数据路径中没有用户空间代理或 sidecar。 - **Kubernetes 原生** -- 适用于标准的 Kubernetes Secrets。只需一个标签即可启用。 - **主机和 IP 过滤** -- 带有 `getkloak.io/hosts` 注解的密钥只会发送到特定的目标主机名或 IP 地址,防止被渗漏到未经授权的服务器。 - **基于端口的过滤** -- 带有 `getkloak.io/port` 注解的密钥被限制为仅在特定目标端口上的连接。 - **广泛的运行时支持** -- 挂钩到 OpenSSL、BoringSSL 和 Go 原生的 `crypto/tls`。适用于 Python、Node.js、Go、Rust、Ruby、PHP、curl 以及任何链接了 OpenSSL 的运行时。 ## 快速开始 ### 前置条件 - Kubernetes 集群 (1.28+) 且 Linux 内核版本为 5.17+ - [Helm](https://helm.sh/docs/intro/install/) 3.12+ - 已配置好集群访问权限的 `kubectl` ### 使用 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 ``` ### 标签和注解 | 键 | 类型 | 范围 | 描述 | |-----|------|-------|-------------| | `getkloak.io/enabled=true` | Label | Secret, Namespace, Pod | 为目标资源启用 Kloak。在命名空间和 Pod 上,控制 webhook 的作用范围。在 Secret 上,触发影子 secret 的创建。 | | `getkloak.io/hosts=host` | Annotation | Secret | 限制密钥只能发送到哪个目标主机或 IP 地址(仅限单个值) | | `getkloak.io/port=443` | Annotation | Secret | 限制密钥只能发送到哪个目标端口 | | `getkloak.io/managed=true` | Label | Secret | 标记由 Kloak 创建的影子 secret(请勿手动设置) | ### 尝试演示 ``` # 完整演示:创建一个带有 K3s 的 Lima VM,部署 Kloak 和示例应用 ./examples/setup-demo.sh export KUBECONFIG=/tmp/kloak-k3s.yaml # 查看日志(应显示 httpbin.org 响应中的真实 secret) kubectl logs -f -l app=demo-python -n kloak-demo -c demo-app # 清理 ./examples/destroy-demo.sh ``` ## 架构 Kloak 由一个控制平面(controller + webhook)和一个完全在内核空间运行的 eBPF 数据平面组成。 ``` 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] P2[Phase 2 Rewrite] XOR[XOR Path] KP[DNS Kprobe/Kretprobe] TP[Connect/Close Tracepoints] PROC[Process Tracepoints] TCP[tcp_sendmsg Kprobe] TC[TC Egress Patch] 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 volume mounts to shadow secrets| P P -->|TLS write with kloak:UUID| UP UP -->|Tail call: plaintext rewrite| P2 UP -->|Tail call: ciphertext rewrite| XOR XOR -->|Stores xor_pending| TCP TCP -->|Stores tc_pending| TC TC -->|Tail call| GHASH P2 -->|Real secret injected| Internet TC -->|Patched ciphertext| Internet KP -->|Populates dns_ip_map| UP TP -->|Populates conn_ip_map / last_verified_fd| UP PROC -->|Tracks process lifecycle| C ``` ### 组件 | 组件 | 描述 | |-----------|-------------| | **Controller** (DaemonSet) | 监视带有 `getkloak.io/enabled=true` 标签的 Secret,创建包含长度匹配的 `kloak:` 占位符的影子 secret,将真实值同步到 eBPF maps 中,并通过 cgroup 发现将 TLS uprobes 附加到容器进程。 | | **Webhook** (Deployment) | 拦截 Pod 创建的 Mutating admission webhook。重写 Secret 卷挂载以指向影子 secret。通过 Pod 标签或命名空间标签评估是否启用。如果影子 secret 尚未创建,则拒绝 Pod(失败即关闭)。两个 webhook 条目确保仅受 kloak 管控的命名空间和 Pod 受到影响;即使 webhook 宕机,非 kloak 工作负载也绝不会受到影响。 | | **TLS Uprobes** | 附加到 `SSL_write` / `SSL_write_ex`(OpenSSL/BoringSSL)和 `crypto/tls.(*Conn).Write`(Go 原生)。拦截出站 TLS 写入,扫描 `kloak:` 前缀。两条重写路径:Phase 2 用于明文重写(加密前),XOR 路径用于密文修补(加密后)。 | | **XOR 路径 + TC Egress** | 对于 Go 原生 TLS:在 uprobe 中计算 XOR 差值,通过 `tcp_sendmsg` kprobe 桥接至 TC egress,后者在传输过程中修补加密数据包,并通过 tail call 调用 `tc_ghash_update` 重新计算 GHASH 身份验证标签。 | | **DNS Kprobe** | 在 `udp_recvmsg` 上的 Kprobe/kretprobe 捕获全系统的 DNS 响应。解析受监控主机名的 A/AAAA 记录,并填充带有 TTL 跟踪的 `dns_ip_map`(IP 到主机名)。 | | **Connect/Close Tracepoints** | 挂钩 `sys_enter/exit_connect` 以跟踪 TCP 连接(在 `conn_ip_map` 中的 fd 到目标 IP 映射)。当目标与经过 DNS 验证的主机名匹配时,将 fd 缓存在 `last_verified_fd` 中。挂钩 `sys_enter_close` 以清理过期的条目。 | | **Process Tracepoints** | 挂钩 `sched_process_exec` 和 `sched_process_exit`,以跟踪容器进程的生命周期,从而进行 uprobe 的附加和清理。 | ### 支持的运行时 | 运行时 | TLS 库 | 挂钩点 | |---------|------------|------------| | Python, Rust, Ruby, PHP, curl | OpenSSL (libssl.so) | `SSL_write` / `SSL_write_ex` uprobe | | Node.js | BoringSSL (静态链接) | `SSL_write` uprobe | | Go | crypto/tls (原生) | `crypto/tls.(*Conn).Write` uprobe | ## DNS 验证的信任链 带有 `getkloak.io/hosts` 注解的密钥只有在通过完整的 DNS 解析链验证目标后才会被重写。这可以防止密钥被渗漏到未经授权的服务器,即使应用程序已被破坏。 ``` 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 P2 as Phase 2 Rewrite 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 kloak:a1b2c3d4 UP->>UP: resolve_host -> api.stripe.com UP->>P2: Tail call P2->>P2: allowed_host matches -> rewrite secret P2->>Srv: Real secret sent to api.stripe.com Note over App,Srv: 4. TLS Write (Blocked) App->>UP: SSL_write to evil.com with kloak:a1b2c3d4 UP->>UP: resolve_host -> evil.com UP->>P2: Tail call P2->>P2: allowed_host mismatch -> BLOCKED P2--xApp: Placeholder sent as-is ``` ### 主机验证如何工作 1. **DNS 捕获** -- `udp_recvmsg` 上的 kprobe 拦截节点上的 DNS 响应。对于 `getkloak.io/hosts` 中列出的主机名,解析后的 IP 会通过 TTL 跟踪存储在 `dns_ip_map` 中。 2. **连接跟踪** -- `sys_enter/exit_connect` 上的 Tracepoints 在 `conn_ip_map` 中记录每个 TCP 连接的 fd 到目标 IP 的映射。如果目标 IP 存在于 `dns_ip_map` 中,该 fd 将被标记为已验证。 3. **主机解析** -- 在 `SSL_write` 时,`resolve_host()` 通过已验证的 fd、`conn_ip_map` 和 `dns_ip_map` 进行链式查找,以确定当前 TLS 连接的主机名。 4. **密钥过滤** -- Phase 2 将解析出的主机名与密钥的 `allowed_host` 进行比较。匹配:重写。不匹配:占位符原样发送,确保密钥安全。 5. **TTL 强制执行** -- DNS 条目包含一个 TTL。过期条目在查找时会被跳过,需要通过新的 DNS 响应重新验证。 ## 工作原理 ### 1. 为您的 Secret 打上标签并添加注解 添加 `getkloak.io/enabled=true` 作为标签以启用 Kloak。使用注解进行主机和端口过滤。Kloak 会生成一个带有 `kloak:` 占位符的影子 secret,这些占位符与原始值的长度相匹配。 ``` 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. 部署您的应用程序 Webhook 会自动重写卷挂载以使用影子 secret。您的应用程序只能看到 `kloak:` 占位符,并且永远不会接触到真实的凭据。 ``` # 应用从挂载的 secret 中读取的内容: kloak:a1b2c3d4-e5f6-7890 ``` ### 3. 内核中的自动重写 当应用程序发出出站 HTTPS 请求时,eBPF uprobe 会拦截 TLS 写入,通过 DNS 信任链验证目标,并在加密前将占位符替换为真实的密钥。 ``` # 应用写入的内容: Authorization: Bearer kloak:a1b2c3d4-e5f6-7890 # 离开 node 的内容(经过 eBPF rewrite 后): Authorization: Bearer sk-live-xyz123 ``` ## 开发 ``` make build # Build the kloak binary make test # Run all tests make docker-build # Build Docker image ``` eBPF 开发需要 Linux。在 macOS 上,Kloak 使用 Lima 虚拟机: ``` 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 ``` ## 许可证 AGPL-3.0
标签:Agentless, AI代理安全, Docker镜像, Go语言, HTTPS拦截, IP 地址批量处理, Secret注入, StruQ, TLS流量拦截, 主机过滤, 内核态安全, 凭据保护, 大语言模型安全, 子域名突变, 安全测试工具, 安全防护, 客户端加密, 微服务安全, 数据处理, 无Sidecar, 日志审计, 机密管理, 程序破解, 端口过滤, 透明代理, 零信任安全