
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 query (port 53)
v
nftables redirect ──> DNS Proxy (:15353)
|
|── Allowlist check (FQDN match)
|── Block: NXDOMAIN response
|── Allow: forward to upstream, add resolved IPs to eBPF allow map
|── Record: domain, resolved IPs, PID, process name
v
Upstream DNS (CoreDNS / external)
Container Process
|
| TCP/UDP egress (any port)
v
eBPF cgroup-skb/egress filter
|── IP in allow map? ──> ALLOW (counted)
|── IP in trusted CIDRs? ──> ALLOW (pod net, svc net)
|── Cloud metadata (169.254.169.254)? ──> BLOCK (always)
|── DoT (port 853)? ──> BLOCK (always)
|── IPv6 (non-loopback)? ──> BLOCK (always)
|── Default ──> BLOCK (drop event to ringbuf)
Pod 수준 공유 (Kubernetes)
K8s pod의 모든 컨테이너는 하나의 네트워크 네임스페이스를 공유합니다. Leitwacht는 공유 상태(DNS 프록시, nftables 규칙, eBPF 맵)를 소유하는 pod당 하나의 NetnsContext를 생성합니다. 각 컨테이너는 자체 cgroup-skb 연결을 가지므로 형제 컨테이너가 필터를 우회할 수 없습니다.
Pod (shared netns)
+-- NetnsContext (1 per pod)
| +-- DNS Proxy (1 goroutine pair)
| +-- nftables redirect rules
| +-- eBPF program + maps
| +-- Ringbuf reader
|
+-- Container A
| +-- CgroupLink (eBPF attached to A's cgroup)
| +-- ViolationRecorder
|
+-- Container B
+-- CgroupLink (eBPF attached to B's cgroup)
+-- ViolationRecorder
에이전트-백엔드 통신
+------------------+ gRPC +------------------+
| Leitwacht Agent | ──────────────────────> | Leitwacht Server |
| (per node) | | (central) |
| | GetPolicy(project) | |
| - Watcher | <───────────────────── | - Policy store |
| - Enforcer | | - Violation DB |
| - DNS Proxy | ReportViolations(job) | - REST API |
| - Reporter | ──────────────────────> | - Frontend SPA |
| - Honeypot LSM | | - Notifications |
| | Heartbeat() | - Baselines |
| | ──────────────────────> | - Anomaly det. |
+------------------+ +------------------+
아키텍처
구성 요소
| 구성 요소 | 설명 |
|---|---|
Agent (cmd/agent) | Runner 노드의 DaemonSet입니다. containerd를 감시하여 작업 컨테이너를 찾고, eBPF + DNS 프록시를 연결하며, 위반을 보고합니다. root + Linux가 필요합니다. |
Server (cmd/server) | 중앙 백엔드입니다. 프론트엔드용 REST API와 에이전트용 gRPC를 제공합니다. 정책, 위반, 기준선을 저장합니다. Postgres 또는 SQLite를 사용합니다. |
Frontend (frontend/) | SvelteKit SPA입니다. 정책 관리, 위반 뷰어, 기준선 관리, 이상 탐지, 감사 로그를 제공합니다. |
주요 패키지
이 리포지토리는 EE 서버 + EE 라이선스 에이전트입니다. 위 다이어그램에 표시된 네트워크 강제 데이터 플레인(eBPF, DNS 프록시, nftables, containerd 감시자)은 별도의 MPL-2.0 Community-Edition 모듈
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 executor를 사용하는 GitLab Runner
- Runner에
FF_NETWORK_PER_BUILD=true설정 (각 작업이 자체 네트워크 네임스페이스를 가짐)
Helm 차트
Server (helm/) — OCI 차트로 게시됩니다. Postgres + ClickHouse와 몇 가지 사전 생성된 시크릿(DB 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>
Agent (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 | 강제 없음 | 강제 없음 | 제외된 프로젝트 |
개발
빌드
# Backend
go build ./cmd/agent
go build ./cmd/server
# Frontend
cd frontend && pnpm install && pnpm build
# gRPC contract regeneration (edit contract/proto/**, then):
cd contract && buf generate proto/
eBPF 데이터 플레인은 이 리포지토리에 없습니다 –
leitwacht-agent(CE) 모듈에 있습니다. 거기서 BPF 스켈레톤을 다시 생성하세요 (해당 리포지토리의 Makefile 참조:make ebpf-generate).
테스트
# Unit tests (any platform)
go test ./...
# Integration / e2e tests requiring sidecars (ClickHouse + Postgres) live under
# internal/store, internal/api, internal/agentgrpc — bring those up first.
# Frontend
cd frontend && pnpm test
린트
# Backend
golangci-lint run ./...
# Frontend
cd frontend && pnpm lint
문서
| 문서 | 내용 |
|---|---|
| docs/DEPLOY.md | 전체 Kubernetes 배포 런북 (시크릿, ClickHouse, 마이그레이션, 라이선스 설치, 노출) |
| docs/MULTI_ORG.md | 다중 조직 / 다중 테넌트 설정 |
| LICENSE, NOTICE | BSL 1.1 조건 (변경 라이선스: MPL-2.0) 및 CE/EE 라이선스 분할 |
leitwacht-agent | MPL-2.0 Community-Edition 데이터 플레인 에이전트 (eBPF / DNS 프록시 / nftables 강제) – 위 다이어그램의 엔진 |