@@ -16,104 +16,70 @@ defaults
1616 timeout queue 15s
1717 timeout tunnel 4h # for websocket
1818
19- frontend test
20- mode http
21- bind * :9090
22-
19+ frontend http-in
20+ bind * :80
21+ option http-buffer-request
22+
2323 unique-id-format %[uuid()]
2424 unique-id-header X-Unique-ID
2525 filter spoe engine crowdsec config /etc/haproxy/crowdsec.cfg
26- acl crowdsec_challenge_backend_path path_beg /crowdsec-challenge/ /crowdsec-internal/challenge/
27-
28- # /crowdsec-internal/challenge/* bypasses the normal SPOE crowdsec-tcp/http
29- # checks below (it's how the bouncer relays challenge follow-up asset/proof
30- # requests to AppSec without re-triggering a fresh challenge decision), so it
31- # gets no dataset-based IP check from CrowdSec at that layer. Rate-limit it
32- # here independently, in addition to the IP/dataset ban check the bouncer
33- # itself now performs in handleInternalChallengeHTTP.
34- stick-table type ip size 100k expire 10s store http_req_rate(10s )
35- http-request track-sc0 src if crowdsec_challenge_backend_path
36- http-request deny status 429 if crowdsec_challenge_backend_path { sc_http_req_rate(0 ) gt 20 }
37-
38- # ACL for body size limit (100000 bytes = ~100KB) - adjust here to change limit globally
39- # Note spop protocol has limitations on the size of the message, so altering this value will not ensure the whole
40- # body is sent for processing but should be enough to prevent overwhelming the SPOA bouncer.
41- acl body_within_limit req.body_size -m int le 51200 # 50KB - stay safely under SPOE frame limit
42-
43- # HTML-capable client detection. Clients without an Accept header are treated as HTML-capable.
44- # '-m sub text/html' covers the common case; '*/*' covers wildcard acceptors.
45- # html_rejected excludes clients that include text/html but mark it as not acceptable (q=0).
26+
27+ acl crowdsec_challenge_backend_path path_beg /crowdsec-internal/challenge/
28+ acl is_challenge var(txn.crowdsec.remediation) -m str "challenge"
29+
30+ # Body size limit: stay safely under SPOE frame limit (~50 KB)
31+ acl body_within_limit req.body_size -m int le 51200
32+
33+ # HTML-capable client detection
4634 acl render_html req.hdr_cnt(Accept) eq 0
4735 acl render_html req.hdr(Accept) -m sub text/html
4836 acl render_html req.hdr(Accept) -m sub */*
49- # Single-quoted (not double-quoted): HAProxy expands $VAR references inside double-quoted
50- # strings, and this regex's own trailing "$" (end-of-line anchor) would otherwise be
51- # misparsed as the start of an environment variable reference.
52- acl html_rejected req.hdr(Accept) -m reg -i 'text/html[[:space:]]*;[[:space:]]*q=0 (\ .0 +)?([,[:space:]]|$)'
37+ acl html_rejected req.hdr(Accept) -m reg "text/html;q=0 "
5338
5439 acl has_contact_url var(txn.crowdsec.contact_us_url) -m found
5540 acl empty_contact_url var(txn.crowdsec.contact_us_url) -m str ""
5641
57- ## If you don't want to render any content, you can use the following line
58- # tcp-request content reject if !{ var(txn.crowdsec.remediation) -m str "allow" }
59-
60- ## Drop ban requests before http handler is called
61- # tcp-request content reject if { var(txn.crowdsec.remediation) -m str "ban" }
62-
63- # Send HTTP group conditionally based on body size
64- # TCP handler already checked IP (triggered on client session), HTTP handler will use that remediation
65- # Send group with body when body size <= limit (or no body present)
66- http-request send-spoe-group crowdsec crowdsec-http-body if !crowdsec_challenge_backend_path body_within_limit || !crowdsec_challenge_backend_path !{ req.body_size -m found }
67- # Send group without body when body exists and size > limit
68- http-request send-spoe-group crowdsec crowdsec-http-no-body if !crowdsec_challenge_backend_path !body_within_limit { req.body_size -m found }
42+ http-request send-spoe-group crowdsec crowdsec-http-body if body_within_limit || !{ req.body_size -m found }
43+ http-request send-spoe-group crowdsec crowdsec-http-no-body if !body_within_limit { req.body_size -m found }
6944
70- ## Set a custom header on the request for upstream services to use
7145 http-request set-header X-Crowdsec-Remediation %[var(txn.crowdsec.remediation)] if { var(txn.crowdsec.remediation) -m found }
72- ## Set a custom header on the request for upstream services to use
7346 http-request set-header X-Crowdsec-IsoCode %[var(txn.crowdsec.isocode)] if { var(txn.crowdsec.isocode) -m found }
7447
75- ## Handle 302 redirect for successful captcha validation (redirect to current request URL)
48+ ## Redirect after successful captcha validation
7649 http-request redirect code 302 location %[url] if { var(txn.crowdsec.remediation) -m str "allow" } { var(txn.crowdsec.redirect) -m found }
77- acl is_challenge var(txn.crowdsec.remediation) -m str "challenge"
78- http-request set-path %[var(txn.crowdsec.challenge_url)] if is_challenge { var(txn.crowdsec.challenge_url) -m found }
7950
80- ## Render ban and captcha remediations with native HAProxy templates.
81- ## Template paths are fixed at /var/lib/crowdsec-haproxy-spoa-bouncer/html/; edit the lf-file
82- ## directives below if you need custom locations (the old CROWDSEC_*_TEMPLATE_PATH env vars
83- ## are not read by this non-Lua config).
51+ ## Serve remediation pages with native HAProxy lf-file directives (no Lua needed)
8452 http-request return status 200 content-type "text/html; charset=utf-8 " hdr Cache-Control "no-cache, no-store" lf-file /var/lib/crowdsec-haproxy-spoa-bouncer/html/captcha.html if { var(txn.crowdsec.remediation) -m str "captcha" } render_html !html_rejected
8553 http-request return status 403 content-type "text/html; charset=utf-8 " hdr Cache-Control "no-cache, no-store" lf-file /var/lib/crowdsec-haproxy-spoa-bouncer/html/ban-with-contact.html if { var(txn.crowdsec.remediation) -m str "ban" } render_html !html_rejected has_contact_url !empty_contact_url
8654 http-request return status 403 content-type "text/html; charset=utf-8 " hdr Cache-Control "no-cache, no-store" lf-file /var/lib/crowdsec-haproxy-spoa-bouncer/html/ban.html if { var(txn.crowdsec.remediation) -m str "ban" } render_html !html_rejected !has_contact_url
8755 http-request return status 403 content-type "text/html; charset=utf-8 " hdr Cache-Control "no-cache, no-store" lf-file /var/lib/crowdsec-haproxy-spoa-bouncer/html/ban.html if { var(txn.crowdsec.remediation) -m str "ban" } render_html !html_rejected empty_contact_url
88- ## Non-HTML clients get plain-text responses; captcha returns 200 to preserve the old
89- ## Lua handler's status code (captcha is a challenge, not a hard rejection).
56+ ## Plain-text fallback for non-HTML clients
9057 http-request return status 200 content-type text/plain hdr Cache-Control "no-cache, no-store" string "Captcha required\ n" if { var(txn.crowdsec.remediation) -m str "captcha" } !render_html
9158 http-request return status 200 content-type text/plain hdr Cache-Control "no-cache, no-store" string "Captcha required\ n" if { var(txn.crowdsec.remediation) -m str "captcha" } html_rejected
9259 http-request return status 403 content-type text/plain hdr Cache-Control "no-cache, no-store" string "Forbidden\ n" if { var(txn.crowdsec.remediation) -m str "ban" } !render_html
9360 http-request return status 403 content-type text/plain hdr Cache-Control "no-cache, no-store" string "Forbidden\ n" if { var(txn.crowdsec.remediation) -m str "ban" } html_rejected
9461
95- ## Handle captcha cookie management via HAProxy (new approach)
96- ## Set captcha cookie when SPOA provides captcha_status (pending or valid)
62+ ## Captcha cookie management
9763 http-after-response set-header Set-Cookie %[var(txn.crowdsec.captcha_cookie)] if { var(txn.crowdsec.captcha_status) -m found } { var(txn.crowdsec.captcha_cookie) -m found }
98- ## Clear captcha cookie when cookie exists but no captcha_status (Allow decision)
9964 http-after-response set-header Set-Cookie %[var(txn.crowdsec.captcha_cookie)] if { var(txn.crowdsec.captcha_cookie) -m found } !{ var(txn.crowdsec.captcha_status) -m found }
10065
10166 # REQUIRED if you use challenge mode - do not remove the following line.
10267 http-request set-header X-Crowdsec-Real-Src %[src] if crowdsec_challenge_backend_path
103-
68+
69+ http-request set-path %[var(txn.crowdsec.challenge_url)] if is_challenge { var(txn.crowdsec.challenge_url) -m found }
10470 use_backend crowdsec-challenge if is_challenge { var(txn.crowdsec.challenge_url) -m found }
10571 use_backend crowdsec-challenge if crowdsec_challenge_backend_path
106- use_backend test_backend
72+ use_backend app
10773
108- backend test_backend
109- mode http
110- server s1 whoami:2020
111-
11274backend crowdsec-spoa
11375 mode tcp
11476 timeout connect 2s
11577 timeout server 60s
116- server s2 spoa:9000
78+ server spoa 127.0.0.1:9000
79+
80+ backend app
81+ mode http
82+ http-request return status 200 content-type "text/html" string "<html><body><h1>Hello from HAProxy</h1></body></html>"
11783
11884backend crowdsec-challenge
11985 mode http
@@ -123,4 +89,4 @@ backend crowdsec-challenge
12389 # Challenge responses are cached in the bouncer process that handled SPOE.
12490 # Keep this backend pinned to that same instance; do not load-balance it
12591 # independently unless challenge is shared.
126- server s3 spoa :9100
92+ server s2 127.0.0.1 :9100
0 commit comments