feat: release 2.1.8-SAC — security phase 1 and SAC-first update hardening

Updater applies chmod/chown on config, spool and state paths on every run.
SAC is the primary update path; optional cron uses the same behavior.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
PTah
2026-07-08 09:44:24 +10:00
parent ec0857b8f2
commit 2048162057
11 changed files with 618 additions and 243 deletions
+22 -32
View File
@@ -22,7 +22,8 @@
| Версия | Фазы | Суть |
|--------|------|------|
| **2.1.7-SAC** | Фаза 1 + выбранное из Фазы 4 | Права, `REPO_URL` обязателен, webhook, docs |
| **2.1.7-SAC** | Фаза 1 + выбранное из Фазы 4 | Права, `REPO_URL` обязателен, webhook, docs | ✅ реализовано |
| **2.1.8-SAC** | Дополнение к 1.1 | SAC-first update: hardening прав на каждом прогоне updater | ✅ реализовано |
| **2.2.0-SAC** | Фаза 2 | Manifest, pinned ref, без слепого `reset --hard` |
| **2.3.0-SAC** | Фаза 3 | Парсер конфига без `source` (minor breaking) |
@@ -34,9 +35,10 @@ Bump: `ssh-monitor` (`SSH_MONITOR_VERSION`) + `version.txt` в каждом ре
### 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`: предупреждение, если существующие пути с ослабленными правами
- [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`
@@ -44,42 +46,30 @@ Bump: `ssh-monitor` (`SSH_MONITOR_VERSION`) + `version.txt` в каждом ре
### 1.2 Проверка прав конфига при старте (C1, частично)
- [ ] Перед загрузкой конфига: если `/etc/ssh-monitor.conf` существует — проверить `root:root`, mode `600` или `400`
- [ ] **Поведение по умолчанию:** `WARN` в лог + stderr, работа продолжается (не ломать старые установки с `644`)
- [ ] Опционально в конфиге: `CONFIG_STRICT_PERMS=1`**exit 1** при нарушении
- [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) — **нужно утвердить завтра**
### 1.3 `BACKUP_WEBHOOK_URL` (H3) — **вариант A (soft-deprecate)**
**Что есть сейчас:** 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`
- [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** или позже, если подтвердим, что нигде не нужен
**Завтра решить:** 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 доменов — оператор сам выбирает зеркало
- [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 если есть
@@ -89,8 +79,8 @@ Bump: `ssh-monitor` (`SSH_MONITOR_VERSION`) + `version.txt` в каждом ре
### 1.5 Документация threat model
- [ ] `README.md` + этот файл: root-агент, доверие к `REPO_URL` и конфигу, санитайз GitHub
- [ ] Рекомендация: критичные хосты — только закрытое зеркало; автообновление осознанно
- [x] `README.md` + этот файл: root-агент, доверие к `REPO_URL` и конфигу, санитайз GitHub
- [x] Рекомендация: критичные хосты — только закрытое зеркало; автообновление осознанно
---
@@ -220,8 +210,8 @@ Bump: `ssh-monitor` (`SSH_MONITOR_VERSION`) + `version.txt` в каждом ре
| Пункт | Версия | Решение |
|-------|--------|---------|
| **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.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) |