build: закрепить новые инварианты приёмкой и документацией
verify_versions_contract получил сверку API namespace. Путь machine-auth
записывается в /etc/hysteria/config.yaml и в post-install.env, то есть по нему
Hysteria обращается к админке. Пока строка была продублирована в шаблонах,
smoke, тестах, приёмке и e2e, расхождение обнаруживалось только на живом
сервере. Теперь Go-константы, API_BASE фронтенда и оба шаблона сверяются
против значений, скомпилированных в оркестратор.
Приёмка проверяет, что:
- fatal_pre_apply недостижим после записи install-state;
- каждый ownership-флаг взводится раньше своего шага;
- у read-only фазы нет универсального раннера, через который можно
проскользнуть;
- инвариант публичного endpoint живёт в preflight и не обращается к внешним
сервисам определения IP;
- purge-v0.sh и clean-host описывают одну границу;
- секреты не попадают в персистентный файл экспорта;
- импорт пиров валидируется так же строго, как их создание;
- удалённые exportConfig/importConfig не вернулись.
Захардкоженная схема =2 в приёмке заменена на значение из versions.env: при
переходе на schema 3 пришлось бы помнить ещё и про эту строку.
Документация: контракт раннеров и ownership в 08, инвариант публичного
endpoint в 08/09/12/13 и README, сетевая идентичность панели и удалённые
export/import в 04, сценарии D1 (отказ сразу после PHASE 0) и D2 (устаревший
DNS после смены IPv4) в 11, версии package.json как не-версия продукта в 02.
This commit is contained in:
+133
@@ -14,6 +14,79 @@ Hardening-проход перед релизом `1.0.0`. Основная те
|
||||
|
||||
### Исправлено
|
||||
|
||||
- **`fatal_pre_apply` мог означать «хост уже изменён».** `install-state.json`
|
||||
пишется сразу после успешного preflight, до установки пакетов, но
|
||||
классификация отказа его не учитывала. Падение `apt-get update` или
|
||||
`apt-get install` объявлялось как «на сервере ничего не изменено»: откат и
|
||||
обработка состояния пропускались, а маркер оставался на диске и ломал
|
||||
следующую установку по clean-host контракту.
|
||||
|
||||
Ownership-флаги переформулированы с «шаг успешно завершился» на «операция
|
||||
могла начать менять систему» и взводятся **перед** мутирующим вызовом:
|
||||
`apt-get` умеет изменить систему и упасть. `fatal_pre_apply` теперь
|
||||
недостижим ни при одном взведённом флаге, включая запись состояния.
|
||||
|
||||
- **Экспорт в админке оставлял секреты на диске навсегда.** `ExportPeer` и
|
||||
выгрузка системного конфига шли через `os.Create` в
|
||||
`/var/lib/hy2xs-admin/export/`, и файл там не удалялся. При
|
||||
`?includeSecrets=true` это означало расшифрованные секреты пиров — фактические
|
||||
учётные данные доступа — в открытом виде, накапливающиеся с каждым нажатием
|
||||
кнопки. Экспорт формируется в памяти; каталога `export/` больше нет.
|
||||
|
||||
- **Generic export/import таблицы `config` выгружал и позволял подменить
|
||||
криптографические ключи приложения.** Выгрузка исключала только сырой Hysteria
|
||||
YAML, а в той же таблице лежат `JWT_SECRET`, `PEER_SECRET_KEY`,
|
||||
`PEER_SECRET_ENCRYPTION_KEY` и `HYSTERIA2_TRAFFIC_STATS_SECRET`. Импорт их не
|
||||
блокировал: подмена `PEER_SECRET_ENCRYPTION_KEY` ломает расшифровку секретов
|
||||
уже существующих пиров. Оба маршрута и их UI удалены — production-сценария у
|
||||
них не было, перенос пиров делают `peer-import`/`peer-export`.
|
||||
|
||||
- **Импорт пиров шёл мимо всей валидации.** Обычное создание пира проходит через
|
||||
`dto.PeerSaveDto`, импорт JSON — нет: в базу попадало имя любой длины и с
|
||||
любыми символами, `disabled` с произвольным числом, отрицательные счётчики.
|
||||
Файл применялся построчно, поэтому ошибка в середине оставляла список пиров
|
||||
наполовину изменённым, а импорт мог перезаписать `bootstrap-admin-peer`, чей
|
||||
секрет продублирован в `/etc/hy2xs/bootstrap-admin.secret`. Партия теперь
|
||||
проверяется целиком до первой записи, неизвестные поля отклоняются,
|
||||
bootstrap-пир защищён.
|
||||
|
||||
- **DNS проверялся на существование A-записи, но не на то, куда она ведёт.**
|
||||
После принудительной смены IPv4 провайдером `doctor` отвечал успехом, хотя
|
||||
клиентская ссылка отправляла людей на чужую машину. Проверялся при этом
|
||||
`HY2XS_DOMAIN`, тогда как в `hysteria2://` уезжает `HY2XS_PUBLIC_HOST`.
|
||||
|
||||
Добавлен инвариант публичного endpoint: A-записи обязаны принадлежать
|
||||
множеству публичных IPv4, назначенных интерфейсам этого сервера. Проверка
|
||||
живёт в общем `preflight`, поэтому действует в `install`, `reconfigure` и
|
||||
`doctor`. Адрес определяется локально, без внешних сервисов определения IP.
|
||||
Строгость управляется `HY2XS_PUBLIC_ENDPOINT_POLICY` (`strict` по умолчанию).
|
||||
|
||||
- **Read-only guard PHASE 0 можно было обойти.** Guard стоял на `writeText`,
|
||||
`writeTextAtomic`, `runVisible`, `runHidden` и `runRawVisible`, но не на
|
||||
универсальном `run`, через который в коде проходили и наблюдение (`ss`,
|
||||
`systemctl is-active`), и настоящие мутации (`useradd`, `install -d`,
|
||||
`mkdir`, `cp -a`, `tar`). Универсального раннера больше нет: есть
|
||||
`runReadOnly*` без guard'а и `runMutating*` под guard'ом, а выбор — явное
|
||||
решение на месте вызова.
|
||||
|
||||
- **Go-санитайзер конфига вырезал секреты из URL только у ключей `url`/`addr`.**
|
||||
Будущее upstream-поле с другим именем (`endpoint:`) уносило встроенные
|
||||
учётные данные и `access_token` наружу целиком; URL внутри списков не
|
||||
обрабатывались вовсе. Граница определяется значением, а не именем ключа —
|
||||
как в TS-санитайзере оркестратора; обе реализации покрыты зеркальными тестами.
|
||||
|
||||
- **`purge-v0.sh --keep-hysteria-binary` противоречил установщику.** Скрипт
|
||||
сохранял `/usr/local/bin/hysteria` и сообщал «хост чист для установки
|
||||
HY2XS v1», хотя clean-host контракт считает этот бинарник legacy-маркером и
|
||||
следующая установка отказалась бы. Флаг удалён.
|
||||
|
||||
- **clean-host не замечал часть того, что удаляет purge.** `/var/lib/hysteria`
|
||||
(ACME-состояние и сертификаты Hysteria), `/var/log/hy2xs`,
|
||||
`/usr/local/lib/hy2xs` и `/usr/local/bin/hy2xs-orchestrator` не были
|
||||
маркерами: сервер, где остался только старый runtime-state Hysteria, проходил
|
||||
проверку и получал свежую установку поверх чужого состояния. Оба списка
|
||||
теперь описывают одну границу, и приёмка это проверяет.
|
||||
|
||||
- **Установщик мог повредить работающий сервер до того, как откажется его
|
||||
трогать.** `install.sh` переписывал `/usr/local/lib/hy2xs`, раскладывал
|
||||
runtime-пакет и перезаписывал `/var/lib/hy2xs/install-state.json`, и лишь
|
||||
@@ -101,8 +174,54 @@ Hardening-проход перед релизом `1.0.0`. Основная те
|
||||
- **Явный флаг `--allow-partial-state` для `repair`.** Прежде согласие на
|
||||
работу поверх незавершённой установки подразумевалось молча.
|
||||
|
||||
- **`HY2XS_PUBLIC_ENDPOINT_POLICY`** (`strict` | `warn` | `off`, по умолчанию
|
||||
`strict`) — строгость проверки того, что публичный endpoint ведёт на этот
|
||||
сервер. Ослабление предназначено для топологий вне baseline: NAT, floating IP,
|
||||
anycast. Отсутствие A-записи фатально при любом значении.
|
||||
|
||||
- **Раздельные API подпроцессов в оркестраторе**: `runReadOnly` /
|
||||
`runReadOnlySecret` для наблюдения и `runMutating*` под read-only guard'ом.
|
||||
|
||||
- **Сверка API namespace на сборке.** Путь machine-auth и базовый префикс
|
||||
админского API объявлены по одной константе на компонент, а
|
||||
`verify_versions_contract` сверяет Go, фронтенд и шаблоны против значений,
|
||||
скомпилированных в оркестратор.
|
||||
|
||||
### Изменено
|
||||
|
||||
- **Пространства имён HTTP API.** Операторский и auth API переехали с `/hui` на
|
||||
`/api`, machine-auth endpoint Hysteria — на `/internal/hysteria/auth`. Прежний
|
||||
общий префикс был наследием H UI: под ним лежали и machine-to-machine auth, и
|
||||
JWT-защищённый админский API, хотя middleware у них не пересекаются. Момент
|
||||
выбран до первого clean-install релиза: после `1.0.0` эти строки стали бы
|
||||
частью фактического v1 compatibility contract.
|
||||
|
||||
- **Сетевая идентичность админки принадлежит оркестратору.** Ключи
|
||||
`H_UI_WEB_PORT`, `H_UI_WEB_CONTEXT`, `H_UI_CRT_PATH`, `H_UI_KEY_PATH` удалены
|
||||
из схемы, seed и интерфейса вместе с собственным TLS-слоем панели. Раньше
|
||||
оркестратор передавал порт аргументом, админка записывала его в SQLite и тут
|
||||
же читала обратно, а UI показывал поля в disabled-виде: второй источник истины,
|
||||
из которого ничего нельзя было изменить. Панель всегда монтируется в `/`.
|
||||
|
||||
- **`HUI_DATA`/`HUI_LOG` → `HY2XS_DATA_DIR`/`HY2XS_LOG_DIR`.** Мост в
|
||||
systemd-юните, перекладывавший canonical env HY2XS в имена старого H UI,
|
||||
удалён.
|
||||
|
||||
- **База админки — `hy2xs-admin.db`** вместо `h_ui.db`; reference-схема —
|
||||
`apps/docs/sql/schema.sql` вместо `h_ui_db.sql`. Совместимость сохранять не
|
||||
требуется: v1 ставится только с нуля. Историческое имя `h_ui.db` остаётся в
|
||||
[docs/14-legacy-cleanup.md](docs/14-legacy-cleanup.md) — там это имя чужого
|
||||
артефакта, который очистка должна найти.
|
||||
|
||||
- **Индикатор загрузки и legacy-цвета переведены на брендовый токен.**
|
||||
NProgress приходил со своим `#29d` и был единственным элементом интерфейса вне
|
||||
палитры HY2XS; страницы `401`/`404` и подсветка выбранной строки таблицы несли
|
||||
цвета исходного admin-шаблона. Все они привязаны к `--el-color-primary`, а не
|
||||
переписаны вторым литералом.
|
||||
|
||||
- **Приёмка сверяет схему конфигурации с `versions.env`**, а не с числом `2`
|
||||
в тексте проверки.
|
||||
|
||||
- **E2E подключается по ссылке из production-кода.** Внутри
|
||||
`tools/test/e2e-hysteria.sh` жила вторая реализация `hysteria2://` URI на
|
||||
bash: дрейф любой из двух реализаций оставлял обе группы тестов зелёными.
|
||||
@@ -129,6 +248,20 @@ Hardening-проход перед релизом `1.0.0`. Основная те
|
||||
не должен обещать updater, которого у продукта нет, а неиспользуемый маршрут
|
||||
остаётся attack surface.
|
||||
|
||||
- `POST /config/exportConfig` и `POST /config/importConfig` — generic-выгрузка и
|
||||
загрузка таблицы `config` вместе с криптографическими ключами приложения.
|
||||
Вместе с ними — кнопки Import/Export в настройках, клиентские функции и
|
||||
строки i18n.
|
||||
|
||||
- Персистентный каталог выгрузок `/var/lib/hy2xs-admin/export/` и
|
||||
файловый helper `util.ExportFile`. Артефакт, который покидает сервер, не
|
||||
должен существовать на сервере дольше самого запроса.
|
||||
|
||||
- Флаг `purge-v0.sh --keep-hysteria-binary`.
|
||||
|
||||
- Мёртвые строки i18n, оставшиеся от H UI: `noHttpsTip`, `defaultPassTip`,
|
||||
`hui*`, `useHysteria2Cert`, `invalidWebContext`, `mustBeInteger`.
|
||||
|
||||
## [1.0.0] — 2026-08-27
|
||||
|
||||
Первый релиз линейки `v1`.
|
||||
|
||||
Reference in New Issue
Block a user