Files

68 lines
7.6 KiB
Markdown
Raw Permalink Normal View 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. Откат
```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` в настройках оставлено выключенным, как было.