gendesign/scripts/smoke-mera-perimeter.sh
bot-backend 1f85ef7d4e
All checks were successful
CI / changes (pull_request) Successful in 8s
CI Trade-In / backend-tests (pull_request) Successful in 3m50s
CI Trade-In / changes (pull_request) Successful in 8s
CI Trade-In / browser-tests (pull_request) Has been skipped
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped
fix(tradein/payments): тело нотификации не течёт в мониторинг и аудит, повторы банка не отбиваются лимитом
PR-D2 платёжного контура МЕРЫ — закрывает утечки до открытия публичных путей
(PR-D3/D4), сам ничего не открывает: _PUBLIC_PATHS (rbac.py), Caddyfile,
roles.yaml, auth_session.py не тронуты.

- sentry_scrub.py: новая scrub_payment_request_body — вырезает
  event.request.data целиком для /api/v1/trade-in/payments/* (sentry_sdk 2.64
  кладёт полное тело запроса в request.data, send_default_pii=False это НЕ
  гейтит — тот флаг управляет только куками). Плюс расширен _PII_KEYS:
  customer_email/customer_phone/pan/expdate/cardid/rebillid/token/terminalkey.
- main.py, scheduler_main.py, tgbot_main.py (все 3 точки инициализации
  sentry_sdk.init в проекте) — тот же обработчик проведён в ОБА канала,
  before_send и before_send_transaction. Мотивирующий инцидент: на соседнем
  продукте вчера закрыли только error-канал, transaction остался без
  обработчика вообще.
- ratelimit.py: точный путь notify — свой щедрый SlidingWindowLimiter
  (3000/60с per-IP, идиома support.py) вместо общего лимитера, но НЕ полное
  отключение — backstop против шторма запросов остаётся, подпись проверяется
  уже после разбора тела (PR-D3). Только notify, не checkout (тот с сессией).
- request_audit.py: notify — в audit skip-набор (defense-in-depth: middleware
  внешний относительно rbac_guard и читает сырой X-Authenticated-User —
  спуфнутый заголовок иначе писал бы фальшивые события с атрибуцией admin).
- smoke-mera-perimeter.sh: негативные проверки-канарейки — notify/checkout
  сейчас закрыты 404 (meraocenka.ru, Caddy не проксирует) и 401
  (gendsgn.ru, rbac ещё не открыл) с обеих сторон периметра.

Тесты: scrub на произвольной глубине + payment-path body-wipe, AST-разбор
(не substring — комментарии в этих же файлах сами упоминают
before_send_transaction) на проводку обоих каналов во всех точках
инициализации, 400 запросов notify без единого 429 + контроль что общий
лимитер по-прежнему активен на других путях, notify вне user_events даже со
спуфнутым X-Authenticated-User: admin.
2026-08-07 16:17:59 +03:00

121 lines
8.2 KiB
Bash
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env bash
# Регресс-тест публичного B2C-периметра МЕРА (ЭТАП 1 плана B2C-запуска).
#
# Проверяет инварианты периметра (см. корневой Caddyfile):
# 1. meraocenka.ru отдаёт 200 анонимно (публичный лэндинг).
# 1b. Подстраница лэндинга /trade-in/mera-public/privacy отдаёт 200 —
# политика ПДн, на которую ссылается футер.
# 2. meraocenka.ru/v2 и /trade-in/v2, /trade-in/api/* (B2B-пути) отдают 404 —
# allowlist-by-default, НЕ были случайно проброшены на B2B-дерево
# tradein-frontend. Проверяются обе формы — с basePath-префиксом и без.
# 3. trade-in API (/me, /history, /admin/*) отдаёт 401 анониму — данные B2B
# закрыты. Именно API, а не страница: см. комментарий у проверки ниже.
# 4. gendsgn.ru/api/v1/admin/* отдаёт 401 анониму (gate Site Finder).
# 5. merahome.ru и meraotsenka.ru отдают 301 на канонический meraocenka.ru.
#
# ВАЖНО: проверки 1 и 2 требуют, чтобы DNS A-record meraocenka.ru → IP VPS
# уже существовал И деплой прошёл (сертификат Let's Encrypt выпущен). Пока
# записи нет — они ожидаемо падают (DNS resolution failure / TLS handshake
# failure), это НЕ регресс периметра gendsgn.ru. Проверки 3 и 4 не зависят от
# DNS нового домена и обязаны быть зелёными всегда.
#
# Запуск вручную:
# bash scripts/smoke-mera-perimeter.sh
# Запуск в CI: .forgejo/workflows/perimeter-smoke.yml (workflow_dispatch + daily cron).
set -uo pipefail
BASE_MERA="${SMOKE_MERA_BASE:-https://meraocenka.ru}"
BASE_MAIN="${SMOKE_MAIN_BASE:-https://gendsgn.ru}"
fail=0
check() {
local desc="$1" url="$2" expected="$3"
local code
code=$(curl -s -o /dev/null -w '%{http_code}' --max-time 15 "$url" 2>/dev/null)
if [ "$code" = "$expected" ]; then
echo "PASS: $desc ($url -> $code)"
else
echo "FAIL: $desc ($url -> got '${code:-<no response>}', expected $expected)"
fail=1
fi
}
echo "== МЕРА B2C perimeter smoke (ЭТАП 1) =="
# 1. Публичный домен отдаёт 200 анонимно.
check "meraocenka.ru root — public 200" "$BASE_MERA/" 200
# 1b. Подстраница лэндинга (политика ПДн) доступна — на неё ссылается футер.
# Путь приезжает с basePath: next/link + basePath=/trade-in эмитит именно
# /trade-in/mera-public/privacy. Если этот handle выпадет из Caddyfile,
# обязательный по 152-ФЗ документ станет недоступен с публичной страницы.
check "meraocenka.ru privacy — public 200" "$BASE_MERA/trade-in/mera-public/privacy" 200
# 2. B2B-путь на публичном домене — 404 (allowlist-by-default), не 200/401.
check "meraocenka.ru/v2 — B2B path must 404" "$BASE_MERA/v2" 404
# 2b. Те же B2B-пути в basePath-форме — 404. Это регресс-тест именно на
# matcher `handle /trade-in/mera-public/*`: расширь его случайно до
# `/trade-in/*` — и B2B-дерево уедет наружу через публичный домен, а
# проверка 2 (/v2 без префикса) этого НЕ заметит.
check "meraocenka.ru/trade-in/v2 — B2B path must 404" "$BASE_MERA/trade-in/v2" 404
check "meraocenka.ru/trade-in/api/* — must 404 (не проксируем API)" "$BASE_MERA/trade-in/api/v1/me" 404
# 2c. Статика проксируется ТОЛЬКО из _next/static/*. Оптимизатор картинок
# /_next/image на лэндинге не нужен (next/image там не импортируется) и
# наружу не открыт — иначе аноним получил бы CPU-нагрузку по запросу.
# Ловит расширение матчера обратно до `/trade-in/_next/*`.
check "meraocenka.ru/_next/image — must 404 (не открываем оптимизатор)" "$BASE_MERA/trade-in/_next/image?url=%2Ftest.png&w=64&q=75" 404
# 3. B2B-данные trade-in по-прежнему закрыты анониму.
#
# ВНИМАНИЕ: проверять СТРАНИЦУ (/trade-in/v2) больше нельзя — она отдаёт 200.
# После #2555/#2558 trade-in ушёл с Caddy basic_auth на собственный логин:
# страница рендерится анониму, а RouteGuard уже на клиенте уводит на /login.
# Гейт данных переехал на API — там и проверяем, иначе тест зелёный при
# открытом наружу бэкенде.
check "trade-in /api/v1/me — 401 anonymous" "$BASE_MAIN/trade-in/api/v1/me" 401
check "trade-in /api/v1/history — 401 anonymous (чужие оценки)" "$BASE_MAIN/trade-in/api/v1/history" 401
check "trade-in /api/v1/admin/* — 401 anonymous" "$BASE_MAIN/trade-in/api/v1/admin/users" 401
# 4. gendsgn.ru/api/v1/admin/* отдаёт 401 анониму (auth gate стоит ДО роутинга
# в FastAPI — конкретный путь неважен, любой /api/v1/admin/* перехватывается
# на уровне Caddy до бэкенда).
check "gendsgn.ru/api/v1/admin/* — 401 anonymous" "$BASE_MAIN/api/v1/admin/users" 401
# 5. Домены-спутники ведут на канонический (301, без следования редиректу —
# curl без -L, поэтому ждём именно код редиректа, а не 200 конечной страницы).
# Как и проверки 1-2, требуют DNS + выпущенного сертификата.
check "merahome.ru — 301 to canonical" "https://merahome.ru/" 301
check "meraotsenka.ru — 301 to canonical" "https://meraotsenka.ru/" 301
# 6. Платёжный периметр (PR-D2) — готовит почву под PR-D3 (роутер) и PR-D4
# (Caddy), но САМ НИЧЕГО НЕ ОТКРЫВАЕТ. Ожидаем закрытое состояние С ОБЕИХ
# СТОРОН прямо сейчас:
# - meraocenka.ru вообще не проксирует /trade-in/api/* (allowlist-by-default,
# см. проверку 2) — 404 от Caddy, до бэкенда не доходит;
# - gendsgn.ru проксирует /trade-in/api/* в tradein-backend, но rbac_guard
# (`_PUBLIC_PATHS` в app/core/rbac.py — ЭТОТ PR её не трогает) не знает
# платёжные пути и требует X-Authenticated-User → 401 анониму.
# Если один из этих чек-ов вдруг перестанет быть 404/401 РАНЬШЕ мержа
# PR-D3/PR-D4 — это и есть преждевременная утечка периметра, которую ловит
# этот смоук (канарейка: осознанно станет красной, когда PR-D3/PR-D4 явно
# откроют эти пути — тогда ожидания здесь надо обновить вместе с ними).
check "meraocenka.ru payments/notify — must 404 (Caddy не проксирует, PR-D4)" \
"$BASE_MERA/trade-in/api/v1/trade-in/payments/notify" 404
check "meraocenka.ru payments/checkout — must 404 (Caddy не проксирует, PR-D4)" \
"$BASE_MERA/trade-in/api/v1/trade-in/payments/checkout" 404
check "trade-in payments/notify — 401 anonymous (rbac закрыт до PR-D3)" \
"$BASE_MAIN/trade-in/api/v1/trade-in/payments/notify" 401
check "trade-in payments/checkout — 401 anonymous (rbac закрыт до PR-D3)" \
"$BASE_MAIN/trade-in/api/v1/trade-in/payments/checkout" 401
echo "========================================"
if [ "$fail" -eq 0 ]; then
echo "ALL CHECKS PASSED"
else
echo "SOME CHECKS FAILED — see FAIL lines above"
fi
exit "$fail"