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>
This commit is contained in:
1 parent
e1146ed4fe
commit
3836049230
9 files changed
+659
No files matched your search
@@ -10,6 +10,9 @@ Welcome to the documentation for the OpenVPN Monitor suite.
|
|||||||
- [Nginx Configuration](Nginx_Configuration.md)
|
- [Nginx Configuration](Nginx_Configuration.md)
|
||||||
- [Security Architecture](Security_Architecture.md): Details on Authentication, 2FA, and Security features.
|
- [Security Architecture](Security_Architecture.md): Details on Authentication, 2FA, and Security features.
|
||||||
|
|
||||||
|
## 🗂 Operations
|
||||||
|
- [Deployment records](../Operations/README.md): state summary, reboot test, plans and rollout records.
|
||||||
|
|
||||||
## 🛠 Changes and results
|
## 🛠 Changes and results
|
||||||
- [Security hardening (2026-09-30)](../Changes/2026-09-30_Security_Hardening.md)
|
- [Security hardening (2026-09-30)](../Changes/2026-09-30_Security_Hardening.md)
|
||||||
- [Admin username change (2026-09-30)](../Changes/2026-09-30_Admin_Username_Change.md)
|
- [Admin username change (2026-09-30)](../Changes/2026-09-30_Admin_Username_Change.md)
|
||||||
|
|||||||
@@ -0,0 +1,168 @@
|
|||||||
|
# Hysteria 2 entry→exit chain (manifest)
|
||||||
|
|
||||||
|
Client ─hy2/UDP443→ **ENTRY** (Alpine, OpenRC) ─hy2/UDP443→ **EXIT** (Debian, systemd) → Internet (IPv4)
|
||||||
|
|
||||||
|
Secrets live only in: `client-uris.txt`, `singbox-*.json`, `/etc/hysteria/*` on hosts (chmod 600). Never commit/print them.
|
||||||
|
|
||||||
|
## Inventory (this deployment)
|
||||||
|
| Role | Host | OS | Access |
|
||||||
|
|---|---|---|---|
|
||||||
|
| ENTRY (Timeweb) | 213.226.125.13 | Alpine 3.23 | `ssh -i private.key root@` (key must be chmod 600) |
|
||||||
|
| EXIT (Gcore Hel) | 85.234.86.140 | Debian 13 | `ssh -i hpriv.key debian@` (sudo NOPASSWD) |
|
||||||
|
|
||||||
|
## Prerequisites (verify first, they were the real blockers)
|
||||||
|
1. **UDP 443 open both ways** (Security Group on EXIT; ENTRY provider). Test: `tcpdump -ni <if> udp port 443` on the receiver while `echo x | nc -u -w1 <ip> 443` from the sender. ICMP/TCP passing proves nothing about UDP.
|
||||||
|
2. Use **official** binary `apernet/hysteria` (HyNetworks/hysteria is a fork). Verify `sha256sum` against `hashes.txt` of the release:
|
||||||
|
`https://github.com/apernet/hysteria/releases/latest/download/{hysteria-linux-amd64,hashes.txt}`
|
||||||
|
3. Hysteria server `socks5`/`http` outbounds are **TCP-only** → for UDP use TUN + policy routing (below), not a SOCKS5 chain.
|
||||||
|
|
||||||
|
## EXIT (Debian) — server
|
||||||
|
- `/usr/local/bin/hysteria`, `/etc/hysteria/{server.crt,server.key(600),config.yaml}`
|
||||||
|
- Self-signed cert: `openssl req -x509 -nodes -newkey ec -pkeyopt ec_paramgen_curve:prime256v1 -days 3650 -subj /CN=<name> -keyout server.key -out server.crt`
|
||||||
|
- `config.yaml`: `listen: :443`, `tls{cert,key}`, `auth{type: password, password: <rand>}`, `masquerade{type: proxy, proxy{url: https://news.ycombinator.com/, rewriteHost: true}}`
|
||||||
|
- systemd unit `hysteria-server` (`ExecStart=hysteria server -c ...`, `Restart=always`, `AmbientCapabilities=CAP_NET_BIND_SERVICE`)
|
||||||
|
- `/etc/sysctl.d/99-quic.conf`: `net.core.rmem_max=7500000`, `net.core.wmem_max=7500000`
|
||||||
|
- No IPv6 uplink here → IPv4 only.
|
||||||
|
|
||||||
|
## ENTRY (Alpine) — tunnel client + client-facing server
|
||||||
|
Packages: `iproute2 libcap-utils openssl`; user `hyedge` (`adduser -S -D -H -s /sbin/nologin`); `/dev/net/tun` must exist and module `tun` must be in `/etc/modules` (loaded module, not builtin; without it the tunnel does not come up after reboot).
|
||||||
|
|
||||||
|
1. **`/etc/hysteria/client.yaml`** (600): `server: <EXIT>:443`, `auth: <pw>`, `tls{sni, insecure: true, pinSHA256: <EXIT cert fp>}`, `lazy: true`, `bandwidth{up: 180 mbps, down: 180 mbps}` (Brutal CC; measured path ≈191-195 Mbps via `hysteria speedtest`, single-flow without it was ~44 Mbps), `socks5{127.0.0.1:1080}`, `http{127.0.0.1:8080}`, `tun{name: hytun, mtu: 1400, timeout: 5m, address{ipv4: 100.100.100.101/30}}` (no `route:` → no auto-routes).
|
||||||
|
2. **`/etc/hysteria/edge.yaml`** (600, owner hyedge): `listen: :443`, own self-signed `edge.crt/edge.key`, `auth{type: userpass, userpass{<dev>: <pw>}}`, `resolver{type: udp, udp{addr: 1.1.1.1:53}}`, `outbounds[direct{mode: "4"}]`, `trafficStats{listen: 127.0.0.1:9999, secret}`, `acl.inline`: `reject` 10/8,172.16/12,192.168/16,127/8,169.254/16,100.64/10 then `direct(all)`.
|
||||||
|
- Do **not** add `reject(::/0)` — it matches any domain having AAAA and rejects dual-stack sites.
|
||||||
|
- DoH resolver timed out via tun; UDP resolver works.
|
||||||
|
3. Binary copy for low port: `cp hysteria hysteria-edge; setcap cap_net_bind_service=+ep /usr/local/bin/hysteria-edge`.
|
||||||
|
4. **OpenRC** (`supervisor=supervise-daemon`, `respawn_delay=3`, `respawn_max=0`):
|
||||||
|
- `hysteria` (client, root) ← `need net`
|
||||||
|
- `hysteria-route` ← `need hysteria`; waits for `hytun`, then:
|
||||||
|
```
|
||||||
|
ip route replace blackhole default metric 1000 table 100 # anti-leak fallback
|
||||||
|
ip route replace default dev hytun metric 10 table 100
|
||||||
|
ip rule add priority 100 ipproto udp sport 443 lookup main # edge replies bypass tun
|
||||||
|
ip rule add priority 101 uidrange <uid hyedge> lookup 100
|
||||||
|
```
|
||||||
|
- `hysteria-edge` (`command_user=hyedge`) ← `need net hysteria-route`
|
||||||
|
- `rc-update add hysteria/hysteria-route/hysteria-edge default`
|
||||||
|
5. **sysctl (critical):** `net.ipv4.conf.all.rp_filter=2` and `default=2` (`/etc/sysctl.d/99-hytun.conf`). With strict `1` the TUN SYN-ACKs are dropped → TCP via tun silently hangs.
|
||||||
|
6. Only uid `hyedge` traffic and, since 2026-09-30, the OpenVPN client subnet (`172.20.1.0/24`, rule 102, see below) enter the tunnel; SSH/root traffic untouched.
|
||||||
|
|
||||||
|
## OpenVPN Monitor & Profiler on ENTRY (added 2026-09-30)
|
||||||
|
ENTRY also runs the web suite for OpenVPN (native OpenRC services, no containers). OpenVPN clients exit through the same `hytun` tunnel to EXIT.
|
||||||
|
|
||||||
|
| Item | Value |
|
||||||
|
|---|---|
|
||||||
|
| Panel | `https://213.226.125.13:8088/` (self-signed TLS, login `tstark`, 2FA; password is set by the owner, not stored here) |
|
||||||
|
| OpenVPN | `udp/1194`, clients `172.20.1.0/24`, full tunnel |
|
||||||
|
| Services | `ovpmon-api`, `ovpmon-gatherer`, `ovpmon-profiler` (user `ovpmon`), `nginx`, `openvpn`, `ovpn-hytun`, `fail2ban` |
|
||||||
|
| Egress | `ip rule 102 from 172.20.1.0/24 → table 100` (hytun, blackhole fallback) + MASQUERADE on `hytun` + FORWARD DROP to any other interface (no leak) |
|
||||||
|
| Privileges | APIs run unprivileged; a root helper (`ovpmon-helper` via `doas`, 6 fixed commands) validates/installs `server.conf`, publishes the CRL and controls `openvpn` |
|
||||||
|
| Hardening | SSH key-only (`00-hardening.conf`), fail2ban (`sshd`, `sshd-ddos`, `ovpmon-login`), input validation, CORS restricted |
|
||||||
|
| Docs | [`SUMMARY-ovpn-monitor.md`](SUMMARY-ovpn-monitor.md) (state, results, security assessment, rollback), `IMPLEMENTATION-01/02-*.md`, `PLAN-validation-and-privilege-drop.md`; application repo `DOCS/` |
|
||||||
|
|
||||||
|
Check: `rc-status | grep -E "ovpmon|openvpn|ovpn-hytun|nginx|fail2ban"`, `ip rule | grep 102`, a VPN client on `https://ifconfig.me` shows the EXIT IP (85.234.86.140).
|
||||||
|
|
||||||
|
## Clients
|
||||||
|
- URI: `hy2://<user>:<pw>@<ENTRY>:443/?sni=<edge sni>&insecure=1&pinSHA256=<hex, no colons>#name` (strip `#name` when scripting pin extraction).
|
||||||
|
- Hiddify/NekoBox/Streisand/Shadowrocket: paste URI. sing-box (SFA/SFI, ≥1.12): JSON profile with `hysteria2` outbound, `password: "<user>:<pw>"`, embedded `tls.certificate` PEM (edge.crt), TUN inbound, DoH via outbound, `hijack-dns`, private + ENTRY IP → direct.
|
||||||
|
|
||||||
|
### Recommended client apps
|
||||||
|
Protocol: Hysteria 2 (QUIC/UDP 443). Server cert is self-signed, so the app must support `pinSHA256`/custom certificate — all apps below do via the URI/QR or sing-box JSON.
|
||||||
|
|
||||||
|
| Platform | App | Import | Notes |
|
||||||
|
|---|---|---|---|
|
||||||
|
| Android | **Hiddify** (primary) | hy2:// URI or QR | sing-box core, pin support, simplest |
|
||||||
|
| Android | NekoBox for Android | hy2:// URI or QR | alternative, more knobs |
|
||||||
|
| Android | SFA (sing-box) | JSON profile only | official sing-box client; use `profiles/singbox/*.json` |
|
||||||
|
| iOS | **Streisand** (primary) | hy2:// URI or QR | free |
|
||||||
|
| iOS | Shadowrocket | hy2:// URI or QR | paid, stable |
|
||||||
|
| iOS | SFI (sing-box) / Hiddify | JSON profile / URI | sing-box >= 1.12 required for the JSON |
|
||||||
|
| Desktop | Hiddify, sing-box CLI, `hysteria client` | URI / JSON / YAML | sing-box needs root/admin for TUN |
|
||||||
|
|
||||||
|
Required client settings (otherwise many sites fail or hang):
|
||||||
|
- **IPv6 off** (Hiddify: IPv6 mode = Disable / IPv4 only). EXIT has no IPv6; apps sending IPv6 literals get `no IPv4 address available`.
|
||||||
|
- Remote DNS via the tunnel (DoH `https://1.1.1.1/dns-query`); do not leak DNS outside.
|
||||||
|
- QUIC blocking is **not** needed (UDP is carried end to end).
|
||||||
|
- Verify: `ifconfig.me` shows the EXIT IP (85.234.86.140); `test-ipv6.com` shows no IPv6.
|
||||||
|
|
||||||
|
App availability/names vary by store and region and change over time; this list comes from the project's client testing (Hiddify confirmed working on a phone) and general knowledge, not a current store check. One profile per person.
|
||||||
|
|
||||||
|
## Profiles (20 client users)
|
||||||
|
`profiles/` (dir 700, files 600; contains secrets): `hy2/clientNN.txt` (hy2:// URI), `singbox/clientNN.json` (sing-box >=1.12), `qr/<user>.png` (QR of the hy2:// URI, 22 files incl. laptop/mobile; made with `qrencode -t PNG -s 8 -m 2 -l M`; decoding verified), `ALL-hy2.txt`, `INDEX.md` (no passwords). Mobile apps: scan QR -> imports hy2:// profile. Users `client01..client20` live in `edge.yaml` `auth.userpass` (backup `edge.yaml.bak4`; `laptop`, `mobile` kept). Wrong password -> client error `authentication error, HTTP status code: 404`.
|
||||||
|
Add/revoke a user: edit `auth.userpass` on ENTRY, `rc-service hysteria-edge restart` (all sessions reconnect within seconds), then regenerate/delete the profile files. Verified login for client01/10/20 via temp client (egress = EXIT IP).
|
||||||
|
|
||||||
|
## Verification checklist
|
||||||
|
```
|
||||||
|
su -s /bin/sh hyedge -c 'curl -4 -s https://ifconfig.me' # = EXIT ip (TCP via tun)
|
||||||
|
su -s /bin/sh hyedge -c 'nslookup example.com 1.1.1.1' # UDP via tun
|
||||||
|
ip route del default dev hytun table 100; <same curl must FAIL>; ip route replace default dev hytun metric 10 table 100
|
||||||
|
hysteria client (temp, socks 127.0.0.1:1081) -> curl -x socks5h://... https://ifconfig.me # = EXIT ip; http://10.0.0.1 rejected
|
||||||
|
```
|
||||||
|
Logs: ENTRY `/var/log/hysteria.log`, `/var/log/hysteria-edge.log`; EXIT `journalctl -u hysteria-server`.
|
||||||
|
|
||||||
|
## Tuning / diagnostics learned
|
||||||
|
- Path MTU ENTRY↔EXIT = 1500 (DF ping + tracepath); hytun mtu 1400 is safe; MTU is **not** the cause of site failures here.
|
||||||
|
- Capacity test: on EXIT temporarily add `speedTest: true` to config.yaml, restart, run `hysteria speedtest -c /etc/hysteria/client.yaml` on ENTRY, then remove it.
|
||||||
|
- `su hyedge -c curl <domain>` is ~4s slower (system resolver = provider DNS, unreachable via EXIT). Not a client issue: hysteria-edge uses its own `resolver` (1.1.1.1). Use `--resolve` or `-x socks5h` when benchmarking.
|
||||||
|
- Clients that connect to **IPv6 literals** get `no IPv4 address available` (EXIT has no IPv6). Fix on the client: IPv6 off / IPv4-only DNS strategy (Hiddify: IPv6 mode = disable). Server cannot fix it without EXIT IPv6.
|
||||||
|
- `UdpRcvbufErrors` rises at ~190 Mbps (quic-go requests 7 MB socket buffers; rmem_max already 7.5 MB) — cosmetic.
|
||||||
|
|
||||||
|
## Hysteria diagnostics (manual)
|
||||||
|
Version: v2.12.3. Replace IPs/paths if the layout differs. On ENTRY run as root.
|
||||||
|
|
||||||
|
### Service state
|
||||||
|
```
|
||||||
|
# ENTRY (OpenRC)
|
||||||
|
for s in hysteria hysteria-route hysteria-edge; do rc-service $s status; done
|
||||||
|
ss -lunp | grep -E ':443\b' # edge listener (hysteria-edge)
|
||||||
|
ss -unp | grep hysteria # client UDP socket to EXIT
|
||||||
|
tail -f /var/log/hysteria.log /var/log/hysteria-edge.log
|
||||||
|
# EXIT (systemd)
|
||||||
|
systemctl status hysteria-server --no-pager; ss -lunp | grep :443
|
||||||
|
journalctl -u hysteria-server -f # "client connected" / "TCP error"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Tunnel (ENTRY -> EXIT)
|
||||||
|
```
|
||||||
|
hysteria ping -c /etc/hysteria/client.yaml 1.1.1.1:443 # RTT of a connect via the tunnel
|
||||||
|
hysteria speedtest -c /etc/hysteria/client.yaml # needs `speedTest: true` on EXIT (remove afterwards)
|
||||||
|
ip -br a show hytun; ip rule | grep -E '^10[01]'; ip route show table 100
|
||||||
|
su -s /bin/sh hyedge -c 'curl -4 -s https://ifconfig.me' # must print the EXIT IP
|
||||||
|
tcpdump -ni hytun -c 20 # traffic entering the tun
|
||||||
|
tcpdump -ni eth0 'udp port 443 and host 85.234.86.140' # QUIC to EXIT (on EXIT: enp3s0, host 213.226.125.13)
|
||||||
|
```
|
||||||
|
- Client log `connected to server ... count: N` – N grows on each reconnect. `TUN UDP error ... EOF` right after an EXIT restart is normal.
|
||||||
|
- `rc-service hysteria status` = `crashed` and `connect error: timeout` -> UDP 443 path/Security Group problem (see prerequisites).
|
||||||
|
|
||||||
|
### Clients (Traffic Stats API on edge, enabled, 127.0.0.1 only)
|
||||||
|
Config block in `edge.yaml`: `trafficStats: {listen: 127.0.0.1:9999, secret: <in /etc/hysteria/.stats-secret, 600>}`.
|
||||||
|
```
|
||||||
|
S=$(cat /etc/hysteria/.stats-secret); A="Authorization: $S"; U=http://127.0.0.1:9999
|
||||||
|
curl -s -H "$A" $U/online # {"mobile":1} devices per user
|
||||||
|
curl -s -H "$A" $U/traffic # {"mobile":{"tx":..,"rx":..}} add ?clear=1 to reset counters
|
||||||
|
curl -s -H "$A" -H 'Accept: text/plain' $U/dump/streams # live streams: user, bytes, lifetime, req-addr
|
||||||
|
curl -s -H "$A" -X POST -d '["mobile"]' $U/kick # drop a user's sessions (they reconnect if creds still valid)
|
||||||
|
```
|
||||||
|
Without the header the API returns 401. To block a user permanently remove it from `auth.userpass` and restart `hysteria-edge`.
|
||||||
|
|
||||||
|
### Useful subcommands
|
||||||
|
```
|
||||||
|
hysteria share -c /etc/hysteria/client.yaml # hy2:// URI for a client config (prints the password!)
|
||||||
|
hysteria check-update # compare with the latest release
|
||||||
|
hysteria version
|
||||||
|
```
|
||||||
|
|
||||||
|
### Error -> meaning
|
||||||
|
| Log text | Meaning |
|
||||||
|
|---|---|
|
||||||
|
| `no IPv4 address available` (edge) | client asked for an IPv6 literal; EXIT has no IPv6 -> disable IPv6 on the client |
|
||||||
|
| `rejected` (edge) | ACL hit (private ranges) |
|
||||||
|
| `resolve error ... deadline exceeded` (edge) | edge `resolver` unreachable through the tunnel |
|
||||||
|
| `no certificate matches the pinned hash` | wrong `pinSHA256` (strip `#name` from the URI fragment) |
|
||||||
|
| `authentication failed` / 404 on auth | wrong `user:password` |
|
||||||
|
| curl via tun hangs, `tcpdump -i hytun` shows SYN-ACK from dst on hytun | strict `rp_filter` -> set `=2` |
|
||||||
|
|
||||||
|
## Known issues / notes
|
||||||
|
- Backups on ENTRY: `/etc/hysteria/*.bak*`. Remote shell: zsh does not word-split variables → use functions for ssh wrappers.
|
||||||
|
- ENTRY reboot tested (2026-09-30): all three services, hytun, rules, sysctl and tunnel came up by themselves; mobile client reconnected. EXIT reboot tested too: hysteria-server and sysctl came up, ENTRY client reconnected automatically (count 2).
|
||||||
|
- strongSwan/IKE to Gcore (62.112.222.168) is a separate, unresolved task (needs UDP 500/4500 reachability); `charon` started manually, not enabled at boot.
|
||||||
|
- SSH on ENTRY is key-only since 2026-09-30 (a cloud-init drop-in used to re-enable passwords; hardening lives in `/etc/ssh/sshd_config.d/00-hardening.conf`). A full ENTRY reboot test after the OpenVPN Monitor changes passed (2026-09-30, see `REBOOT-TEST.md`).
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
# Артефакт внедрения: п.1 — валидация настроек Profiler
|
||||||
|
|
||||||
|
Дата: 2026-09-30. Узел: 213.226.125.13 (Alpine, OpenRC). Приложение: `/opt/OpenVPN-Monitoring-Simple`. План: `PLAN-validation-and-privilege-drop.md`.
|
||||||
|
Коммит в репозитории приложения на узле: `05f44b9` («Profiler: validate server/PKI settings before they reach OpenVPN config»), пуш не выполнялся.
|
||||||
|
|
||||||
|
## 1. Что внедрено
|
||||||
|
|
||||||
|
| Слой | Файл | Суть |
|
||||||
|
|---|---|---|
|
||||||
|
| A | `APP_PROFILER/schemas.py` | валидаторы `SystemSettingsUpdate`, `PKISettingUpdate` (422) |
|
||||||
|
| B | `APP_PROFILER/services/validation.py` (новый), `services/generator.py` | `check_script`: скрипт только из `/etc/openvpn/scripts/`, root-owned, без записи group/other |
|
||||||
|
| C | `APP_PROFILER/services/generator.py`, `routers/server.py` | отказ при `\n`, `\r`, NUL, управляющих символах, `"`, `\`; `ValueError` → HTTP 400 |
|
||||||
|
|
||||||
|
## 2. Порядок внедрения
|
||||||
|
|
||||||
|
1. Бэкап изменяемых файлов: `/root/app-bak/p1/` (`schemas.py`, `generator.py`, `server.py`).
|
||||||
|
2. Добавлен `services/validation.py`; правки `schemas.py`, `generator.py`, `routers/server.py` (Python-патч с проверкой якорей `assert`).
|
||||||
|
3. Проверка импорта: `venv/bin/python -c "import schemas"`.
|
||||||
|
4. `rc-service ovpmon-profiler restart`.
|
||||||
|
5. Тесты (скрипт запускался на узле с локально выпущенным токеном, т.к. на учётке включена 2FA).
|
||||||
|
6. Документация: `DOCS/Changes/2026-09-30_Settings_Validation.md`, ссылки в `README.md`, `DOCS/General/Index.md`; коммит.
|
||||||
|
|
||||||
|
## 3. Доказательства (результаты тестов)
|
||||||
|
|
||||||
|
| Проверка | Результат |
|
||||||
|
|---|---|
|
||||||
|
| Круговой `PUT` текущих настроек server / pki | 200 / 200 |
|
||||||
|
| 17 вредоносных или невалидных значений | все 422 |
|
||||||
|
| 4 валидных изменения | все 200 |
|
||||||
|
| Скрипты: root 755 / group-writable / симлинк / нет файла / вне каталога / traversal / пусто | принят / отклонён / отклонён / отклонён / отклонён / отклонён / принят |
|
||||||
|
| Слой C при обходе схемы (5 инъекций) | все заблокированы, ничего не записано |
|
||||||
|
| Рендер текущих настроек против рабочего `server.conf` | идентичен |
|
||||||
|
| После тестов | настройки восстановлены и совпадают с исходными; `rc-status`: все сервисы `started` |
|
||||||
|
|
||||||
|
## 4. Откат
|
||||||
|
|
||||||
|
```sh
|
||||||
|
cd /opt/OpenVPN-Monitoring-Simple/APP_PROFILER
|
||||||
|
cp /root/app-bak/p1/schemas.py schemas.py
|
||||||
|
cp /root/app-bak/p1/generator.py services/generator.py
|
||||||
|
cp /root/app-bak/p1/server.py routers/server.py
|
||||||
|
rm services/validation.py
|
||||||
|
rc-service ovpmon-profiler restart
|
||||||
|
```
|
||||||
|
Либо `git revert 05f44b9` в репозитории.
|
||||||
|
|
||||||
|
## 5. Оговорки
|
||||||
|
- Каталога `/etc/openvpn/scripts/` по умолчанию нет (для теста создавался временно и удалён): перед включением скриптов создать `root:root 755`, файлы `755` от root.
|
||||||
|
- Валидаторы действуют на обновления; ранее сохранённые значения проверяются при рендере (слой C).
|
||||||
|
- Слой B/C защищает конфиг, но сервис по-прежнему работает от root — это закрывает пункт 2 (см. `IMPLEMENTATION-02-…` после выполнения).
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
# Артефакт внедрения: п.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` в настройках оставлено выключенным, как было.
|
||||||
@@ -0,0 +1,82 @@
|
|||||||
|
# План: валидация настроек (п.1) и запуск API не от root (п.2)
|
||||||
|
|
||||||
|
Дата: 2026-09-30. Узел: 213.226.125.13 (Alpine, OpenRC). Приложение: `/opt/OpenVPN-Monitoring-Simple`.
|
||||||
|
Основание: SUMMARY-ovpn-monitor.md, раздел 6.5 — администратор панели фактически равен root, т.к. Profiler работает от root, а настройки без проверки попадают в `server.conf`.
|
||||||
|
|
||||||
|
## Исходные факты
|
||||||
|
- `APP_CORE` (API, gatherer) root не нужен: читает `openvpn-status.log`, пишет свою SQLite-БД и лог.
|
||||||
|
- Profiler требует root только для: записи `/etc/openvpn/server.conf`, `rc-service openvpn`, чтения PKI-файлов самим OpenVPN.
|
||||||
|
- В коде уже есть fallback `staging/server.conf`.
|
||||||
|
- В шаблоне `script-security 2` стоит внутри `{% if user_defined_cdscripts %}`.
|
||||||
|
- `doas` установлен; конфигурация только `permit nopass :wheel`.
|
||||||
|
|
||||||
|
## Пункт 1. Валидация настроек (3 слоя)
|
||||||
|
|
||||||
|
**Слой A. Схемы (`APP_PROFILER/schemas.py`)** — валидаторы только на `*Update`-схемах (старые значения из БД не ломают `GET`).
|
||||||
|
|
||||||
|
| Поле | Правило |
|
||||||
|
|---|---|
|
||||||
|
| `port`, `management_port` | 1–65535 |
|
||||||
|
| `vpn_network`, `vpn_netmask` | `ipaddress`: сеть и корректная маска |
|
||||||
|
| `split_routes[]` | CIDR или «сеть маска», нормализация через `ipaddress` |
|
||||||
|
| `dns_servers[]` | IPv4/IPv6 через `ipaddress` |
|
||||||
|
| `public_ip` | IP или hostname по маске, без пробелов и кавычек |
|
||||||
|
| `management_interface_address` | только loopback |
|
||||||
|
| `tun_mtu` / `mssfix` | 576–9000 / 536–1500 |
|
||||||
|
| `connect_script`, `disconnect_script` | пусто или `^/etc/openvpn/scripts/[A-Za-z0-9_.-]{1,64}$` |
|
||||||
|
| PKI `fqdn_ca`, `fqdn_server` | маска как у `username` |
|
||||||
|
| PKI `easyrsa_dn` | `cn_only` или `org` |
|
||||||
|
| PKI `req_country` | `^[A-Z]{2}$` |
|
||||||
|
| PKI `req_province/city/org/ou` | `^[A-Za-z0-9 .,_-]{0,64}$` |
|
||||||
|
| PKI `req_email` | простая маска email |
|
||||||
|
| PKI `key_size` | 2048 / 3072 / 4096 |
|
||||||
|
| PKI сроки | целые в разумных пределах |
|
||||||
|
|
||||||
|
**Слой B. Скрипты (`routers/server.py`, до генерации):** `realpath` внутри `/etc/openvpn/scripts/`, файл существует, владелец root, нет прав записи для group/other.
|
||||||
|
|
||||||
|
**Слой C. Генератор (`services/generator.py`):** перед записью проверить, что ни одно значение не содержит `\n`, `\r`, `"`, `\`; иначе ошибка и файл не пишется.
|
||||||
|
|
||||||
|
Тесты: невалидные значения (перевод строки в DNS, `../` в скрипте, `/` в организации, порт 0) → 422; валидный набор → 200 и корректный `server.conf`.
|
||||||
|
|
||||||
|
## Пункт 2. Profiler и API не от root
|
||||||
|
|
||||||
|
Схема: пользователь `ovpmon` + маленький root-помощник, сам валидирующий конфиг (защита в глубину: инъекция в конфиг не даёт root даже при обходе слоёв A–C).
|
||||||
|
|
||||||
|
1. **Пользователь и права.** Системный `ovpmon`. Владелец: `/var/lib/ovpmon`, `/var/log/ovpmon`, `APP_PROFILER/{easy-rsa,client-config,staging,*.db,profiler.log}`, `APP_CORE/{*.db,*.log}`. `pki/`: каталоги 755, `crl.pem` 644, приватные ключи 600. Код и `venv` остаются root-owned. `/etc/ovpmon/env` остаётся root 600.
|
||||||
|
2. **OpenRC:** `command_user="ovpmon:ovpmon"` в `ovpmon-*`.
|
||||||
|
3. **Помощник `/usr/local/sbin/ovpmon-helper`** (root, 755, Python). Подкоманды без пользовательских аргументов:
|
||||||
|
- `install-config`: читает только `/var/lib/ovpmon/staging/server.conf` (один раз в память, защита от TOCTOU), проверяет allowlist директив (запрещены `up`, `down`, `plugin`, `route-up`, `tls-verify`, `auth-user-pass-verify`, `learn-address`, неизвестные; `client-connect/disconnect` и `script-security` — только со скриптом из `/etc/openvpn/scripts/` с проверкой владельца и прав), пишет `/etc/openvpn/server.conf` атомарно (root:root 644).
|
||||||
|
- `service start|stop|restart|status` только для `openvpn`.
|
||||||
|
4. **`doas`:** `/etc/doas.d/ovpmon.conf`, точные `args` для каждой подкоманды.
|
||||||
|
5. **Код Profiler:** на хосте `services/process.py` и `routers/server.py` вызывают `doas ovpmon-helper …`; ветка контейнера и `staging` не меняются.
|
||||||
|
6. **`APP_CORE`:** только смена владельца и `command_user`.
|
||||||
|
7. **Каталог скриптов:** `/etc/openvpn/scripts/` root:root 755, по умолчанию пустой.
|
||||||
|
|
||||||
|
### Порядок
|
||||||
|
1. Бэкап `/etc/openvpn`, БД, `/etc/init.d/ovpmon-*`, код, `easy-rsa/`.
|
||||||
|
2. Слои A–C + тесты, перезапуск Profiler.
|
||||||
|
3. Пользователь, `chown`, помощник, `doas`; ручная проверка помощника.
|
||||||
|
4. Переключить `process.py` и `server.py` на помощник.
|
||||||
|
5. Переключить init-скрипты на `command_user`, перезапускать по одному.
|
||||||
|
6. Обновить документацию и SUMMARY, отдельные коммиты.
|
||||||
|
|
||||||
|
### Проверка (п.2)
|
||||||
|
- `ps`: Profiler и API под `ovpmon`.
|
||||||
|
- От `ovpmon`: нет доступа к `/etc/shadow`, `/root`, `/etc/hysteria/*.yaml`, запись в `/etc/openvpn` запрещена.
|
||||||
|
- UI: Initialize PKI, генерация и запись конфига, Start/Stop/Restart OpenVPN, создание/скачивание/отзыв профиля, мониторинг.
|
||||||
|
- Инъекция директивы в `staging/server.conf` отвергается помощником, `/etc/openvpn/server.conf` не меняется.
|
||||||
|
- Прямые вызовы `doas` с другими аргументами отклоняются.
|
||||||
|
- OpenVPN-клиент подключается и выходит через hel.
|
||||||
|
- Не сломаны fail2ban, HTTPS 8088, `ip rule 102`.
|
||||||
|
|
||||||
|
## Риски
|
||||||
|
| Риск | Смягчение |
|
||||||
|
|---|---|
|
||||||
|
| Сервис не стартует из-за прав | бэкап, `chown` до переключения, откат правкой init-скриптов |
|
||||||
|
| Allowlist блокирует легальный конфиг | первый прогон в режиме проверки без записи; список директив из `server.conf.j2` |
|
||||||
|
| Ошибка в `doas` `args` | негативные тесты от `ovpmon`, запасная root-SSH-сессия |
|
||||||
|
| Потеря ключей PKI | копия `easy-rsa/` перед `chown` |
|
||||||
|
|
||||||
|
## Статус
|
||||||
|
- Пункт 1 — реализован (2026-09-30), см. SUMMARY раздел 6.6 и DOCS/Changes/2026-09-30_Settings_Validation.md.
|
||||||
|
- Пункт 2 — реализован (2026-09-30), см. IMPLEMENTATION-02-privilege-separation.md и DOCS/Changes/2026-09-30_Privilege_Separation.md.
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
# Operations records (deployment on ENTRY, Timeweb)
|
||||||
|
|
||||||
|
Records of one concrete deployment (host addresses, results, rollback). No passwords or keys are stored here.
|
||||||
|
|
||||||
|
| Document | Content |
|
||||||
|
|---|---|
|
||||||
|
| [SUMMARY-ovpn-monitor.md](SUMMARY-ovpn-monitor.md) | Current state, access, install, egress via Hysteria2, security findings/fixes, risk assessment, commits, backups, open items |
|
||||||
|
| [Hysteria_Chain_Manifest.md](Hysteria_Chain_Manifest.md) | Hysteria 2 entry-exit chain manifest, diagnostics, plus the OpenVPN Monitor section |
|
||||||
|
| [REBOOT-TEST.md](REBOOT-TEST.md) | Reboot tests (Hysteria chain; ENTRY after the OpenVPN Monitor deployment) |
|
||||||
|
| [PLAN-validation-and-privilege-drop.md](PLAN-validation-and-privilege-drop.md) | Plan: settings validation and running the API unprivileged |
|
||||||
|
| [IMPLEMENTATION-01-settings-validation.md](IMPLEMENTATION-01-settings-validation.md) | Rollout record: settings validation |
|
||||||
|
| [IMPLEMENTATION-02-privilege-separation.md](IMPLEMENTATION-02-privilege-separation.md) | Rollout record: privilege separation and CRL publishing |
|
||||||
@@ -0,0 +1,106 @@
|
|||||||
|
# Reboot test: ENTRY (Timeweb) and EXIT (Hel)
|
||||||
|
|
||||||
|
Date: 2026-09-30. Goal: confirm the Hysteria 2 chain recovers by itself after a reboot of each node, with no manual steps.
|
||||||
|
|
||||||
|
Nodes: ENTRY 213.226.125.13 (Alpine, OpenRC), EXIT 85.234.86.140 (Debian 13, systemd).
|
||||||
|
|
||||||
|
## Methodology
|
||||||
|
|
||||||
|
Order: ENTRY first, then EXIT. Each node is rebooted separately, so the other side is always up and its reconnect behavior is observable.
|
||||||
|
|
||||||
|
### 1. Pre-flight (before each reboot)
|
||||||
|
| Check | ENTRY | EXIT |
|
||||||
|
|---|---|---|
|
||||||
|
| Services enabled | `rc-update show default` (hysteria, hysteria-route, hysteria-edge) | `systemctl is-enabled hysteria-server` |
|
||||||
|
| Kernel module for TUN persists | `lsmod \| grep tun`, `grep -x tun /etc/modules` | not needed |
|
||||||
|
| sysctl persisted | `ls /etc/sysctl.d/` (99-hytun.conf, 99-quic.conf) | `ls /etc/sysctl.d/99-quic.conf` |
|
||||||
|
| Temporary test settings removed | n/a | `grep -c speedTest /etc/hysteria/config.yaml` = 0 |
|
||||||
|
|
||||||
|
Any gap found is fixed before rebooting.
|
||||||
|
|
||||||
|
### 2. Reboot
|
||||||
|
`sync`, then a delayed `reboot` / `systemctl reboot` (detached, so the SSH command returns). Wait 45 s, then poll SSH every 6 s (up to 20 tries) until `uptime` answers.
|
||||||
|
|
||||||
|
### 3. Post-boot verification
|
||||||
|
ENTRY:
|
||||||
|
1. `rc-service hysteria|hysteria-route|hysteria-edge status` → `started`.
|
||||||
|
2. `ls -l /dev/net/tun`, `ip -br a show hytun`.
|
||||||
|
3. `ip rule | grep -E '^10[01]'` and `ip route show table 100` → both rules, default via hytun, blackhole fallback.
|
||||||
|
4. `sysctl net.ipv4.conf.all.rp_filter net.core.rmem_max` → 2 and 7500000.
|
||||||
|
5. `ss -lunp | grep :443` → `hysteria-edge` listening.
|
||||||
|
6. Tunnel TCP: `su -s /bin/sh hyedge -c 'curl -4 -s https://ifconfig.me'` → must equal the EXIT IP.
|
||||||
|
7. Tunnel UDP: `su -s /bin/sh hyedge -c 'nslookup example.com 1.1.1.1'` → address returned.
|
||||||
|
8. Logs: `/var/log/hysteria.log`, `/var/log/hysteria-edge.log`; a real client (mobile) reconnecting is a bonus signal.
|
||||||
|
|
||||||
|
EXIT:
|
||||||
|
1. `systemctl is-active hysteria-server`, `ss -lunp | grep :443`.
|
||||||
|
2. `sudo /usr/sbin/sysctl net.core.rmem_max net.core.wmem_max` (`sysctl` is not in a non-root user's PATH).
|
||||||
|
3. `journalctl -u hysteria-server -b` → "server up and running" and "client connected" from ENTRY.
|
||||||
|
4. From ENTRY: client log shows a new `connected to server` (count incremented) and the `hyedge` curl test (step 6 above) returns the EXIT IP.
|
||||||
|
|
||||||
|
## Results
|
||||||
|
|
||||||
|
### ENTRY reboot (Timeweb)
|
||||||
|
| Check | Result |
|
||||||
|
|---|---|
|
||||||
|
| Pre-flight | **Gap found:** module `tun` loaded by hand but absent from `/etc/modules` → after reboot `/dev/net/tun` could be missing and the tunnel would not start. Fixed (`echo tun >> /etc/modules`) before rebooting. |
|
||||||
|
| SSH back | Yes, about 45-60 s after the reboot command |
|
||||||
|
| Services | hysteria, hysteria-route, hysteria-edge all `started` |
|
||||||
|
| TUN / routing | `/dev/net/tun` present, `hytun` up (100.100.100.101/30), rules 100 and 101 present, table 100: default dev hytun + blackhole |
|
||||||
|
| sysctl | `rp_filter=2`, `rmem_max=7500000` applied |
|
||||||
|
| Listener | `hysteria-edge` on UDP 443 |
|
||||||
|
| Tunnel TCP | `85.234.86.140` (EXIT IP) |
|
||||||
|
| Tunnel UDP | DNS answer received |
|
||||||
|
| Client | Hiddify (`mobile`) reconnected on its own right after boot |
|
||||||
|
| Noise in logs | A few dial timeouts to individual remote hosts (unreachable destinations, not a tunnel fault) |
|
||||||
|
|
||||||
|
Verdict: **PASS** (after the `/etc/modules` fix).
|
||||||
|
|
||||||
|
### EXIT reboot (Hel)
|
||||||
|
| Check | Result |
|
||||||
|
|---|---|
|
||||||
|
| Pre-flight | `hysteria-server` enabled, `99-quic.conf` present, `speedTest` absent from config (0) |
|
||||||
|
| SSH back | Yes, about 45-50 s after the reboot command |
|
||||||
|
| Service | `hysteria-server` `active`, listening on UDP 443 |
|
||||||
|
| sysctl | `rmem_max` and `wmem_max` = 7500000 |
|
||||||
|
| Journal | "server up and running" at boot, then "client connected" from 213.226.125.13 about 30 s later |
|
||||||
|
| ENTRY side | Client reconnected automatically (`connected to server`, `count: 2`); brief `TUN UDP error ... EOF` while EXIT was down; `hyedge` curl returned `85.234.86.140` |
|
||||||
|
|
||||||
|
Verdict: **PASS**.
|
||||||
|
|
||||||
|
## Conclusions
|
||||||
|
- Both nodes recover unattended; the ENTRY hysteria client (supervise-daemon, `lazy: true`) reconnects without restart when EXIT comes back.
|
||||||
|
- During an EXIT outage the anti-leak blackhole route in table 100 keeps `hyedge` traffic from leaving via ENTRY's own uplink (leak test done separately: removing the tun default route makes the test curl fail instead of exposing the ENTRY IP).
|
||||||
|
- Not covered: simultaneous reboot of both nodes, power-loss/unclean shutdown, and IKE/strongSwan (separate task, `charon` is not enabled at boot).
|
||||||
|
|
||||||
|
## ENTRY reboot after the OpenVPN Monitor deployment (2026-09-30)
|
||||||
|
|
||||||
|
Goal: confirm the whole ENTRY stack (Hysteria chain + OpenVPN Monitor, OpenVPN, egress rules, hardening) recovers by itself. Only ENTRY was rebooted; EXIT stayed up.
|
||||||
|
|
||||||
|
### Pre-flight
|
||||||
|
- `rc-update show default`: `hysteria`, `hysteria-route`, `hysteria-edge`, `ovpn-hytun`, `openvpn`, `ovpmon-api`, `ovpmon-gatherer`, `ovpmon-profiler`, `nginx`, `fail2ban`, `sshd`, `chronyd` enabled.
|
||||||
|
- `tun` in `/etc/modules`; sysctl files present (`ip_forward=1`, `rp_filter=2`); `sshd -t` and `doas -C /etc/doas.d/ovpmon.conf` valid.
|
||||||
|
- Baseline snapshot `/root/pre-reboot-snapshot.txt`: service states, `ip rule 100-102`, table 100, nat/FORWARD rules, listeners, process owners.
|
||||||
|
|
||||||
|
### Reboot and result
|
||||||
|
`sync`, detached `reboot`; SSH answered again after ~45 s. Post-boot snapshot `/root/post-reboot-snapshot.txt` is **identical** to the baseline.
|
||||||
|
|
||||||
|
| Check | Result |
|
||||||
|
|---|---|
|
||||||
|
| All services in `default` | started by themselves |
|
||||||
|
| API processes | gunicorn/uvicorn/gatherer run as `ovpmon` |
|
||||||
|
| `hytun`, `tun0`, `rp_filter=2`, `ip_forward=1` | present |
|
||||||
|
| `ip rule` 100/101/102, table 100 (hytun + blackhole), MASQUERADE to `hytun`, FORWARD DROP guard | restored by `hysteria-route` / `ovpn-hytun` |
|
||||||
|
| Tunnel TCP as `hyedge` | `85.234.86.140` (EXIT) |
|
||||||
|
| Hysteria client | reconnected to EXIT |
|
||||||
|
| Panel | `https://…:8088/` 200, profiler without token 401, HTTP→HTTPS 301, login `tstark` works |
|
||||||
|
| Helper/doas | `service status` and `process stats` OK; gatherer reads `openvpn-status.log` (`root:ovpmon 640` persisted) |
|
||||||
|
| `/etc/openvpn/crl.pem`, `/var/lib/ovpmon` ownership | intact |
|
||||||
|
| fail2ban | 3 jails active |
|
||||||
|
| SSH | `passwordauthentication no`, `permitrootlogin prohibit-password` |
|
||||||
|
| Errors in `ovpmon-*` logs since boot | none (older entries predate the boot) |
|
||||||
|
| Real VPN client | reconnected by itself ~3.5 min after boot (UDP client waits for its `keepalive 10 120` timeout); ~5 MB downloaded through the tunnel, MASQUERADE counters rose, leak guard `FORWARD DROP` counter stayed 0 |
|
||||||
|
|
||||||
|
### Notes
|
||||||
|
- Expected client gap after a server reboot is up to ~2-4 minutes for UDP OpenVPN clients (ping-restart), not a fault of the server.
|
||||||
|
- `openvpn-status.log` keeps `root:ovpmon 640` because the file persists on disk; if it is ever deleted, OpenVPN recreates it as `600` and the gatherer cannot read it until the next `ovpmon-helper service start|restart` (which fixes the mode). Not triggered by a reboot.
|
||||||
@@ -0,0 +1,169 @@
|
|||||||
|
# Summary: OpenVPN Monitor & Profiler на ENTRY (Timeweb) + выход через hysteria2 → hel
|
||||||
|
|
||||||
|
Актуализировано: 2026-09-30 (по всем доработкам). Узел: 213.226.125.13 (Alpine 3.23.6, OpenRC), доступ `ssh -i private.key root@213.226.125.13` (только по ключу).
|
||||||
|
Пароль администратора панели в документе **не хранится** (после развёртывания заменён владельцем; сгенерированный начальный пароль больше не действует). Документ не содержит секретов, но остаётся внутренним (`chmod 600`).
|
||||||
|
|
||||||
|
Связанные документы:
|
||||||
|
- Рядом в `DOCS/Operations/`: `Hysteria_Chain_Manifest.md` (манифест цепочки Hysteria + раздел про OpenVPN Monitor), `PLAN-validation-and-privilege-drop.md`, `IMPLEMENTATION-01-settings-validation.md`, `IMPLEMENTATION-02-privilege-separation.md`.
|
||||||
|
- В репозитории приложения на узле (`/opt/OpenVPN-Monitoring-Simple/DOCS/`): `General/Deployment_Docker.md`, `General/Deployment_Native.md`, `Changes/2026-09-30_*.md` (Security_Hardening, Admin_Username_Change, Settings_Validation, Privilege_Separation, Egress_via_Hysteria2), `General/privilege-separation/` (помощник и правила doas).
|
||||||
|
|
||||||
|
## 1. Текущее состояние
|
||||||
|
|
||||||
|
| Компонент | Состояние |
|
||||||
|
|---|---|
|
||||||
|
| Панель | `https://213.226.125.13:8088/` — nginx, TLS 1.2/1.3, самоподписанный сертификат |
|
||||||
|
| Учётная запись | логин `tstark` (не `admin`), 2FA включена владельцем, встроенной `admin/password` нет |
|
||||||
|
| Сервисы OpenRC (автозагрузка) | `ovpmon-api` (gunicorn, 127.0.0.1:5001), `ovpmon-gatherer`, `ovpmon-profiler` (uvicorn, 127.0.0.1:8000) — **от пользователя `ovpmon`**; `nginx`; `openvpn`; `ovpn-hytun`; `fail2ban` |
|
||||||
|
| OpenVPN | udp/1194, сеть клиентов 172.20.1.0/24, полный туннель, клиентский трафик уходит через hysteria2 на hel; сквозной тест реальным клиентом пройден |
|
||||||
|
| Root-помощник | `/usr/local/sbin/ovpmon-helper` + 6 правил `doas` (`/etc/doas.d/ovpmon.conf`) |
|
||||||
|
| SSH | только ключи (`PasswordAuthentication no`, `PermitRootLogin prohibit-password`, `MaxAuthTries 3`) |
|
||||||
|
| `crl_verify` | поддерживается (CRL публикуется помощником), в настройках сейчас **выключен** |
|
||||||
|
| Hysteria (entry/client, UDP 443, 1080, 8080, 9999) | не затрагивалась доработками |
|
||||||
|
|
||||||
|
## 2. Доступ к админке
|
||||||
|
|
||||||
|
| Параметр | Значение |
|
||||||
|
|---|---|
|
||||||
|
| URL | `https://213.226.125.13:8088/` (HTTP на этом порту → 301 на HTTPS) |
|
||||||
|
| Логин | `tstark` |
|
||||||
|
| Пароль / 2FA | задаются и меняются владельцем в UI: **Account** (Change Password, Change Username, Two-Factor Authentication) |
|
||||||
|
| Сертификат | самоподписанный EC P-256, SAN `IP:213.226.125.13, DNS:ovpmon`, до 2029-01-02, файлы `/etc/ovpmon/tls/ovpmon.{crt,key}` |
|
||||||
|
| SHA-256 отпечаток | `E1:7F:A1:C1:F9:E8:22:C0:32:59:82:EF:D2:D7:FC:75:F6:3D:7E:8E:48:FA:55:88:9F:69:4D:15:3E:2B:7B:58` |
|
||||||
|
|
||||||
|
- Проверка клиентом: `curl --cacert ovpmon.crt https://213.226.125.13:8088/` (скопировать `ovpmon.crt` с узла).
|
||||||
|
- Первая фаза уже выполнена (PKI инициализирован); далее в UI: генерация server.conf, запуск OpenVPN, профили клиентов.
|
||||||
|
- JWT-секрет: `/etc/ovpmon/env` (`root:root 600`).
|
||||||
|
- Аварийное восстановление (если таблица `users` пуста): временно задать `OVPMON_INITIAL_ADMIN_USER` / `OVPMON_INITIAL_ADMIN_PASSWORD` в `/etc/ovpmon/env`, `rc-service ovpmon-api restart`, затем удалить переменные и перезапустить сервис. Без переменных пользователь не создаётся (в логе ERROR).
|
||||||
|
|
||||||
|
## 3. Установка окружения (без контейнеров)
|
||||||
|
|
||||||
|
Приложение: `/opt/OpenVPN-Monitoring-Simple` (не `/opt/ovpn-bash-profiler` — там только bash-профайлер без веб-части). Docker-compose репозитория не используется; сервисы вынесены в OpenRC.
|
||||||
|
|
||||||
|
1. Пакеты: `apk add python3 py3-pip nginx nodejs npm openvpn easy-rsa iptables bash` (+ `fail2ban`, `doas` уже был).
|
||||||
|
2. Backend: venv в `APP_CORE` (+ `gunicorn`) и `APP_PROFILER`, зависимости из `requirements.txt`.
|
||||||
|
3. UI: `npm install && npm run build` в `APP_UI`, `dist/` → `/var/www/ovpmon` (предыдущая сборка: `/var/www/ovpmon.bak`).
|
||||||
|
4. Конфиг: `/etc/ovpmon/env` (600): общий JWT-секрет, пути БД `/var/lib/ovpmon/*.db`, путь `openvpn-status.log`, `OVPMON_CORS_ORIGINS=https://213.226.125.13:8088`. Каталоги: `/var/lib/ovpmon` (+ `staging/`), `/var/log/ovpmon`, `/var/log/openvpn`.
|
||||||
|
5. Сервисы (`supervisor=supervise-daemon`, `respawn_delay=3`, `command_user="ovpmon:ovpmon"`, окружение подгружается в `start_pre` из `/etc/ovpmon/env`).
|
||||||
|
6. nginx: `/etc/nginx/http.d/ovpmon.conf` — 8088 TLS; `/` статика UI, `/api/` → :5001, `/profiles-api/` → :8000/api/.
|
||||||
|
7. Для Alpine: каталог `APP_PROFILER/easy-rsa` копируется из `/usr/share/easy-rsa` вручную (в Docker это делает entrypoint); симлинк `/etc/openvpn/openvpn.conf → server.conf`, чтобы кнопка Start в UI (`rc-service openvpn`) работала.
|
||||||
|
8. Параметры OpenVPN (`/etc/openvpn/server.conf`, генерируется приложением): udp/1194, `server 172.20.1.0/24`, `tun-mtu 1400`, `mssfix 1360`, `redirect-gateway def1`, DNS 1.1.1.1 и 8.8.8.8, `user nobody`, `group nogroup`.
|
||||||
|
|
||||||
|
## 4. Трафик клиентов OpenVPN → hysteria2 → hel
|
||||||
|
|
||||||
|
```
|
||||||
|
OpenVPN client ─udp/1194→ tun0 (172.20.1.1/24)
|
||||||
|
→ ip rule 102: from 172.20.1.0/24 → table 100
|
||||||
|
→ table 100: default dev hytun (metric 10); blackhole default (metric 1000)
|
||||||
|
→ iptables nat POSTROUTING -s 172.20.1.0/24 -o hytun -j MASQUERADE
|
||||||
|
→ hytun (TUN клиента hysteria, 100.100.100.101/30, mtu 1400)
|
||||||
|
→ hysteria client ─hy2/QUIC UDP 443→ EXIT 85.234.86.140 → Интернет (IPv4)
|
||||||
|
```
|
||||||
|
|
||||||
|
OpenRC-сервис `ovpn-hytun` (`need hysteria-route`, `before openvpn`) ставит `ip rule 102`, MASQUERADE на `hytun`, TCP MSS clamp и анти-утечку `FORWARD -s 172.20.1.0/24 ! -o hytun -j DROP`, а также `net.ipv4.ip_forward=1` (`/etc/sysctl.d/98-forward.conf`). Правила идемпотентны, `stop` их снимает.
|
||||||
|
|
||||||
|
- Приоритеты: 100 — ответы `sport 443` в main, 101 — `uid hyedge`, 102 — подсеть OpenVPN; нужен `rp_filter=2`.
|
||||||
|
- Служебный трафик (SSH, порт 1194 OpenVPN) идёт мимо туннеля. При падении hysteria таблица 100 уходит в blackhole: клиенты теряют интернет, утечки нет. На EXIT изменений не потребовалось.
|
||||||
|
- Проверка: `ip rule | grep 102`, `iptables -t nat -S POSTROUTING`; клиент на `https://ifconfig.me` видит `85.234.86.140`; эталон на узле — `su -s /bin/sh hyedge -c "curl -4 https://ifconfig.me"`.
|
||||||
|
- Статус: TCP/UDP через `hytun` подтверждены на узле, сквозной тест с реальным VPN-клиентом пройден (1194/udp достижим снаружи).
|
||||||
|
|
||||||
|
## 5. HTTPS
|
||||||
|
|
||||||
|
nginx на 8088: TLS 1.2/1.3 (TLS 1.1 отклоняется), HSTS, `X-Content-Type-Options`, `X-Frame-Options: DENY`, `Referrer-Policy`, `Cache-Control: no-store`, `server_tokens off`, редирект HTTP→HTTPS (`error_page 497`), `limit_req` 5/мин на `POST /api/auth/login`. Бэкенды слушают только 127.0.0.1. Прежний HTTP-конфиг: `/root/ovpmon.conf.bak`. Не объявлять второй `ssl_session_cache shared:SSL` другого размера — конфликт с `nginx.conf`. Порт управления OpenVPN (management) не включён; при включении — только loopback.
|
||||||
|
|
||||||
|
## 6. Безопасность: находки и их устранение
|
||||||
|
|
||||||
|
| # | Критичность | Находка | Исправление | Проверка |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| 1 | Критично | SSH: пароль root по сети (drop-in `50-cloud-init.conf` возвращал `PasswordAuthentication yes`; уже шёл перебор — 113 неудачных попыток) | `/etc/ssh/sshd_config.d/00-hardening.conf`: пароли и kbd-interactive выключены, `PermitRootLogin prohibit-password`, `MaxAuthTries 3`, `LoginGraceTime 30`; бэкап `/root/sshd_config.bak` | вход по ключу ок, по паролю `Permission denied (publickey)` |
|
||||||
|
| 2 | Высоко | Path traversal в Profiler (`username` без валидации, сервис от root) | маска `^[A-Za-z0-9][A-Za-z0-9_.-]{0,63}$` в схеме, `validate_username()` в `pki.py`, проверки `realpath` в роутере и генераторе | `../../tmp/pwn`, `a/b`, `..`, `-x` → 422, файл не создан |
|
||||||
|
| 3 | Высоко | Обход 2FA: временный токен принимался всеми ручками | `token_required` (Flask) и `verify_token` (Profiler) отклоняют `is_2fa_pending`; принимает его только `verify-2fa` | temp-токен → 401 везде, полный токен → 200 |
|
||||||
|
| 4 | Средне | `enable_2fa` писал OTP и TOTP-секрет в лог | убрано из сообщения | в логе секретов нет |
|
||||||
|
| 5 | Средне | CORS `*` + credentials | origin из `OVPMON_CORS_ORIGINS` (по умолчанию только same-origin), для Profiler сужены методы/заголовки | чужой `Origin` не получает `Access-Control-Allow-Origin` |
|
||||||
|
| 6 | Средне | Нет защиты от перебора | fail2ban: jails `sshd`, `sshd-ddos`, `ovpmon-login` (401/429/503 на логине), IP администратора в `ignoreip` | jails активны, баны SSH-сканеров зафиксированы |
|
||||||
|
| 7 | Средне | Панель по HTTP | TLS в nginx (раздел 5) | HTTPS 200, TLS 1.1 отклонён, rate-limit 503 |
|
||||||
|
| 8 | Высоко | Инъекция в `server.conf` через настройки (админ-токен → root) | валидация настроек в 3 слоя (раздел 7) | 17 вредных значений → 422, обход схемы блокируется рендером |
|
||||||
|
| 9 | Высоко | Сервисы API работали от root | пользователь `ovpmon` + root-помощник (раздел 8) | процессы под `ovpmon`, инъекции директив отклоняются |
|
||||||
|
| 10 | Низко | Стандартное имя `admin`, встроенный `admin/password` | смена имени в приложении, дефолтная учётка удалена (раздел 9) | логин `tstark` 200, `admin` 401 |
|
||||||
|
| 11 | Функц. | `crl_verify` не работал бы без root (`nobody` не читает `pki/`) | публикация CRL в `/etc/openvpn/crl.pem` (раздел 8) | клиент проходит, отозванный отклоняется |
|
||||||
|
|
||||||
|
Проверено без замечаний: алгоритм JWT зафиксирован HS256, секрет — 32 случайных байта; SQL f-строки используют только внутренние имена таблиц/колонок; nginx работает под непривилегированным пользователем; порты 1194/udp и 443/udp открыты по назначению со своей аутентификацией.
|
||||||
|
|
||||||
|
## 7. Валидация настроек Profiler (п.1 плана)
|
||||||
|
|
||||||
|
Слои: **A** — схемы `SystemSettingsUpdate` / `PKISettingUpdate` (порты, подсеть+маска, маршруты, DNS, `public_ip`, management только loopback, MTU/MSS, пути скриптов, поля DN и FQDN для PKI, размер ключа, сроки); **B** — скрипты подключения только из `/etc/openvpn/scripts/` (root-владелец, без записи group/other, без симлинков наружу); **C** — генератор отказывается писать `server.conf`/`.ovpn`, если значение содержит `\n`, `\r`, NUL, управляющие символы, `"` или `\`.
|
||||||
|
Результат: 17 вредоносных значений → 422; 4 валидных изменения → 200; инъекции мимо схемы блокируются; рендер текущих настроек идентичен рабочему конфигу. Каталога `/etc/openvpn/scripts/` по умолчанию нет — создать `root:root 755`, файлы `755`, перед включением скриптов. Артефакт: `IMPLEMENTATION-01-settings-validation.md`.
|
||||||
|
|
||||||
|
## 8. API не от root: пользователь `ovpmon`, root-помощник, CRL (п.2 плана)
|
||||||
|
|
||||||
|
```
|
||||||
|
ovpmon-api / gatherer / profiler (user ovpmon)
|
||||||
|
└─ doas -n /usr/local/sbin/ovpmon-helper <фиксированные аргументы> (6 точных правил)
|
||||||
|
├─ install-config — проверка staged server.conf по allowlist директив, атомарная установка в /etc/openvpn
|
||||||
|
├─ publish-crl — копия pki/crl.pem → /etc/openvpn/crl.pem (root:root 644)
|
||||||
|
└─ service start|stop|restart|status — rc-service openvpn
|
||||||
|
```
|
||||||
|
|
||||||
|
- `ovpmon` владеет `/var/lib/ovpmon`, `/var/log/ovpmon`, `APP_PROFILER/{easy-rsa,client-config,profiler.log}`, логами и `__pycache__`; код и venv — root, только чтение. От root остались лишь supervise-daemon.
|
||||||
|
- Allowlist `install-config`: только директивы шаблона с проверенными аргументами (`dev tun`, `proto`, `port`, пути PKI, `server`, `log`, `status`, `push` трёх видов, `user nobody`, `group nogroup`, шифры, `client-connect/disconnect` только из `/etc/openvpn/scripts/` и т.д.); `up`, `down`, `plugin`, `route-up`, `tls-verify`, `setenv`, `config` и всё неизвестное отклоняются; файл читается один раз, только обычный файл `ovpmon`, ASCII, ≤ 64 КиБ.
|
||||||
|
- CRL: помощник копирует CRL после `gen-crl` (init/revoke), при `server/configure` и при каждом `start|restart`; для `crl-verify` разрешён только путь `/etc/openvpn/crl.pem`; ошибка публикации при отзыве возвращается ошибкой API.
|
||||||
|
- Проверено: 17 инъекций директив, отсутствие `user nobody`, cert вне PKI, CR, симлинк — всё отклонено, боевой конфиг не менялся; `doas` отказывает на любую иную команду; `ovpmon` не читает `/etc/shadow`, `/root`, `/etc/hysteria/*.yaml`, `/etc/ovpmon/env`, не пишет в `/etc/openvpn`, `/etc/init.d`, `authorized_keys`, код, не запускает `iptables`; API (мониторинг, конфиг, `server/configure`, создание/скачивание/отзыв профиля, рестарт OpenVPN) работает; CRL проверен на отдельном экземпляре OpenVPN от `nobody` (валидный клиент подключается, отозванный получает `certificate revoked`).
|
||||||
|
- Артефакт и откат: `IMPLEMENTATION-02-privilege-separation.md`.
|
||||||
|
|
||||||
|
## 9. Смена имени администратора
|
||||||
|
|
||||||
|
`POST /api/auth/change-username` (JWT + текущий пароль + OTP при 2FA; маска `^[A-Za-z][A-Za-z0-9_.-]{2,31}$`, запрещены `admin, administrator, root, user, test, guest`, уникальность без учёта регистра → 409, неверный пароль/OTP учитываются rate-limit); кнопка «Change Username» в Account; в 2FA-URI подставляется реальное имя; `ensure_default_admin` больше не создаёт `admin/password` (только из `OVPMON_INITIAL_ADMIN_*`); баг UI «в шапке всегда Admin» исправлен (синхронизация с `/user/me`, событие `ovpmon-user-changed`). JWT хранит `user_id`, поэтому смена имени сессии не рвёт. Бэкап БД: `/root/openvpn_monitor.db.bak-2026-09-30`.
|
||||||
|
Ручной тест владельца в UI (смена пароля, включение 2FA) пройден.
|
||||||
|
|
||||||
|
## 10. Оценка вероятности получения root (актуальная)
|
||||||
|
|
||||||
|
**Без учётных данных, извне — низкая.** SSH только по ключам + fail2ban; панель за TLS, rate-limit и fail2ban; все API за JWT; OpenVPN — mTLS + `tls-auth`; Hysteria — пароль на пользователя; служебные порты на loopback. Остаточный риск: неизвестные уязвимости OpenVPN/nginx/sshd/hysteria/ядра и отставание пакетов (актуальность индекса `apk` и ядра не проверялась).
|
||||||
|
|
||||||
|
**С учётными данными панели (пароль + 2FA / токен).** Раньше это давало root (через инъекцию в `server.conf` от root-процесса). Теперь: валидация настроек (слои A–C), API работает от `ovpmon`, а помощник не пропустит опасные директивы. Остаточные пути:
|
||||||
|
1. уязвимость в `ovpmon-helper`, `doas` или ядре;
|
||||||
|
2. включение скриптов подключения (исполняются от `nobody`, берутся только из root-каталога `/etc/openvpn/scripts/`);
|
||||||
|
3. `ovpmon` владеет PKI, включая ключ CA: компрометация API позволяет выпускать клиентские сертификаты (доступ к VPN), но не даёт root. Проверки путей PKI в помощнике выполняются на момент установки (теоретическое окно TOCTOU с подменой файла на симлинк до старта OpenVPN — влияет на работоспособность, не на выполнение кода).
|
||||||
|
|
||||||
|
Итог: цепочка «интернет → root» сводится к неизвестной уязвимости (0-day) в привилегированных компонентах; компрометация админа панели больше не эквивалентна root.
|
||||||
|
|
||||||
|
## 11. Репозиторий, резервные копии, откат
|
||||||
|
|
||||||
|
Репозиторий приложения на узле (`/opt/OpenVPN-Monitoring-Simple`, ветка `main`), коммиты **не запушены** (автор `iclaoudezin`, пуш за пользователем):
|
||||||
|
|
||||||
|
| Коммит | Содержание |
|
||||||
|
|---|---|
|
||||||
|
| `11c1b63` | смена имени, 2FA-исправления, валидация `username`, CORS, UI |
|
||||||
|
| `9b2882d` | README и документация (Docker/native, Changes) |
|
||||||
|
| `5de0501` | docker-compose: обязательный `JWT_SECRET`, seed админа, порты |
|
||||||
|
| `05f44b9` | валидация настроек Profiler |
|
||||||
|
| `6f9e800` | API не от root + root-помощник |
|
||||||
|
| `9ffdbfa` | публикация CRL для `crl_verify` |
|
||||||
|
| `e1146ed` | README приложения: раздел Security defaults, ссылки на `privilege-separation/` |
|
||||||
|
|
||||||
|
Не вошли в коммиты: `APP_CORE/config.ini` и `APP_UI/package-lock.json` (побочные правки приложения/npm), `venv/`, `easy-rsa/` и `client-config/` (приватные ключи — в `.gitignore` не добавлены, добавить отдельным коммитом).
|
||||||
|
|
||||||
|
Бэкапы на узле: `/root/backup-p2-2026-09-30-1229.tar` (`/etc/openvpn`, `/var/lib/ovpmon`, easy-rsa, init-скрипты, doas, код), `/root/openvpn_monitor.db.bak-2026-09-30`, `/root/ovpmon.conf.bak`, `/root/sshd_config.bak`, `/root/app-bak/` (файлы приложения по этапам, `README.md.orig`, `p1/ p2/ p3/`), `/var/www/ovpmon.bak`. Откат — в `IMPLEMENTATION-02-privilege-separation.md` и `DOCS/Changes/*`.
|
||||||
|
|
||||||
|
## 12. Открытые вопросы и рекомендации
|
||||||
|
|
||||||
|
- Пуш коммитов (учётные данные внешнего репозитория у пользователя).
|
||||||
|
- Тест перезагрузки ENTRY после доработок **пройден** (2026-09-30): снимок состояния до/после идентичен, все сервисы, правила выхода, hardening и панель поднялись сами; VPN-клиент переподключился сам через ~3,5 мин (ping-restart UDP-клиента), трафик идёт через `hytun`, утечек нет. Подробности — `REBOOT-TEST.md`, раздел «ENTRY reboot after the OpenVPN Monitor deployment». Нюанс: права `root:ovpmon 640` на `openvpn-status.log` сохраняются, пока файл на диске; при его удалении OpenVPN создаст файл с 600 до ближайшего `start|restart` через помощник.
|
||||||
|
- Самоподписанный сертификат: сверять отпечаток; при появлении домена — Let's Encrypt.
|
||||||
|
- Ограничить панель по IP или пускать через VPN; сократить срок жизни JWT (сейчас 8 ч) и отзывать токены при смене пароля/имени.
|
||||||
|
- Включить `crl_verify` в настройках, если нужна проверка отзыва (публикация CRL уже работает).
|
||||||
|
- Создать `/etc/openvpn/scripts/` (`root:root 755`) перед использованием скриптов подключения.
|
||||||
|
- В шаблоне клиентского `.ovpn` есть Windows-опция `windows-driver` — не подходит Linux-клиентам; при необходимости вынести в условие (шаблон не менялся).
|
||||||
|
- Регулярно `apk upgrade`, следить за advisory OpenVPN/nginx/OpenSSH/hysteria.
|
||||||
|
- В списке профилей остались записи `revoked` от тестов (`ok-user1`, `p2test-*`, `crl-e2e-*`).
|
||||||
|
|
||||||
|
## Журнал изменений (2026-09-30)
|
||||||
|
|
||||||
|
1. Развёртывание приложения без контейнеров (OpenRC, nginx :8088), инициализация PKI (фикс каталога `easy-rsa`).
|
||||||
|
2. Выход клиентов OpenVPN через `hytun` → hel (`ovpn-hytun`), сквозной тест пройден.
|
||||||
|
3. HTTPS на 8088, rate-limit, заголовки.
|
||||||
|
4. Аудит безопасности; SSH key-only, fail2ban, валидация `username`, CORS.
|
||||||
|
5. Смена имени администратора (данные + API + UI + без дефолтной учётки); исправление обхода 2FA и лога секретов; баг имени в шапке.
|
||||||
|
6. Документация и коммиты (README, Docker/native, Changes), актуализация `docker-compose.yml`.
|
||||||
|
7. Валидация настроек Profiler (п.1).
|
||||||
|
8. API не от root, root-помощник (п.2), публикация CRL.
|
||||||
|
9. Актуализация SUMMARY и README.
|
||||||
|
10. Тест перезагрузки ENTRY: пройден (`REBOOT-TEST.md`).
|
||||||
@@ -48,6 +48,7 @@ No default user is created. Seed the initial admin with `OVPMON_INITIAL_ADMIN_US
|
|||||||
|
|
||||||
- Index: [DOCS/General/Index.md](DOCS/General/Index.md)
|
- Index: [DOCS/General/Index.md](DOCS/General/Index.md)
|
||||||
- Deployment: [Docker](DOCS/General/Deployment_Docker.md) · [System services](DOCS/General/Deployment_Native.md) · [Nginx](DOCS/General/Nginx_Configuration.md) · [Service management](DOCS/General/Service_Management.md)
|
- Deployment: [Docker](DOCS/General/Deployment_Docker.md) · [System services](DOCS/General/Deployment_Native.md) · [Nginx](DOCS/General/Nginx_Configuration.md) · [Service management](DOCS/General/Service_Management.md)
|
||||||
|
- Deployment records (state, results, reboot test, plans): [DOCS/Operations](DOCS/Operations/README.md)
|
||||||
- Security model: [Security Architecture](DOCS/General/Security_Architecture.md) · root helper and doas rules: [`DOCS/General/privilege-separation/`](DOCS/General/privilege-separation/)
|
- Security model: [Security Architecture](DOCS/General/Security_Architecture.md) · root helper and doas rules: [`DOCS/General/privilege-separation/`](DOCS/General/privilege-separation/)
|
||||||
- APIs: [Monitoring](DOCS/Core_Monitoring/API_Reference.md) · [Profiler](DOCS/Profiler_Management/API_Reference.md)
|
- APIs: [Monitoring](DOCS/Core_Monitoring/API_Reference.md) · [Profiler](DOCS/Profiler_Management/API_Reference.md)
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user