Назад к обновлениям
New releaseAug 20, 2026

LeitWacht v1.21.0

Leitwacht control plane — безопасность выполнения для GitLab Runner CI/CD: создание политик, мультиарендность, аудит, интеграция с GitLab

Поделиться

GitLab Runner Leitwacht

Принудительное соблюдение сетевого исходящего трафика для контейнеров CI/CD GitLab Runner. 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)

Общий доступ на уровне подов (Kubernetes)

Все контейнеры в поде K8s используют одно сетевое пространство имен. Leitwacht создает один NetnsContext на под, который владеет общим состоянием (DNS-прокси, правила nftables, карты eBPF). Каждый контейнер получает собственное присоединение 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)DaemonSet на узлах раннеров. Следит за containerd для контейнеров заданий, подключает eBPF + DNS-прокси, сообщает о нарушениях. Требует root + Linux.
Server (cmd/server)Центральный бэкенд. REST API для фронтенда, gRPC для агентов. Хранит политики, нарушения, базовые линии. Postgres или SQLite.
Frontend (frontend/)SvelteKit SPA. Управление политиками, просмотр нарушений, управление базовыми линиями, обнаружение аномалий, журнал аудита.

Ключевые пакеты

Этот репозиторий содержит EE сервер и агента под лицензией EE. Плоскость данных принудительного соблюдения сетевых правил, показанная на диаграммах выше (eBPF, DNS-прокси, nftables, наблюдатель containerd), находится в отдельном модуле Community Edition под лицензией MPL-2.0 leitwacht-agent, который этот репозиторий использует как зависимость; cmd/agent + ee/ — это сборка EE, которая повторно использует его и подключает к бэкенду.

ПакетНазначение
internal/agentgrpcgRPC сервер: принимает отчеты о нарушениях от агентов, предоставляет потоки политик и правил
internal/apiОбработчики REST API, промежуточное ПО аутентификации, разграничение RBAC
internal/authАутентификация OIDC (GitLab), сессии, CSRF
internal/storeДоступ к данным через GORM (проекты, нарушения, базовые линии) + ClickHouse
internal/license, internal/licensemgrПроверка лицензии EE (Ed25519 JWS) + жизненный цикл и шлюз конфигурации (см. LICENSE)
internal/notifyДоставка оповещений (email/SES, Slack) + шаблоны
internal/retentionХранение данных / очистка
internal/mcp, internal/mcpauthMCP сервер + аутентификация
ee/reportergRPC клиент агента EE, отправляющий нарушения и уведомления в бэкенд
ee/policycache, ee/rulewatcher, ee/rulestoreКэш политик агента EE + наблюдение/хранение правил (BBolt)
contract/Общий Go модуль: исходники .proto + сгенерированные типы проводов agentpb

Развертывание

Предварительные требования

  • Кластер Kubernetes с рантаймом containerd
  • GitLab Runner, использующий исполнитель Kubernetes
  • FF_NETWORK_PER_BUILD=true на раннерах (каждое задание получает собственное сетевое пространство имен)

Helm-чарты

Server (helm/) — опубликован как OCI-чарт. Требует 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>

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Пересылать все, записывать нарушенияРазрешать все, записывать блокировкиПостроение базовой линии, начальное развертывание
blockNXDOMAIN для не разрешенныхОтбрасывать 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Агент плоскости данных Community Edition под лицензией MPL-2.0 (принуждение через eBPF / DNS-прокси / nftables) — движок, лежащий в основе приведенных выше диаграмм

Категории