* ✨ tests: end-to-end suite scaffold + stream-mode scenario Add tests/e2e/ structure with shared bash helpers and the first scenario (stream-mode): spin up real Traefik + Crowdsec via docker compose, mount the repo as a local plugin, add a ban via cscli, verify the bouncer blocks the matching X-Forwarded-For, then delete the decision and verify pass-through. Adds .github/workflows/e2e.yml with one matrix job per scenario (stream-mode for now), and Makefile targets `e2e` and `e2e_<scenario>` for local runs. Refs #328 * ✨ tests: revert to docker provider + bump versions + add 6 scenarios Reverts the stream-mode scenario to the Traefik docker provider (the file provider was a workaround for a local docker daemon API version mismatch, irrelevant in CI). Bumps Traefik to v3.7.1 and Crowdsec to v1.7.8 across all scenarios. Adds six new E2E scenarios: - live-mode: short defaultDecisionSeconds, verifies cache-then-recheck - none-mode: verifies LAPI is queried per request, no caching - trusted-ips: clientTrustedIPs bypass even when the trusted IP is banned - custom-ban-page: BanHTMLFilePath body + Content-Type validation - captcha: captcha decision serves the captcha page (HTTP 200) - appsec: SQLi probe blocked by appsec-virtual-patching Each scenario uses an isolated compose project, mounts the repo as a local plugin, and asserts behavior via curl. Workflow matrix and Makefile E2E_SCENARIOS updated accordingly. Refs #328 * 🐛 tests: set CROWDSEC_BYPASS_DB_VOLUME_CHECK for v1.7+ Crowdsec v1.7 refuses to start without an explicit volume mount on /var/lib/crowdsec/data (or the bypass env var). For E2E we don't need db persistence — set the bypass everywhere so the stack boots. * 🐛 tests: appsec scenario uses OWASP CRS inband collection appsec-virtual-patching only ships CVE-specific rules, not generic SQLi. Switch to crowdsecurity/appsec-crs-inband (the blocking OWASP Core Rule Set) and use a SQLi probe that CRS paranoia level 1 matches (rule 942100/942130). * ♻️ tests: run e2e suite in a single sequential job The matrix spawned one runner per scenario, so the Traefik, Crowdsec and whoami images were pulled — and Crowdsec booted — once per scenario. Run the whole suite in a single job with `make -k e2e` instead: Docker caches the images locally so they are pulled only once, and `-k` keeps the remaining scenarios running after a failure (make still exits non-zero). Scenarios already share the canonical `crowdsec` container name and the 8000 port, so they were meant to run sequentially anyway. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * ✨ tests: add binary e2e suite (Traefik binary + Go mock LAPI), run it in CI Add a second e2e suite that runs Traefik as a downloaded binary with the plugin loaded from source, and replaces Crowdsec with a small stdlib-only Go LAPI mock driven via /admin endpoints. No Docker, no real Crowdsec. The mock lives in its own nested Go module (tests/e2e/mock/mocklapi) so it stays out of the plugin module's build, lint, test and vendor. This suite validates the plugin's own behaviour (live/none/stream modes, caching, trusted-IP bypass, ban/captcha rendering). Crowdsec and AppSec correctness are out of scope on purpose — they are validated upstream by the maintainer — so the AppSec scenario is intentionally absent and the README says so to avoid misfiled issues. CI now runs this suite only (`make e2e_mock`), since it needs neither Docker nor a real Crowdsec. The Docker suite (tests/e2e/scenarios) is kept for local debugging (`make e2e`). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * 🔧 e2e mock: address review — clarify backend flag, move ignore to root - Document the --backend-addr flag: it is the stub upstream service Traefik proxies allowed requests to (the traefik/whoami equivalent), not AppSec. - Move the .cache/ ignore rule from the per-suite .gitignore to the repo root .gitignore, and make the wording accurate: the cache persists across local runs but is recreated on every (fresh-runner) CI run. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * ♻️ tests: move local Docker e2e suite to its own PR (#333) Per review, split the e2e work so each PR is focused. CI runs the binary + mock-LAPI suite (this PR); the heavier, local-only Docker suite (real Traefik + Crowdsec, incl. appsec) now lives in #333. Removes tests/e2e/scenarios, tests/e2e/lib and the Docker-suite README, and drops the `e2e` Make target here (kept in #333). The binary/mock suite and its `e2e_mock` target are unchanged. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * 🔥 e2e mock: simplify the Crowdsec LAPI mock Per review, trim the mock to the minimum the plugin actually exercises: - Drop the stream delta bookkeeping (startup flag + "already streamed" set). The plugin re-Sets/Deletes its cache on every poll, so reporting the whole active set as "new" and removed ones as "deleted" is enough. - Shrink the Decision struct to the three fields the plugin reads (value/type/duration); drop id/origin/scope/scenario and the id counter. - Drop API-key auth and the /admin/reset endpoint — no scenario exercises either. Also drop the now-unused lapi_reset helper. - Replace the store struct + methods with two package-level maps + a mutex. mocklapi/main.go: 234 -> 118 lines. All six scenarios still pass. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * ⚡ e2e mock: replace fixed sleeps with condition polling The `sleep 4` / `sleep 3` after a decision change were magic numbers tied to updateIntervalSeconds / defaultDecisionSeconds. Replace them with waits on the actual condition: - After a ban/unban, poll with wait_for_status until the expected code shows up (stream propagation / live-mode cache TTL). - Captcha keeps status 200 before and after, so gate on the body marker via a new wait_for_body_contains helper. - Control assertions that must NOT change stay immediate (assert_status). Self-documenting, faster on the happy path (returns on the first poll that sees the change), and more robust under slow CI. No fixed sleeps remain. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * ⬆️ e2e mock: bump module go directive to 1.23 Align the mock module with the project's Go version (CI uses 1.23). Part of standardising the whole project on Go 1.23; the plugin module is bumped separately. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * ⬇️ e2e mock: keep Go floor at 1.22 (yaegi ceiling) Revert the mock module back to go 1.22 and make the e2e workflow read the Go version from go.mod (go-version-file) instead of hardcoding 1.23. Rationale: the plugin is interpreted by yaegi, and even Traefik v3.7.1 ships yaegi v0.16.1 (Go 1.22), so the project stays on 1.22. The earlier bump to 1.23 is dropped (plugin go.mod stays 1.22, see #330 for the Renovate cap + CI pin). Mock + all six scenarios verified on Go 1.22. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * ✅ e2e mock: add AppSec scenario + custom remediation header assertion Address review feedback on the binary e2e suite: - mocklapi: add an AppSec WAF stand-in (--appsec-addr) that blocks any URI containing "rpc2" — the exact probe from examples/appsec-enabled — and allows the rest. Lets the suite exercise the plugin's AppSec wiring (header forwarding + allow/block enforcement) without the real CRS engine. - new scenarios/appsec: benign request passes, /foo/rpc2 is 403. - custom-ban-page: assert the banned response carries the custom remediation header (remediationHeadersCustomName), per review. - README: drop the "don't open issues / AppSec intentionally absent" framing; describe what the suite actually covers, including AppSec wiring. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
3.5 KiB
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 |
| 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. 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.
# 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
- Create
scenarios/<name>/dynamic.ymlandrun.sh(copystream-mode/as a template). - In
run.sh, define abodyfunction with the assertions and callrun_scenario "<name>" "$HERE" body. - Drive decisions with
lapi_add_decision <ip> [type] [duration]andlapi_delete_decision <ip>. - Add
<name>toE2E_MOCK_SCENARIOSin theMakefile.