spinningfactory/kloak
GitHub: spinningfactory/kloak
基于 eBPF 的 Kubernetes 原生密钥保护工具,在内核层面透明替换 TLS 流量中的占位符为真实凭据,实现零信任密钥隔离。
Stars: 133 | Forks: 2
无代理保护您的机密
Kubernetes eBPF HTTPS 拦截器。无需更改应用程序或使用 sidecar,即可实现透明的密钥注入。
[](https://github.com/spinningfactory/kloak/actions/workflows/ci.yml)

[](https://go.dev/)
[](https://kubernetes.io/)
[](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, 日志审计, 机密管理, 程序破解, 端口过滤, 透明代理, 零信任安全