wgbutler
wgbutler — менеджер кластера для оверлейной сети на базе WireGuard: инициализация кластера, присоединение нод, демон для синхронизации состояния (gossip) и автоматической конфигурации WireGuard.
Возможности
- CLI команды:
init— инициализация первого узла кластера и выпуск первого join‑tokenjoin— присоединение нового узла по join‑tokendaemon— запуск HTTP‑демона (API + gossip loop)diag— best‑effort диагностика (без утечек секретов)token— управление join‑token (create/list/revoke)node— управление нодами (list/set/delete)client— управление статическими клиентами WireGuard (create/list/delete/move)
- Full‑mesh WireGuard peers между нодами (по умолчанию).
- Опциональный relay mode для нод без public IP (за NAT): NATed-ноды могут маршрутизировать overlay‑трафик к другим NATed-нода через relay, сохраняя direct peers там, где это возможно.
AdvertisedSubnets— подсети, которые нода «анонсирует» (попадают вAllowedIPs).- Админ‑API (token/mTLS) + secure mode (QC allowlist).
- Защита от «сломанных» изменений: большинство мутаций state применяются атомарно вместе с WireGuard‑ре-конфигурацией.
Поддержка ОС
- Linux: реальная настройка WireGuard интерфейса через
ip(8)иwg(8). - macOS/Windows: сборка возможна, но
ConfigureInterfaceявляется no‑op (интерфейс автоматически не настраивается).
Где хранятся конфиги
По умолчанию wgbutler использует каталог:
/etc/wgbutler/state.json— состояние кластераconfig.json— локальная конфигурация кластера (в т.ч. admin token)node.json— локальная конфигурация ноды (в т.ч. приватный WG ключ)
Файлы пишутся с правами 0600, директория — 0700.
Быстрый старт
1) Инициализация первого узла
На первом сервере (Linux):
wgbutler init \
--network-name mynet \
--network-cidr 10.0.0.0/16 \
--public-endpoint your.public.host:51820
Команда выведет Join-Token: ....
2) Запуск демона
На первом сервере:
wgbutler daemon
По умолчанию HTTP API слушает :51821.
3) Присоединение нового узла
На новом сервере:
wgbutler join \
--cluster-ip <ip-or-hostname-of-existing-node> \
--cluster-token <JOIN_TOKEN> \
--public-endpoint your.public.host:51820
wgbutler daemon
Администрирование (CLI)
Большинство админ-команд читают admin_token, api_listen и (опционально) api_tls_listen из config.json.
Если нужно — можно передавать --api-endpoint/--admin-token явно, либо использовать mTLS (--auth mtls + TLS flags).
Join tokens
Примечание: в secure mode мутации join-token выполняются через QC (/api/v1/admin/secure/qc/*); wgbutler token create/revoke вернёт 409. Для secure mode используйте WGButlerAcademy.
wgbutler token create --ttl 3600
wgbutler token list
wgbutler token revoke <TOKEN_ID>
Nodes
Примечание: в secure mode wgbutler node set/delete пока отключены (вернут 409).
wgbutler node list
wgbutler node status
# Best-effort reachability signals (last state sync, handshake age when available)
wgbutler node reachability
wgbutler node set --id <NODE_ID> \
--public-endpoint host:51820 \
--advertised-subnets '10.1.0.0/16,10.2.0.0/16'
# Включить relay-режим на ноде (обычно на ноде с публичным endpoint)
wgbutler node set --id <NODE_ID> --relay true
wgbutler node delete <NODE_ID>
Примечание: удаление ноды запрещено, если к ней привязаны клиенты.
Clients
Примечание: в secure mode мутации клиентов через CLI (client create/delete/move) отключены (вернут 409); используйте WGButlerAcademy. Перенос клиента (move) в secure mode не поддерживается.
Создать клиента и получить конфиг (конфиг печатается в stdout):
wgbutler client create --name laptop > laptop.conf
Список клиентов:
wgbutler client list
wgbutler client list --attached-node <NODE_ID>
Удалить клиента:
wgbutler client delete <CLIENT_ID>
Перенести клиента на другую ноду и выдать новый конфиг:
wgbutler client move --to-node <TARGET_NODE_ID> <CLIENT_ID> > client.conf
HTTP API (кратко)
- Public:
GET /api/v1/healthGET /api/v1/statePOST /api/v1/join
- Local/overlay (только локально или из overlay):
GET /api/v1/control_pubkey
- Admin:
- В insecure mode:
X-Admin-Tokenи/или mTLS (в зависимости отadmin_auth). - В secure mode: только mTLS client certificate + allowlist по
SHA256(SPKI)(admin token отвергается). - Только чтение (оба режима):
GET /api/v1/tokensGET /api/v1/nodesGET /api/v1/clientsGET /api/v1/reachabilityGET /api/v1/topology(true peer graph; computed per-node peers)GET /api/v1/admin/info(TLS listener info + recommended https base URL for admin clients)
- Мутации в insecure mode:
POST /api/v1/tokensDELETE /api/v1/tokens/{id}PATCH/DELETE /api/v1/nodes/{id}POST /api/v1/clientsDELETE /api/v1/clients/{id}POST /api/v1/clients/{id}/move
- Мутации в secure mode (QC):
GET /api/v1/admin/secure/headPOST /api/v1/admin/secure/qc/signPOST /api/v1/admin/secure/qc/commit
- В insecure mode:
TLS/mTLS (admin first)
По умолчанию API работает по HTTP. Можно включить дополнительный HTTPS listener и переключить аутентификацию админ-эндпоинтов:
config.json (локально на каждой ноде):
api_tls_listen— адрес HTTPS listener (например,:51822)api_tls_cert_file/api_tls_key_file— серверный сертификат/ключ (PEM)api_tls_client_ca_file— опциональный CA bundle (PEM). В secure mode используется только как «hint» при выборе клиентского сертификата.admin_auth—token(default) |mtls|token_or_mtls
CLI примеры:
# token auth (как раньше)
wgbutler token list
# mTLS auth (без X-Admin-Token)
wgbutler token list \
--auth mtls \
--api-endpoint https://HOST:51822 \
--tls-ca /path/to/ca.pem \
--tls-cert /path/to/admin.crt \
--tls-key /path/to/admin.key
Secure mode (QC allowlist)
wgbutler поддерживает режим secure: состояние кластера становится «цепочкой коммитов», принимаемой только при наличии QC (quorum certificate) от фиксированного комитета.
Ключевые свойства:
- Комитет по умолчанию:
n=4,threshold=3(Byzantinef=1). - Gossip в secure mode принимает только QC-валидный
stateи выбирает по(epoch, seq). - Admin token после включения secure mode полностью инвалидируется (токен больше не работает ни на одной ноде).
- Admin auth в secure mode: mTLS client certificate + allowlist по
SHA256(SPKI)(chain-verify на уровне TLS не требуется; self-signed допустим).
Включение secure mode (1 запрос)
Условия:
- В кластере должно быть минимум 4 ноды.
Запрос:
POST /api/v1/admin/secure/enable- заголовок:
X-Admin-Token: ... - body:
{ "admin_spki_sha256": "<64 hex>" }
admin_spki_sha256 — это SHA256(SPKI) публичного ключа mTLS сертификата администратора (hex).
Пример (возьми только hex-строку из вывода):
openssl x509 -in admin.crt -noout -pubkey \
| openssl pkey -pubin -outform DER \
| openssl dgst -sha256 -hex
Admin операции в secure mode (QC протокол)
В secure mode мутации state делаются через QC-endpoints:
GET /api/v1/admin/secure/head— текущий head (epoch/seq/state_hash + committee)POST /api/v1/admin/secure/qc/sign— подпись комитета (вызывается на committee nodes)POST /api/v1/admin/secure/qc/commit— коммит операции при наличии кворума подписей
Поддержанные операции QC сейчас:
- join-token: create/revoke
- clients: create/delete
Примечания:
- В secure mode старые мутационные endpoints (
POST/DELETE /api/v1/tokens,POST/DELETE /api/v1/clients,POST /api/v1/clients/{id}/move,PATCH/DELETE /api/v1/nodes/{id}) возвращают409. wgbutler joinв secure mode:POST /api/v1/joinтребуетcontrol_public_key(ed25519 pubkey, base64). CLIwgbutler joinдобавляет автоматически.
Ограничения / допущения MVP
- Только IPv4 для адресов WG.
- Full‑mesh топология (каждый узел знает про остальных) по умолчанию.
- Для нод без public IP доступен opt-in relay mode (через
node set --relay trueна relay‑ноде). - Автонастройка интерфейса реализована только на Linux.
- Linux: роуты для
AdvertisedSubnetsна уровне ОС настраиваются best‑effort черезip route(маршруты помеченыproto 77). - Linux: forwarding/NAT для
AdvertisedSubnets— opt‑in gateway mode (черезnode.jsonили флагиinit/join:--gateway-forwarding,--gateway-nat,--gateway-nat-interface). joinпо умолчанию обращается к API на порту51821(можно изменить через--api-portили указать--cluster-ipкакhost:port).
Эксплуатация (runbook)
docs/runbook.md— минимальный runbook для Linux (systemd, порты, файлы, troubleshooting).
Docker
В репозитории есть:
Dockerfiledocker-compose.yml
Сборка образа:
docker build -t wgbutler:local .
Запуск демона через compose (Linux; требуется /dev/net/tun и CAP_NET_ADMIN):
docker compose up -d wgbutler
Если нужно запускать образ из registry (latest), используй docker-compose.registry.yml:
docker compose -f docker-compose.registry.yml up -d wgbutler
На новом узле join можно сделать автоматически через oneshot-сервис wgbutler-join (он запустится перед wgbutler):
export WGBUTLER_JOIN_CLUSTER_IP=NODE1.PUBLIC.IP:51821
export WGBUTLER_JOIN_TOKEN=TOKEN_ID
# (optional) если у ноды есть reachable endpoint (например, публичный IP)
export WGBUTLER_JOIN_PUBLIC_ENDPOINT=NODE2.PUBLIC.IP:51820
docker compose -f docker-compose.registry.yml up -d wgbutler
Дополнительно (опционально):
WGBUTLER_JOIN_ADVERTISED_SUBNETS(например:10.1.0.0/16,10.2.0.0/16)WGBUTLER_JOIN_API_PORT(если не хочешь указывать порт вWGBUTLER_JOIN_CLUSTER_IP)WGBUTLER_JOIN_GATEWAY_FORWARDING=true|1WGBUTLER_JOIN_GATEWAY_NAT=true|1WGBUTLER_JOIN_GATEWAY_NAT_INTERFACE=eth0
Одноразовая инициализация первого узла (пример; подставь свои значения):
docker compose --profile init run --rm wgbutler-init \
--network-name mynet \
--network-cidr 10.0.0.0/16 \
--public-endpoint YOUR.PUBLIC.IP:51820
После init можно запускать wgbutler (daemon) обычным docker compose up -d wgbutler.
Сборка
go build ./...
Linux binary (пример):
GOOS=linux GOARCH=amd64 go build -o wgbutler-linux-amd64 ./cmd/wgbutler