- 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>
69 lines
7.6 KiB
Markdown
69 lines
7.6 KiB
Markdown
# Артефакт внедрения: п.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. Откат
|
||
|
||
```sh
|
||
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` в настройках оставлено выключенным, как было.
|