fix(coraza-spoe): match upstream's required spoe shape (groups, arg order, names)
All checks were successful
HAProxy Manager Build and Push / Build-and-Push (push) Successful in 1m18s
All checks were successful
HAProxy Manager Build and Push / Build-and-Push (push) Successful in 1m18s
Three real bugs in the SPOE config caught when HAProxy validated the
generated file:
1. spoe-agent must declare `groups` not `messages`. The `messages` form
doesn't make the message reachable via `send-spoe-group`; HAProxy
complained:
unable to find SPOE group 'coraza-check' into SPOE engine 'coraza'
2. send-spoe-group references a spoe-GROUP name, which needs its own
block. Added `spoe-group coraza-req { messages coraza-req }` as
the indirection layer.
3. Arg names + ORDER are required to match what Coraza-SPOA parses
positionally. My version had `dest-ip`/`dest-port`; upstream's
example/haproxy/coraza.cfg (v0.7.1) uses `dst-ip`/`dst-port`.
Renamed and reordered to match upstream verbatim, including the
`app=str(haproxy)` literal that matches our config.yaml application
name.
Also corrected misleading comment about `set-on-error continue`: that
option actually sets a variable on error; the fail-open behavior comes
from us deliberately NOT adding a `http-request deny if errored` rule
in the frontend. Renamed the variable to `error` (matching upstream)
and updated comments to be accurate.
Listener template's send-spoe-group action updated to reference the
new group name `coraza-req`.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -5,25 +5,33 @@
|
|||||||
# via `filter spoe engine coraza config /etc/haproxy/coraza-spoe.cfg`.
|
# via `filter spoe engine coraza config /etc/haproxy/coraza-spoe.cfg`.
|
||||||
#
|
#
|
||||||
# Engine name "coraza" must match the engine name in the filter line in the
|
# Engine name "coraza" must match the engine name in the filter line in the
|
||||||
# main config and the application name "haproxy" must match the application
|
# main config; group name "coraza-req" must match the send-spoe-group action.
|
||||||
# block name in coraza-spoa's config.yaml.
|
# Application name "haproxy" must match the application block in coraza-spoa's
|
||||||
|
# config.yaml.
|
||||||
|
#
|
||||||
|
# Reference: this config follows the shape from coraza-spoa's upstream
|
||||||
|
# example/haproxy/coraza.cfg (v0.7.1). Arg names + ordering are required by
|
||||||
|
# Coraza-SPOA exactly as specified — DO NOT reorder or rename without
|
||||||
|
# coordinating with the agent.
|
||||||
|
|
||||||
[coraza]
|
[coraza]
|
||||||
|
|
||||||
spoe-agent coraza
|
spoe-agent coraza
|
||||||
# The single message we send (defined below) — per-request inspection.
|
# `groups` (not `messages`) lists the spoe-group names this engine offers
|
||||||
messages coraza-check
|
# via `send-spoe-group` actions. The same group name appears below in a
|
||||||
|
# spoe-group block, which in turn references the actual message.
|
||||||
|
groups coraza-req
|
||||||
|
|
||||||
# Prefix for any variables the agent sets back on the request.
|
# Prefix for variables the agent sets back on the request transaction —
|
||||||
|
# e.g. var(txn.coraza.error) when set-on-error triggers.
|
||||||
option var-prefix coraza
|
option var-prefix coraza
|
||||||
|
|
||||||
# FAIL-OPEN. If the SPOA is unreachable or times out, requests flow
|
# On agent error/timeout, set var(txn.coraza.error). We DON'T add a
|
||||||
# through uninspected rather than failing. For a hosting platform,
|
# corresponding `http-request deny if { var(txn.coraza.error) -m bool }`
|
||||||
# availability beats unconditional inspection coverage.
|
# in the frontend, so the request continues uninspected. This is the
|
||||||
option set-on-error continue
|
# fail-open posture: WAF outage shouldn't 503 customer traffic.
|
||||||
|
option set-on-error error
|
||||||
|
|
||||||
# Aggressive timeouts: we don't want the WAF to materially slow page
|
|
||||||
# loads. processing 100ms is the per-request inspection budget.
|
|
||||||
timeout hello 2s
|
timeout hello 2s
|
||||||
timeout idle 2m
|
timeout idle 2m
|
||||||
timeout processing 100ms
|
timeout processing 100ms
|
||||||
@@ -31,12 +39,16 @@ spoe-agent coraza
|
|||||||
use-backend coraza-spoa-backend
|
use-backend coraza-spoa-backend
|
||||||
log global
|
log global
|
||||||
|
|
||||||
spoe-message coraza-check
|
# Per-request inspection message. No `event` directive — fires only when
|
||||||
# Send the request shape to Coraza for inspection.
|
# explicitly invoked from haproxy.cfg via `http-request send-spoe-group`.
|
||||||
# `app=str(haproxy)` matches the application named "haproxy" in
|
# Arg order/names are mandatory: Coraza-SPOA parses positionally and renames
|
||||||
# coraza-spoa's config.yaml — that's how Coraza picks which ruleset
|
# break the agent. `app=str(haproxy)` is the literal application name from
|
||||||
# to apply.
|
# coraza-spoa's config.yaml `applications:` block.
|
||||||
# NOTE: args must be on ONE line. HAProxy does not support backslash
|
spoe-message coraza-req
|
||||||
# line continuations in spoe configs (verified the hard way 2026-05-12).
|
args app=str(haproxy) src-ip=src src-port=src_port dst-ip=dst dst-port=dst_port method=method path=path query=query version=req.ver headers=req.hdrs body=req.body
|
||||||
args app=str(haproxy) src-ip=src src-port=src_port dest-ip=dst dest-port=dst_port method=method path=path query=query version=req.ver headers=req.hdrs body=req.body
|
|
||||||
event on-frontend-http-request
|
# Group binding for send-spoe-group invocation in the frontend. One group,
|
||||||
|
# one message; could add more in the future (e.g. coraza-res for response
|
||||||
|
# inspection — currently disabled in coraza-spoa's config.yaml).
|
||||||
|
spoe-group coraza-req
|
||||||
|
messages coraza-req
|
||||||
|
|||||||
@@ -58,7 +58,9 @@ frontend web
|
|||||||
# Coraza WAF inspection via SPOE. Runs AFTER rate-limit and IP-block
|
# Coraza WAF inspection via SPOE. Runs AFTER rate-limit and IP-block
|
||||||
# guards (no point asking the WAF about requests we're already dropping)
|
# guards (no point asking the WAF about requests we're already dropping)
|
||||||
# and AFTER the real-client-IP resolution (so Coraza sees the right src).
|
# and AFTER the real-client-IP resolution (so Coraza sees the right src).
|
||||||
# Fail-open: see `option set-on-error continue` in /etc/haproxy/coraza-spoe.cfg.
|
# Fail-open: option set-on-error in coraza-spoe.cfg only SETS the error
|
||||||
|
# var; we deliberately don't have a `http-request deny if errored` rule,
|
||||||
|
# so SPOA outages let traffic through uninspected.
|
||||||
filter spoe engine coraza config /etc/haproxy/coraza-spoe.cfg
|
filter spoe engine coraza config /etc/haproxy/coraza-spoe.cfg
|
||||||
http-request send-spoe-group coraza coraza-check
|
http-request send-spoe-group coraza coraza-req
|
||||||
{%- endif %}
|
{%- endif %}
|
||||||
|
|||||||
Reference in New Issue
Block a user