Files
OpenVPN-Monitoring-Simple/DOCS/Operations/IMPLEMENTATION-02-privilege-separation.md
T
iclaoudezinandClaude Sonnet 5.5 3836049230 Docs: add operations records (SUMMARY, Hysteria chain manifest, reboot test, plans)
- DOCS/Operations: current-state SUMMARY of the ENTRY deployment (access,
  install, egress via Hysteria2, security findings and fixes, risk
  assessment, commits/backups, open items), the Hysteria chain manifest
  with an OpenVPN Monitor section, reboot test results, and the plan and
  rollout records for settings validation and privilege separation.
- Link them from README and DOCS/General/Index.md.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-09-30 12:59:55 +00:00

7.6 KiB
Raw Blame History

Артефакт внедрения: п.2 — API без root + root-помощник

Дата: 2026-09-30. Узел: 213.226.125.13 (Alpine, OpenRC). Приложение: /opt/OpenVPN-Monitoring-Simple. План: PLAN-validation-and-privilege-drop.md. Коммит в репозитории приложения на узле: 6f9e800 («Run API services unprivileged; add root helper…»; после --amend, ранее упоминался как 10020ca), дополнение по CRL — 9ffdbfa, пуш не выполнялся. Полное описание: DOCS/Changes/2026-09-30_Privilege_Separation.md.

1. Что внедрено

Компонент Расположение Суть
Пользователь ovpmon (uid 109, nologin) владелец данных: /var/lib/ovpmon, /var/log/ovpmon, easy-rsa, client-config, логи, __pycache__
OpenRC /etc/init.d/ovpmon-{api,gatherer,profiler} command_user="ovpmon:ovpmon"; /etc/ovpmon/env остаётся root 600
Помощник /usr/local/sbin/ovpmon-helper (root, 755) install-config (allowlist директив, атомарная установка) и service start|stop|restart|status
doas /etc/doas.d/ovpmon.conf ровно 5 команд помощника, без других аргументов
Код Profiler services/process.py, routers/server.py без root: staging → помощник; ветки root/контейнера не менялись

2. Порядок внедрения

  1. Бэкап: /root/backup-p2-2026-09-30-1229.tar, /root/app-bak/p2/ (init-скрипты, process.py, server.py).
  2. Создание ovpmon, установка помощника и doas-правил; проверка помощника от имени ovpmon (сервисы ещё root).
  3. Патч process.py / server.py (+ except HTTPException: raise, чтобы отказ 400 не превращался в 500).
  4. chown данных, command_user в init-скриптах; перезапуск gatherer → api → profiler с проверкой каждого.
  5. Функциональные и негативные тесты; рестарт OpenVPN через API.
  6. Документация и коммит (исправлен эпизод с порчей Deployment_Native.md: восстановлен из предыдущего коммита и переделан, коммит переписан --amend, пуша не было).

3. Доказательства

Проверка Результат
Процессы gunicorn, uvicorn, gatherer — ovpmon; root остались только supervise-daemon
Помощник принимает рабочий server.conf да, файл в /etc/openvpn не изменился (sha256 совпал)
17 инъекций директив, отсутствие user nobody, cert вне PKI, CR, симлинк staged-файла все отклонены, боевой конфиг не тронут
doas: произвольная команда, другие аргументы помощника Operation not permitted
ovpmon не может: читать /etc/shadow, /root, /etc/hysteria/*.yaml, /etc/ovpmon/env; писать в /etc/openvpn, /etc/init.d, authorized_keys, код приложения; запускать iptables все отказы
API: мониторинг, config, process stats, server/configure (конфиг идентичен), создание/скачивание/отзыв профиля всё 200
Рестарт OpenVPN через API (helper) 200; OpenVPN и tun0 подняты, статус-лог читается gatherer, ip rule 102 и MASQUERADE на месте, ошибок в логе gatherer нет

Побочный эффект тестов: один рестарт OpenVPN (~секунды разрыва VPN-сессий; клиент переподключается сам).

4. Откат

cp /root/app-bak/p2/ovpmon-{api,gatherer,profiler} /etc/init.d/
chown -R root:root /var/lib/ovpmon /var/log/ovpmon /opt/OpenVPN-Monitoring-Simple/APP_PROFILER/{easy-rsa,client-config}
for s in ovpmon-gatherer ovpmon-api ovpmon-profiler; do rc-service $s restart; done

(помощник и doas-правила можно оставить; код Profiler при root работает по прежней ветке).

5. Оговорки

  • crl_verify: ограничение снято (2026-09-30, коммит 9ffdbfa) — см. раздел 6 ниже.
  • Проверка путей PKI в помощнике выполняется на момент установки; каталог PKI принадлежит ovpmon — держать его только за этим пользователем.
  • Скрипты подключения по-прежнему требуют /etc/openvpn/scripts/ (root, 755) — каталога нет по умолчанию.

6. Дополнение: публикация CRL для crl_verify (2026-09-30)

Проблема: OpenVPN после старта работает от nobody, а pki/ создаётся с правами 700 (владелец ovpmon), поэтому при включённом crl_verify все клиенты получили бы отказ. Решение: новая команда помощника publish-crl (6-е правило doas) копирует pki/crl.pem в /etc/openvpn/crl.pem (root:root 644) с проверками (файл внутри PKI, не симлинк, владелец ovpmon, ≤ 1 МиБ, PEM-маркеры и разбор openssl crl). Вызывается после gen-crl (init/revoke), при server/configure и при каждом service start|restart; ошибка публикации при отзыве возвращается как ошибка API. Генератор без root пишет crl-verify /etc/openvpn/crl.pem, помощник разрешает для crl-verify только этот путь.

Проверка (отдельный временный экземпляр OpenVPN на другом порту и подсети, от nobody, тот же PKI и CRL; боевой OpenVPN не перезапускался):

Проверка Результат
publish-crl от ovpmon; права копии; чтение nobody ok; root:root 644; читается; исходный pki/crl.pem для nobody по-прежнему недоступен
мусорный файл / поддельные PEM-маркеры / симлинк вместо CRL отклонены
crl_verify=true через API + server/configure 200, в конфиге crl-verify /etc/openvpn/crl.pem, помощник принял; настройка возвращена, боевой server.conf идентичен исходному
валидный клиент при активной проверке CRL подключается
отзыв через API и повторное подключение certificate revoked, клиент отклонён, ошибок чтения CRL нет

Побочное: в списке профилей остались записи со статусом revoked от тестов (ok-user1, p2test-*, crl-e2e-*). Тестовый экземпляр и файлы удалены, боевой OpenVPN (pid 8884) и правила выхода через hel не затронуты. Значение crl_verify в настройках оставлено выключенным, как было.