16 KiB
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 (отдельный 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.sh→apply_runtime_security_hardeningна каждом update (SAC, cron, вручную): retrofitchmod/chownсуществующих путей из конфига - В
--check-config: предупреждение, если существующие пути с ослабленными правами
Файлы: ssh-monitor, sac-client.sh, при необходимости update_ssh_monitor.sh
1.2 Проверка прав конфига при старте (C1, частично)
- Перед загрузкой конфига: если
/etc/ssh-monitor.confсуществует — проверитьroot:root, mode600или400 - Поведение по умолчанию:
WARNв лог + stderr, работа продолжается (не ломать старые установки с644) - Опционально в конфиге:
CONFIG_STRICT_PERMS=1→ exit 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 unitEnvironment=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:
UseSAC∈off|exclusive|dual|fallback;GIT_VERIFY_MODE∈off|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.7–2.1.9 — фаза 1 + SAC-first + watchdog server label
- Релиз 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
- Дефолт
GIT_REFв 2.2.0: тег изversion.txtvsmain - Миграция хостов: SAC-first update,
known_hosts,REPO_URLчерез SAC - Ingest mass-update backlog — см. security-alert-center
docs/ingest-mass-update-backlog.ru.md
Открытые вопросы на завтра
- 1.3 —
BACKUP_WEBHOOK_URL: soft-deprecate (A), удалить сразу (B), оставить (C)? - 1.2 — достаточно WARN по умолчанию или сразу strict на новых установках?
- 2.1 — дефолт
GIT_REF: тегversion.txtили веткаmain? - Подтверждение: ни на одном хосте не используется Slack/Discord webhook через
BACKUP_WEBHOOK_URL?
Документ создан для ревью. После утверждения — работа по фазам в отдельных коммитах с bump версии.