# Security roadmap — ssh-monitor Чек-лист правок по аудиту безопасности. **Код пока не трогаем** — документ для совместного просмотра и утверждения фаз. Связанные файлы: `ssh-monitor`, `update_ssh_monitor.sh`, `sac-client.sh`, `ssh-monitor-watchdog`, `ssh-monitor.conf.example`. --- ## Модель доверия (согласовано) | Объект | Кто отвечает | |--------|----------------| | **`REPO_URL`** | Оператор. Указывает **своё** зеркало (kalinamall, GitHub после санитайза, форк). Жёсткий allowlist в коде **не нужен**. | | **Закрытое зеркало** | Доверенный git в LAN/VPN — основной путь обновления. | | **Публичный GitHub** | Санитизированная копия без секретов; обновление оттуда допустимо, если оператор сам прописал URL. | | **`/etc/ssh-monitor.conf`** | Доверенный root-only файл; агент работает от root. | | **Аудиты** | Нерегулярно; roadmap закрывает разумный baseline, не «вечный SOC». | --- ## Порядок релизов | Версия | Фазы | Суть | |--------|------|------| | **2.1.7-SAC** | Фаза 1 + выбранное из Фазы 4 | Права, `REPO_URL` обязателен, webhook, docs | ✅ реализовано | | **2.1.8-SAC** | Дополнение к 1.1 | SAC-first update: hardening прав на каждом прогоне updater | ✅ реализовано | | **2.1.9-SAC** | Дополнение к 1.x | Watchdog: `🖥️ Сервер:` в Telegram (как у агента) | ✅ реализовано | | **2.2.3-SAC** | Дополнение к 2.2.x | Подавление sudo TG при SAC bootstrap/update | ✅ реализовано | | **2.2.1-SAC** | Дополнение к 2.2.0 | Подавление lifecycle/watchdog TG при SAC-update | ✅ реализовано | | **2.2.0-SAC** | **Фаза 2** | Manifest, pinned ref, без слепого `reset --hard` | ✅ реализовано | | **2.3.0-SAC** | Фаза 3 | Парсер конфига без `source` (minor breaking) | Bump: `ssh-monitor` (`SSH_MONITOR_VERSION`) + `version.txt` в каждом релизе. **Текущий статус (2026-07-08):** фаза 2 закрыта (2.2.0-SAC). **Дальше — фаза 3 → релиз 2.3.0-SAC.** Backlog по ingest при массовом update (SAC + ops): [ingest-mass-update-backlog.ru.md](https://git.kalinamall.ru/PapaTramp/security-alert-center/src/branch/main/docs/ingest-mass-update-backlog.ru.md) (отдельный ToDo, не блокирует 2.2.0). --- ## Фаза 1 — 2.1.7-SAC ### 1.1 Права на state / spool (M4) - [x] При создании каталогов и файлов состояния: `chown root:root`, каталоги `chmod 700`, файлы `chmod 600` - [x] Затронуть: `SAC_SPOOL_DIR`, `SAC_FAIL_COUNT_FILE`, heartbeat / last_* (где создаёт `ssh-monitor` / `sac-client.sh` / deploy) - [x] **2.1.8:** `update_ssh_monitor.sh` → `apply_runtime_security_hardening` на **каждом** update (SAC, cron, вручную): retrofit `chmod`/`chown` существующих путей из конфига - [x] В `--check-config`: предупреждение, если существующие пути с ослабленными правами **Файлы:** `ssh-monitor`, `sac-client.sh`, при необходимости `update_ssh_monitor.sh` --- ### 1.2 Проверка прав конфига при старте (C1, частично) - [x] Перед загрузкой конфига: если `/etc/ssh-monitor.conf` существует — проверить `root:root`, mode `600` или `400` - [x] **Поведение по умолчанию:** `WARN` в лог + stderr, работа продолжается (не ломать старые установки с `644`) - [x] Опционально в конфиге: `CONFIG_STRICT_PERMS=1` → **exit 1** при нарушении **Решение агента:** strict выключен по умолчанию; в README рекомендовать `chmod 600` и позже `400`. --- ### 1.3 `BACKUP_WEBHOOK_URL` (H3) — **вариант A (soft-deprecate)** - [x] В `ssh-monitor.conf.example`: закомментировать / убрать из «активного» блока, комментарий `DEPRECATED: не используется в типовом деплое; будет удалён в 2.3.x` - [x] В README / `docs/notifications.ru.md`: пометить **legacy / необязательно** - [x] В `--check-config`: если задан — `WARN: BACKUP_WEBHOOK_URL deprecated` - [ ] **Удаление кода** — отложить до **2.3.0** или позже, если подтвердим, что нигде не нужен --- ### 1.4 `REPO_URL` — только из env, без дефолта в коде (C3) — **утверждено** - [x] Убрать захардкоженный default `https://git.kalinamall.ru/...` из `update_ssh_monitor.sh` - [x] При старте updater: если `REPO_URL` пуст — **exit 1**, сообщение в **stderr** и **`$LOG_FILE`** - [x] Текст ошибки: что задать (`export REPO_URL=...` или в systemd unit `Environment=REPO_URL=...`) - [x] SAC: основной путь обновления (кнопка в UI); cron/timer — опционально. Bootstrap SAC → `--deploy` - [x] **Не** делать allowlist доменов — оператор сам выбирает зеркало **Файлы:** `update_ssh_monitor.sh`, `docs/auto-update.ru.md`, пример unit/timer если есть **Заметка:** после 2.1.7 на всех хостах нужно явно прописать `REPO_URL` до следующего обновления. --- ### 1.5 Документация threat model - [x] `README.md` + этот файл: root-агент, доверие к `REPO_URL` и конфигу, санитайз GitHub - [x] Рекомендация: критичные хосты — только закрытое зеркало; автообновление осознанно --- ## Фаза 2 — 2.2.0-SAC ### 2.1 Pinned ref и режим верификации (C2) — **утверждено, детали на агенте** Новые переменные (env или ключи в `/etc/ssh-monitor.conf`, читаемые updater’ом): | Переменная | Назначение | Рекомендация | |------------|------------|--------------| | **`GIT_REF`** | Что checkout: тег (`2.2.0-SAC`), ветка (`main`) или commit SHA | **Тег релиза** — оптимально для прода | | **`GIT_VERIFY_MODE`** | `off` \| `tag` \| `commit` | См. ниже | **`GIT_VERIFY_MODE`:** | Значение | Поведение | Когда использовать | |----------|-----------|-------------------| | **`off`** | После fetch принять ref как есть (как сейчас, но без `reset --hard` по умолчанию) | Тест, если manifest ниже достаточен | | **`tag`** | `GIT_REF` должен быть **аннотированным тегом**; опционально `git tag -v` (см. 2.4) | **Рекомендуется для прода** | | **`commit`** | `GIT_REF` = полный SHA; сверка с manifest | Жёсткий pin без тегов | - [x] Реализовать чтение `GIT_REF` / `GIT_VERIFY_MODE` в `update_ssh_monitor.sh` - [x] Дефолты для 2.2.0: `GIT_REF` пуст → `GIT_BRANCH`; `GIT_VERIFY_MODE=off` в скрипте, **`tag`** в prod-документации - [x] **`docs/auto-update.ru.md`**: отдельный раздел с примерами systemd и таблицей режимов --- ### 2.2 Без слепого `reset --hard` (C2) — **утверждено** - [x] При failed `git pull --ff-only`: **не** делать `reset --hard` автоматически - [x] Лог + stderr: «история разошлась, требуется ручное вмешательство или новый clone» - [x] Опционально: `GIT_ALLOW_RESET=1` только для ручного/CI (документировать риск) --- ### 2.3 Release manifest (C2, C4) — **утверждено** - [x] Файл в репо, например `release/manifest-2.2.0-SAC.json`: ```json { "version": "2.2.0-SAC", "git_commit": "abc123...", "files": { "ssh-monitor": "sha256:...", "sac-client.sh": "sha256:...", "update_ssh_monitor.sh": "sha256:...", "ssh-monitor-watchdog": "sha256:..." } } ``` - [x] Публиковать manifest при каждом релизе (в git, рядом с тегом) - [x] Updater: после checkout сверять SHA256 файлов из клона с manifest **до** копирования в `/usr/local/bin` - [x] Несовпадение → exit 1, ничего не перезаписывать - [x] Скрипт/цель в `Makefile` + `contrib/manifest/generate.py`; pre-commit hook; ручной `.local/` (не в remote) **Связь с 2.4:** manifest из **того же** `REPO_URL` защищает от подмены файлов в clone и от «не того» коммита; не защищает от полной компрометации git-сервера (для этого GPG). --- ### 2.4 GPG-подписи тегов — **опционально, скорее отложить** **Вопрос:** если обновляем только из своего `REPO_URL` (kalinamall), нужен ли GPG? | Угроза | Manifest + pinned tag | + GPG tag | |--------|----------------------|-----------| | Подмена файлов в clone / wrong commit | Да | Да | | Компрометация аккаунта git (вредоносный force-push) | **Нет** | Да (если ключ не украден) | | Доверенный LAN git у оператора | Обычно достаточно manifest | Nice-to-have | **Решение для roadmap:** - [x] **2.2.0:** не блокировать релиз на GPG; `GIT_VERIFY_MODE=tag` без `-v` - [ ] **Backlog:** `GIT_GPG_VERIFY=1` + ключ в `/etc/ssh-monitor/trusted-release-key.asc`, если позже понадобится - [x] Документировать: при доверенном закрытом зеркале manifest + `GIT_REF=tag` — **достаточный минимум** --- ### 2.5 Re-exec updater только после verify (C4) — **утверждено** - [x] `UPDATER_REEXEC` / копирование `update_ssh_monitor.sh` в `/opt/scripts/` — только если manifest SHA256 совпал - [x] Иначе: лог, старая версия updater остаётся, exit 1 --- ## Фаза 3 — 2.3.0-SAC (парсер конфига) ### 3.1 Парсер без `source` (C1) — **утверждено** - [ ] Whitelist ключей: `TELEGRAM_*`, `SAC_*`, `MAIL_*`, `NOTIFY_*`, числовые лимиты, пути и т.д. - [ ] Формат строк: `KEY="value"` / `KEY='value'` / `KEY=value` - [ ] Игнор `#` комментариев; неизвестные ключи — WARN (или сохранять в sidecar — не нужно) **Файлы:** `ssh-monitor`, `ssh-monitor-watchdog` (если source конфиг) --- ### 3.2 Миграция — **решение агента** - [ ] **2.3.0:** парсер по умолчанию; `source` **удалить** - [ ] Синтаксис файла **не меняется** для пользователя — те же `KEY="value"` - [ ] В release notes: «поведение то же, выполнение bash из конфига невозможно» - [ ] `ssh-monitor --check-config` проверяет неизвестные/битые строки - [ ] Отдельная команда миграции **не нужна** (формат тот же) --- ### 3.3 Валидация значений — **решение агента** - [ ] URL: `SAC_URL`, `MAIL_SMTP_HOST` — схема https/http, не пустой host - [ ] Enum: `UseSAC` ∈ `off|exclusive|dual|fallback` - [ ] Числа: существующие `validate_numeric_or_default` расширить - [ ] IP/CIDR в `WHITELIST_*` — базовая проверка формата - [ ] Ошибки валидации → exit 1 на старте и в `--check-config` --- ### 3.x Удаление `BACKUP_WEBHOOK` (если утвердили soft-deprecate в 1.3) - [ ] Удалить код и документацию в **2.3.0** или **2.4.0**, если подтверждено неиспользование --- ## Фаза 4 — что делаем, что нет | Пункт | Версия | Решение | |-------|--------|---------| | **4.4** `ensure_ipset_installed` не на каждый update | 2.1.7 | **Делаем:** только `--deploy` / `first_deploy.sh`; обычный update не вызывает `apt-get` | ✅ | | **4.3** Права whitelist-файла | 2.1.7 | **Делаем:** при загрузке `/etc/ssh_monitor_whitelist.txt` — WARN если не root:root | ✅ | | **4.5** JSON healthcheck через `json.dumps` | 2.2.0 | **Делаем:** мелкий fix L3 | ✅ | | **4.1** Секреты не через environ в Python | — | **Не делаем** (мало выигрыша при root) | | **4.2** Telegram token в URL | — | **Не делаем** (ограничение Bot API) | | **4.6** systemd hardening (non-root) | — | **Не делаем** (ipset/iptables требуют root) | | **M6** Ротация логов | — | Уже есть `contrib/logrotate.d/` — только упоминание в README | | **M7** `set_config_kv` sed | 2.2.0 | **Делаем:** экранирование `/` и `"` в `val` при merge конфига | ✅ | --- ## Вне scope (согласовано не раздувать) - Жёсткий allowlist `REPO_URL` / зеркал — оператор задаёт свой URL - Cert pinning TLS - HashiCorp Vault / systemd-creds для секретов (backlog на годы) - Регулярные внешние аудиты --- ## Чек-лист «перед стартом работы завтра» - [x] **Релиз 2.1.7–2.1.9** — фаза 1 + SAC-first + watchdog server label - [x] **Релиз 2.2.0** — scope: 2.1–2.3, 2.5, 4.5, 4.7(M7); **2.4 GPG отложить** - [ ] **Релиз 2.3.0** — scope: 3.1–3.3, удаление webhook (если A) - [ ] **Дефолт `GIT_REF`** в 2.2.0: тег из `version.txt` vs `main` - [x] **Миграция хостов:** SAC-first update, `known_hosts`, `REPO_URL` через SAC - [ ] **Ingest mass-update backlog** — см. security-alert-center `docs/ingest-mass-update-backlog.ru.md` --- ## Открытые вопросы на завтра 1. **1.3** — `BACKUP_WEBHOOK_URL`: soft-deprecate (A), удалить сразу (B), оставить (C)? 2. **1.2** — достаточно WARN по умолчанию или сразу strict на новых установках? 3. **2.1** — дефолт `GIT_REF`: тег `version.txt` или ветка `main`? 4. Подтверждение: **ни на одном хосте** не используется Slack/Discord webhook через `BACKUP_WEBHOOK_URL`? --- *Документ создан для ревью. После утверждения — работа по фазам в отдельных коммитах с bump версии.*