업데이트로 돌아가기
New releaseAug 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 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/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 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, NOTICEBSL 1.1 조건 (변경 라이선스: MPL-2.0) 및 CE/EE 라이선스 분할
leitwacht-agentMPL-2.0 Community-Edition 데이터 플레인 에이전트 (eBPF / DNS 프록시 / nftables 강제) – 위 다이어그램의 엔진

카테고리