leshy-controller

Леший - контроллер.
Компоненты
bpf-фильтр
См. README
Структура репозитория
│
├── cbpf/
│ ├── filter/
│ │ └── `*.c`
│ │
│ ├── tests/
│ │ └── `*.c`
│ │
│ ├── Makefile
│ │
│ └── README.md
│
├── cmd/
│ └── controller/
│ └── `main.go`
│ # Точка входа
│ # - читает конфиг
│ # - настраивает контекст сигналов
│ # - вызывает bootstrap.New(cfg) и затем application.Run(ctx)
│
├── internal/
│ ├── application/
│ │ ├── filter/
│ │ │ ├── `port.go` # Контракт backend для сценария allow/stats
│ │ │ ├── `service.go` # UseCase для /allow
│ │ │ └── `stats.go` # Модель расширенной статистики
│ │ └── management/
│ │ ├── `types.go` # Настройки trust-контура + runtime status
│ │ ├── `port.go` # Контракты usecase/repository/verifier/runtime applier
│ │ └── `service.go` # Bootstrap + валидация settings + авторизация JWT
│ │
│ ├── bootstrap/
│ │ └── `bootstrap.go` # Composition root + применение сохраненных settings при старте
│ │
│ ├── config/
│ │ ├── `config.go` # Чтение yaml и runtime-валидация общих параметров
│ │ └── `config_test.go`
│ │
│ ├── contracts/
│ │ ├── rest/v1/
│ │ │ └── `types.go` # DTO REST API v1 (включая runtime_attached/runtime_iface)
│ │ └── grpc/v1/
│ │ ├── `leshy_controller_v1.pb.go`
│ │ └── `leshy_controller_v1_grpc.pb.go` # Сгенерированные контракты из docs/api/grpc/*.proto
│ │
│ ├── infrastructure/
│ │ ├── jwtauth/
│ │ │ └── `verifier.go` # EdDSA JWT verifier + JWKS fetch/cache
│ │ ├── leshybpf/
│ │ │ ├── `attach_manager.go` # Attach lifecycle
│ │ │ ├── `attach_tc.go` # Загрузка/attach tc+bpf
│ │ │ ├── `filter_backend.go` # Backend adapter для application/filter
│ │ │ ├── `runtime_settings_applier.go` # Динамическое применение iface/ports/handshake/inactive-timer
│ │ │ ├── `diagnostics_linux.go` # Linux debug diagnostics
│ │ │ └── `*.go` # guarded ports, pending, stats, utils
│ │ └── sqlite/
│ │ ├── `db.go` # Open/prepare sqlite file
│ │ ├── `settings_repository.go` # Raw SQL репозиторий management settings
│ │ └── migrations/
│ │ ├── `embed.go` # embed SQL миграций
│ │ ├── `runner.go` # применение миграций на старте
│ │ └── `sql/`
│ │ └── `0001_create_management_settings.sql`
│ │
│ ├── interfaces/
│ │ ├── grpc/
│ │ │ ├── server/
│ │ │ │ └── `server.go` # gRPC transport (runtime.Server)
│ │ │ └── v1/
│ │ │ └── `api.go` # gRPC handlers v1 + auth/validation/error mapping -> usecases
│ │ │
│ │ ├── rest/
│ │ │ ├── httpserver/
│ │ │ │ ├── `server.go` # HTTP transport (runtime.Server)
│ │ │ │ └── `handlers.go`
│ │ │ ├── router/
│ │ │ │ └── `router.go` # Root router (/api/v1, /api/healthz)
│ │ │ └── v1/
│ │ │ └── `api.go` # HTTP handlers v1, auth middleware + mapping -> usecases
│ │ │
│ └── runtime/
│ ├── `app.go` # Оркестратор жизненного цикла приложения
│ ├── `close_server.go` # Adapter для graceful close ресурсов
│ └── `server.go` # Контракт транспорта Start/Stop/Name
│
├── devops/
│ └── ... (скрипты сборки)
│
├── docs/
│ ├── examples/
│ │ └── `basic.md`
│ │
│ ├── cbpf/
│ │ └── `cbpf-schema.drawio`
│ │
│ ├── plantuml/
│ │ ├── `c4-component.puml`
│ │ ├── `c4-containter.puml`
│ │ └── `c4-context.puml`
│ │
│ ├── api/
│ │ ├── grpc/
│ │ │ └── `leshy_controller_v1.proto` # gRPC-контракт, зеркалирующий HTTP API
│ │ └── swagger/
│ │
│ ├── badges/
│ │ └── `coverage.svg`
│ │
│ ├── `architecture.md`
│ │
│ └── `c4.md`
│
├── .pre-commit-config.yaml # Pre-commit хуки
├── .clang-format # Форматирование C
├── .golangci.yaml # Go линтер
├── Makefile # Основной Makefile
├── go.mod # Go модули
├── go.sum
├── LICENSE
└── README.md
Комментарии
infrastructure/leshybpf — это конкретный secondary adapter (инфраструктурный драйвер) для Linux/eBPF/TC.
Usecase-слой (internal/application/filter) не знает про *ebpf.Map, tc, bpftool и syscalls: он общается с инфраструктурой только через порт filter.Backend.
Внутри leshybpf собрана вся “железная” логика: attach/pin, работа с картами, byte order, и опциональная диагностика (только в debug).
API транспорт переключается через api_server_mode: http (REST) или grpc (protobuf контракт из docs/api/grpc).
Вызовы консольных утилит
Используется в режиме отладки. Если отсутствует в системе - будет залогировано предупреждение. Необходима для расширенного анализа вывода bpf-программ.
tc
Используется для расширенной аналитики планировщика пакетов ядра.
x86/x64
Все тестирование и адаптация исключительно выполнялось для x64. На x86 с большой долей вероятности будут ошибки конвертации.
Отладка
Отладка, во многом, опирается на bpftool. Пример сборки из исходных текстов - https://gist.github.com/devalv/0d4af62eca14b1ea91b8f7c2c6f3f163
Подключение внешней системы
Текущая схема:
POST /api/v1/management/settings доступен только в bootstrap-режиме (заголовок X-Bootstrap-Token).
PATCH /api/v1/management/settings доступен только после первичной конфигурации и авторизуется тем же Bearer access token, что и /allow.
- В
settings передаются runtime-параметры фильтра: iface, guarded_ports_range, handshake_window_sec, inactive_timer_sec.
POST /api/v1/management/block очищает разрешающие правила (pending и active_flows), созданные через /allow.
- После успешного
POST или PATCH приложение динамически поднимает/обновляет eBPF runtime.
JWT для /allow, PATCH /management/settings и POST /management/block проверяется по JWKS внешней системы (EdDSA / Ed25519).
Примечание: insecure-режим с отключением TLS-проверки в leshy-controller не предусмотрен.
Дополнительные примеры
openAPI
gRPC proto
Генерация Go-контрактов из protobuf:
make grpc
Режим API сервера
api_server_mode: http:
- запускается REST API на
api_listen_addr;
- доступны
/api/healthz и /api/v1/*.
api_server_mode: grpc:
- запускается gRPC сервер на
api_listen_addr;
- доступны сервисы
HealthService, FilterService, ManagementService из docs/api/grpc/leshy_controller_v1.proto.
Сброс настроек
- Остановите leshy-controller
- Удалите локальную БД (файл)
- Запустите leshy-controller
Поведение после настройки (http)
- После первого успешного
POST /api/v1/management/settings bootstrap-endpoint блокируется (409 management settings are locked), включая сценарий после перезапуска приложения.
- Для последующих изменений используется
PATCH /api/v1/management/settings (Bearer JWT).
- После перезапуска приложение читает сохранённые settings из SQLite и повторно применяет их в runtime (attach выполняется автоматически при наличии сохраненных настроек).
- Для ротации ключей публикуйте новый ключ в JWKS с новым
kid, затем выпускайте новые JWT с этим kid.
- Если
management_bootstrap_token не задан в конфиге, POST /api/v1/management/settings вернёт 503.
- Если settings еще не заданы, runtime не подключен:
POST /api/v1/allow вернет 503 filter is not configured
GET /api/v1/stats вернет 503 filter is not configured
GET /api/healthz вернет JSON с runtime_attached: false
- Runtime-статус дублируется в:
GET /api/healthz (runtime_attached)
GET /api/v1/management/settings
POST /api/v1/management/settings
PATCH /api/v1/management/settings
POST /api/v1/management/block
- Типовые ответы для
PATCH /api/v1/management/settings:
200 при успешном обновлении;
400 при ошибках валидации тела запроса;
401 при невалидном или отсутствующем Authorization: Bearer ...;
409 если настройки еще не были заданы;
503 если авторизация временно недоступна (authorization unavailable).
- Типовые ответы для
POST /api/v1/management/block:
200 при успешной очистке разрешающих правил;
401 при невалидном или отсутствующем Authorization: Bearer ...;
409 если настройки еще не были заданы;
503 если авторизация или filter-runtime недоступны;
500 при внутренней ошибке очистки.
Поведение после настройки (grpc)
- После первого успешного
ManagementService/CreateSettings bootstrap-вызов блокируется и повторный вызов вернет FailedPrecondition (management settings are locked), включая сценарий после перезапуска приложения.
- Для последующих изменений используется
ManagementService/UpdateSettings (Bearer JWT в metadata authorization).
- После перезапуска приложение читает сохранённые settings из SQLite и повторно применяет их в runtime (attach выполняется автоматически при наличии сохраненных настроек).
- Для ротации ключей публикуйте новый ключ в JWKS с новым
kid, затем выпускайте новые JWT с этим kid.
- Если
management_bootstrap_token не задан в конфиге, ManagementService/CreateSettings вернёт Unavailable.
- Если settings еще не заданы, runtime не подключен:
FilterService/Allow вернет Unavailable (Authorization is unavailable);
FilterService/Stats вернет Unavailable (Authorization is unavailable);
HealthService/Health вернет runtime_attached: false.
- Runtime-статус дублируется в:
HealthService/Health (runtime_attached);
ManagementService/GetSettings;
ManagementService/CreateSettings;
ManagementService/UpdateSettings;
ManagementService/Block.
- Типовые ответы для
ManagementService/UpdateSettings:
OK при успешном обновлении;
InvalidArgument при ошибках валидации запроса;
Unauthenticated при невалидном или отсутствующем authorization: Bearer ...;
FailedPrecondition если настройки еще не были заданы;
Unavailable если авторизация временно недоступна (authorization unavailable).
- Типовые ответы для
ManagementService/Block:
OK при успешной очистке разрешающих правил;
Unauthenticated при невалидном или отсутствующем authorization: Bearer ...;
FailedPrecondition если настройки еще не были заданы;
Unavailable если авторизация или filter-runtime недоступны;
Internal при внутренней ошибке очистки.
Дополнительная документация
architecture.md