Backend: force_http1 (bool, default false) — zwingt HAProxy auf der Backend-Verbindung zu HTTP/1.1 statt h2,http/1.1 zu verhandeln. Nötig für Legacy-Apps die kein h2 sprechen. Domain: disable_h3 (bool, default false) — unterdrückt Alt-Svc- Response-Header für diese Domain. Browser erhalten keinen h3/QUIC- Hinweis und bleiben auf h2/http1.1. Migration 0027, Model+Service+HAProxy-Template+UI+i18n. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
189 lines
8.8 KiB
Smarty
189 lines
8.8 KiB
Smarty
# Generated by edgeguard-api — DO NOT EDIT.
|
||
# Source: internal/haproxy/haproxy.go (template: haproxy.cfg.tpl).
|
||
# Re-generate via `edgeguard-ctl render-config`.
|
||
|
||
global
|
||
log /dev/log local0 info
|
||
log /dev/log local1 notice
|
||
user haproxy
|
||
group haproxy
|
||
daemon
|
||
ssl-default-bind-options ssl-min-ver TLSv1.2 no-tls-tickets
|
||
ssl-default-bind-ciphersuites TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256
|
||
# HAProxy 3.0.x in Trixie ist mit USE_QUIC=1 + USE_QUIC_OPENSSL_COMPAT=1
|
||
# gebaut; die System-OpenSSL kann QUIC aber nur über den Kompat-Layer.
|
||
# Ohne dieses Flag verweigert HAProxy `bind quic4@…` mit "this SSL
|
||
# library does not support the QUIC protocol".
|
||
limited-quic
|
||
|
||
defaults
|
||
log global
|
||
mode http
|
||
option httplog
|
||
option dontlognull
|
||
option forwardfor
|
||
timeout connect 5s
|
||
timeout client 60s
|
||
timeout server 60s
|
||
timeout http-request 10s
|
||
# timeout tunnel wird PER BACKEND aktiviert (websocket-Flag),
|
||
# damit nur WS-Workloads die lange Idle-Toleranz haben und normale
|
||
# HTTP-Backends saubere Connection-Hygiene behalten.
|
||
|
||
# ── Public :80 ─────────────────────────────────────────────────────────
|
||
# ACME-01 challenges proxy to edgeguard-api which serves the webroot.
|
||
# Everything else redirects to HTTPS.
|
||
frontend public_http
|
||
bind :80
|
||
|
||
acl is_acme path_beg /.well-known/acme-challenge/
|
||
|
||
# Redirect to HTTPS first (skipped for ACME paths) — must come
|
||
# before use_backend so HAProxy doesn't warn about ordering.
|
||
http-request redirect scheme https code 301 unless is_acme
|
||
|
||
use_backend api_backend if is_acme
|
||
|
||
# ── Public :443 (Customer-Backends only) ──────────────────────────────
|
||
# TLS termination. Reads certs from /etc/edgeguard/tls/ — postinst
|
||
# seeds a self-signed _default.pem so HAProxy starts before certbot
|
||
# has issued anything.
|
||
#
|
||
# WICHTIG: kein default_backend → unbekannte Hosts kriegen 503. Die
|
||
# Management-UI sitzt bewusst auf :3443 (siehe mgmt_https unten),
|
||
# damit ein versehentlich offengelassenes Wildcard-DNS nie auf das
|
||
# Admin-Panel fällt. mailgateway/enconf-Pattern.
|
||
frontend public_https
|
||
bind :443 ssl crt /etc/edgeguard/tls/ alpn h2,http/1.1
|
||
# HTTP/3 via QUIC (UDP/443). HAProxy 3.0.x ist mit USE_QUIC=1
|
||
# gebaut; Browser fallen via Alt-Svc-Header (siehe unten) für
|
||
# Folge-Requests auf h3 zurück.
|
||
bind quic4@:443 ssl crt /etc/edgeguard/tls/ alpn h3
|
||
|
||
# Alt-Svc: signalisiert dass h3 auf demselben Port verfügbar ist.
|
||
# ma=86400 = Browser darf den Hinweis 24h cachen.
|
||
http-response set-header Alt-Svc "h3=\":443\"; ma=86400"
|
||
|
||
# Client-IP-Weiterleitung an Backends. `option forwardfor` (defaults)
|
||
# setzt X-Forwarded-For; wir ergänzen Proto + RealIP damit Apps
|
||
# erkennen können (a) dass der Client HTTPS sprach und (b) die
|
||
# echte Source-IP ohne XFF-Chain-Parsing brauchen.
|
||
http-request set-header X-Forwarded-Proto https
|
||
http-request set-header X-Real-IP %[src]
|
||
|
||
{{- if .GlobalMaintenance}}
|
||
# Whole-Box-Maintenance — Settings → Maintenance-Mode aktiv. Dieser
|
||
# Block kommt VOR allen per-Domain ACLs und blockt JEDEN Customer-
|
||
# Request. Mgmt-UI auf :3443 (mgmt_https) ist davon nicht betroffen.
|
||
http-request return status 503 content-type "text/plain; charset=utf-8" string "{{.GlobalMaintenanceMessage}}"
|
||
{{- end}}
|
||
|
||
{{- range $d := .Domains}}
|
||
{{- if $d.RedirectFromHost}}
|
||
# www-Redirect: {{$d.RedirectFromHost}} → {{$d.Name}}
|
||
http-request redirect prefix https://{{$d.Name}} code 301 if { hdr(host) -i {{$d.RedirectFromHost}} }
|
||
{{- end}}
|
||
{{- if $d.MaintenanceMode}}
|
||
# Wartungs-Modus für {{$d.Name}} — alle Requests werden mit 503 beantwortet.
|
||
http-request return status 503 content-type "text/plain; charset=utf-8" string "{{$d.MaintMessage}}" if { hdr(host) -i {{$d.Name}} }
|
||
{{- end}}
|
||
{{- if $d.MaxBodyBytes}}
|
||
# Body-Size-Cap für {{$d.Name}}: {{$d.MaxBodyBytes}} Bytes.
|
||
# Nur Content-Length wird geprüft — Chunked-Bodies wären erst nach
|
||
# http-buffer-request abgreifbar, das wollen wir auf Frontend-Level
|
||
# nicht aktivieren (verbraucht RAM pro Connection).
|
||
http-request deny deny_status 413 if { hdr(host) -i {{$d.Name}} } { req.hdr_val(content-length) -m int gt {{$d.MaxBodyBytes}} }
|
||
{{- end}}
|
||
{{- if $d.RateLimitThreshold}}
|
||
# Rate-Limit für {{$d.Name}}: {{$d.RateLimitRPS}} req/s pro Client-IP
|
||
# (Schwelle = rps × 10s-Fenster = {{$d.RateLimitThreshold}} hits).
|
||
http-request track-sc0 src table rl_{{$d.ID}} if { hdr(host) -i {{$d.Name}} }
|
||
http-request deny deny_status 429 if { hdr(host) -i {{$d.Name}} } { sc_http_req_rate(0) gt {{$d.RateLimitThreshold}} }
|
||
{{- end}}
|
||
{{- if $d.HSTSHeader}}
|
||
# del + set damit ein vom Backend gesetztes HSTS (z. B. nginx mit
|
||
# eigener add_header-Direktive) garantiert von unserer Policy
|
||
# überschrieben wird — sonst tauchen 2 Strict-Transport-Security
|
||
# Header in der Response auf.
|
||
http-response del-header Strict-Transport-Security if { hdr(host) -i {{$d.Name}} }
|
||
http-response set-header Strict-Transport-Security "{{$d.HSTSHeader}}" if { hdr(host) -i {{$d.Name}} }
|
||
{{- end}}
|
||
{{- if $d.DisableH3}}
|
||
# HTTP/3 (QUIC) Werbung für diese Domain unterdrücken: Alt-Svc-Header
|
||
# entfernen damit Browser nicht auf h3 upgraden. Der globale set-header
|
||
# oben hat ihn bereits gesetzt; dieses del-header kommt DANACH und
|
||
# überschreibt ihn für Requests an diese Domain.
|
||
http-response del-header Alt-Svc if { hdr(host) -i {{$d.Name}} }
|
||
{{- end}}
|
||
{{- range $h := $d.ResponseHeaders}}
|
||
http-response del-header {{$h.Name}} if { hdr(host) -i {{$d.Name}} }
|
||
http-response set-header {{$h.Name}} "{{$h.Value}}" if { hdr(host) -i {{$d.Name}} }
|
||
{{- end}}
|
||
{{- range $r := $d.Routes}}
|
||
use_backend eg_backend_{{$r.BackendID}} if { hdr(host) -i {{$d.Name}} } { path_beg {{$r.PathPrefix}} }
|
||
{{- end}}
|
||
{{- if $d.PrimaryBackendID}}
|
||
use_backend eg_backend_{{$d.PrimaryBackendID}} if { hdr(host) -i {{$d.Name}} }
|
||
{{- end}}
|
||
{{- end}}
|
||
|
||
# ── Mgmt :3443 (Admin-UI only) ────────────────────────────────────────
|
||
# Eigener Port für die Management-UI — gleicher Cert-Pool, aber kein
|
||
# Customer-Routing. Anti-Lockout-Regel im nft-Template lässt 3443
|
||
# immer durch. Erreichbar über jede Domain die auf die Box zeigt
|
||
# (Hostname egal — default_backend), inkl. der direkten IP.
|
||
frontend mgmt_https
|
||
bind :3443 ssl crt /etc/edgeguard/tls/ alpn h2,http/1.1
|
||
http-response set-header Strict-Transport-Security "max-age=31536000"
|
||
http-request set-header X-Forwarded-Proto https
|
||
http-request set-header X-Real-IP %[src]
|
||
default_backend api_backend
|
||
|
||
# ── Internal stats ─────────────────────────────────────────────────────
|
||
frontend internal_stats
|
||
bind 127.0.0.1:8404
|
||
stats enable
|
||
stats uri /stats
|
||
stats refresh 10s
|
||
stats admin if { src 127.0.0.1 }
|
||
|
||
# ── Backends ───────────────────────────────────────────────────────────
|
||
|
||
# edgeguard-api itself: management UI, REST API, ACME webroot.
|
||
# timeout tunnel 1h: /api/v1/firewall/log/live ist ein langlebiger
|
||
# WebSocket-Stream — ohne Tunnel-Override fällt er nach `timeout
|
||
# server 60s` ohne Events um.
|
||
backend api_backend
|
||
timeout tunnel 1h
|
||
server api1 127.0.0.1:9443 check
|
||
|
||
{{- /* Per-Domain Rate-Limit Stick-Tables.
|
||
Eigenes Backend pro Domain damit verschiedene Schwellen nicht
|
||
miteinander interferieren; HAProxy 3.0 erlaubt mehrere
|
||
stick-table-Backends die per Frontend mit track-sc0 angesteuert
|
||
werden. expire 10s hält den Speicher knapp. */ -}}
|
||
{{- range $d := .Domains}}
|
||
{{- if $d.RateLimitThreshold}}
|
||
|
||
backend rl_{{$d.ID}}
|
||
# Rate-Limit-Counter für {{$d.Name}}; gefüllt von public_https.
|
||
stick-table type ip size 100k expire 10s store http_req_rate(10s)
|
||
{{- end}}
|
||
{{- end}}
|
||
|
||
{{- range $b := .Backends}}
|
||
|
||
backend eg_backend_{{$b.ID}}
|
||
balance {{$b.LBAlgorithm}}
|
||
{{- if $b.WebSocket}}
|
||
timeout tunnel 1h
|
||
{{- end}}
|
||
{{- if $b.HealthCheckPath}}
|
||
option httpchk
|
||
http-check send meth GET uri {{$b.HealthCheckPath}}
|
||
{{- end}}
|
||
{{- range $s := $b.Servers}}
|
||
server {{$s.Name | safeID}} {{$s.Address}}:{{$s.Port}}{{if eq $b.Scheme "https"}} ssl verify none alpn {{if $b.ForceHTTP1}}http/1.1{{else}}h2,http/1.1{{end}}{{end}}{{if $b.HealthCheckPath}} check inter 5s{{if eq $b.Scheme "https"}} check-alpn http/1.1{{end}}{{end}} weight {{$s.Weight}}{{if $s.Backup}} backup{{end}}
|
||
{{- end}}
|
||
{{- end}}
|