Files
ssh-monitor/docs/security-roadmap.ru.md
T
PapaTramp 18a65078fc feat: safe config parser without source (2.3.0-SAC)
Whitelist-based /etc/ssh-monitor.conf load; validate URLs/enums/CIDR; remove BACKUP_WEBHOOK_URL.
2026-07-08 12:57:36 +10:00

16 KiB
Raw Blame History

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.0-SAC Фаза 3 Парсер конфига без source, валидация, удаление BACKUP_WEBHOOK
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): фаза 3 закрыта (2.3.0-SAC). Дальше — ingest backlog (SAC) и опционально GPG / GIT_REF=tag на проде.
Backlog по ingest при массовом update (SAC + ops): ingest-mass-update-backlog.ru.md (отдельный ToDo).


Фаза 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)
  • 2.1.8: update_ssh_monitor.shapply_runtime_security_hardening на каждом update (SAC, cron, вручную): retrofit chmod/chown существующих путей из конфига
  • В --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=1exit 1 при нарушении

Решение агента: strict выключен по умолчанию; в README рекомендовать chmod 600 и позже 400.


1.3 BACKUP_WEBHOOK_URL (H3) — вариант A (soft-deprecate)

  • В ssh-monitor.conf.example: закомментировать / убрать из «активного» блока, комментарий DEPRECATED: не используется в типовом деплое; будет удалён в 2.3.x
  • В README / docs/notifications.ru.md: пометить legacy / необязательно
  • В --check-config: если задан — WARN (удалён в 2.3.0)
  • Удаление кода2.3.0-SAC

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: основной путь обновления (кнопка в UI); cron/timer — опционально. Bootstrap SAC → --deploy
  • Не делать 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 пуст → GIT_BRANCH; GIT_VERIFY_MODE=off в скрипте, 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:
{
  "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 + 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:

  • 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

Файлы: ssh-monitor-perms.sh (ssh_monitor_config_load_file), ssh-monitor, ssh-monitor-watchdog


3.2 Миграция — решение агента

  • 2.3.0: парсер по умолчанию; source удалён
  • Синтаксис файла не меняется для пользователя — те же KEY="value"
  • В release notes: «поведение то же, выполнение bash из конфига невозможно»
  • ssh-monitor --check-config проверяет неизвестные/битые строки (WARN) и значения (ERROR)
  • Отдельная команда миграции не нужна (формат тот же)

3.3 Валидация значений — решение агента

  • URL: SAC_URL — http(s)://host
  • Enum: UseSACoff|exclusive|dual|fallback; GIT_VERIFY_MODEoff|tag|commit
  • Числа: существующие validate_numeric_or_default
  • IP/CIDR в WHITELIST_* — базовая проверка формата
  • Ошибки валидации → exit 1 на старте и в --check-config

3.x Удаление BACKUP_WEBHOOK (если утвердили soft-deprecate в 1.3)

  • Удалить код и документацию в 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 на годы)
  • Регулярные внешние аудиты

Чек-лист «перед стартом работы завтра»

  • Релиз 2.1.72.1.9 — фаза 1 + SAC-first + watchdog server label
  • Релиз 2.2.0 — scope: 2.12.3, 2.5, 4.5, 4.7(M7); 2.4 GPG отложить
  • Релиз 2.3.0 — scope: 3.13.3, удаление webhook
  • Дефолт GIT_REF в 2.2.0: тег из version.txt vs main
  • Миграция хостов: SAC-first update, known_hosts, REPO_URL через SAC
  • Ingest mass-update backlog — см. security-alert-center docs/ingest-mass-update-backlog.ru.md

Открытые вопросы на завтра

  1. 1.3BACKUP_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 версии.