Files
crowdsec-bouncer-traefik-pl…/tests/e2e/mock
31874263f2 🐛 Do not consider body unreadable when it's http.NoBody (#352)
* Do not consider body unreadable when it's http.NoBody

Fix #351

* 🐛 appsec: only drop unreadable bodies for methods that carry one

Gate the crowdsecAppsecUnreadableBodyBlock drop on the request method,
mirroring the reference lua-cs-bouncer METHODS_WITH_BODY list
(POST/PUT/PATCH/DELETE). Browser GETs over HTTP/3 never carry a
Content-Length (quic-go always wraps the stream in a non-nil body with
ContentLength -1), so they were all wrongly dropped; they are now
forwarded to appsec headers-only, while gRPC streams (always POST) stay
droppable.

Fix #351

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* 🍱 add test and remove useless comment

* 🍱 add test for GET with unreadable body

* 🍱 fix log

* 🍱 fix test

---------

Co-authored-by: mhx <mathieu@hanotaux.fr>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: maxlerebourg <maxlerebourg@gmail.com>
2026-07-24 20:49:43 +02:00
..
2026-07-05 19:56:15 +02:00
2026-07-05 19:56:15 +02:00

Binary e2e suite (Traefik binary + mock LAPI)

This suite runs Traefik as a downloaded binary with the plugin loaded from the local source tree, and replaces Crowdsec with a small HTTP mock (mocklapi/, a stdlib-only Go command). No Docker, no real Crowdsec.

It is what CI runs (make e2e_mock). A separate, local-only Docker suite (real Traefik + Crowdsec, under tests/e2e/scenarios) is kept for high-fidelity debugging against a real Crowdsec but is not exercised in CI; it ships in its own PR (#333).

Scope — what this suite tests

These tests validate the plugin's own behaviour: the request flow through the Traefik middleware, the live / none / stream modes, caching, trusted-IP bypass, ban / captcha page rendering, and the AppSec request path (header forwarding + enforcing the engine's allow/block verdict).

The mock stands in for Crowdsec, emulating the slice of the LAPI HTTP contract the plugin consumes — including a single, deterministic AppSec rule (block any URI containing rpc2, the probe from examples/appsec-enabled). It is not the real WAF engine, so this suite exercises the plugin's AppSec wiring rather than the detection accuracy of OWASP CRS / virtual patching — that lives upstream in Crowdsec.

What runs

Component How
Traefik Binary v3.7.1, downloaded into .cache/ (reused across local runs; re-downloaded on fresh CI runners)
Plugin Loaded via experimental.localPlugins from the repo root (symlinked into plugins-local/)
LAPI mocklapi — a stdlib-only Go command (its own nested module), compiled and cached under .cache/, driven through /admin endpoints instead of cscli. Serves plain HTTP, or HTTPS when --lapi-tls-cert/--lapi-tls-key are passed (the tls-system-ca scenario)
AppSec WAF stand-in built into the mock — blocks URIs containing rpc2, allows the rest
Backend A plain HTTP responder built into the mock

Fixed ports (override with env vars if needed): Traefik 8000, LAPI 8090, backend 8091, AppSec 8092.

Running locally

Prerequisites: bash, curl, go, tar (plus openssl for the tls-system-ca scenario, which mints a throwaway CA at runtime). On first use the Traefik binary is fetched and the mock is compiled into .cache/. That cache is reused across local runs; CI runs on fresh runners, so both are recreated on every CI run.

The tls-system-ca scenario verifies that, with no custom CA configured, the bouncer falls back to the OS/system trust store for an HTTPS LAPI: it serves the mock over TLS and points the Traefik process's SSL_CERT_FILE at the test CA (trusted → 200) or an empty bundle (untrusted → 403, proving it still verifies).

# one scenario
make e2e_mock_stream-mode
# or directly
./tests/e2e/mock/scenarios/stream-mode/run.sh

# the whole suite
make e2e_mock

Layout

mock/
  lib/
    common.sh     # stack lifecycle, Traefik download, mock build, assertions, admin client
    traefik.yml   # static Traefik config (shared by all scenarios)
  mocklapi/
    go.mod        # nested module — kept out of the plugin's build/lint/vendor
    main.go       # mock LAPI + AppSec stand-in + backend
  scenarios/
    <name>/
      dynamic.yml # Traefik dynamic config (router + bouncer middleware + backend)
      run.sh      # assertions for the scenario
      *.html      # optional fixtures (ban / captcha templates)

dynamic.yml uses placeholders (@@APIKEY@@, @@LAPI_HOST@@, @@BACKEND_URL@@, @@SCENARIO_DIR@@) that common.sh substitutes at runtime.

Adding a scenario

  1. Create scenarios/<name>/dynamic.yml and run.sh (copy stream-mode/ as a template).
  2. In run.sh, define a body function with the assertions and call run_scenario "<name>" "$HERE" body.
  3. Drive decisions with lapi_add_decision <ip> [type] [duration] and lapi_delete_decision <ip>.
  4. Add <name> to E2E_MOCK_SCENARIOS in the Makefile.