# HAProxy example configuration for rustguac # # This config terminates TLS at HAProxy and proxies to rustguac on localhost. # rustguac listens on 127.0.0.1:8089 with its own TLS (double encryption on # loopback — belt and suspenders). Adjust "verify none" to "verify required # ca-file /path/to/ca.pem" if using a real CA-signed cert for rustguac. # # WebSocket support requires tunnel timeout for long-lived connections. # # Knocknoc integration (https://knocknoc.io): # knocknoc-agent dynamically adds/removes client IPs to HAProxy ACLs via # the admin socket, providing zero-trust network access. Only users who # have authenticated through Knocknoc can reach the login page. # All other paths (OIDC flow, API, WebSocket, share links) pass through # to rustguac which has its own auth layer. global log /dev/log local0 log /dev/log local1 notice # Admin socket — knocknoc-agent uses this to manage dynamic ACLs. # Ensure knocknoc-agent user is in the haproxy group: # adduser knocknoc-agent haproxy stats socket /run/haproxy/admin.sock mode 0660 level admin user haproxy group haproxy user haproxy group haproxy daemon # Modern TLS defaults ssl-default-bind-ciphersuites TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256 ssl-default-bind-ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384 ssl-default-bind-options ssl-min-ver TLSv1.2 no-tls-tickets ssl-default-server-ciphersuites TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256 ssl-default-server-options ssl-min-ver TLSv1.2 defaults log global mode http option httplog option dontlognull timeout connect 5s timeout client 30s timeout server 30s # WebSocket/tunnel connections need a long timeout — sessions can last hours. # This should be >= rustguac's session_max_duration_secs (default 8h). timeout tunnel 8h # Abort slow client sends (protects against slowloris) timeout http-request 10s timeout http-keep-alive 5s frontend https bind *:443 ssl crt /etc/ssl/private/rustguac.pem alpn h2,http/1.1 bind *:80 mode http # Redirect HTTP → HTTPS http-request redirect scheme https unless { ssl_fc } # Forward client IP to rustguac (used by rate limiting and audit logging). # Delete any incoming X-Forwarded-For first to prevent spoofing, then let # HAProxy add the real client IP as a fresh single-valued header. http-request del-header X-Forwarded-For option forwardfor http-request set-header X-Forwarded-Port %[dst_port] http-request set-header X-Forwarded-Proto https if { ssl_fc } # # rustguac must trust HAProxy's source IP to honour X-Forwarded-For. # Add this to your rustguac config.toml: # trusted_proxies = ["127.0.0.1/32"] # HSTS header (HAProxy layer — rustguac also sets this when TLS is enabled) http-response set-header Strict-Transport-Security "max-age=31536000; includeSubDomains" if { ssl_fc } # ── Knocknoc dynamic IP allowlist ──────────────────────────────────── # # knocknoc-agent populates this ACL via the admin socket. The ACL ID (600) # must match what you configure in Knocknoc admin (Knocs > Identity Aware # Proxy > ACL ID). Pick any unused ID. # # Verify currently allowed IPs: # echo "show acl #600" | socat stdio /run/haproxy/admin.sock acl knoc_rustguac src -u 600 # ── Route classification ───────────────────────────────────────────── # Match requests to this rustguac instance by hostname acl is_rustguac hdr(host) -i console.example.com # Internal/server IPs that always bypass Knocknoc. # IMPORTANT: Include the server's own public IP if hairpin NAT is possible, # and 127.0.0.0/8 for local health checks. acl is_internal src 127.0.0.0/8 # Front page — gate behind Knocknoc to hide the login UI from scanners. # Note: only gate `/` (the login page). Don't gate the API, OIDC flow, # WebSocket, or share links — rustguac has its own auth for those, and # gating them at HAProxy breaks OIDC callbacks and share link access. acl is_root path / # ── Routing rules ──────────────────────────────────────────────────── # Front page: require Knocknoc or internal IP use_backend rustguac if is_rustguac is_root knoc_rustguac use_backend rustguac if is_rustguac is_root is_internal use_backend denied if is_rustguac is_root # All other paths pass through to rustguac (has its own auth) use_backend rustguac if is_rustguac default_backend denied backend rustguac mode http balance roundrobin # Health check against the unauthenticated /api/health endpoint. option httpchk GET /api/health http-check expect status 200 # rustguac on loopback with TLS. Use "verify none" for self-signed certs, # or "verify required ca-file /opt/rustguac/tls/cert.pem" for strict checking. server rustguac 127.0.0.1:8089 ssl verify none check inter 30s backend denied mode http # Custom 403 page. The .http file includes full HTTP headers + HTML body. errorfile 403 /etc/haproxy/errors/403.http http-request deny deny_status 403