[NX-203 Issue] Add production proxy profile with validation and documentation
All checks were successful
Container CVE Scan (development) / Scan backend/frontend images for CVEs (push) Successful in 2m40s
PostgreSQL Compatibility Matrix / PG14 smoke (push) Successful in 8s
PostgreSQL Compatibility Matrix / PG15 smoke (push) Successful in 7s
PostgreSQL Compatibility Matrix / PG16 smoke (push) Successful in 7s
PostgreSQL Compatibility Matrix / PG17 smoke (push) Successful in 7s
PostgreSQL Compatibility Matrix / PG18 smoke (push) Successful in 8s
Proxy Profile Validation / validate (push) Successful in 3s
All checks were successful
Container CVE Scan (development) / Scan backend/frontend images for CVEs (push) Successful in 2m40s
PostgreSQL Compatibility Matrix / PG14 smoke (push) Successful in 8s
PostgreSQL Compatibility Matrix / PG15 smoke (push) Successful in 7s
PostgreSQL Compatibility Matrix / PG16 smoke (push) Successful in 7s
PostgreSQL Compatibility Matrix / PG17 smoke (push) Successful in 7s
PostgreSQL Compatibility Matrix / PG18 smoke (push) Successful in 8s
Proxy Profile Validation / validate (push) Successful in 3s
Introduced a secure, repeatable production proxy profile for reverse proxy and HTTPS deployment, including NGINX configuration, environment variables, and CORS guidance. Added CI workflow for static validation of proxy guardrails and detailed documentation to ensure best practices in deployment.
This commit is contained in:
@@ -56,5 +56,5 @@ INIT_ADMIN_PASSWORD=ChangeMe123!
|
||||
# ------------------------------
|
||||
# Frontend
|
||||
# ------------------------------
|
||||
# Host port mapped to frontend container port 80.
|
||||
# Host port mapped to frontend container port 8080.
|
||||
FRONTEND_PORT=5173
|
||||
|
||||
35
.github/workflows/proxy-profile-validation.yml
vendored
Normal file
35
.github/workflows/proxy-profile-validation.yml
vendored
Normal file
@@ -0,0 +1,35 @@
|
||||
name: Proxy Profile Validation
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: ["main", "master", "development"]
|
||||
paths:
|
||||
- "frontend/**"
|
||||
- "ops/profiles/prod/**"
|
||||
- "ops/scripts/validate_proxy_profile.sh"
|
||||
- ".github/workflows/proxy-profile-validation.yml"
|
||||
- "README.md"
|
||||
- ".env.example"
|
||||
- "ops/.env.example"
|
||||
pull_request:
|
||||
paths:
|
||||
- "frontend/**"
|
||||
- "ops/profiles/prod/**"
|
||||
- "ops/scripts/validate_proxy_profile.sh"
|
||||
- ".github/workflows/proxy-profile-validation.yml"
|
||||
- "README.md"
|
||||
- ".env.example"
|
||||
- "ops/.env.example"
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
validate:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 1
|
||||
|
||||
- name: Validate proxy profile and mixed-content guardrails
|
||||
run: bash ops/scripts/validate_proxy_profile.sh
|
||||
16
README.md
16
README.md
@@ -20,6 +20,7 @@ It combines FastAPI, React, and PostgreSQL in a Docker Compose stack with RBAC,
|
||||
- [API Error Format](#api-error-format)
|
||||
- [`pg_stat_statements` Requirement](#pg_stat_statements-requirement)
|
||||
- [Reverse Proxy / SSL Guidance](#reverse-proxy--ssl-guidance)
|
||||
- [Production Proxy Profile](#production-proxy-profile)
|
||||
- [PostgreSQL Compatibility Smoke Test](#postgresql-compatibility-smoke-test)
|
||||
- [Dependency Exception Flow](#dependency-exception-flow)
|
||||
- [Troubleshooting](#troubleshooting)
|
||||
@@ -372,6 +373,21 @@ For production, serve frontend and API under the same public origin via reverse
|
||||
|
||||
This prevents mixed-content and CORS issues.
|
||||
|
||||
## Production Proxy Profile
|
||||
|
||||
A secure, repeatable production profile is included:
|
||||
|
||||
- `ops/profiles/prod/.env.production.example`
|
||||
- `ops/profiles/prod/nginx/nexapg.conf`
|
||||
- `docs/deployment/proxy-production-profile.md`
|
||||
|
||||
Highlights:
|
||||
|
||||
- explicit CORS recommendations per environment (`dev`, `staging`, `prod`)
|
||||
- required reverse-proxy header forwarding for backend context
|
||||
- API path forwarding (`/api/` -> backend)
|
||||
- mixed-content prevention guidance for HTTPS deployments
|
||||
|
||||
## PostgreSQL Compatibility Smoke Test
|
||||
|
||||
Run manually against one DSN:
|
||||
|
||||
78
docs/deployment/proxy-production-profile.md
Normal file
78
docs/deployment/proxy-production-profile.md
Normal file
@@ -0,0 +1,78 @@
|
||||
# Production Proxy Profile (HTTPS)
|
||||
|
||||
This profile defines a secure and repeatable NexaPG deployment behind a reverse proxy.
|
||||
|
||||
## Included Profile Files
|
||||
|
||||
- `ops/profiles/prod/.env.production.example`
|
||||
- `ops/profiles/prod/nginx/nexapg.conf`
|
||||
|
||||
## CORS Recommendations by Environment
|
||||
|
||||
| Environment | Recommended `CORS_ORIGINS` | Notes |
|
||||
|---|---|---|
|
||||
| `dev` | `*` or local explicit origins | `*` is acceptable only for local/dev usage. |
|
||||
| `staging` | Exact staging UI origins | Example: `https://staging-monitor.example.com` |
|
||||
| `prod` | Exact production UI origin(s) only | No wildcard; use comma-separated HTTPS origins if needed. |
|
||||
|
||||
Examples:
|
||||
|
||||
```env
|
||||
# dev only
|
||||
CORS_ORIGINS=*
|
||||
|
||||
# staging
|
||||
CORS_ORIGINS=https://staging-monitor.example.com
|
||||
|
||||
# prod
|
||||
CORS_ORIGINS=https://monitor.example.com
|
||||
```
|
||||
|
||||
## Reverse Proxy Requirements
|
||||
|
||||
For stable auth, CORS, and request context handling, forward these headers to backend:
|
||||
|
||||
- `Host`
|
||||
- `X-Real-IP`
|
||||
- `X-Forwarded-For`
|
||||
- `X-Forwarded-Proto`
|
||||
- `X-Forwarded-Host`
|
||||
- `X-Forwarded-Port`
|
||||
|
||||
Also forward API paths:
|
||||
|
||||
- `/api/` -> backend service (`:8000`)
|
||||
|
||||
## Mixed-Content Prevention
|
||||
|
||||
NexaPG frontend is designed to avoid mixed-content in HTTPS mode:
|
||||
|
||||
- Build/runtime default API base is relative (`/api/v1`)
|
||||
- `frontend/src/api.js` upgrades `http` API URL to `https` when page runs on HTTPS
|
||||
|
||||
Recommended production setting:
|
||||
|
||||
```env
|
||||
VITE_API_URL=/api/v1
|
||||
```
|
||||
|
||||
## Validation Checklist
|
||||
|
||||
1. Open app over HTTPS and verify:
|
||||
- login request is `https://.../api/v1/auth/login`
|
||||
- no browser mixed-content errors in console
|
||||
2. Verify CORS behavior:
|
||||
- allowed origin works
|
||||
- unknown origin is blocked
|
||||
3. Verify backend receives forwarded protocol:
|
||||
- proxied responses succeed with no redirect/proto issues
|
||||
|
||||
## CI Validation
|
||||
|
||||
`Proxy Profile Validation` workflow runs static guardrail checks:
|
||||
|
||||
- relative `VITE_API_URL` default
|
||||
- required API proxy path in frontend NGINX config
|
||||
- required forwarded headers
|
||||
- HTTPS mixed-content guard in frontend API resolver
|
||||
- production profile forbids wildcard CORS
|
||||
@@ -49,7 +49,7 @@ INIT_ADMIN_PASSWORD=ChangeMe123!
|
||||
# ------------------------------
|
||||
# Frontend
|
||||
# ------------------------------
|
||||
# Host port mapped to frontend container port 80.
|
||||
# Host port mapped to frontend container port 8080.
|
||||
FRONTEND_PORT=5173
|
||||
# Base API URL used at frontend build time.
|
||||
# For reverse proxy + SSL, keep this relative to avoid mixed-content issues.
|
||||
|
||||
48
ops/profiles/prod/.env.production.example
Normal file
48
ops/profiles/prod/.env.production.example
Normal file
@@ -0,0 +1,48 @@
|
||||
# NexaPG production profile (reverse proxy + HTTPS)
|
||||
# Copy to .env and adjust values for your environment.
|
||||
|
||||
# ------------------------------
|
||||
# Application
|
||||
# ------------------------------
|
||||
APP_NAME=NexaPG Monitor
|
||||
ENVIRONMENT=prod
|
||||
LOG_LEVEL=INFO
|
||||
|
||||
# ------------------------------
|
||||
# Core Database
|
||||
# ------------------------------
|
||||
DB_NAME=nexapg
|
||||
DB_USER=nexapg
|
||||
DB_PASSWORD=change_me
|
||||
DB_PORT=5433
|
||||
|
||||
# ------------------------------
|
||||
# Backend
|
||||
# ------------------------------
|
||||
BACKEND_PORT=8000
|
||||
JWT_SECRET_KEY=replace_with_long_random_secret
|
||||
JWT_ALGORITHM=HS256
|
||||
JWT_ACCESS_TOKEN_MINUTES=15
|
||||
JWT_REFRESH_TOKEN_MINUTES=10080
|
||||
ENCRYPTION_KEY=REPLACE_WITH_FERNET_KEY
|
||||
|
||||
# Production CORS:
|
||||
# - no wildcard
|
||||
# - set exact public UI origin(s)
|
||||
CORS_ORIGINS=https://monitor.example.com
|
||||
|
||||
POLL_INTERVAL_SECONDS=30
|
||||
ALERT_ACTIVE_CONNECTION_RATIO_MIN_TOTAL_CONNECTIONS=5
|
||||
ALERT_ROLLBACK_RATIO_WINDOW_MINUTES=15
|
||||
ALERT_ROLLBACK_RATIO_MIN_TOTAL_TRANSACTIONS=100
|
||||
ALERT_ROLLBACK_RATIO_MIN_ROLLBACKS=10
|
||||
|
||||
INIT_ADMIN_EMAIL=admin@example.com
|
||||
INIT_ADMIN_PASSWORD=ChangeMe123!
|
||||
|
||||
# ------------------------------
|
||||
# Frontend
|
||||
# ------------------------------
|
||||
# Keep frontend API base relative to avoid HTTPS mixed-content.
|
||||
FRONTEND_PORT=5173
|
||||
VITE_API_URL=/api/v1
|
||||
49
ops/profiles/prod/nginx/nexapg.conf
Normal file
49
ops/profiles/prod/nginx/nexapg.conf
Normal file
@@ -0,0 +1,49 @@
|
||||
# NGINX reverse proxy profile for NexaPG (HTTPS).
|
||||
# Replace monitor.example.com and certificate paths.
|
||||
|
||||
server {
|
||||
listen 80;
|
||||
server_name monitor.example.com;
|
||||
return 301 https://$host$request_uri;
|
||||
}
|
||||
|
||||
server {
|
||||
listen 443 ssl http2;
|
||||
server_name monitor.example.com;
|
||||
|
||||
ssl_certificate /etc/letsencrypt/live/monitor.example.com/fullchain.pem;
|
||||
ssl_certificate_key /etc/letsencrypt/live/monitor.example.com/privkey.pem;
|
||||
ssl_session_timeout 1d;
|
||||
ssl_session_cache shared:SSL:10m;
|
||||
ssl_session_tickets off;
|
||||
|
||||
# Baseline security headers
|
||||
add_header X-Content-Type-Options "nosniff" always;
|
||||
add_header X-Frame-Options "SAMEORIGIN" always;
|
||||
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
|
||||
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
|
||||
|
||||
# Frontend app
|
||||
location / {
|
||||
proxy_pass http://127.0.0.1:5173;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_set_header X-Forwarded-Host $host;
|
||||
proxy_set_header X-Forwarded-Port $server_port;
|
||||
}
|
||||
|
||||
# API forwarding to backend
|
||||
location /api/ {
|
||||
proxy_pass http://127.0.0.1:8000;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_set_header X-Forwarded-Host $host;
|
||||
proxy_set_header X-Forwarded-Port $server_port;
|
||||
}
|
||||
}
|
||||
38
ops/scripts/validate_proxy_profile.sh
Normal file
38
ops/scripts/validate_proxy_profile.sh
Normal file
@@ -0,0 +1,38 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
echo "[proxy-profile] validating reverse-proxy and mixed-content guardrails"
|
||||
|
||||
require_pattern() {
|
||||
local file="$1"
|
||||
local pattern="$2"
|
||||
local message="$3"
|
||||
if ! grep -Eq "$pattern" "$file"; then
|
||||
echo "[proxy-profile] FAIL: $message ($file)"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Frontend should default to relative API base in container builds.
|
||||
require_pattern "frontend/Dockerfile" "ARG VITE_API_URL=/api/v1" \
|
||||
"VITE_API_URL default must be relative (/api/v1)"
|
||||
|
||||
# Frontend runtime proxy should forward /api with forward headers.
|
||||
require_pattern "frontend/nginx.conf" "location /api/" \
|
||||
"frontend nginx must proxy /api/"
|
||||
require_pattern "frontend/nginx.conf" "proxy_set_header X-Forwarded-Proto" \
|
||||
"frontend nginx must set X-Forwarded-Proto"
|
||||
require_pattern "frontend/nginx.conf" "proxy_set_header X-Forwarded-For" \
|
||||
"frontend nginx must set X-Forwarded-For"
|
||||
require_pattern "frontend/nginx.conf" "proxy_set_header Host" \
|
||||
"frontend nginx must forward Host"
|
||||
|
||||
# Mixed-content guard in frontend API client.
|
||||
require_pattern "frontend/src/api.js" "window\\.location\\.protocol === \"https:\".*parsed\\.protocol === \"http:\"" \
|
||||
"frontend api client must contain HTTPS mixed-content protection"
|
||||
|
||||
# Production profile must not use wildcard CORS.
|
||||
require_pattern "ops/profiles/prod/.env.production.example" "^CORS_ORIGINS=https://[^*]+$" \
|
||||
"production profile must use explicit HTTPS CORS origins"
|
||||
|
||||
echo "[proxy-profile] PASS"
|
||||
Reference in New Issue
Block a user