262 lines
16 KiB
Markdown
262 lines
16 KiB
Markdown
# 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.3.2-SAC** | **Фаза 4.2** | Shutdown: не крутить `sac-fail.count`; docs `SAC_TIMEOUT_SEC` | ✅ реализовано |
|
||
| **2.3.1-SAC** | Hotfix | `--check-config` + whitelist IPv4 | ✅ реализовано |
|
||
| **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):** фаза **4.2** закрыта (2.3.2-SAC). Дальше — ops (`UseSAC=exclusive`, пачки update) и опционально GPG / `GIT_REF=tag` на проде.
|
||
Backlog по ingest при массовом update (SAC + ops): [ingest-mass-update-backlog.ru.md](https://git.papatramp.ru/PapaTramp/security-alert-center/src/branch/main/docs/ingest-mass-update-backlog.ru.md) (отдельный ToDo).
|
||
|
||
---
|
||
|
||
## Фаза 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 (удалён в 2.3.0)
|
||
- [x] **Удаление кода** — **2.3.0-SAC**
|
||
|
||
---
|
||
|
||
### 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) — **утверждено**
|
||
|
||
- [x] Whitelist ключей: `TELEGRAM_*`, `SAC_*`, `MAIL_*`, `NOTIFY_*`, числовые лимиты, пути и т.д.
|
||
- [x] Формат строк: `KEY="value"` / `KEY='value'` / `KEY=value`
|
||
- [x] Игнор `#` комментариев; неизвестные ключи — WARN
|
||
|
||
**Файлы:** `ssh-monitor-perms.sh` (`ssh_monitor_config_load_file`), `ssh-monitor`, `ssh-monitor-watchdog`
|
||
|
||
---
|
||
|
||
### 3.2 Миграция — **решение агента**
|
||
|
||
- [x] **2.3.0:** парсер по умолчанию; `source` **удалён**
|
||
- [x] Синтаксис файла **не меняется** для пользователя — те же `KEY="value"`
|
||
- [x] В release notes: «поведение то же, выполнение bash из конфига невозможно»
|
||
- [x] `ssh-monitor --check-config` проверяет неизвестные/битые строки (WARN) и значения (ERROR)
|
||
- [x] Отдельная команда миграции **не нужна** (формат тот же)
|
||
|
||
---
|
||
|
||
### 3.3 Валидация значений — **решение агента**
|
||
|
||
- [x] URL: `SAC_URL` — http(s)://host
|
||
- [x] Enum: `UseSAC` ∈ `off|exclusive|dual|fallback`; `GIT_VERIFY_MODE` ∈ `off|tag|commit`
|
||
- [x] Числа: существующие `validate_numeric_or_default`
|
||
- [x] IP/CIDR в `WHITELIST_*` — базовая проверка формата
|
||
- [x] Ошибки валидации → exit 1 на старте и в `--check-config`
|
||
|
||
---
|
||
|
||
### 3.x Удаление `BACKUP_WEBHOOK` (если утвердили soft-deprecate в 1.3)
|
||
|
||
- [x] Удалить код и документацию в **2.3.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 отложить**
|
||
- [x] **Релиз 2.3.0** — scope: 3.1–3.3, удаление webhook
|
||
- [ ] **Дефолт `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 версии.*
|