
LeitWacht v1.21.0
Leitwacht 控制平面 — GitLab Runner CI/CD 的运行时安全:策略编写、多租户、审计、GitLab 集成
GitLab Runner Leitwacht
用于 GitLab Runner CI/CD 容器的网络出口强制工具。Leitwacht 通过控制每个作业容器可以访问的网络来防止供应链攻击泄露秘密。
工作原理
Leitwacht 是一个独立的代理程序,与 GitLab Runner 一起在每个节点上运行。它拦截容器启动事件,附加执行原语,并将所有网络活动报告给中央后端。无需修改 GitLab Runner。
强制栈(每个容器)
Container Process
|
| DNS 查询 (端口 53)
v
nftables 重定向 ──> DNS 代理 (:15353)
|
|── 白名单检查(FQDN 匹配)
|── 阻止:返回 NXDOMAIN
|── 允许:转发到上游,将解析的 IP 添加到 eBPF 允许映射
|── 记录:域名、解析的 IP、PID、进程名称
v
上游 DNS(CoreDNS / 外部)
Container Process
|
| TCP/UDP 出口(任意端口)
v
eBPF cgroup-skb/egress 过滤器
|── IP 在允许映射中? ──> 允许(计数)
|── IP 在受信任 CIDR 中? ──> 允许(Pod 网络,Service 网络)
|── 云元数据 (169.254.169.254)? ──> 阻止(始终)
|── DoT (端口 853)? ──> 阻止(始终)
|── IPv6(非回环)? ──> 阻止(始终)
|── 默认 ──> 阻止(丢弃事件发送到 ringbuf)
Pod 级共享(Kubernetes)
K8s Pod 中的所有容器共享同一个网络命名空间。Leitwacht 为每个 Pod 创建一个 NetnsContext,负责共享状态(DNS 代理、nftables 规则、eBPF 映射)。每个容器拥有独立的 cgroup-skb 附件,防止兄弟容器绕过过滤器。
Pod(共享 netns)
+-- NetnsContext(每个 Pod 一个)
| +-- DNS 代理(1 对 goroutine)
| +-- nftables 重定向规则
| +-- eBPF 程序 + 映射
| +-- Ringbuf 读取器
|
+-- 容器 A
| +-- CgroupLink(eBPF 附加到 A 的 cgroup)
| +-- ViolationRecorder
|
+-- 容器 B
+-- CgroupLink(eBPF 附加到 B 的 cgroup)
+-- ViolationRecorder
代理-后端通信
+------------------+ gRPC +------------------+
| Leitwacht 代理 | ──────────────────────> | Leitwacht 服务器 |
| (每个节点) | | (中央) |
| | GetPolicy(project) | |
| - 监视器 | <───────────────────── | - 策略存储 |
| - 执行器 | | - 违规数据库 |
| - DNS 代理 | ReportViolations(job) | - REST API |
| - 报告器 | ──────────────────────> | - 前端 SPA |
| - Honeypot LSM | | - 通知 |
| | Heartbeat() | - 基线 |
| | ──────────────────────> | - 异常检测 |
+------------------+ +------------------+
架构
组件
| 组件 | 描述 |
|---|---|
代理 (cmd/agent) | 运行在 Runner 节点上的 DaemonSet。监视 containerd 中的作业容器,附加 eBPF + DNS 代理,报告违规。需要 root 权限 + Linux。 |
服务器 (cmd/server) | 中央后端。为前端提供 REST API,为代理提供 gRPC 接口。存储策略、违规、基线。支持 Postgres 或 SQLite。 |
前端 (frontend/) | SvelteKit SPA。策略管理、违规查看器、基线管理、异常检测、审计日志。 |
关键包
此仓库是 EE 服务器 + EE 许可证下的代理。 上述图表中显示的网络强制数据平面(eBPF、DNS 代理、nftables、containerd 监视器)位于单独的、MPL-2.0 社区版模块
leitwacht-agent中,此仓库将其作为依赖项使用;cmd/agent+ee/是 EE 构建,重用该模块并与后端连接。
| 包 | 用途 |
|---|---|
internal/agentgrpc | gRPC 服务器:接收代理违规报告,提供策略 + 规则流 |
internal/api | REST API 处理程序、认证中间件、RBAC 作用域 |
internal/auth | OIDC(GitLab)认证、会话、CSRF |
internal/store | GORM 数据访问(项目、违规、基线)+ ClickHouse |
internal/license, internal/licensemgr | EE 许可证验证(Ed25519 JWS)+ 生命周期和配置门控(见 LICENSE) |
internal/notify | 告警投递(电子邮件/SES、Slack)+ 模板 |
internal/retention | 数据保留 / 清理 |
internal/mcp, internal/mcpauth | MCP 服务器 + 认证 |
ee/reporter | EE 代理 gRPC 客户端,向后端推送违规和建议 |
ee/policycache, ee/rulewatcher, ee/rulestore | EE 代理策略缓存 + 规则监视/存储(BBolt) |
contract/ | 共享 Go 模块:.proto 源文件 + 生成的 agentpb 网络类型 |
部署
先决条件
- 使用 containerd 运行时的 Kubernetes 集群
- 使用 Kubernetes 执行器的 GitLab Runner
- Runner 上设置
FF_NETWORK_PER_BUILD=true(每个作业拥有独立的网络命名空间)
Helm Charts
服务器 (helm/) — 作为 OCI Chart 发布。需要 Postgres + ClickHouse 和一些预先创建的秘密(数据库 DSN、认证密钥)。完整操作手册见 docs/DEPLOY.md。
helm install leitwacht \
oci://registry.gitlab.com/leitwacht/leitwacht/charts/leitwacht --version 1.18.0 \
--set image.tag=1.18.0 \
--set ingress.domain=example.com --set ingress.hosts[0]=leitwacht.example.com \
--set grpcIngress.domain=example.com --set grpcIngress.hosts[0]=grpc-leitwacht.example.com \
--set app.oidcIssuer=https://gitlab.com --set app.oidcClientId=<oauth-app-id> \
--set clickhouse.embedded.password=<password>
代理 (helm/agent/):
helm install leitwacht-agent \
oci://registry.gitlab.com/leitwacht/leitwacht/charts/leitwacht-agent --version 1.18.0 \
--set agent.backendAddr="leitwacht.namespace.svc.cluster.local:9090" \
--set agent.defaultAction=audit \
--set agent.dnsUpstream="10.96.0.10:53,1.1.1.1:53" \
--set agent.trustedCIDRs="10.244.0.0/16,10.96.0.0/12"
强制模式
| 模式 | DNS 策略 | eBPF 过滤器 | 使用场景 |
|---|---|---|---|
| audit | 转发所有,记录违规 | 允许所有,记录丢弃 | 基线构建,初始部署 |
| block | 非白名单域名返回 NXDOMAIN | 丢弃非白名单 IP | 生产强制 |
| allow | 不强制 | 不强制 | 豁免项目 |
开发
构建
# 后端
go build ./cmd/agent
go build ./cmd/server
# 前端
cd frontend && pnpm install && pnpm build
# gRPC 合约重新生成(编辑 contract/proto/**,然后执行):
cd contract && buf generate proto/
eBPF 数据平面不在此仓库中——它位于
leitwacht-agent(CE)模块中。在该仓库中重新生成 BPF 骨架(参见该仓库的 Makefile:make ebpf-generate)。
测试
# 单元测试(任何平台)
go test ./...
# 需要 sidecar(ClickHouse + Postgres)的集成 / e2e 测试位于
# internal/store, internal/api, internal/agentgrpc —— 先启动这些服务。
# 前端
cd frontend && pnpm test
代码检查
# 后端
golangci-lint run ./...
# 前端
cd frontend && pnpm lint
文档
| 文档 | 内容 |
|---|---|
| docs/DEPLOY.md | 完整的 Kubernetes 部署操作手册(Secret、ClickHouse、迁移、许可证安装、暴露服务) |
| docs/MULTI_ORG.md | 多组织 / 多租户设置 |
| LICENSE, NOTICE | BSL 1.1 条款(更改许可证为 MPL-2.0)以及 CE/EE 许可证拆分说明 |
leitwacht-agent | MPL-2.0 社区版数据平面代理(eBPF / DNS 代理 / nftables 强制)——上述图表背后的引擎 |