diff --git a/APP_CORE/openvpn_api_v3.py b/APP_CORE/openvpn_api_v3.py index 80be03d..01db519 100644 --- a/APP_CORE/openvpn_api_v3.py +++ b/APP_CORE/openvpn_api_v3.py @@ -25,7 +25,8 @@ logger = logging.getLogger(__name__) app = Flask(__name__) # Enable CORS for all routes with specific headers support -CORS(app, resources={r"/api/*": {"origins": ["https://213.226.125.13:8088"]}}, supports_credentials=True) +# Cross-origin access is off by default (the UI is same-origin behind Nginx); allow extra origins via OVPMON_CORS_ORIGINS (comma-separated) +CORS(app, resources={r"/api/*": {"origins": [o.strip() for o in os.getenv("OVPMON_CORS_ORIGINS", "").split(",") if o.strip()]}}, supports_credentials=True) class OpenVPNAPI: def get_config_value(self, section, key, fallback=None): diff --git a/APP_PROFILER/main.py b/APP_PROFILER/main.py index e904e82..0357ec0 100644 --- a/APP_PROFILER/main.py +++ b/APP_PROFILER/main.py @@ -26,7 +26,7 @@ app = FastAPI( # Enable CORS app.add_middleware( CORSMiddleware, - allow_origins=["https://213.226.125.13:8088"], + allow_origins=[o.strip() for o in os.getenv("OVPMON_CORS_ORIGINS", "").split(",") if o.strip()], allow_credentials=True, allow_methods=["GET", "POST", "PUT", "DELETE"], allow_headers=["Authorization", "Content-Type"], diff --git a/DOCS/General/Deployment_Docker.md b/DOCS/General/Deployment_Docker.md index fbc33d2..ad32006 100644 --- a/DOCS/General/Deployment_Docker.md +++ b/DOCS/General/Deployment_Docker.md @@ -15,7 +15,7 @@ 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`): +1. Create `.env` next to `docker-compose.yml` (`JWT_SECRET` is mandatory: compose refuses to start without it): ```bash cat > .env <`, sign in, **PKI Configuration → Initialize PKI**. -4. Remove `OVPMON_INITIAL_ADMIN_*` from `.env` and recreate `app-api`. +4. Remove `OVPMON_INITIAL_ADMIN_*` from `.env` and run `docker-compose up -d app-api` (the seed is used only while the users table is empty). + +## Compose settings + +| Item | Value | +|---|---| +| Published ports | `80/tcp` (UI) and `1194/udp` (VPN) only; `5001` and `8000` are `expose`d on `ovp-net` and reached through Nginx in `app-ui` | +| Secrets | `JWT_SECRET` (required) → `OVPMON_API_SECRET_KEY` for both APIs | +| Initial admin | `OVPMON_INITIAL_ADMIN_USER` / `OVPMON_INITIAL_ADMIN_PASSWORD` (optional, first start) | +| CORS | `OVPMON_CORS_ORIGINS` (optional, comma-separated; off by default because the UI is same-origin) | +| Restart policy | `unless-stopped` | ## 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. +- Terminate TLS in front of `app-ui` (reverse proxy or a certificate mounted into the UI container); see [Nginx configuration](Nginx_Configuration.md). The container serves plain HTTP on 80. +- `ovp-profiler` is privileged (`NET_ADMIN`, TUN): keep its API off the host network. +- Back up the `db_data` and `ovp_pki` volumes; never commit `.env`. ## Operations diff --git a/DOCS/General/Deployment_Native.md b/DOCS/General/Deployment_Native.md index f96cbf6..552d2d5 100644 --- a/DOCS/General/Deployment_Native.md +++ b/DOCS/General/Deployment_Native.md @@ -27,6 +27,7 @@ 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 +OVPMON_CORS_ORIGINS=https://:8088 ``` 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. diff --git a/README.md b/README.md index 3e9a4cc..18f8aec 100644 --- a/README.md +++ b/README.md @@ -30,6 +30,7 @@ No default user is created. Seed the initial admin with `OVPMON_INITIAL_ADMIN_US |---|---| | `OVPMON_API_SECRET_KEY` | JWT secret shared by both APIs (**must be random**) | | `OVPMON_INITIAL_ADMIN_USER` / `_PASSWORD` | One-time admin seed | +| `OVPMON_CORS_ORIGINS` | Extra allowed CORS origins, comma-separated (empty = same-origin only) | | `OVPMON_OPENVPN_MONITOR_DB_PATH` | Monitoring DB | | `OVPMON_PROFILER_DB_PATH` | Profiler DB | | `OVPMON_OPENVPN_MONITOR_LOG_PATH` | `openvpn-status.log` path | diff --git a/docker-compose.yml b/docker-compose.yml index e1f8330..a375bab 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,9 +1,16 @@ -version: '3.8' +# OpenVPN Monitor & Profiler (containers) +# Required: JWT_SECRET in .env (openssl rand -hex 32) +# First start only: OVPMON_INITIAL_ADMIN_USER / OVPMON_INITIAL_ADMIN_PASSWORD in .env +# Optional: OVPMON_CORS_ORIGINS (comma-separated origins; off by default, UI is same-origin via Nginx) + +x-secret: &jwt-secret + OVPMON_API_SECRET_KEY: ${JWT_SECRET:?JWT_SECRET must be set in .env (openssl rand -hex 32)} services: app-ui: build: ./APP_UI container_name: ovp-ui + restart: unless-stopped ports: - "80:80" depends_on: @@ -12,75 +19,81 @@ services: networks: - ovp-net environment: - - OVP_API_HOST=ovp-api - - OVP_API_PORT=5001 - - OVP_PROFILER_HOST=ovp-profiler - - OVP_PROFILER_PORT=8000 - + OVP_API_HOST: ovp-api + OVP_API_PORT: 5001 + OVP_PROFILER_HOST: ovp-profiler + OVP_PROFILER_PORT: 8000 app-gatherer: build: context: ./APP_CORE dockerfile: Dockerfile.gatherer container_name: ovp-gatherer + restart: unless-stopped volumes: - ovp_logs:/var/log/openvpn - db_data:/app/db depends_on: - app-profiler - environment: - - OVPMON_OPENVPN_MONITOR_DB_PATH=/app/db/openvpn_monitor.db - - OVPMON_OPENVPN_MONITOR_LOG_PATH=/var/log/openvpn/openvpn-status.log - - OVPMON_LOGGING_LEVEL=INFO networks: - ovp-net + environment: + OVPMON_OPENVPN_MONITOR_DB_PATH: /app/db/openvpn_monitor.db + OVPMON_OPENVPN_MONITOR_LOG_PATH: /var/log/openvpn/openvpn-status.log + OVPMON_LOGGING_LEVEL: INFO app-api: build: context: ./APP_CORE dockerfile: Dockerfile.api container_name: ovp-api - ports: - - "5001:5001" + restart: unless-stopped + # Not published: reached only through app-ui (Nginx) on ovp-net + expose: + - "5001" volumes: - db_data:/app/db networks: - ovp-net - environment: - - OVPMON_API_SECRET_KEY=${JWT_SECRET:-supersecret} - - OVPMON_API_PORT=5001 - - OVPMON_OPENVPN_MONITOR_DB_PATH=/app/db/openvpn_monitor.db - - OVPMON_LOGGING_LEVEL=INFO depends_on: - app-gatherer + environment: + <<: *jwt-secret + OVPMON_API_PORT: 5001 + OVPMON_OPENVPN_MONITOR_DB_PATH: /app/db/openvpn_monitor.db + OVPMON_LOGGING_LEVEL: INFO + # Initial admin: used only while the users table is empty (remove after first start) + OVPMON_INITIAL_ADMIN_USER: ${OVPMON_INITIAL_ADMIN_USER:-} + OVPMON_INITIAL_ADMIN_PASSWORD: ${OVPMON_INITIAL_ADMIN_PASSWORD:-} + OVPMON_CORS_ORIGINS: ${OVPMON_CORS_ORIGINS:-} app-profiler: build: ./APP_PROFILER container_name: ovp-profiler + restart: unless-stopped cap_add: - NET_ADMIN sysctls: - net.ipv4.ip_forward=1 devices: - - "/dev/net/tun:/dev/net/tun" ports: - - "8000:8000" + # VPN port only; the profiler API (8000) is reached through app-ui - "1194:1194/udp" + expose: + - "8000" volumes: - ovp_logs:/var/log/openvpn - ovp_config:/etc/openvpn - db_data:/app/db - ovp_client_config:/app/client-config - ovp_pki:/app/easy-rsa - - networks: - ovp-net environment: - - OVPMON_API_SECRET_KEY=${JWT_SECRET:-supersecret} - - OVPMON_PROFILER_DB_PATH=/app/db/ovpn_profiler.db - + <<: *jwt-secret + OVPMON_PROFILER_DB_PATH: /app/db/ovpn_profiler.db + OVPMON_CORS_ORIGINS: ${OVPMON_CORS_ORIGINS:-} networks: ovp-net: