PR 2/3: opt-in SPOE integration for Coraza WAF
All checks were successful
HAProxy Manager Build and Push / Build-and-Push (push) Successful in 1m59s

Adds the plumbing that lets haproxy-manager talk to the coraza-spoa sidecar
added in PR 1, while keeping the default behavior bit-identical for any
deployment that doesn't set the new env var (the home network / standalone
use cases).

Single gate: HAPROXY_CORAZA_SPOE_BACKEND env var on the haproxy-manager
container. Unset (default) = generate_config() renders zero SPOE-related
output. Set (e.g. "coraza-spoa:9000") = three things happen at config
generation time:

  1. hap_listener.tpl injects 5 lines at the end of the frontend block:
       filter spoe engine coraza config /etc/haproxy/coraza-spoe.cfg
       http-request send-spoe-group coraza coraza-check
     ...placed AFTER rate-limit and IP-block guards so we don't waste WAF
     calls on requests we were going to drop anyway.

  2. A new TCP backend (hap_coraza_spoa_backend.tpl) is appended:
       backend coraza-spoa-backend
           mode tcp
           server coraza-spoa <env-var-target> check ...

  3. The SPOE engine config (hap_coraza_spoe_engine.tpl) is rendered and
     written to /etc/haproxy/coraza-spoe.cfg, defining the spoe-agent
     "coraza" + spoe-message "coraza-check". This sets:
       - option set-on-error continue   (FAIL-OPEN if SPOA is unreachable)
       - timeout processing 100ms       (per-request inspection budget)
       - app=str(haproxy)               (matches sidecar's application name)

Verification (template render only, before staging deploy):
  - hap_listener.tpl with no env var: 55 lines, zero SPOE references
  - hap_listener.tpl with env var:    62 lines, filter + send-spoe-group present
  - Engine cfg + backend block render with correct agent_target substitution

Next: PR 3 wires this into WHP (sidecar deploy via container-manager.sh
extension, server-settings UI for on/off, AI Monitor source for the audit
log). Staging verification of PR 1 + PR 2 together happens after PR 3.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-12 16:49:29 -07:00
parent 4e0c22e9c9
commit 73b9104565
4 changed files with 102 additions and 2 deletions

View File

@@ -1710,16 +1710,25 @@ def generate_config():
config_parts = []
# Optional Coraza WAF integration. When HAPROXY_CORAZA_SPOE_BACKEND is
# set on the haproxy-manager container, we render an extra TCP backend
# pointing at a coraza-spoa sidecar AND inject a `filter spoe ...` line
# into the frontend via hap_listener.tpl. Unset (the default for
# standalone deployments, home networks, and any non-WHP use of this
# image) -> the generated haproxy.cfg is byte-identical to today's.
coraza_spoe_backend = os.environ.get('HAPROXY_CORAZA_SPOE_BACKEND')
# Add Haproxy Default Headers
default_headers = template_env.get_template('hap_header.tpl').render()
config_parts.append(default_headers)
# Update blocked IPs map file first
update_blocked_ips_map()
# Add Listener Block
listener_block = template_env.get_template('hap_listener.tpl').render(
crt_path = SSL_CERTS_DIR
crt_path = SSL_CERTS_DIR,
coraza_spoe_backend = coraza_spoe_backend,
)
config_parts.append(listener_block)
@@ -1839,6 +1848,25 @@ backend default-backend
config_parts.append(fallback_backend)
# Add Backends
config_parts.append('\n' .join(config_backends) + '\n')
# Coraza WAF backend + SPOE engine config file (only when env var set).
# Writing /etc/haproxy/coraza-spoe.cfg here keeps it in sync with the
# filter line that hap_listener.tpl just rendered into the frontend.
if coraza_spoe_backend:
coraza_backend_block = template_env.get_template(
'hap_coraza_spoa_backend.tpl'
).render(agent_target=coraza_spoe_backend)
config_parts.append(coraza_backend_block)
coraza_spoe_cfg = template_env.get_template(
'hap_coraza_spoe_engine.tpl'
).render()
coraza_spoe_path = '/etc/haproxy/coraza-spoe.cfg'
with open(coraza_spoe_path, 'w') as f:
f.write(coraza_spoe_cfg)
logger.info(f"Coraza SPOE engine config written to {coraza_spoe_path} "
f"(SPOA target: {coraza_spoe_backend})")
# Write complete configuration to tmp
temp_config_path = "/etc/haproxy/haproxy.cfg"