22 KiB
22 KiB
E2 API-контракт transport control-plane
Дата: 2026-03-05 Статус: in-progress Владелец: Engineering
1) Цель
- Зафиксировать стабильный
/api/v1/transport/*контракт для управления несколькими transport-клиентами через единый Go control-plane. - Обеспечить одинаковый API для desktop/web/iOS/android.
- Встроить anti-conflict workflow:
validate -> confirm -> apply.
2) Область и ограничения
- Контракт описывает API-слой и DTO; низкоуровневый backend-runner (systemd/process supervisor) реализуется отдельно.
- Все изменения policy должны идти только через
validateиapply. - Для совместимости с текущим API проект использует паттерн:
- HTTP
200+"ok": falseдля операционных ошибок; - HTTP
4xxдля ошибки запроса (bad json, invalid id, missing fields).
- HTTP
3) Общие правила
3.1 Базовые поля ответа
{
"ok": true,
"message": "ok",
"request_id": "req-01JABC...",
"data": {}
}
3.2 Ошибка доменной валидации
{
"ok": false,
"message": "policy has blocking conflicts",
"code": "POLICY_CONFLICT_BLOCK",
"issues": []
}
3.3 Идемпотентность
- Для mutating POST/PATCH/DELETE клиент передаёт
Idempotency-Key. - Для
applyдополнительно используетсяpolicy_revision(optimistic lock). - Для
POST /api/v1/transport/policies/applyиPOST /api/v1/transport/policies/rollbackbackend хранит persisted replay-state:- одинаковые
(scope, Idempotency-Key, request payload)возвращают один и тот же сохранённый response без повторного runtime apply; - повторное использование того же
Idempotency-Keyс другим payload возвращаетIDEMPOTENCY_KEY_REUSED.
- одинаковые
4) Модели данных
4.1 TransportClient
{
"id": "phoenix-eu",
"name": "Phoenix EU",
"kind": "phoenix",
"enabled": true,
"status": "up",
"iface": "phx0",
"routing_table": "agvpn_phoenix_eu",
"mark_hex": "0x110",
"priority_base": 13050,
"capabilities": ["tcp", "udp", "ssh_tunnel"],
"health": {
"last_check": "2026-03-05T10:11:12Z",
"latency_ms": 83,
"last_error": ""
},
"config": {
"runtime_mode": "exec",
"runner": "systemd",
"endpoint": "eu.example.net:443",
"profile": "default"
},
"updated_at": "2026-03-05T10:11:12Z"
}
4.2 RouteIntent
{
"selector_type": "domain",
"selector_value": "youtube.com",
"client_id": "phoenix-eu",
"priority": 100,
"mode": "strict"
}
4.3 ConflictRecord
{
"key": "domain:youtube.com",
"type": "ownership",
"severity": "block",
"owners": ["phoenix-eu", "dnstt-home"],
"reason": "one selector is assigned to multiple clients",
"suggested_resolution": "keep only one owner or use force_override"
}
5) Endpoints: clients
5.1 GET /api/v1/transport/clients
- Назначение: список клиентов.
- Query:
enabled_only=true|false(optional)kind=singbox|dnstt|phoenix(optional)
Response:
{
"ok": true,
"message": "ok",
"items": [],
"count": 3
}
5.2 POST /api/v1/transport/clients
- Назначение: создать клиента.
Request:
{
"id": "dnstt-home",
"name": "DNSTT Home",
"kind": "dnstt",
"enabled": true,
"config": {
"runtime_mode": "exec",
"runner": "systemd",
"packaging_profile": "bundled",
"bin_root": "/opt/selective-vpn/bin",
"server": "1.2.3.4:443",
"domain": "tunnel.example.org",
"pubkey": "base64..."
}
}
Response:
{
"ok": true,
"message": "client created",
"item": {}
}
5.3 GET /api/v1/transport/clients/{id}
- Назначение: получить детальную карточку клиента.
5.4 PATCH /api/v1/transport/clients/{id}
- Назначение: частичное обновление метаданных/конфига.
- Поддерживаемые поля:
name,enabled,config.
5.5 DELETE /api/v1/transport/clients/{id}
- Назначение: удалить клиента.
- Правило: удаление запрещено, если есть активные policy-ссылки без
force=true.
5.6 POST /api/v1/transport/clients/{id}/start
5.7 POST /api/v1/transport/clients/{id}/stop
5.8 POST /api/v1/transport/clients/{id}/restart
- Назначение: lifecycle операции backend-клиента.
- Ответ: унифицированный
cmdResult-совместимый формат + backend runtime поля (status_before/status_after,runtime.metrics,runtime.last_error).
Пример:
{
"ok": true,
"message": "start done",
"exitCode": 0,
"client_id": "phoenix-eu",
"kind": "phoenix",
"action": "start",
"status_before": "down",
"status_after": "up",
"health": { "last_check": "2026-03-07T10:11:12Z", "latency_ms": 83, "last_error": "" },
"runtime": {
"backend": "phoenix",
"allowed_actions": ["start", "stop", "restart"],
"metrics": { "restarts": 1, "state_changes": 2, "uptime_sec": 17 }
}
}
5.9 GET /api/v1/transport/clients/{id}/health
- Назначение: быстрый probe статуса и деградации.
Response:
{
"ok": true,
"message": "ok",
"code": "TRANSPORT_CLIENT_DEGRADED",
"client_id": "phoenix-eu",
"kind": "phoenix",
"status": "degraded",
"latency_ms": 480,
"last_error": "upstream timeout",
"health": {
"last_check": "2026-03-07T10:11:12Z",
"latency_ms": 480,
"last_error": "upstream timeout"
},
"runtime": {
"backend": "phoenix",
"metrics": { "restarts": 1, "state_changes": 4, "uptime_sec": 0 },
"last_error": {
"code": "BACKEND_RUNTIME_ERROR",
"message": "upstream timeout",
"retryable": true
}
}
}
5.10 GET /api/v1/transport/clients/{id}/metrics
- Назначение: read-only срез lifecycle metrics для UI (desktop/web/iOS/android) без знания backend-внутренностей.
Response:
{
"ok": true,
"message": "ok",
"client_id": "phoenix-eu",
"kind": "phoenix",
"status": "up",
"metrics": {
"restarts": 2,
"state_changes": 8,
"uptime_sec": 341,
"last_transition_at": "2026-03-07T10:11:12Z"
},
"runtime": {
"backend": "phoenix",
"last_action": "restart",
"last_action_at": "2026-03-07T10:11:12Z"
}
}
5.11 POST /api/v1/transport/clients/{id}/provision
- Назначение: backend-side provision (создание/обновление unit/runner-конфигурации) перед lifecycle-операциями.
- Для
runner=systemdпишет unit-файлы и делаетsystemctl daemon-reload.
5.12 GET /api/v1/transport/runtime/observability
- Назначение: unified multi-interface runtime snapshot для карточек/дашбордов без ручной склейки
interfaces + clients + egress + policy. - Источники:
transport-interfaces,transport-clients,- compile-plan policy,
egress identityдля active client на интерфейсе.
Response:
{
"ok": true,
"message": "ok",
"generated_at": "2026-03-16T12:10:00Z",
"count": 2,
"items": [
{
"iface_id": "edge-a",
"name": "Edge A",
"mode": "dedicated",
"runtime_iface": "tun-edge",
"active_iface": "tun-edge0",
"netns_name": "svpn-edge-a",
"routing_table": "agvpn_if_edge_a",
"client_id": "sb-main",
"client_ids": ["sb-main", "dnstt-fallback"],
"status": "degraded",
"latency_ms": 81,
"last_error": "fallback probe failed",
"last_check": "2026-03-16T12:09:30Z",
"egress": {
"scope": "transport:sb-main",
"source": "transport",
"source_id": "sb-main",
"ip": "203.0.113.10",
"country_code": "SG",
"country_name": "Singapore",
"stale": false
},
"counters": {
"client_count": 2,
"enabled_count": 2,
"up_count": 1,
"degraded_count": 1,
"rule_count": 4
},
"engine_counts": [
{ "kind": "dnstt", "count": 1, "degraded_count": 1 },
{ "kind": "singbox", "count": 1, "up_count": 1 }
]
}
]
}
6) Endpoints: policies
6.1 GET /api/v1/transport/policies
- Назначение: получить текущую политику и ревизию.
Response:
{
"ok": true,
"message": "ok",
"policy_revision": 12,
"intents": []
}
6.2 POST /api/v1/transport/policies/validate
- Назначение: dry-run валидация без применения.
Request:
{
"base_revision": 12,
"intents": [],
"options": {
"allow_warnings": true,
"force_override": false
}
}
Response:
{
"ok": true,
"message": "validation complete",
"valid": false,
"summary": {
"block_count": 1,
"warn_count": 2
},
"conflicts": [],
"diff": {
"added": 10,
"changed": 3,
"removed": 1
}
}
6.3 POST /api/v1/transport/policies/apply
- Назначение: атомарное применение новой policy.
- Обязательные условия:
base_revisionсовпадает с текущей ревизией,- нет blocking-конфликтов или задан
force_override=trueс подтверждением.
Request:
{
"base_revision": 12,
"intents": [],
"options": {
"force_override": true,
"confirm_token": "cnf-01JABC..."
}
}
Response:
{
"ok": true,
"message": "policy applied",
"policy_revision": 13,
"apply_id": "apl-01JABC...",
"rollback_available": true
}
Ошибка конкурентного изменения:
{
"ok": false,
"message": "stale policy revision",
"code": "POLICY_REVISION_MISMATCH",
"current_revision": 13
}
6.4 POST /api/v1/transport/policies/rollback
- Назначение: откатить policy к предыдущему snapshot.
- Условия:
- snapshot должен существовать,
base_revision(если задан) должен совпадать с текущей ревизией,- snapshot проходит текущую валидацию конфликтов.
Request:
{
"base_revision": 13
}
Response:
{
"ok": true,
"message": "policy rollback applied",
"policy_revision": 14,
"apply_id": "rbk-01JABC...",
"rollback_available": true
}
6.5 GET /api/v1/transport/conflicts
- Назначение: получить актуальные конфликты активной конфигурации.
Response:
{
"ok": true,
"message": "ok",
"items": [],
"has_blocking": true
}
6.6 GET /api/v1/transport/capabilities
- Назначение: матрица возможностей backend-клиентов и текущей платформы.
Response:
{
"ok": true,
"message": "ok",
"clients": {
"singbox": { "tcp": true, "udp": true, "dns_tunnel": true, "ssh_tunnel": false },
"dnstt": { "tcp": true, "udp": false, "dns_tunnel": true, "ssh_tunnel": true },
"phoenix": { "tcp": true, "udp": true, "dns_tunnel": false, "ssh_tunnel": true }
},
"runtime_modes": {
"exec": true,
"embedded": false,
"sidecar": false
},
"packaging_profiles": {
"system": true,
"bundled": true
},
"lifecycle": ["provision", "start", "stop", "restart"],
"health_fields": ["status", "latency_ms", "last_error", "health.last_check"],
"metrics_fields": ["restarts", "state_changes", "uptime_sec", "last_transition_at"],
"error_codes": [
"TRANSPORT_CLIENT_NOT_FOUND",
"TRANSPORT_CLIENT_SAVE_FAILED",
"TRANSPORT_CLIENT_DEGRADED",
"BACKEND_RUNTIME_ERROR",
"TRANSPORT_BACKEND_UNIT_REQUIRED",
"TRANSPORT_BACKEND_ACTION_FAILED",
"TRANSPORT_BACKEND_HEALTH_FAILED",
"TRANSPORT_BACKEND_PROVISION_CONFIG_REQUIRED",
"TRANSPORT_BACKEND_PROVISION_FAILED",
"TRANSPORT_BACKEND_RUNTIME_MODE_UNSUPPORTED"
]
}
Примечание по config.runtime_mode:
exec— текущий production режим (внешний companion-бинарь под управлением backend-адаптера);embedded,sidecar— зарезервированы для следующих фаз; при попытке lifecycle/provision сейчас возвращаетсяTRANSPORT_BACKEND_RUNTIME_MODE_UNSUPPORTED;- alias
external|companionнормализуются вexec.
Примечание по packaging для runtime_mode=exec:
packaging_profile=system(default): поиск бинарей в системных путях (/usr/bin,/usr/local/bin,$PATH);packaging_profile=bundled: поиск вbin_root(default/opt/selective-vpn/bin) с опциональным fallback в system (packaging_system_fallback=true);require_binary=true: fail-fast на этапеprovision/template build, если целевой бинарь не найден;- для ручного override (
singbox_bin,dnstt_bin,phoenix_bin)require_binary=trueтакже валидирует существование. - manual updater/rollback MVP:
scripts/transport-packaging/update.shчитает pinned manifest, проверяетsha256, устанавливает release и атомарно переключает symlink;update.shподдерживает trusted-source policy (--source-policy), optional/required detached signature verify (signature.type=openssl-sha256) и staged rollout (rollout.stage/percent,--rollout-stage,--cohort-id);scripts/transport-packaging/auto_update.sh— opt-in scheduler-wrapper (enabled=true) с interval gate/lock/jitter для безопасного фонового запуска;scripts/transport-packaging/rollback.shоткатывает компонент на предыдущую запись вBIN_ROOT/.packaging/*.history.
Примечание для dnstt:
- При
runner=systemdдопускается единая оркестрацияdnstt + ssh overlay:unit: systemd unit DNSTT-клиента;exec_start: явный override команды запуска DNSTT-клиента (опционально);- если
exec_startне задан, Go-ядро строит команду по шаблону из полей:- resolver:
resolver_mode=doh|dot|udp+doh_url|dot_addr|udp_addr|resolver_addr, - ключ:
pubkeyилиpubkey_file, - endpoint:
domain+local_addr(default127.0.0.1:7000);
- resolver:
ssh_tunnelилиssh_overlay:true;ssh_unit: systemd unit SSH-туннеля;ssh_exec_start(илиssh_host+ssh_user+ssh_port+socks_port): команда запуска SSH overlay.
Примечание для singbox и phoenix:
- при
runner=systemdexec_startтакже опционален; - при отсутствии
exec_startкоманда строится шаблонами ядра:singbox:<bin> run -c <config_path>;phoenix:<bin> -config <config_path>.
Примечание для runner=systemd (общий tuning):
restart_policy:no|on-success|on-failure|on-abnormal|on-watchdog|on-abort|always(defaultalways);restart_sec: задержка перезапуска в секундах (default2);start_limit_interval_sec,start_limit_burst: анти-flap лимиты unit (defaults300,30);timeout_start_sec,timeout_stop_sec: таймауты старта/остановки (defaults90,20);watchdog_sec: опциональный systemd watchdog (default0, отключён);- для
dnstt + ssh overlayподдержаныssh_*overrides тех же ключей (ssh_restart_sec,ssh_watchdog_secи т.д.) для отдельного tuning SSH unit.
Примечание для runner=systemd (unit hardening):
hardening_profile:baseline|strict|off(defaultbaseline);hardening_enabled:true|false(может принудительно включить/выключить hardening);- baseline-профиль включает:
NoNewPrivileges=yes,PrivateTmp=yes,ProtectSystem=full,ProtectHome=read-only,ProtectControlGroups=yes,ProtectKernelModules=yes,ProtectKernelTunables=yes,RestrictSUIDSGID=yes,LockPersonality=yes,UMask=0077;
- strict-профиль дополнительно включает
ProtectSystem=strictиPrivateDevices=yes; - тонкие override-ключи:
no_new_privileges,private_tmp,protect_system,protect_home,protect_control_groups,protect_kernel_modules,protect_kernel_tunables,restrict_suid_sgid,lock_personality,private_devices,umask;
- для overlay-пары поддержаны
ssh_*overrides этих же hardening-ключей (напримерssh_hardening_enabled,ssh_protect_system,ssh_umask).
7) События SSE (проект)
transport_client_state_changed{"id":"phoenix-eu","from":"starting","to":"up"}
transport_client_provisioned{"id":"dnstt-home","ok":true,"msg":"provision done"}
transport_policy_validated{"valid":false,"block_count":1,"warn_count":2}
transport_policy_applied{"apply_id":"apl-...","policy_revision":13}
transport_runtime_snapshot_changed{"reason":"transport_client_state_changed","generated_at":"2026-03-16T12:10:00Z","client_ids":["sb-main"],"iface_ids":["edge-a"],"items":[...]}- payload переиспользует тот же DTO, что и
GET /api/v1/transport/runtime/observability, чтобы UI мог либо сделать re-fetch, либо обновиться напрямую без ручной агрегации.
transport_conflict_detected{"key":"domain:youtube.com","severity":"block"}
8) Правила anti-conflict
- Ownership lock:
- один
selector_type + selector_valueпринадлежит только одномуclient_id.
- один
- По умолчанию конфликты
severity=blockблокируютapply. force_overrideразрешен только сconfirm_token, полученным на этапеvalidate.- Для UX предупреждений backend возвращает:
- список конфликтов,
- потенциальный impact (
flows_rebind_required,session_drop_risk), - diff по изменениям политики.
9) Безопасность и аудит
- Все mutating endpoints требуют
Authorization+ RBAC scopetransport:write. - Для операций
apply,delete,force_overrideобязателен audit record:- user id,
- request id,
- previous revision,
- new revision,
- short diff summary.
10) Минимальный план внедрения
- E2.1: ввести DTO и read-only endpoints (
GET clients,GET policies,GET capabilities). - E2.2: добавить
validateс ownership/overlap анализом. - E2.3: добавить
applyс optimistic lock + rollback snapshot. - E2.4: подключить SSE события и UI flow подтверждения.
11) Статус реализации в коде (2026-03-07)
- Реализовано в
selective-vpn-api/app/transport_handlers.go:GET/POST /api/v1/transport/clientsGET/PATCH/DELETE /api/v1/transport/clients/{id}POST /api/v1/transport/clients/{id}/provisionPOST /api/v1/transport/clients/{id}/{start|stop|restart}GET /api/v1/transport/clients/{id}/healthGET /api/v1/transport/clients/{id}/metricsGET /api/v1/transport/policiesPOST /api/v1/transport/policies/validatePOST /api/v1/transport/policies/applyPOST /api/v1/transport/policies/rollbackGET /api/v1/transport/conflictsGET /api/v1/transport/capabilities
- D4.1-контракт в Go:
- унифицированные DTO для lifecycle/health/metrics/errors,
- runtime-срез в
TransportClient(backend,allowed_actions, counters,last_error), - method-level ответы с кодами ошибок (
TRANSPORT_CLIENT_*,BACKEND_RUNTIME_ERROR).
- D4.2 foundation в Go:
- backend-адаптеры
mock/systemdс выбором поclient.config.runner, - для
dnsttподдержан режим dual-unit orchestration (ssh overlay) вprovision/lifecycle/health, - шаблонный build
exec_startв Go дляsingbox|dnstt|phoenix(с manual override черезconfig.exec_start), - systemd tuning для restart/start-limit/timeout/watchdog с отдельными
ssh_*override для overlay unit, - unit hardening профили (
baseline/strict/off) иssh_*hardening overrides для overlay unit.
- backend-адаптеры
- Валидация конфликтов:
- ownership conflict (
selectorна несколько клиентов), - overlap CIDR между разными клиентами,
- unknown client / invalid selector.
- ownership conflict (
- Apply flow:
base_revisionlock,confirm_tokenприforce_override,- snapshot предыдущей policy (
transport-policies.prev.json), - SSE события
transport_policy_validated,transport_policy_applied,transport_conflict_detected.
- Allocator policy v2:
- резервные диапазоны для
mark_hexиpriority_base, - детерминированное восстановление слотов при загрузке state,
- auto re-balance при коллизиях/битых слотах в
transport-clients.json, - детерминированная генерация уникальных
routing_table(с защитой от коллизий длинных ID).
- резервные диапазоны для