1
0
Fork 0

Add support for CORS in HAProxy stats dashboard

- Introduced `HAPROXY_STATS_CORS_ORIGIN` environment variable for enabling CORS in HAProxy stats.
- Updated HAProxy configuration template to handle CORS preflight and response headers.
- Added tests for static configuration mode with CORS enabled.
- Updated documentation to include new CORS configuration details and examples.
This commit is contained in:
Joao Gilberto Magalhaes 2026-02-16 20:41:49 -05:00
parent 353fd392f6
commit 491f4a8c09
8 changed files with 203 additions and 23 deletions

View file

@ -0,0 +1,77 @@
global
log stdout format raw local0 info
maxconn 2000
tune.ssl.default-dh-param 2048
# intermediate configuration
ssl-default-bind-ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384
ssl-default-bind-ciphersuites TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256
ssl-default-bind-options prefer-client-ciphers no-sslv3 no-tlsv10 no-tlsv11 no-tls-tickets
ssl-default-server-ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384
ssl-default-server-ciphersuites TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256
ssl-default-server-options no-sslv3 no-tlsv10 no-tlsv11 no-tls-tickets
ssl-dh-param-file /etc/easyhaproxy/haproxy/dhparam
defaults
log global
unique-id-format %{+X}o\ %ci:%cp_%fi:%fp_%Ts_%rt:%pid
unique-id-header X-Edge-Request-ID
option httplog
timeout connect 3s
timeout client 10s
timeout server 10m
frontend stats
bind *:1936
mode http
http-request use-service prometheus-exporter if { path /metrics }
# CORS for stats dashboard (only for configured origin)
acl from_ui hdr(Origin) -i http://localhost:3000
acl preflight method OPTIONS
# Preflight response
http-request return status 204 hdr "Access-Control-Allow-Origin" "%[req.hdr(Origin)]" hdr "Access-Control-Allow-Methods" "GET, OPTIONS" hdr "Access-Control-Allow-Headers" "Authorization, Content-Type" hdr "Access-Control-Max-Age" "86400" hdr "Vary" "Origin" if from_ui preflight
# Actual response headers
http-after-response set-header Access-Control-Allow-Origin "http://localhost:3000"
http-after-response set-header Access-Control-Allow-Methods "GET, OPTIONS"
http-after-response set-header Access-Control-Allow-Headers "Authorization, Content-Type"
http-after-response set-header Access-Control-Expose-Headers "X-Request-ID"
http-after-response set-header Vary "Origin"
stats enable
stats hide-version
stats realm Haproxy\ Statistics
stats uri /
stats auth admin:test123
default_backend srv_stats
backend srv_stats
mode http
server Local 127.0.0.1:1936
frontend http_in_80
bind *:80
mode http
acl is_rule_host1_com_br_80_1 hdr(host) -i host1.com.br
acl is_rule_host1_com_br_80_2 hdr(host) -i host1.com.br:80
use_backend srv_host1_com_br_80 if is_rule_host1_com_br_80_1 OR is_rule_host1_com_br_80_2
backend srv_host1_com_br_80
balance roundrobin
mode http
option forwardfor
http-request set-header X-Forwarded-Port %[dst_port]
http-request add-header X-Forwarded-Proto https if { ssl_fc }
http-request set-header X-Forwarded-Host %[req.hdr(Host)]
http-request set-header X-Request-ID %[uuid()]
server srv-0 container:5000 check weight 1
backend certbot_backend
mode http
server certbot 127.0.0.1:2080

11
tests/fixtures/static_cors.yml vendored Normal file
View file

@ -0,0 +1,11 @@
stats:
username: admin
password: test123
port: 1936
cors_origin: http://localhost:3000
customerrors: false
containers:
"host1.com.br:80":
ip: ["container:5000"]

View file

@ -149,8 +149,8 @@ def test_container_env_stats_password():
"stats": {
"username": "admin",
"password": "xyz",
"port": "1936"
"port": "1936",
"cors_origin": ""
},
"logLevel": {
"easyhaproxy": Functions.DEBUG,
@ -191,7 +191,8 @@ def test_container_env_stats_password_2():
"stats": {
"username": "abc",
"password": "xyz",
"port": "2101"
"port": "2101",
"cors_origin": ""
},
"logLevel": {
"easyhaproxy": Functions.DEBUG,

View file

@ -90,4 +90,36 @@ def test_processor_static_multiple_domains_same_container():
# Both should point to the same container
assert haproxy_cfg.count('server srv-0 webapp:8080') == 2
def test_processor_static_with_cors():
"""Test that CORS configuration is properly generated when cors_origin is set"""
ProcessorInterface.static_file = os.path.join(
os.path.dirname(os.path.realpath(__file__)),
"./fixtures/static_cors.yml"
)
static = ProcessorInterface.factory(ProcessorInterface.STATIC)
haproxy_cfg = static.get_haproxy_conf()
# Verify CORS configuration is present in stats frontend
assert '# CORS for stats dashboard (only for configured origin)' in haproxy_cfg
assert 'acl from_ui hdr(Origin) -i http://localhost:3000' in haproxy_cfg
assert 'acl preflight method OPTIONS' in haproxy_cfg
# Verify preflight response
assert 'http-request return status 204' in haproxy_cfg
assert 'hdr "Access-Control-Allow-Origin"' in haproxy_cfg
assert 'hdr "Access-Control-Allow-Methods" "GET, OPTIONS"' in haproxy_cfg
assert 'hdr "Access-Control-Allow-Headers" "Authorization, Content-Type"' in haproxy_cfg
assert 'if from_ui preflight' in haproxy_cfg
# Verify actual response headers (no ACL condition in response phase)
assert 'http-after-response set-header Access-Control-Allow-Origin "http://localhost:3000"' in haproxy_cfg
assert 'http-after-response set-header Access-Control-Expose-Headers "X-Request-ID"' in haproxy_cfg
assert 'http-after-response set-header Vary "Origin"' in haproxy_cfg
# Verify the full config matches expected
assert haproxy_cfg == Functions.load(
os.path.join(os.path.dirname(os.path.realpath(__file__)), "./expected/static-cors.txt"))
# test_processor_static()