diff --git a/docker-compose.override.yml b/docker-compose.override.yml index 16934fa..290eb2b 100644 --- a/docker-compose.override.yml +++ b/docker-compose.override.yml @@ -81,6 +81,20 @@ services: OPENBAO_TOKEN: dev-openbao-token POLL_INTERVAL_SECONDS: 60 WIREGUARD_GATEWAY: wireguard + TUNNEL_PORT_MIN: 49000 + TUNNEL_PORT_MAX: 49100 + TUNNEL_IDLE_TIMEOUT: 300 + SSH_RELAY_PORT: 8080 + SSH_IDLE_TIMEOUT: 900 + SSH_MAX_SESSIONS: 200 + SSH_MAX_PER_USER: 10 + SSH_MAX_PER_DEVICE: 20 + ports: + - "127.0.0.1:49000-49100:49000-49100" + ulimits: + nofile: + soft: 8192 + hard: 8192 cap_add: - NET_ADMIN depends_on: @@ -90,6 +104,11 @@ services: condition: service_healthy nats: condition: service_healthy + healthcheck: + test: ["CMD-SHELL", "wget --spider -q http://localhost:8080/healthz || exit 1"] + interval: 30s + timeout: 3s + retries: 3 deploy: resources: limits: diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml index 27f1dbb..6f0193d 100644 --- a/docker-compose.prod.yml +++ b/docker-compose.prod.yml @@ -44,6 +44,20 @@ services: environment: ENVIRONMENT: production LOG_LEVEL: info + TUNNEL_PORT_MIN: 49000 + TUNNEL_PORT_MAX: 49100 + TUNNEL_IDLE_TIMEOUT: 300 + SSH_RELAY_PORT: 8080 + SSH_IDLE_TIMEOUT: 900 + SSH_MAX_SESSIONS: 200 + SSH_MAX_PER_USER: 10 + SSH_MAX_PER_DEVICE: 20 + ports: + - "127.0.0.1:49000-49100:49000-49100" + ulimits: + nofile: + soft: 8192 + hard: 8192 depends_on: postgres: condition: service_healthy @@ -51,10 +65,15 @@ services: condition: service_healthy nats: condition: service_healthy + healthcheck: + test: ["CMD-SHELL", "wget --spider -q http://localhost:8080/healthz || exit 1"] + interval: 30s + timeout: 3s + retries: 3 deploy: resources: limits: - memory: 256M + memory: 512M # increased from 256M for tunnel/SSH overhead restart: unless-stopped logging: driver: json-file diff --git a/infrastructure/docker/nginx-spa.conf b/infrastructure/docker/nginx-spa.conf index d775db5..3ae014d 100644 --- a/infrastructure/docker/nginx-spa.conf +++ b/infrastructure/docker/nginx-spa.conf @@ -1,3 +1,8 @@ +map $http_upgrade $connection_upgrade { + default upgrade; + '' close; +} + server { listen 80; server_name _; @@ -18,7 +23,7 @@ server { # CSP for React SPA with Tailwind CSS and Leaflet maps # worker-src required for SRP key derivation Web Worker (Safari won't fall back to script-src) - 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'; worker-src 'self'; frame-ancestors 'none'; base-uri 'self'; form-action 'self';" 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 'none'; base-uri 'self'; form-action 'self';" always; # Proxy API requests to the backend service # The api container is reachable via Docker internal DNS as "api" on port 8000 @@ -40,6 +45,29 @@ server { proxy_hide_header Content-Security-Policy; } + # WebSocket proxy for SSH terminal + location /ws/ssh { + resolver 127.0.0.11 valid=10s ipv6=off; + set $poller_upstream http://poller:8080; + + proxy_pass $poller_upstream; + proxy_http_version 1.1; + + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection $connection_upgrade; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header Host $host; + + proxy_read_timeout 1800s; + proxy_send_timeout 1800s; + + proxy_buffering off; + proxy_request_buffering off; + proxy_busy_buffers_size 512k; + proxy_buffers 8 512k; + } + # Serve static assets with long cache headers # Note: add_header in a location block clears parent-block headers, # so we re-add the essential security header for static assets.