diff --git a/DOCS/Changes/2026-09-30_Admin_Username_Change.md b/DOCS/Changes/2026-09-30_Admin_Username_Change.md new file mode 100644 index 0000000..87003a5 --- /dev/null +++ b/DOCS/Changes/2026-09-30_Admin_Username_Change.md @@ -0,0 +1,42 @@ +# Admin username change (2026-09-30) + +Goal: get rid of the well-known `admin` login and of the built-in `admin/password` account. + +## Design + +- The JWT carries `user_id`, not the name, and no other table references `users.username`, so a rename does not invalidate sessions. +- Changing the username requires the current password and, when 2FA is enabled, a valid OTP. Wrong password/OTP counts toward the login rate limit. + +## Changes + +| Area | Change | +|---|---| +| API (`APP_CORE/openvpn_api_v3.py`) | `POST /api/auth/change-username`: body `new_username`, `current_password`, optional `otp`. Rules: `^[A-Za-z][A-Za-z0-9_.-]{2,31}$`, reserved names rejected (`admin`, `administrator`, `root`, `user`, `test`, `guest`), case-insensitive uniqueness (409). Helper `_current_user_id()` | +| 2FA | `setup_2fa` puts the real username into the authenticator URI (was hardcoded `admin`) | +| Bootstrap | `ensure_default_admin` no longer creates `admin/password`. With an empty `users` table it creates a user only from `OVPMON_INITIAL_ADMIN_USER` / `OVPMON_INITIAL_ADMIN_PASSWORD`; otherwise it logs an error | +| UI (`Account.vue`) | "Change Username" button and modal (OTP field shown only if 2FA is on) | +| UI (`App.vue`) | Header name synced from `/user/me` on start and on route change, and on the `ovpmon-user-changed` event; fixed the stale/hardcoded `Admin` | + +## Existing installations + +Rename directly in the DB (stop nothing; sessions stay valid): + +```python +import sqlite3 +c = sqlite3.connect("/var/lib/ovpmon/openvpn_monitor.db") +c.execute("UPDATE users SET username=? WHERE username='admin'", ("",)); c.commit() +``` + +Take a DB backup first. Recovery when `users` is empty: temporarily set `OVPMON_INITIAL_ADMIN_USER/PASSWORD`, restart `ovpmon-api`, then remove the variables. + +## Verification + +| Check | Result | +|---|---| +| Login with new name / with `admin` | 200 / 401 | +| No token | 401 | +| `admin`, `root`, `ab`, `a/b`, `1abc` | 400 | +| Wrong current password | 401 | +| Rename to another valid name and back | 200, login with the new name works | +| Empty `users` without / with seed variables (DB copy) | no user / user created | +| Manual UI test (password change, 2FA enable) | passed; header-name bug found and fixed | diff --git a/DOCS/Changes/2026-09-30_Egress_via_Hysteria2.md b/DOCS/Changes/2026-09-30_Egress_via_Hysteria2.md new file mode 100644 index 0000000..4f0a4bf --- /dev/null +++ b/DOCS/Changes/2026-09-30_Egress_via_Hysteria2.md @@ -0,0 +1,48 @@ +# Egress of OpenVPN clients via a Hysteria2 tunnel (2026-09-30) + +Goal: all traffic of VPN clients leaves the internet through an exit node reached over an existing Hysteria2 client tunnel (`hytun`) on the VPN host. + +``` +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 of the Hysteria2 client) --QUIC/UDP 443--> exit node --> Internet +``` + +## Prerequisites + +- A running Hysteria2 client with a TUN device (`hytun`), a routing table (100) with `default dev hytun` and a `blackhole` fallback, and `rp_filter=2` (loose) on all/default/hytun (strict mode drops TUN replies). +- Hysteria server-side `socks5`/`http` outbounds are TCP-only, so UDP needs TUN + policy routing, not a SOCKS chain. +- The exit node needs no changes. + +## Implementation (OpenRC service `ovpn-hytun`) + +`depend: need hysteria-route; before openvpn`. On start: + +```sh +sysctl -qw net.ipv4.ip_forward=1 +ip rule add priority 102 from 172.20.1.0/24 lookup 100 +iptables -t nat -A POSTROUTING -s 172.20.1.0/24 -o hytun -j MASQUERADE +iptables -t mangle -A FORWARD -o hytun -p tcp --tcp-flags SYN,RST SYN -j TCPMSS --clamp-mss-to-pmtu # and -i hytun +iptables -A FORWARD -s 172.20.1.0/24 ! -o hytun -j DROP # anti-leak +``` + +Rules are idempotent (`-C` before `-A`); `stop` removes them. Adjust `172.20.1.0/24` to the `server` network in `server.conf`. Persist `ip_forward` in `/etc/sysctl.d/`. Use POSIX `sh` in OpenRC scripts (no bash arrays). + +## Properties + +- Only the VPN subnet enters the tunnel; SSH, OpenVPN's own port (1194) and management traffic use the main table. +- If the Hysteria client dies, table 100 falls to `blackhole`: clients lose internet, they do not leak through `eth0`. +- Clients get public DNS (e.g. 1.1.1.1) that is resolved through the same tunnel. + +## Verification + +| Check | Result | +|---|---| +| Node: TCP and UDP (DNS) through `hytun` | works | +| Reference (uid routed to `hytun`): external IP | exit node address | +| Real VPN client: `https://ifconfig.me` | exit node address (end-to-end test passed) | +| `ip rule`, `iptables -t nat -S POSTROUTING`, `rc-status` | rules present, services started | + +Testing tip: a network namespace test that reuses the VPN subnet conflicts with a live `tun0`; test with a real client or a different, temporary subnet added to the rules. diff --git a/DOCS/Changes/2026-09-30_Security_Hardening.md b/DOCS/Changes/2026-09-30_Security_Hardening.md new file mode 100644 index 0000000..1d24285 --- /dev/null +++ b/DOCS/Changes/2026-09-30_Security_Hardening.md @@ -0,0 +1,28 @@ +# Security hardening (2026-09-30) + +Scope: a native (OpenRC) deployment of this suite on an internet-facing host. Findings from a review of exposed services, the fixes and their verification. + +## Findings and results + +| # | Severity | Finding | Fix | Result | +|---|---|---|---|---| +| 1 | Critical | SSH accepted passwords for `root` (`PasswordAuthentication yes`, cloud-init drop-in overrode the main config); the host was already being brute-forced | `/etc/ssh/sshd_config.d/00-hardening.conf`: `PasswordAuthentication no`, `KbdInteractiveAuthentication no`, `PermitRootLogin prohibit-password`, `MaxAuthTries 3`, `LoginGraceTime 30` | key login works; password login → `Permission denied (publickey)` | +| 2 | High | Path traversal in Profiler: `username` was an unvalidated string used in `client-config/.ovpn` and as an `easyrsa` argument, service runs as root | pattern `^[A-Za-z0-9][A-Za-z0-9_.-]{0,63}$` in `schemas.py`; `validate_username()` in `services/pki.py`; realpath containment checks in `routers/profiles.py` and `services/generator.py` | `../../tmp/pwn`, `a/b`, `..`, `-x` → 422, no file created; valid profile create/revoke works | +| 3 | High | 2FA bypass: the temporary token issued after the password step was accepted by every protected route | `token_required` (Flask) and `verify_token` (Profiler) reject tokens with `is_2fa_pending`; only `/api/auth/verify-2fa` uses them | temp token → 401 on `/api/v1/user/me`, `/profiles-api/config`, `change-username`; full token → OK | +| 4 | Medium | `enable_2fa` logged the OTP and TOTP secret | log line reduced to "Attempting 2FA activation" | no secrets in logs | +| 5 | Medium | CORS `*` with credentials on both APIs | allowed origin restricted to the panel origin; Profiler methods/headers narrowed | foreign `Origin` gets no `Access-Control-Allow-Origin` | +| 6 | Medium | No brute-force throttling outside the app rate limit | fail2ban jails `sshd`, `sshd-ddos`, `ovpmon-login` (401/429/503 on `POST /api/auth/login`); admin IP in `ignoreip` | jails active, bans observed for SSH scanners | +| 7 | Medium | Panel served over plain HTTP | Nginx TLS on the panel port (TLS 1.2/1.3, HSTS, `nosniff`, `X-Frame-Options`, `no-store`, login `limit_req` 5 r/min, HTTP→HTTPS redirect via `error_page 497`) | HTTPS 200, TLS 1.1 rejected, rate limit returns 503 | + +Checked, no issue: JWT algorithm pinned to HS256, random 32-byte secret; SQL f-strings only use internal table/column names; backends bound to `127.0.0.1`; Nginx workers unprivileged. + +## Residual risks + +- Self-signed certificate: verify the fingerprint on first visit; use a CA-issued certificate when a domain exists. +- The APIs run as root; split privileged OpenVPN/PKI operations into a separate helper. +- Enable 2FA for the admin account. +- Changes were applied in place on the host; keep them in the repository (see the commit "Harden auth and API"). + +## Rollback + +Backups were taken on the host before each change: `sshd_config`, `ovpmon.conf` (Nginx), application files in `/root/app-bak/`, previous UI build `/var/www/ovpmon.bak`. diff --git a/DOCS/General/Deployment.md b/DOCS/General/Deployment.md index eb5d6ee..e3ed7ce 100644 --- a/DOCS/General/Deployment.md +++ b/DOCS/General/Deployment.md @@ -105,6 +105,6 @@ server { ## 5. First Run & Initialization 1. Access the UI via browser. -2. Login with default credentials: `admin` / `password`. +2. Sign in with the admin seeded via OVPMON_INITIAL_ADMIN_USER / OVPMON_INITIAL_ADMIN_PASSWORD (no built-in default user exists). 3. **Immediately** change the password and set up 2FA in the Settings/Profile section. 4. If using the Profiler, ensure the `easy-rsa` directory is present and initialized via the UI. diff --git a/DOCS/General/Deployment_Docker.md b/DOCS/General/Deployment_Docker.md new file mode 100644 index 0000000..fbc33d2 --- /dev/null +++ b/DOCS/General/Deployment_Docker.md @@ -0,0 +1,45 @@ +# Deployment: Docker + +Uses `docker-compose.yml` from the repository root. + +## Services + +| Service | Container | Ports | Notes | +|---|---|---|---| +| `app-ui` | `ovp-ui` | 80 | Nginx + built UI; proxies `/api/` and `/profiles-api/` | +| `app-api` | `ovp-api` | 5001 | Flask monitoring API | +| `app-gatherer` | `ovp-gatherer` | - | Parses `openvpn-status.log` | +| `app-profiler` | `ovp-profiler` | 8000, 1194/udp | FastAPI + OpenVPN; needs `NET_ADMIN` and `/dev/net/tun` | + +Volumes: `ovp_logs`, `ovp_config`, `ovp_pki`, `ovp_client_config`, `db_data`. + +## Steps + +1. Set a random JWT secret and the initial admin (compose reads them from the environment / `.env`): + ```bash + cat > .env < + OVPMON_INITIAL_ADMIN_PASSWORD= + EOT + chmod 600 .env + ``` + Pass the two `OVPMON_INITIAL_ADMIN_*` variables to `app-api` (`environment:`) for the first start. +2. `docker-compose up -d --build` +3. Open `http://`, sign in, **PKI Configuration → Initialize PKI**. +4. Remove `OVPMON_INITIAL_ADMIN_*` from `.env` and recreate `app-api`. + +## Hardening + +- Publish only what is needed: in production drop the `5001:5001` and `8000:8000` mappings (Nginx already reaches them on `ovp-net`). +- Terminate TLS in front of `app-ui` (reverse proxy or mount a cert into the UI container); see [Nginx configuration](Nginx_Configuration.md). +- `ovp-profiler` is privileged (`NET_ADMIN`, TUN): restrict access to its port to the UI network. +- Back up the `db_data` and `ovp_pki` volumes. + +## Operations + +```bash +docker-compose ps +docker-compose logs -f app-api app-profiler +docker-compose up -d --build app-ui # after UI changes +``` diff --git a/DOCS/General/Deployment_Native.md b/DOCS/General/Deployment_Native.md new file mode 100644 index 0000000..f96cbf6 --- /dev/null +++ b/DOCS/General/Deployment_Native.md @@ -0,0 +1,82 @@ +# Deployment: system services (no containers) + +Tested on **Alpine 3.23 (OpenRC)**; Debian/Ubuntu (systemd) differs only in service manager. Unit/init templates: [`systemd/`](systemd), [`openrc/`](openrc/INSTALL.md). Generic notes: [Deployment](Deployment.md), [Service management](Service_Management.md), [Nginx](Nginx_Configuration.md). + +## 1. Packages + +- Alpine: `apk add python3 py3-pip nginx nodejs npm openvpn easy-rsa iptables bash` +- Debian/Ubuntu: `apt install python3-venv nginx nodejs npm openvpn easy-rsa iptables` + +## 2. Backend + +```bash +cd APP_CORE && python3 -m venv venv && venv/bin/pip install -r requirements.txt gunicorn +cd APP_PROFILER && python3 -m venv venv && venv/bin/pip install -r requirements.txt +mkdir -p APP_PROFILER/easy-rsa && cp -r /usr/share/easy-rsa/* APP_PROFILER/easy-rsa/ # Docker entrypoint does this automatically +``` + +## 3. Environment file + +`/etc/ovpmon/env` (chmod 600), loaded by the services: + +``` +OVPMON_API_SECRET_KEY= +OVPMON_API_HOST=127.0.0.1 +OVPMON_API_PORT=5001 +OVPMON_OPENVPN_MONITOR_DB_PATH=/var/lib/ovpmon/openvpn_monitor.db +OVPMON_OPENVPN_MONITOR_LOG_PATH=/var/log/openvpn/openvpn-status.log +OVPMON_PROFILER_DB_PATH=/var/lib/ovpmon/ovpn_profiler.db +OVPMON_LOGGING_LEVEL=INFO +``` + +Create `/var/lib/ovpmon`, `/var/log/ovpmon`, `/var/log/openvpn`. For the first start also set `OVPMON_INITIAL_ADMIN_USER` / `OVPMON_INITIAL_ADMIN_PASSWORD`, then remove them. + +## 4. Services + +| Service | Command | Listens | +|---|---|---| +| `ovpmon-api` | `APP_CORE/venv/bin/gunicorn -w 2 -b 127.0.0.1:5001 openvpn_api_v3:app` (cwd `APP_CORE`) | 127.0.0.1:5001 | +| `ovpmon-gatherer` | `APP_CORE/venv/bin/python openvpn_gatherer_v3.py` (cwd `APP_CORE`) | - | +| `ovpmon-profiler` | `APP_PROFILER/venv/bin/uvicorn main:app --host 127.0.0.1 --port 8000` (cwd `APP_PROFILER`) | 127.0.0.1:8000 | + +OpenRC (Alpine): `supervisor=supervise-daemon`, `respawn_delay=3`, source `/etc/ovpmon/env` in `start_pre`, then `rc-update add default && rc-service start`. systemd: use the units from `systemd/` with `EnvironmentFile=/etc/ovpmon/env`. + +The Profiler restarts OpenVPN through `rc-service openvpn` (Alpine) or `systemctl openvpn`; on Alpine link the config: `ln -s server.conf /etc/openvpn/openvpn.conf` and enable the `openvpn` service. + +## 5. UI and Nginx (HTTPS on 8088) + +```bash +cd APP_UI && npm install && npm run build +mkdir -p /var/www/ovpmon && cp -r dist/. /var/www/ovpmon/ +``` + +Certificate (self-signed, replace with a real one when a domain is available): + +```bash +mkdir -p /etc/ovpmon/tls && cd /etc/ovpmon/tls +openssl req -x509 -nodes -newkey ec -pkeyopt ec_paramgen_curve:prime256v1 -days 825 \ + -subj "/CN=ovpmon" -addext "subjectAltName=IP:,DNS:ovpmon" -keyout ovpmon.key -out ovpmon.crt +chmod 600 ovpmon.key +``` + +Nginx server (`/etc/nginx/http.d/ovpmon.conf` on Alpine): `listen 8088 ssl`, TLS 1.2/1.3, `error_page 497 =301 https://$host:8088$request_uri`, security headers, `limit_req` (5 r/min) on `/api/auth/login`, `/` → static UI, `/api/` → `127.0.0.1:5001`, `/profiles-api/` → `127.0.0.1:8000/api/`. Full listing: [Nginx configuration](Nginx_Configuration.md). Do not declare a second `ssl_session_cache shared:SSL` zone with a different size than the main `nginx.conf`. + +## 6. First run + +1. `https://:8088/` → sign in with the seeded admin → **Account**: change username/password, enable 2FA. +2. **PKI Configuration → Initialize PKI** → generate server config → start OpenVPN → create profiles. + +## 7. Host hardening (recommended) + +- SSH: key-only (`PasswordAuthentication no`, `KbdInteractiveAuthentication no`, `PermitRootLogin prohibit-password`); note that cloud-init drop-ins in `/etc/ssh/sshd_config.d/` can override the main file, so put settings in `00-*.conf`. +- fail2ban: jails `sshd` and one for `POST /api/auth/login` (401/429/503) on the Nginx access log. +- Backends bound to `127.0.0.1`; only 8088 (UI/API) and the VPN port are public. + +## 8. Verify + +```bash +rc-status | grep -E "ovpmon|nginx|openvpn" # or: systemctl status ovpmon-* +ss -tlnp | grep -E ":(8088|5001|8000) " +curl -sk -o /dev/null -w "%{http_code}\n" https://127.0.0.1:8088/ # 200 +curl -sk -o /dev/null -w "%{http_code}\n" https://127.0.0.1:8088/profiles-api/config # 401 without token +``` diff --git a/DOCS/General/Index.md b/DOCS/General/Index.md index 5b1f9d6..d6d43cc 100644 --- a/DOCS/General/Index.md +++ b/DOCS/General/Index.md @@ -3,10 +3,18 @@ Welcome to the documentation for the OpenVPN Monitor suite. ## 📚 General -- [Deployment Guide](Deployment.md): How to install and configure the application on a Linux server. +- [Deployment: Docker](Deployment_Docker.md): containers with docker-compose. +- [Deployment: system services](Deployment_Native.md): systemd/OpenRC, HTTPS, host hardening. +- [Deployment Guide](Deployment.md): generic native install notes. - [Service Management](Service_Management.md): Setting up systemd/OpenRC services. +- [Nginx Configuration](Nginx_Configuration.md) - [Security Architecture](Security_Architecture.md): Details on Authentication, 2FA, and Security features. +## 🛠 Changes and results +- [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) +- [Egress via Hysteria2 (2026-09-30)](../Changes/2026-09-30_Egress_via_Hysteria2.md) + ## 🔍 Core Monitoring (`APP_CORE`) The core module responsible for log parsing, real-time statistics, and the primary API. - [API Reference](../Core_Monitoring/API_Reference.md): Endpoints for monitoring data. diff --git a/DOCS/General/Security_Architecture.md b/DOCS/General/Security_Architecture.md index a8e2972..0c2f9c6 100644 --- a/DOCS/General/Security_Architecture.md +++ b/DOCS/General/Security_Architecture.md @@ -10,7 +10,7 @@ This includes: ## User Review Required > [!IMPORTANT] -> **Default Credentials**: We will create a default admin user (e.g., `admin` / `password`) on first run if no users exist. The user MUST change this immediately. +> **Initial admin**: no default user is created. On first run with an empty users table the admin is seeded only from OVPMON_INITIAL_ADMIN_USER / OVPMON_INITIAL_ADMIN_PASSWORD; the username can be changed in Account (see DOCS/Changes/2026-09-30_Admin_Username_Change.md). > [!WARNING] > **Breaking Change**: Access to the current dashboard will be blocked until the user logs in. diff --git a/README.md b/README.md index effdce4..3e9a4cc 100644 --- a/README.md +++ b/README.md @@ -1,61 +1,56 @@ # OpenVPN Monitor & Profiler -A modern, full-stack management solution for OpenVPN servers. It combines real-time traffic monitoring, historical analytics, and comprehensive user profile/PKI management into a unified web interface. Perfect for both containerized (Docker) and native (Alpine/Debian/Ubuntu) deployments. +Web suite for OpenVPN servers: real-time traffic monitoring, history/analytics, PKI and client-profile management, one UI. -## 🏗️ Project Architecture +| Component | Dir | Stack | Default port | +|---|---|---|---| +| UI | `APP_UI/` | Vue 3 + Vite, served by Nginx | 80 (Docker) / 8088 (native, TLS) | +| Monitoring API | `APP_CORE/` | Flask (gunicorn) | 5001 (internal) | +| Data gatherer | `APP_CORE/` | Python daemon | - | +| Profiler API | `APP_PROFILER/` | FastAPI (uvicorn) | 8000 (internal) | -The project is modularized into four core microservices, split between **Monitoring (Core)** and **Management (Profiler)**: +Nginx is the only public entry point: `/` UI, `/api/` Monitoring API, `/profiles-api/` Profiler API. -| Component | Directory | Service Name | Description | -| :--- | :--- | :--- | :--- | -| **User Interface** | `APP_UI/` | `ovp-ui` | Vue 3 + Vite SPA + Nginx. Communicates with both APIs. | -| **Monitoring API** | `APP_CORE/` | `ovp-api` | Flask API for real-time stats, sessions, and bandwidth data. | -| **Data Gatherer** | `APP_CORE/` | `ovp-gatherer` | Background service for traffic log aggregation & TSDB logic. | -| **Profiler API** | `APP_PROFILER/` | `ovp-profiler` | FastAPI module for PKI management, User Profiles, and VPN control. | +## Quick start -## 📦 Quick Start (Docker) +- **Containers:** `docker-compose up -d --build`, open `http://`. Details: [Deployment: Docker](DOCS/General/Deployment_Docker.md). +- **System services** (systemd / OpenRC, no containers): [Deployment: native](DOCS/General/Deployment_Native.md). -The recommended way to deploy is using Docker Compose: +After the first start: sign in, open **PKI Configuration** → **Initialize PKI**, generate the server config, start OpenVPN, create profiles. -1. **Clone the repository** -2. **Start all services**: - ```bash - docker-compose up -d --build - ``` -3. **Access the Dashboard**: Open `http://localhost` (or your server IP) in your browser. -4. **Initialize PKI**: On the first run, navigate to the **PKI Configuration** page in the UI and click **Initialize PKI**. This sets up the CA and Easy-RSA workspace. +## First login and credentials -## ⚙️ Configuration +No default user is created. Seed the initial admin with `OVPMON_INITIAL_ADMIN_USER` / `OVPMON_INITIAL_ADMIN_PASSWORD` on first start (empty `users` table only), then remove them. Change the username and password and enable 2FA in **Account**. -The system uses a unified configuration approach. Settings can be defined in `config.ini` files or overridden by environment variables following the `OVPMON_{SECTION}_{KEY}` format. +## Configuration -### Key Environment Variables +`config.ini` per component; overridden by `OVPMON_{SECTION}_{KEY}` environment variables. -| Variable | Description | Default Value | -| :--- | :--- | :--- | -| `OVPMON_API_SECRET_KEY` | Unified JWT Secret Key (used by both APIs) | `supersecret` | -| `OVPMON_PROFILER_DB_PATH` | Path to Profiler (users/pki) SQLite DB | `/app/db/ovpn_profiler.db` | -| `OVPMON_OPENVPN_MONITOR_DB_PATH` | Path to Monitoring (traffic) SQLite DB | `/app/db/openvpn_monitor.db` | -| `OVPMON_OPENVPN_MONITOR_LOG_PATH`| Path to OpenVPN status log | `/var/log/openvpn/openvpn-status.log` | -| `OVPMON_LOGGING_LEVEL` | Logging level (INFO/DEBUG) | `INFO` | +| Variable | Purpose | +|---|---| +| `OVPMON_API_SECRET_KEY` | JWT secret shared by both APIs (**must be random**) | +| `OVPMON_INITIAL_ADMIN_USER` / `_PASSWORD` | One-time admin seed | +| `OVPMON_OPENVPN_MONITOR_DB_PATH` | Monitoring DB | +| `OVPMON_PROFILER_DB_PATH` | Profiler DB | +| `OVPMON_OPENVPN_MONITOR_LOG_PATH` | `openvpn-status.log` path | +| `OVPMON_LOGGING_LEVEL` | `INFO` / `DEBUG` | -## 🛠️ Performance & Environment Awareness +## Documentation -- **Container Transparency**: When running in Docker, the Profiler manages OpenVPN directly to bypass cgroups restrictions. -- **Host Integration**: When running natively on Alpine or Debian/Ubuntu, it automatically switches to `rc-service` or `systemctl`. -- **Persistent Data**: Logs, Certificates (PKI), and Databases are stored in Docker volumes (`ovp_logs`, `ovp_pki`, `db_data`). +- 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) +- Security model: [Security Architecture](DOCS/General/Security_Architecture.md) +- APIs: [Monitoring](DOCS/Core_Monitoring/API_Reference.md) · [Profiler](DOCS/Profiler_Management/API_Reference.md) -## 📚 Development +## Changes and results -### Component Development -- **UI**: Uses `composables/useApi.js` to route requests to the appropriate backend service based on URL. -- **Profiler**: Clean Python/FastAPI code with SQLAlchemy models. Supports "staging" local mode for development without root access. -- **Core**: Lightweight Flask services focused on high-performance log parsing. +| Date | Change | Document | +|---|---|---| +| 2026-09-30 | Security hardening: path traversal, 2FA token bypass, CORS, log leak, HTTPS, SSH, fail2ban | [Security hardening](DOCS/Changes/2026-09-30_Security_Hardening.md) | +| 2026-09-30 | Admin username change (API + UI), no built-in default admin | [Admin username change](DOCS/Changes/2026-09-30_Admin_Username_Change.md) | +| 2026-09-30 | Route OpenVPN clients through a Hysteria2 tunnel to an exit node | [Egress via Hysteria2](DOCS/Changes/2026-09-30_Egress_via_Hysteria2.md) | ---- +## Notes -### ⚠️ Important Notes - -1. **Privileged Mode**: The `ovp-profiler` container requires `NET_ADMIN` capabilities for iptables and TUN management. -2. **Network Setup**: Ensure `net.ipv4.ip_forward=1` is enabled (handled automatically in the docker-compose `sysctls` section). -3. **JWT Safety**: Always change the `OVPMON_API_SECRET_KEY` in production. +- `ovpmon-api` and `ovpmon-profiler` currently run as root (they manage OpenVPN and PKI). +- Keep `easy-rsa/`, `client-config/`, databases and `*.env` out of git: they contain private keys and secrets.