fix(admin): различать достижимость Traffic Stats API и соответствие профилю

Признак на странице конфигурации отвечал только на вопрос «достучится ли
админка», поэтому 0.0.0.0 показывался как норма — хотя внутренний control plane
при нём опубликован на всех интерфейсах, а оркестратор такой конфигурации не
создаёт. Состояний теперь четыре: канон профиля, wildcard, не-канонический
loopback и недостижимый адрес.

Backend не тронут: он по-прежнему отвечает только на вопрос достижимости —
превращать лишнюю публикацию в отказ обслуживания значило бы отключить всех
пиров. Исправлено ложное утверждение в его комментарии: пустой хост `:36712` в
Go означает все интерфейсы, а не loopback.

Удалены мёртвые фразы common.wait/enableSuccess/disableSuccess — остатки
операций запуска, остановки и смены версии Hysteria, которых у панели нет.
This commit is contained in:
2026-09-03 03:39:42 +05:00
parent cb20d8d28f
commit b315001288
10 changed files with 204 additions and 30 deletions
@@ -32,6 +32,7 @@ rc3](2026-09-02-v1.0.0-rc3-preflight-findings.md) и сверка Hysteria-ин
| ADM-HY2-08 | Мёртвые остатки прежней архитектуры | P4 | закрыт |
| GATE-03 | Гейт освобождения admission-замка проверял форму, а не замок | P3 | закрыт |
| DOC-03 | Отчёт rc3 не называл, к какому дереву относятся выводы | P3 | закрыт |
| DIAG-01 | Панель считала wildcard нормальным состоянием control plane | P3 | закрыт |
ROT-01 и ADM-HY2-01…08 пришли внешним разбором; уточнения ниже — при проверке
его выводов по коду и исходникам upstream. Три из них меняли предложенное
@@ -149,6 +150,43 @@ LAN, публичный адрес), `apps/service/config_traffic_stats_test.go`
---
## DIAG-01 — панель считала wildcard нормальным состоянием control plane
Найдено при перепроверке фикса ADM-HY2-01 и относится только к диагностике: на
data plane не влияет.
Первая версия признака на странице конфигурации отвечала на вопрос
«достучится ли админка» и молчала по второму, не менее важному, — «тот ли это
адрес, который создаёт оркестратор»:
```ts
host === "" || host === "127.0.0.1" || host === "0.0.0.0"
```
`0.0.0.0` — не loopback, а wildcard: внутренний control plane при нём
опубликован на всех интерфейсах, и при `HY2XS_FIREWALL_MODE=external|off` его не
прикрывает ничто. Оркестратор такой конфигурации не создаёт, значит она
появилась правкой руками — и диагностический экран обязан это назвать, а не
показывать как норму. Официальная документация Traffic Stats API отдельно
предупреждает об ограничении доступа к этому listener'у.
Там же обнаружилась вторая неточность, уже в моей формулировке: **пустой хост
(`:36712`) — тоже wildcard, а не loopback.** В Go `:port` означает все
интерфейсы; комментарий в `parseTrafficStatsPort` утверждал обратное. На
поведение это не влияло (wildcard включает loopback, поэтому обмен состоится),
но описание контракта было ложным и исправлено.
**Как закрыто.** Backend не тронут: он по-прежнему отвечает ровно на вопрос
достижимости, и превращать лишнюю публикацию в отказ обслуживания нельзя — это
отключило бы всех пиров. Разделены понятия на стороне панели:
| адрес | состояние | что показано |
| --- | --- | --- |
| `127.0.0.1` | `canonical` | без пометки |
| `0.0.0.0`, пустой хост | `wildcard` | предупреждение: API доступен, но слушает все интерфейсы |
| `127.0.0.x` | `nonCanonicalLoopback` | предупреждение: доступен, но оркестратор такого не создаёт |
| прочее | `unreachable` | ошибка: доступ пиров уже не работает |
## ADM-HY2-02 — JSON-журнал не разбирался ни одной строкой
Юнит запускает Hysteria с `HYSTERIA_LOG_FORMAT=json`. Разбор складывал запись
@@ -370,4 +408,7 @@ go test -race ./service/... -count=1
6. открыть страницу журнала Hysteria: записи разобраны, контекст виден,
секретов нет;
7. добавить в конфиг секцию вне профиля (например `resolver`) и открыть страницу
конфигурации: секция обязана попасть в «расхождение конфигурации».
конфигурации: секция обязана попасть в «расхождение конфигурации»;
8. заменить `trafficStats.listen` на `0.0.0.0:36712`: продукт продолжает
работать, а страница конфигурации показывает предупреждение о публикации на
всех интерфейсах.