You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
docs: add CDN configuration guide for HAProxy SPOA with firewall recommendations
- Document how to configure SPOA when HAProxy is behind a CDN
- Use X-Real-IP header extraction instead of direct client IP
- Add critical firewall security note about trusted proxy validation
- Include guidance on X-Forwarded-For with left-to-right (1) and right-to-left (-1) IP extraction
- Provide CDN-specific header names and HAProxy functions reference
Copy file name to clipboardExpand all lines: crowdsec-docs/unversioned/bouncers/haproxy_spoa.mdx
+131Lines changed: 131 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -225,6 +225,137 @@ recaptcha
225
225
turnstile
226
226
```
227
227
228
+
#### HAProxy Behind a CDN
229
+
230
+
When HAProxy is deployed behind an upstream Content Delivery Network (CDN), the source IP seen by HAProxy will be the CDN's edge server IP, not the real client IP. To properly evaluate and apply security rules based on the actual client IP, you need to configure the SPOA to extract the real IP from the CDN-provided header.
231
+
232
+
:::info
233
+
234
+
Most CDNs add an `X-Real-IP` or `X-Forwarded-For` header to the request to pass the original client IP. Ensure your CDN is configured to add this header, and adjust the examples below if your CDN uses a different header name.
235
+
236
+
:::
237
+
238
+
##### Configuration Changes
239
+
240
+
When HAProxy is behind a CDN, modify your `/etc/haproxy/crowdsec.cfg` to:
241
+
242
+
1.**Use only the `crowdsec-http` message** (the `crowdsec-ip` message will capture the CDN edge IP, which is not useful)
243
+
2.**Extract the real client IP** from the CDN header using `req.hdr_ip()` to convert it to HAProxy's IP type
244
+
3.**Pass the real IP to the bouncer** via the SPOE message
-**Single message**: Only `crowdsec-http` is used. The `crowdsec-ip` message would run at `on-client-session` and capture the CDN's IP, not the real client IP, so it's omitted.
286
+
-**IP extraction**: The `req.hdr_ip(x-real-ip)` function extracts the IP from the `X-Real-IP` header and converts it to HAProxy's IP type, which is required by the SPOE protocol.
287
+
-**Header name**: If your CDN uses a different header (e.g., `X-Forwarded-For`, `CF-Connecting-IP` for Cloudflare), adjust the header name accordingly. For Cloudflare specifically, use `req.hdr_ip(cf-connecting-ip)`.
288
+
289
+
:::warning Firewall Rules for Trusted Proxies
290
+
291
+
Since your SPOA bouncer now relies on the `X-Real-IP` header to determine the client IP, **it is critical to ensure that only your trusted upstream CDN proxy can connect to your HAProxy server**.
292
+
293
+
If you do not properly firewall your HAProxy port, an attacker could connect directly and spoof the `X-Real-IP` header, bypassing your security rules.
294
+
295
+
**Ensure your firewall is configured to only allow connections to your HAProxy port (typically 80/443) from your upstream CDN provider's IP ranges.** Always verify your CDN provider's current IP ranges and keep your firewall rules up to date.
296
+
297
+
:::
298
+
299
+
##### HAProxy Configuration
300
+
301
+
Your `/etc/haproxy/haproxy.cfg` frontend configuration remains mostly the same, but ensure the CDN header is being passed through:
302
+
303
+
```haproxy
304
+
frontend http-in
305
+
bind *:80
306
+
307
+
# Ensure the CDN header is preserved (may already be done by your CDN)
308
+
# You can optionally add debugging with set-header
http-request redirect code 302 location %[var(txn.crowdsec.redirect)] if { var(txn.crowdsec.remediation) -m str "allow" } { var(txn.crowdsec.redirect) -m found }
316
+
317
+
## Call lua script only for ban and captcha remediations (performance optimization)
318
+
http-request lua.crowdsec_handle if { var(txn.crowdsec.remediation) -m str "captcha" }
319
+
http-request lua.crowdsec_handle if { var(txn.crowdsec.remediation) -m str "ban" }
320
+
321
+
## Handle captcha cookie management via HAProxy (new approach)
322
+
## Set captcha cookie when SPOA provides captcha_status (pending or valid)
323
+
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 }
324
+
## Clear captcha cookie when cookie exists but no captcha_status (Allow decision)
325
+
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 }
326
+
327
+
use_backend <whatever>
328
+
329
+
backend crowdsec-spoa
330
+
mode tcp
331
+
server s1 127.0.0.1:9000
332
+
```
333
+
334
+
##### Common CDN Headers
335
+
336
+
| CDN Provider | Header Name | HAProxy Function |
337
+
|--------------|------------|------------------|
338
+
| Generic / Most CDNs |`X-Real-IP`|`req.hdr_ip(x-real-ip)`|
If your CDN uses `X-Forwarded-For` with multiple IPs (comma-separated), you'll need to extract the correct IP. For example:
347
+
348
+
```haproxy
349
+
src-ip=req.hdr_ip(x-forwarded-for,1)
350
+
```
351
+
352
+
This tells HAProxy to use the first IP from the comma-separated list. If your CDN appends IPs from right to left (instead of left to right), you can use `-1` to extract the rightmost IP:
0 commit comments