fix(v1): сделать отзыв доступа, бэкап и диагностику соответствующими своим именам

Проход по операциям, которые делают не то, что обещает их имя.

P0. Удаление bootstrap-admin-peer не было отзывом доступа. Признаком «создавать
пир или нет» служило наличие строки в таблице, а HY2XS_ADMIN_CON_PASS
продолжает жить в /etc/hy2xs/hy2xs.env — его читает systemd-юнит. Оператор
удалял пира, доступ исчезал, и ближайший restart возвращал того же пира с тем же
секретом. Молча. Признаком стала отметка BOOTSTRAP_PEER_SEEDED в таблице config:
«создавался когда-либо», а не «существует сейчас». Отметка и пир пишутся одной
транзакцией.

P1. Резервная копия с includeSecrets=true проглатывала и ошибку расшифровки, и
отсутствие шифртекста, отдавая пира с пустым secret и успешный ответ. Теперь
недоступный секрет любого пира отклоняет весь запрос с указанием имени.

P1. DecryptPeerSecret возвращала содержимое колонки как расшифрованный секрет,
если оно не начиналось с v1: — остаток поколения с открытыми секретами.

P1. doctor перезапускал hysteria-server и hy2xs-admin: диагностика подозрения на
проблему обрывала все живые соединения.

P1. Админка сама генерировала HYSTERIA2_TRAFFIC_STATS_SECRET, записать который в
/etc/hysteria/config.yaml она не может. Сервис объявлял себя здоровым, а machine
auth переставал совпадать.

P1. Обходы проверки зависимостей (accepted-risk/skipped) не могли произвести
артефакт: приёмка требует dependency_security_gate=true. Удалены из сборки и
документации, отсутствие проверяется приёмкой.

P2. UPDATE по отсутствующей строке config считался успехом, и cron
перепланировался при несохранённом значении. Решение по RowsAffected.

P2. Слой данных не отличал «записи нет» от «база не ответила»: sentinel-значения
ErrPeerNotFound / ErrAdminUserNotFound / ErrConfigNotFound / ErrStorage.

P2. Удалены алиасы /:id/client-url и /:id/qr.

Контракт разработки: apps/go.mod объявляет toolchain go1.26.7 (директива go —
языковой baseline, а не выбор компилятора), tools/dev/doctor.sh|.ps1 сверяют
среду с versions.env.
This commit is contained in:
2026-08-30 06:48:50 +05:00
parent e30fdaa004
commit cf094f6e6f
33 changed files with 1976 additions and 154 deletions
+41 -26
View File
@@ -27,24 +27,40 @@ set -euo pipefail
# production-зависимостями и порогом PNPM_AUDIT_LEVEL:
# devDependencies в артефакт не попадают.
# Аварийный выход существует по той же причине, что и ALLOW_DIRTY_BUILD: новое
# advisory может выйти в неудачный момент, и решение «выпускаем, зная о нём»
# должно приниматься человеком ЯВНО, а не пропуском шага по умолчанию.
ALLOW_VULNERABLE_DEPENDENCIES="${ALLOW_VULNERABLE_DEPENDENCIES:-false}"
# Аварийного выхода у этого шага НЕТ, и это осознанное решение.
#
# Раньше существовали два: ALLOW_VULNERABLE_DEPENDENCIES=true записывал в
# metadata `dependency_security_gate=accepted-risk`, SKIP_SECURITY_SCAN=true —
# `skipped`. Оба были описаны в README и docs/02 как способ выпустить релиз,
# зная об уязвимости.
#
# Способом они не были. Финальная приёмка архива требует буквально
#
# grep -q '^dependency_security_gate=true$' metadata/package.env
#
# то есть сборка с любым из этих значений доходила до самого конца — компиляция,
# бандл, тесты, метаданные, tar — и падала на последнем шаге. Продукт
# документировал операцию, которую сам же запрещал, а обнаруживалось это через
# полный цикл сборки.
#
# Из двух непротиворечивых вариантов выбран строгий: гейт обязателен, значение
# в metadata ровно одно. Контракт при этом читается однозначно:
#
# релизный артефакт HY2XS невозможно собрать с непройденной проверкой
# зависимостей.
#
# Для локальной работы обходить нечего: `go test ./...`, `govulncheck ./...` и
# `pnpm audit` запускаются напрямую и к созданию tarball отношения не имеют.
security_gate_failed() {
local scanner="$1"
local details="$2"
if [ "$ALLOW_VULNERABLE_DEPENDENCIES" = "true" ]; then
log_info "WARNING: $scanner нашёл уязвимости, но сборка продолжена по ALLOW_VULNERABLE_DEPENDENCIES=true"
log_info "$details"
DEPENDENCY_SECURITY_GATE="accepted-risk"
export DEPENDENCY_SECURITY_GATE
return 0
fi
fail "$scanner: найдены уязвимости в зависимостях.
Обойти этот шаг нельзя: релизный пакет HY2XS собирается только с пройденной
проверкой. Обновите граф зависимостей (apps/go.sum, apps/frontend/pnpm-lock.yaml)
или версию toolchain в versions.env.
fail "$scanner: найдены уязвимости в зависимостях. Обновите граф или, осознанно принимая риск, выставьте ALLOW_VULNERABLE_DEPENDENCIES=true.
$details"
}
@@ -106,25 +122,24 @@ $report"
security_gate_failed "pnpm audit" "$report"
}
# Результат шага уезжает в metadata/package.env: по готовому tarball должно
# быть видно, проверялся он или собран с пропущенной проверкой. Ровно так же
# устроен hysteria_compat_gate.
# Результат шага уезжает в metadata/package.env — так же, как hysteria_compat_gate.
#
# Значение у поля теперь ровно одно: `true`. Пропущенного состояния не бывает,
# потому что не бывает пакета, собранного с пропущенной проверкой; поле остаётся
# в metadata как утверждение о готовом артефакте, а не как переключатель.
run_dependency_security_gate() {
if [ "${SKIP_SECURITY_SCAN:-false}" = "true" ]; then
log_info "WARNING: проверка зависимостей на уязвимости пропущена (SKIP_SECURITY_SCAN=true)"
DEPENDENCY_SECURITY_GATE="skipped"
export DEPENDENCY_SECURITY_GATE
return 0
fi
[ -n "${GOVULNCHECK_VERSION:-}" ] \
|| fail "security: GOVULNCHECK_VERSION не задан; load_versions_contract должен выполниться первым"
[ -n "${PNPM_AUDIT_LEVEL:-}" ] \
|| fail "security: PNPM_AUDIT_LEVEL не задан; load_versions_contract должен выполниться первым"
DEPENDENCY_SECURITY_GATE="true"
export DEPENDENCY_SECURITY_GATE
run_go_vulnerability_gate
run_frontend_vulnerability_gate
# Флаг выставляется ПОСЛЕ обеих проверок, а не до них. Разницы в поведении
# сейчас нет — обе ветки отказа завершают сборку, — но «утверждение о
# результате», записанное перед получением результата, рано или поздно
# переживает свою причину.
DEPENDENCY_SECURITY_GATE="true"
export DEPENDENCY_SECURITY_GATE
}