Docs: concise README, split deployment guides, add change records

- README: short overview, quick start, config table and links.
- DOCS/General: Deployment_Docker.md and Deployment_Native.md (system
  services, HTTPS, host hardening); refresh Index.md.
- DOCS/Changes: security hardening, admin username change and egress
  via Hysteria2 with results and verification.
- Drop mentions of the built-in admin/password account.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
iclaoudezinandClaude Sonnet 5.5 committed 2026-09-30 12:12:09 +00:00
1 parent 11c1b6379b
commit 9b2882d5f4
9 files changed
+294 -46

No files matched your search

@@ -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'", ("<new-login>",)); 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 |
@@ -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.
@@ -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/<username>.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`.
+1 -1
View File
@@ -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.
+45
View File
@@ -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 <<EOT
JWT_SECRET=$(openssl rand -hex 32)
OVPMON_INITIAL_ADMIN_USER=<login>
OVPMON_INITIAL_ADMIN_PASSWORD=<strong 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://<host>`, 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
```
+82
View File
@@ -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=<openssl rand -hex 32>
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 <svc> default && rc-service <svc> 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:<HOST-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://<host>: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
```
+9 -1
View File
@@ -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.
+1 -1
View File
@@ -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.
+38 -43
View File
@@ -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://<host>`. 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.