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
+65 -3
View File
@@ -12,8 +12,14 @@ set -euo pipefail
# строке '/hui', а dead-route скан падал на router_test.go, который ПЕРЕЧИСЛЯЕТ
# удалённые маршруты, чтобы доказать их отсутствие. Помощник существует, чтобы
# третьего раза не было.
#
# Отбрасываются оба вида строк-комментариев: `//` для Go и TypeScript, `#` для
# shell. Раньше стриплось только `//`, поэтому сканы по tools/build/*.sh не
# работали вовсе — там комментарии начинаются с решётки, и объяснение «почему
# этого больше нет» считалось за код. Ни один из языков не использует префикс
# другого для чего-то иного, так что общий фильтр безопасен для обоих.
code_without_comments() {
grep -v '^[[:space:]]*//' "$1" 2>/dev/null || true
grep -vE '^[[:space:]]*(//|#)' "$1" 2>/dev/null || true
}
# code_mentions_in отдаёт список файлов, где подстрока встречается В КОДЕ.
@@ -551,11 +557,67 @@ run_dependency_hygiene_acceptance() {
grep -q '^GOVULNCHECK_VERSION=' versions.env \
|| fail "acceptance: версия govulncheck должна быть частью контракта сборки"
# По готовому tarball должно быть видно, проверялся он или собран с
# пропущенной проверкой — ровно как hysteria_compat_gate.
# По готовому tarball должно быть видно, что он проверялся — ровно как
# hysteria_compat_gate.
grep -q '^dependency_security_gate=true$' "$package_dir/metadata/package.env" \
|| fail "acceptance: релизный пакет обязан собираться с включённой проверкой зависимостей"
# У шага не должно быть обходов — ни объявленных, ни забытых.
#
# Раньше их было два: ALLOW_VULNERABLE_DEPENDENCIES=true писал в metadata
# `accepted-risk`, SKIP_SECURITY_SCAN=true — `skipped`. Оба документировались
# README и docs/02 как способ выпустить релиз, зная об уязвимости, и оба
# гарантированно падали здесь же, строкой выше: приёмка требует буквально
# `true`. Сборка проходила весь цикл и умирала на последнем шаге ради
# операции, которую продукт в итоге запрещает.
#
# Противоречие закрыто в пользу строгой политики, и теперь это проверяется, а
# не только описано. Ищется КОД: комментарий выше объясняет, почему обходов
# нет, и обязан называть их по имени.
local bypass_var bypass_hits
for bypass_var in ALLOW_VULNERABLE_DEPENDENCIES SKIP_SECURITY_SCAN; do
bypass_hits="$(code_mentions_in "$bypass_var" tools/build)"
[ -z "$bypass_hits" ] \
|| fail "acceptance: обход проверки зависимостей $bypass_var вернулся в: $bypass_hits"
done
# И в документации тоже: описанный, но нерабочий аварийный выход — хуже, чем
# его отсутствие. Именно из-за такого описания противоречие и прожило до
# приёмки.
#
# У markdown нет строк-комментариев, поэтому code_without_comments здесь не
# применим и правило простое: место для истории — CHANGELOG.md, он под скан не
# попадает. README и docs описывают текущую политику, а не отменённую.
! grep -rq 'ALLOW_VULNERABLE_DEPENDENCIES\|SKIP_SECURITY_SCAN' README.md docs \
|| fail "acceptance: README/docs снова описывают обход проверки зависимостей, которого нет в сборке; место для истории — CHANGELOG.md"
# Единственное значение поля — true. `accepted-risk`/`skipped` не должны
# вернуться даже как строковые литералы: их некому произвести.
local dead_gate_value
for dead_gate_value in accepted-risk skipped; do
! grep -rqF "DEPENDENCY_SECURITY_GATE=\"${dead_gate_value}\"" tools/build \
|| fail "acceptance: у dependency_security_gate снова появилось значение ${dead_gate_value}"
done
log_step "Acceptance: versions.env is a developer contract, not only a build contract"
# Сборка соблюдала versions.env, а машина разработчика не проверялась никак:
# локальный Go 1.25 собирал проект успешно, пока релизный бинарь собирался на
# 1.26.7 и наследовал ЕЁ stdlib. Проверялся не тот код, который уезжает в
# production.
local doctor_script
for doctor_script in tools/dev/doctor.sh tools/dev/doctor.ps1; do
[ -f "$doctor_script" ] \
|| fail "acceptance: проверка среды разработки $doctor_script отсутствует"
grep -q 'versions.env' "$doctor_script" \
|| fail "acceptance: $doctor_script обязан читать контракт из versions.env, а не носить свои значения"
done
[ -x tools/dev/doctor.sh ] \
|| fail "acceptance: tools/dev/doctor.sh не исполняемый"
# Собственных версий у doctor быть не должно: второй список версий неизбежно
# разъедется с versions.env.
! grep -qE '(GO|NODE|PNPM|BUN)_VERSION[[:space:]]*=[[:space:]]*[0-9]' tools/dev/doctor.sh \
|| fail "acceptance: tools/dev/doctor.sh завёл собственные значения версий"
log_step "Acceptance: the Go toolchain is on a supported line"
# Go компилирует hy2xs-admin, поэтому его stdlib уезжает в production-бинарь.
# На 1.21 govulncheck находил 17 вызываемых уязвимостей в одной только stdlib.