15 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.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, mode600или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 unitEnvironment=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:
{
"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.txtvsmain - Миграция хостов 2.1.7: рассылка примера
Environment=REPO_URL=...для systemd / SAC - GPG: отложить до явного запроса
Открытые вопросы на завтра
- 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 версии.