Hub: node signature before the WebSocket upgrade

The hub accepted a WebSocket from anyone on /node/v1, a path in the open
code, and replied with a hello nonce: one connection was enough to tell a
scanner what it had found. The agent now signs the upgrade request itself
(X-Node-Id, X-Node-Ts, X-Node-Sig; on first binding also the join secret
and its key), and the hub checks it before Accept. Without a valid
signature it answers Go's plain "404 page not found". Inside the socket
the protocol is unchanged, and the auth frame must come from the node that
signed the request.

Older agents send no signature. The hub lets them in while some node has
never connected the new way (nodes.preauth), logs them as outdated, and
closes that door by itself once every node is updated; HUB_LEGACY=on/off
overrides. The timestamp window is a day: node clocks drift, and the
headers travel inside pinned TLS. The agent treats a 404 at upgrade as a
rejection and backs off for a minute.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013bNGYiDst62wH3ZGTRPFLJ
This commit is contained in:
Claude
2026-10-28 11:58:06 +00:00
parent a3d4664214
commit fd5e2cc761
6 changed files with 391 additions and 15 deletions
+16 -1
View File
@@ -103,9 +103,24 @@ iptables, заголовки ядра) остаются. Ноду после у
(`SUB_TLS=on`) и хаба нод создаются со случайным именем вместо общих `sen` / `awg-node`:
клиенты и ноды сверяют ключ, а не имя, так что прежние сертификаты продолжают работать.
### Порт хаба нод
Агент ноды подписывает своим ключом уже сам запрос на WebSocket (заголовки `X-Node-Id`,
`X-Node-Ts`, `X-Node-Sig`, при первой привязке — ещё секрет из строки `awgjoin://`). Хаб
проверяет подпись до апгрейда и без неё отвечает `404 page not found`, как пустой сервер:
одним подключением к `/node/v1` его больше не узнать. Агенты прежних версий подписи не шлют —
хаб пускает их, пока в панели есть нода, ни разу не подключавшаяся по-новому, и пишет об этом
в лог. Обновили все ноды — путь без подписи закрывается сам. `HUB_LEGACY=on` / `off` в
`cli.env` (и `awg-ctrl restart`) — принудительно. Часы ноды могут расходиться с панелью до
суток.
### Внешний Prometheus
Вкладка «Монитор» → «Внешний Prometheus»: выдать токен «только метрики» и скопировать готовый
Prometheus на ноде («Подключить Prometheus» у панели без своего VPN) от секретного пути не
зависит: он забирает метрики у агента на `127.0.0.1`, а тот — у панели внутри TLS-соединения
с хабом. Снаружи этот трафик не отличить от обычной связи ноды с панелью.
Для стороннего сервера мониторинга — вкладка «Монитор» → «Внешний Prometheus»: выдать токен «только метрики» и скопировать готовый
фрагмент `prometheus.yml`. Адрес в нём берётся от страницы панели, поэтому уже содержит
секретный путь (`metrics_path: /<путь>/metrics`) или подпапку nginx. Без пути `/metrics`
снаружи отвечает 404. Панель по голому HTTP передаёт токен и метрики открытым текстом —