feat: implement Remote WinBox worker, API, frontend integration, OpenBao persistence, and supporting docs
This commit is contained in:
@@ -0,0 +1,71 @@
|
||||
# The Other Dude — Apache reverse proxy example
|
||||
#
|
||||
# Required modules:
|
||||
# a2enmod proxy proxy_http proxy_wstunnel rewrite ssl headers
|
||||
#
|
||||
# This config assumes:
|
||||
# - TOD frontend runs on FRONTEND_HOST:3000
|
||||
# - TOD API runs on API_HOST:8001
|
||||
# - WinBox worker Xpra ports are on WORKER_HOST:10100-10119
|
||||
#
|
||||
# Replace tod.example.com and upstream addresses with your values.
|
||||
|
||||
<VirtualHost *:80>
|
||||
ServerName tod.example.com
|
||||
RewriteEngine On
|
||||
RewriteRule ^(.*)$ https://%{HTTP_HOST}$1 [R=301,L]
|
||||
</VirtualHost>
|
||||
|
||||
<VirtualHost *:443>
|
||||
ServerName tod.example.com
|
||||
|
||||
SSLEngine on
|
||||
SSLCertificateFile /etc/ssl/certs/tod.example.com.pem
|
||||
SSLCertificateKeyFile /etc/ssl/private/tod.example.com.key
|
||||
|
||||
# ── Security headers ──────────────────────────────────────────────
|
||||
Header always set X-Frame-Options "SAMEORIGIN"
|
||||
Header always set X-Content-Type-Options "nosniff"
|
||||
Header always set X-XSS-Protection "1; mode=block"
|
||||
Header always set Referrer-Policy "strict-origin-when-cross-origin"
|
||||
|
||||
# ── Xpra (Remote WinBox) ─────────────────────────────────────────
|
||||
# Must appear BEFORE the general proxy rules.
|
||||
# WebSocket upgrade is required. Do NOT enable mod_deflate on this path
|
||||
# — compressing WebSocket binary frames corrupts Xpra mouse/keyboard data.
|
||||
#
|
||||
# ProxyPassMatch uses regex to capture the port and forward to the worker.
|
||||
# Ports 10100-10119 (up to 20 concurrent sessions).
|
||||
RewriteEngine On
|
||||
|
||||
# WebSocket upgrade for Xpra
|
||||
RewriteCond %{HTTP:Upgrade} =websocket [NC]
|
||||
RewriteRule ^/xpra/(\d+)/(.*)$ ws://YOUR_TOD_HOST:$1/$2 [P,L]
|
||||
|
||||
# Regular HTTP requests for Xpra HTML5 client assets
|
||||
ProxyPassMatch "^/xpra/(\d+)/(.*)" "http://YOUR_TOD_HOST:$1/$2"
|
||||
|
||||
# Relaxed CSP for Xpra HTML5 client (inline scripts + eval)
|
||||
<LocationMatch "^/xpra/">
|
||||
Header always set Content-Security-Policy "default-src 'self' 'unsafe-inline' 'unsafe-eval' ws: wss: data: blob:; frame-ancestors 'self';"
|
||||
SetEnv no-gzip 1
|
||||
</LocationMatch>
|
||||
|
||||
# ── API ───────────────────────────────────────────────────────────
|
||||
ProxyPass /api/ http://YOUR_TOD_HOST:8001/api/
|
||||
ProxyPassReverse /api/ http://YOUR_TOD_HOST:8001/api/
|
||||
|
||||
ProxyTimeout 300
|
||||
RequestHeader set X-Forwarded-Proto "https"
|
||||
|
||||
<Location /api/>
|
||||
# Let the API set its own CSP
|
||||
Header unset Content-Security-Policy
|
||||
</Location>
|
||||
|
||||
# ── Frontend (SPA) ────────────────────────────────────────────────
|
||||
ProxyPass / http://YOUR_TOD_HOST:3000/
|
||||
ProxyPassReverse / http://YOUR_TOD_HOST:3000/
|
||||
|
||||
ProxyPreserveHost On
|
||||
</VirtualHost>
|
||||
@@ -0,0 +1,71 @@
|
||||
# The Other Dude — Caddy reverse proxy example
|
||||
#
|
||||
# This config assumes:
|
||||
# - TOD frontend runs on FRONTEND_HOST:3000
|
||||
# - TOD API runs on API_HOST:8001
|
||||
# - WinBox worker Xpra ports are on WORKER_HOST:10100-10119
|
||||
#
|
||||
# Replace tod.example.com and the upstream IPs with your values.
|
||||
# Caddy handles TLS automatically via Let's Encrypt.
|
||||
|
||||
tod.example.com {
|
||||
log {
|
||||
output file /var/log/caddy/tod.log {
|
||||
roll_size 50mb
|
||||
roll_keep 5
|
||||
}
|
||||
format json
|
||||
}
|
||||
|
||||
encode zstd gzip
|
||||
|
||||
header {
|
||||
X-Content-Type-Options "nosniff"
|
||||
X-Frame-Options "SAMEORIGIN"
|
||||
X-XSS-Protection "1; mode=block"
|
||||
Referrer-Policy "strict-origin-when-cross-origin"
|
||||
-Server
|
||||
}
|
||||
|
||||
# ── Xpra (Remote WinBox) ──────────────────────────────────────────
|
||||
# Proxies the Xpra HTML5 client to winbox-worker Xpra ports.
|
||||
# Port range 10100-10119 (up to 20 concurrent sessions).
|
||||
# Uses scoped compression to avoid corrupting WebSocket binary frames.
|
||||
@xpra path_regexp xpra ^/xpra/(101[0-1][0-9])/(.*)$
|
||||
handle @xpra {
|
||||
# Override parent encode — only compress text assets, NOT WebSocket frames
|
||||
encode {
|
||||
gzip
|
||||
match {
|
||||
header Content-Type text/*
|
||||
header Content-Type application/javascript*
|
||||
header Content-Type application/json*
|
||||
}
|
||||
}
|
||||
uri strip_prefix /xpra/{re.xpra.1}
|
||||
reverse_proxy {$WORKER_HOST:YOUR_TOD_HOST}:{re.xpra.1} {
|
||||
header_up Host {host}
|
||||
header_up X-Real-IP {remote_host}
|
||||
}
|
||||
}
|
||||
|
||||
# ── API ───────────────────────────────────────────────────────────
|
||||
handle /api/* {
|
||||
reverse_proxy http://{$API_HOST:YOUR_TOD_HOST}:8001 {
|
||||
header_up Host {host}
|
||||
header_up X-Real-IP {remote_host}
|
||||
transport http {
|
||||
dial_timeout 30s
|
||||
response_header_timeout 60s
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# ── Frontend (SPA) ────────────────────────────────────────────────
|
||||
handle {
|
||||
reverse_proxy http://{$FRONTEND_HOST:YOUR_TOD_HOST}:3000 {
|
||||
header_up Host {host}
|
||||
header_up X-Real-IP {remote_host}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
# The Other Dude — HAProxy reverse proxy example
|
||||
#
|
||||
# This config assumes:
|
||||
# - TOD frontend runs on FRONTEND_HOST:3000
|
||||
# - TOD API runs on API_HOST:8001
|
||||
# - WinBox worker Xpra ports are on WORKER_HOST:10100-10119
|
||||
# - TLS is terminated by HAProxy
|
||||
#
|
||||
# Replace tod.example.com and upstream addresses with your values.
|
||||
#
|
||||
# IMPORTANT: Do NOT enable compression on the xpra backend —
|
||||
# compressing WebSocket binary frames corrupts Xpra mouse/keyboard data.
|
||||
|
||||
global
|
||||
log stdout format raw local0
|
||||
maxconn 4096
|
||||
|
||||
defaults
|
||||
log global
|
||||
mode http
|
||||
option httplog
|
||||
timeout connect 10s
|
||||
timeout client 300s
|
||||
timeout server 300s
|
||||
timeout tunnel 3600s
|
||||
|
||||
# ── Frontend ─────────────────────────────────────────────────────────
|
||||
|
||||
frontend https
|
||||
bind *:443 ssl crt /etc/ssl/certs/tod.example.com.pem
|
||||
bind *:80
|
||||
redirect scheme https code 301 if !{ ssl_fc }
|
||||
|
||||
# Security headers
|
||||
http-response set-header X-Frame-Options "SAMEORIGIN"
|
||||
http-response set-header X-Content-Type-Options "nosniff"
|
||||
http-response set-header Referrer-Policy "strict-origin-when-cross-origin"
|
||||
|
||||
# Routing rules (order matters — first match wins)
|
||||
acl is_xpra path_beg /xpra/
|
||||
acl is_api path_beg /api/
|
||||
|
||||
use_backend xpra if is_xpra
|
||||
use_backend api if is_api
|
||||
default_backend frontend
|
||||
|
||||
# ── Backends ─────────────────────────────────────────────────────────
|
||||
|
||||
backend api
|
||||
option forwardfor
|
||||
http-request set-header X-Forwarded-Proto https
|
||||
server api1 YOUR_TOD_HOST:8001 check
|
||||
|
||||
backend frontend
|
||||
option forwardfor
|
||||
server fe1 YOUR_TOD_HOST:3000 check
|
||||
|
||||
# Xpra backend — uses a Lua or map-based approach to extract the port
|
||||
# from the URL path. This example covers port 10100; add servers for
|
||||
# 10101-10119 as needed, or use HAProxy's Lua scripting for dynamic routing.
|
||||
#
|
||||
# WARNING: Do NOT add "compression" directives to this backend.
|
||||
backend xpra
|
||||
option forwardfor
|
||||
|
||||
# Strip /xpra/{port} prefix
|
||||
http-request set-path %[path,regsub(^/xpra/[0-9]+/,/)]
|
||||
|
||||
# Route to the correct port based on URL
|
||||
# For dynamic port routing, use a map file or Lua script.
|
||||
# Static example for port 10100:
|
||||
acl xpra_10100 path_beg /xpra/10100/
|
||||
use-server xpra10100 if xpra_10100
|
||||
|
||||
server xpra10100 YOUR_TOD_HOST:10100 check
|
||||
# server xpra10101 YOUR_TOD_HOST:10101 check
|
||||
# ... add through 10119 as needed
|
||||
90
infrastructure/reverse-proxy-examples/nginx/tod.conf.example
Normal file
90
infrastructure/reverse-proxy-examples/nginx/tod.conf.example
Normal file
@@ -0,0 +1,90 @@
|
||||
# The Other Dude — nginx reverse proxy example
|
||||
#
|
||||
# This config assumes:
|
||||
# - TOD frontend runs on FRONTEND_HOST:3000
|
||||
# - TOD API runs on API_HOST:8001
|
||||
# - WinBox worker Xpra ports are on WORKER_HOST:10100-10119
|
||||
# - TLS is terminated by nginx (or upstream load balancer)
|
||||
#
|
||||
# Replace tod.example.com and upstream addresses with your values.
|
||||
|
||||
map $http_upgrade $connection_upgrade {
|
||||
default upgrade;
|
||||
'' close;
|
||||
}
|
||||
|
||||
upstream tod_frontend {
|
||||
server YOUR_TOD_HOST:3000;
|
||||
}
|
||||
|
||||
upstream tod_api {
|
||||
server YOUR_TOD_HOST:8001;
|
||||
}
|
||||
|
||||
server {
|
||||
listen 80;
|
||||
server_name tod.example.com;
|
||||
return 301 https://$host$request_uri;
|
||||
}
|
||||
|
||||
server {
|
||||
listen 443 ssl http2;
|
||||
server_name tod.example.com;
|
||||
|
||||
ssl_certificate /etc/ssl/certs/tod.example.com.pem;
|
||||
ssl_certificate_key /etc/ssl/private/tod.example.com.key;
|
||||
|
||||
# ── Security headers ──────────────────────────────────────────────
|
||||
add_header X-Frame-Options "SAMEORIGIN" always;
|
||||
add_header X-Content-Type-Options "nosniff" always;
|
||||
add_header X-XSS-Protection "1; mode=block" always;
|
||||
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
|
||||
add_header Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https://*.tile.openstreetmap.org; font-src 'self'; connect-src 'self' ws: wss:; worker-src 'self'; frame-ancestors 'self'; base-uri 'self'; form-action 'self';" always;
|
||||
|
||||
# ── API ───────────────────────────────────────────────────────────
|
||||
location /api/ {
|
||||
proxy_pass http://tod_api;
|
||||
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_buffering off;
|
||||
proxy_read_timeout 300s;
|
||||
proxy_hide_header Content-Security-Policy;
|
||||
}
|
||||
|
||||
# ── Xpra (Remote WinBox) ─────────────────────────────────────────
|
||||
# Proxies Xpra HTML5 client to winbox-worker ports 10100-10119.
|
||||
# WebSocket support is required. Do NOT enable gzip on this location
|
||||
# — compressing WebSocket binary frames corrupts Xpra mouse/keyboard data.
|
||||
location ~ ^/xpra/(\d+)/(.*) {
|
||||
set $xpra_port $1;
|
||||
set $xpra_path $2;
|
||||
|
||||
proxy_pass http://YOUR_TOD_HOST:$xpra_port/$xpra_path$is_args$args;
|
||||
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 Upgrade $http_upgrade;
|
||||
proxy_set_header Connection $connection_upgrade;
|
||||
proxy_read_timeout 300s;
|
||||
proxy_buffering off;
|
||||
|
||||
# Xpra HTML5 client needs relaxed CSP (inline scripts + eval)
|
||||
# Adding add_header in a location block replaces all server-level headers in nginx
|
||||
add_header Content-Security-Policy "default-src 'self' 'unsafe-inline' 'unsafe-eval' ws: wss: data: blob:; frame-ancestors 'self';" always;
|
||||
add_header X-Content-Type-Options "nosniff" always;
|
||||
}
|
||||
|
||||
# ── Frontend (SPA) ────────────────────────────────────────────────
|
||||
location / {
|
||||
proxy_pass http://tod_frontend;
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
# The Other Dude — Traefik dynamic configuration example
|
||||
#
|
||||
# This config assumes:
|
||||
# - TOD frontend runs on FRONTEND_HOST:3000
|
||||
# - TOD API runs on API_HOST:8001
|
||||
# - WinBox worker Xpra ports are on WORKER_HOST:10100-10119
|
||||
# - Traefik entrypoints: web (80) and websecure (443)
|
||||
#
|
||||
# Replace tod.example.com and upstream addresses with your values.
|
||||
#
|
||||
# For Docker-based Traefik, labels can replace this file.
|
||||
# This example uses file provider for clarity.
|
||||
|
||||
http:
|
||||
routers:
|
||||
# ── Xpra (Remote WinBox) ────────────────────────────────────────
|
||||
# Must be higher priority than the frontend catch-all.
|
||||
# Each Xpra port needs its own service since Traefik doesn't
|
||||
# support dynamic port extraction from path regex.
|
||||
# Shown for port 10100; duplicate for 10101-10119 as needed.
|
||||
tod-xpra-10100:
|
||||
rule: "Host(`tod.example.com`) && PathPrefix(`/xpra/10100/`)"
|
||||
entryPoints: [websecure]
|
||||
service: tod-xpra-10100
|
||||
middlewares: [xpra-strip, xpra-headers]
|
||||
tls:
|
||||
certResolver: letsencrypt
|
||||
priority: 30
|
||||
|
||||
# ── API ─────────────────────────────────────────────────────────
|
||||
tod-api:
|
||||
rule: "Host(`tod.example.com`) && PathPrefix(`/api/`)"
|
||||
entryPoints: [websecure]
|
||||
service: tod-api
|
||||
middlewares: [security-headers]
|
||||
tls:
|
||||
certResolver: letsencrypt
|
||||
priority: 20
|
||||
|
||||
# ── Frontend (SPA) ──────────────────────────────────────────────
|
||||
tod-frontend:
|
||||
rule: "Host(`tod.example.com`)"
|
||||
entryPoints: [websecure]
|
||||
service: tod-frontend
|
||||
middlewares: [security-headers]
|
||||
tls:
|
||||
certResolver: letsencrypt
|
||||
priority: 10
|
||||
|
||||
services:
|
||||
tod-xpra-10100:
|
||||
loadBalancer:
|
||||
servers:
|
||||
- url: "http://YOUR_TOD_HOST:10100"
|
||||
# Add tod-xpra-10101 through tod-xpra-10119 as needed
|
||||
|
||||
tod-api:
|
||||
loadBalancer:
|
||||
servers:
|
||||
- url: "http://YOUR_TOD_HOST:8001"
|
||||
|
||||
tod-frontend:
|
||||
loadBalancer:
|
||||
servers:
|
||||
- url: "http://YOUR_TOD_HOST:3000"
|
||||
|
||||
middlewares:
|
||||
xpra-strip:
|
||||
# Strip /xpra/{port} prefix before forwarding
|
||||
stripPrefixRegex:
|
||||
regex: ["^/xpra/[0-9]+"]
|
||||
|
||||
xpra-headers:
|
||||
headers:
|
||||
# Relaxed CSP for Xpra HTML5 client (inline scripts + eval)
|
||||
customResponseHeaders:
|
||||
Content-Security-Policy: "default-src 'self' 'unsafe-inline' 'unsafe-eval' ws: wss: data: blob:; frame-ancestors 'self';"
|
||||
X-Content-Type-Options: "nosniff"
|
||||
# IMPORTANT: Disable compression for Xpra — compressing WebSocket
|
||||
# binary frames corrupts mouse/keyboard coordinate data.
|
||||
|
||||
security-headers:
|
||||
headers:
|
||||
frameDeny: true
|
||||
contentTypeNosniff: true
|
||||
browserXssFilter: true
|
||||
referrerPolicy: "strict-origin-when-cross-origin"
|
||||
customResponseHeaders:
|
||||
X-Frame-Options: "SAMEORIGIN"
|
||||
|
||||
# IMPORTANT: Disable Traefik's built-in compression for Xpra routes.
|
||||
# If using --entrypoints.websecure.http.middlewares=compress@...,
|
||||
# exclude the xpra router or WebSocket binary frames will be corrupted.
|
||||
Reference in New Issue
Block a user