Безопасность: проверка секретов, блокировка перебора входа, безопасные редиректы
Безопасность, пункты 1–4 ревью (docs/changes/021): - приложение не стартует со слабыми секретами: API_TOKEN и SESSION_SECRET пустые, change-me или короче 32 символов, ADMIN_PASSWORD короче 12, невалидный SECRET_KEY; значения секретов в ошибку не попадают; - вход в UI: 5 неверных попыток за 10 минут блокируют IP на 10 минут (429, пароль не проверяется); неверный пароль — 401; событие auth.locked пишется один раз, журнал не засыпается перебором; - SESSION_COOKIE_SECURE — флаг Secure у cookie сессии (по умолчанию выкл.); - _safe_next: редирект только на локальный путь (/ui/move, удаление бэкапов); - .env.bak-* в .gitignore. Тесты: 28 из 28. Стенд: change-me → 401, новый токен → 200, отказ старта со слабым токеном, блокировка входа и редиректы проверены, боевые данные не изменены. Ручная проверка UI пользователем на момент коммита не подтверждена. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
6b5c521461
commit
c4079724c5
11 files changed
+332
-58
No files matched your search
@@ -0,0 +1,35 @@
|
||||
# Итоги: 021 — безопасность (по ревью 2026-09-27)
|
||||
|
||||
Источник — ревью `docs/reviews/2026-09-27-codebase-review.md`, раздел «Безопасность» (пункты 1–4).
|
||||
|
||||
## Сделано
|
||||
- **п. 1 — проверка секретов при старте** (`app/config.py::insecure_settings`, `app/main.py::lifespan` — первым шагом):
|
||||
`API_TOKEN`, `SESSION_SECRET` — не пустые, не `change-me`, ≥ 32 символов; `ADMIN_PASSWORD` — ≥ 12; `SECRET_KEY` — валидный ключ Fernet.
|
||||
Иначе `RuntimeError` со списком проблем без значений секретов, приложение не стартует. `.env.example` — пустые секреты с требованиями и командой генерации.
|
||||
- **п. 2 — ограничение попыток входа по IP** (`app/ui/routes.py::login`): 5 неверных за 10 минут → IP заблокирован на 10 минут, пароль не проверяется, 429.
|
||||
Неверный пароль — 401 (было 200). Счётчики `security.lockout_remaining/register_failure/reset_failures` (параметр `user` → `key`), ключ `login:<ip>`
|
||||
не пересекается с ключом очистки журнала. События: `auth.failed` (IP, оставшиеся попытки), `auth.locked` — **один раз** при начале блокировки, `auth.login` с IP.
|
||||
- **п. 3 — Secure-cookie**: `SESSION_COOKIE_SECURE` (по умолчанию `false`), README — включить за TLS.
|
||||
- **п. 4 — безопасный `next`** (`_safe_next`): только локальный путь с нужным префиксом; `//`, `\`, управляющие символы, схема или хост → адрес по умолчанию.
|
||||
Применён в `/ui/move` и групповом удалении бэкапов.
|
||||
- `.env` стенда: сгенерированы новые `API_TOKEN` (43), `SESSION_SECRET` (64), `ADMIN_PASSWORD` (20); прежний — `.env.bak-20260928`;
|
||||
`.gitignore` — шаблон `.env.bak-*`. README: «Безопасность», «Настройки», «Ограничения», история изменений.
|
||||
|
||||
## Найдено на ревью и исправлено
|
||||
- Каждая попытка входа с заблокированного IP писала `auth.locked` — перебором можно было засыпать журнал и вытеснить историю ротацией. Теперь событие пишется один раз.
|
||||
- Тест блокировки проверял 303 без `follow_redirects=False` и падал.
|
||||
- Найдено до реализации: на стенде `API_TOKEN` был `change-me` (API открыт с общеизвестным токеном), `SESSION_SECRET` — 10 символов, `ADMIN_PASSWORD` — 8.
|
||||
|
||||
## Проверено
|
||||
- `pytest`: 28 из 28 (новые: слабые секреты без утечки значений, отказ старта, блокировка входа по IP, открытый редирект).
|
||||
- Отказ старта: временный контейнер без тома с `API_TOKEN=change-me` → `RuntimeError`; значений секретов в логе нет.
|
||||
- API: `change-me` → 401, новый токен → 200.
|
||||
- Вход: 5 неверных → 401 ×5; 6-я с верным паролем и ещё 3 → 429; с 127.0.0.1 (изнутри контейнера) → 303; события: `auth.failed` ×4, `auth.locked` ×1.
|
||||
- Редирект: `/\evil.com`, `//evil.com`, `https://evil.com` → `/`; `/?f_group=none` сохраняется.
|
||||
- Боевые данные: группы 4/4, устройства 14/14, бэкапы 47/47, задачи 73/73 — совпадают по ID; добавлено 7 событий входа проверки. Блокировка снята перезапуском контейнера.
|
||||
|
||||
## Оговорки
|
||||
- **Ломающее для эксплуатации**: новые пароль администратора и токен API (в `.env`); старые сессии UI и скрипты со старым токеном не работают.
|
||||
- Блокировка входа — в памяти процесса (сбрасывается перезапуском). За reverse-proxy все клиенты видны с IP прокси — нужна настройка доверенных прокси.
|
||||
- API-токен не ограничен по попыткам (≥ 32 случайных символа — перебор непрактичен).
|
||||
- Ручная проверка UI пользователем на момент коммита не подтверждена.
|
||||
Reference in new issue
Block a user