docs: security roadmap по аудиту (фазы 1–4, чек-лист)
This commit is contained in:
@@ -0,0 +1,265 @@
|
||||
# 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.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` в каждом релизе.
|
||||
|
||||
---
|
||||
|
||||
## Фаза 1 — 2.1.7-SAC
|
||||
|
||||
### 1.1 Права на state / spool (M4)
|
||||
|
||||
- [ ] При создании каталогов и файлов состояния: `chown root:root`, каталоги `chmod 700`, файлы `chmod 600`
|
||||
- [ ] Затронуть: `SAC_SPOOL_DIR`, `SAC_FAIL_COUNT_FILE`, heartbeat / last_* (где создаёт `ssh-monitor` / `sac-client.sh` / deploy)
|
||||
- [ ] В `--check-config`: предупреждение, если существующие пути с ослабленными правами
|
||||
|
||||
**Файлы:** `ssh-monitor`, `sac-client.sh`, при необходимости `update_ssh_monitor.sh`
|
||||
|
||||
---
|
||||
|
||||
### 1.2 Проверка прав конфига при старте (C1, частично)
|
||||
|
||||
- [ ] Перед загрузкой конфига: если `/etc/ssh-monitor.conf` существует — проверить `root:root`, mode `600` или `400`
|
||||
- [ ] **Поведение по умолчанию:** `WARN` в лог + stderr, работа продолжается (не ломать старые установки с `644`)
|
||||
- [ ] Опционально в конфиге: `CONFIG_STRICT_PERMS=1` → **exit 1** при нарушении
|
||||
|
||||
**Решение агента:** strict выключен по умолчанию; в README рекомендовать `chmod 600` и позже `400`.
|
||||
|
||||
---
|
||||
|
||||
### 1.3 `BACKUP_WEBHOOK_URL` (H3) — **нужно утвердить завтра**
|
||||
|
||||
**Что есть сейчас:** fallback POST JSON `{"text":"..."}`, если **все** каналы `NOTIFY_CHAIN` не доставили сообщение; то же в watchdog при сбое Telegram. По умолчанию пусто. В проде, судя по обсуждению, **не используется**.
|
||||
|
||||
| Вариант | Плюсы | Минусы |
|
||||
|---------|-------|--------|
|
||||
| **A. Soft-deprecate (рекомендация)** | Не ломает тех, у кого Slack webhook | Код остаётся |
|
||||
| **B. Удалить в 2.1.7** | Меньше attack surface | Breaking, если кто-то использует |
|
||||
| **C. Оставить как есть** | Без изменений | Риск H3 при компрометации конфига |
|
||||
|
||||
**Рекомендация для 2.1.7 (вариант A):**
|
||||
|
||||
- [ ] В `ssh-monitor.conf.example`: закомментировать / убрать из «активного» блока, комментарий `DEPRECATED: не используется в типовом деплое; будет удалён в 2.3.x`
|
||||
- [ ] В README / `docs/notifications.ru.md`: пометить **legacy / необязательно**
|
||||
- [ ] В `--check-config`: если задан — `WARN: BACKUP_WEBHOOK_URL deprecated`
|
||||
- [ ] **Удаление кода** — отложить до **2.3.0** или позже, если подтвердим, что нигде не нужен
|
||||
|
||||
**Завтра решить:** A / B / C.
|
||||
|
||||
---
|
||||
|
||||
### 1.4 `REPO_URL` — только из env, без дефолта в коде (C3) — **утверждено**
|
||||
|
||||
- [ ] Убрать захардкоженный default `https://git.kalinamall.ru/...` из `update_ssh_monitor.sh`
|
||||
- [ ] При старте updater: если `REPO_URL` пуст — **exit 1**, сообщение в **stderr** и **`$LOG_FILE`**
|
||||
- [ ] Текст ошибки: что задать (`export REPO_URL=...` или в systemd unit `Environment=REPO_URL=...`)
|
||||
- [ ] SAC / cron / timer: документировать обязательную передачу `REPO_URL` (SAC уже может передавать при SSH-обновлении)
|
||||
- [ ] **Не** делать allowlist доменов — оператор сам выбирает зеркало
|
||||
|
||||
**Файлы:** `update_ssh_monitor.sh`, `docs/auto-update.ru.md`, пример unit/timer если есть
|
||||
|
||||
**Заметка:** после 2.1.7 на всех хостах нужно явно прописать `REPO_URL` до следующего обновления.
|
||||
|
||||
---
|
||||
|
||||
### 1.5 Документация threat model
|
||||
|
||||
- [ ] `README.md` + этот файл: root-агент, доверие к `REPO_URL` и конфигу, санитайз GitHub
|
||||
- [ ] Рекомендация: критичные хосты — только закрытое зеркало; автообновление осознанно
|
||||
|
||||
---
|
||||
|
||||
## Фаза 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 без тегов |
|
||||
|
||||
- [ ] Реализовать чтение `GIT_REF` / `GIT_VERIFY_MODE` в `update_ssh_monitor.sh`
|
||||
- [ ] Дефолты для 2.2.0: `GIT_REF` = тег из `version.txt` или `main` (утвердить завтра); `GIT_VERIFY_MODE=tag` для prod-документации
|
||||
- [ ] **`docs/auto-update.ru.md`**: отдельный раздел с примерами systemd и таблицей режимов (после реализации)
|
||||
|
||||
---
|
||||
|
||||
### 2.2 Без слепого `reset --hard` (C2) — **утверждено**
|
||||
|
||||
- [ ] При failed `git pull --ff-only`: **не** делать `reset --hard` автоматически
|
||||
- [ ] Лог + stderr: «история разошлась, требуется ручное вмешательство или новый clone»
|
||||
- [ ] Опционально: `GIT_ALLOW_RESET=1` только для ручного/CI (документировать риск)
|
||||
|
||||
---
|
||||
|
||||
### 2.3 Release manifest (C2, C4) — **утверждено**
|
||||
|
||||
- [ ] Файл в репо, например `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:..."
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] Публиковать manifest при каждом релизе (в git, рядом с тегом)
|
||||
- [ ] Updater: после checkout сверять SHA256 файлов из клона с manifest **до** копирования в `/usr/local/bin`
|
||||
- [ ] Несовпадение → exit 1, ничего не перезаписывать
|
||||
- [ ] Скрипт/цель в `Makefile` или `scripts/build-release-manifest.sh` для генерации
|
||||
|
||||
**Связь с 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:**
|
||||
|
||||
- [ ] **2.2.0:** не блокировать релиз на GPG; `GIT_VERIFY_MODE=tag` без `-v`
|
||||
- [ ] **Backlog:** `GIT_GPG_VERIFY=1` + ключ в `/etc/ssh-monitor/trusted-release-key.asc`, если позже понадобится
|
||||
- [ ] Документировать: при доверенном закрытом зеркале manifest + `GIT_REF=tag` — **достаточный минимум**
|
||||
|
||||
---
|
||||
|
||||
### 2.5 Re-exec updater только после verify (C4) — **утверждено**
|
||||
|
||||
- [ ] `UPDATER_REEXEC` / копирование `update_ssh_monitor.sh` в `/opt/scripts/` — только если manifest SHA256 совпал
|
||||
- [ ] Иначе: лог, старая версия 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 на годы)
|
||||
- Регулярные внешние аудиты
|
||||
|
||||
---
|
||||
|
||||
## Чек-лист «перед стартом работы завтра»
|
||||
|
||||
Утвердить галочками:
|
||||
|
||||
- [ ] **Релиз 2.1.7** — scope: 1.1, 1.2 (warn), 1.4, 1.5, 4.4, 4.3 + **1.3 вариант A/B/C**
|
||||
- [ ] **Релиз 2.2.0** — scope: 2.1–2.3, 2.5, 4.5, 4.7(M7); **2.4 отложить**
|
||||
- [ ] **Релиз 2.3.0** — scope: 3.1–3.3, удаление webhook (если A)
|
||||
- [ ] **Дефолт `GIT_REF`** в 2.2.0: тег из `version.txt` vs `main`
|
||||
- [ ] **Миграция хостов 2.1.7:** рассылка примера `Environment=REPO_URL=...` для systemd / SAC
|
||||
- [ ] **GPG:** отложить до явного запроса
|
||||
|
||||
---
|
||||
|
||||
## Открытые вопросы на завтра
|
||||
|
||||
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 версии.*
|
||||
Reference in New Issue
Block a user