返回更新列表
新发布Aug 20, 2026

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/agentgrpcgRPC 服务器:接收代理违规报告,提供策略 + 规则流
internal/apiREST API 处理程序、认证中间件、RBAC 作用域
internal/authOIDC(GitLab)认证、会话、CSRF
internal/storeGORM 数据访问(项目、违规、基线)+ ClickHouse
internal/license, internal/licensemgrEE 许可证验证(Ed25519 JWS)+ 生命周期和配置门控(见 LICENSE)
internal/notify告警投递(电子邮件/SES、Slack)+ 模板
internal/retention数据保留 / 清理
internal/mcp, internal/mcpauthMCP 服务器 + 认证
ee/reporterEE 代理 gRPC 客户端,向后端推送违规和建议
ee/policycache, ee/rulewatcher, ee/rulestoreEE 代理策略缓存 + 规则监视/存储(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, NOTICEBSL 1.1 条款(更改许可证为 MPL-2.0)以及 CE/EE 许可证拆分说明
leitwacht-agentMPL-2.0 社区版数据平面代理(eBPF / DNS 代理 / nftables 强制)——上述图表背后的引擎

分类