mirror of
https://github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin.git
synced 2026-07-19 18:52:20 +02:00
* ✨ 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>
27 lines
1008 B
Bash
Executable File
27 lines
1008 B
Bash
Executable File
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
|
|
HERE="$(cd "$(dirname "$0")" && pwd)"
|
|
# shellcheck source=../../lib/common.sh
|
|
source "$HERE/../../lib/common.sh"
|
|
|
|
SCENARIO=captcha
|
|
|
|
body() {
|
|
echo "[$SCENARIO] adding captcha decision for 1.2.3.4"
|
|
lapi_add_decision 1.2.3.4 captcha 5m
|
|
|
|
# Status stays 200 before/after (captcha page vs backend), so gate on the body
|
|
# marker appearing once the captcha decision has been polled.
|
|
echo "[$SCENARIO] captcha page must be served once the decision is polled (200 + marker)"
|
|
wait_for_body_contains "http://127.0.0.1:${WEB_PORT}/foo" "E2E_CAPTCHA_PAGE_MARKER" 15 -H "X-Forwarded-For: 1.2.3.4"
|
|
|
|
echo "[$SCENARIO] captcha response is HTTP 200 (the captcha page itself, not a 403)"
|
|
assert_status "http://127.0.0.1:${WEB_PORT}/foo" 200 -H "X-Forwarded-For: 1.2.3.4"
|
|
|
|
echo "[$SCENARIO] non-flagged IP must still pass through to the backend"
|
|
assert_status "http://127.0.0.1:${WEB_PORT}/foo" 200 -H "X-Forwarded-For: 5.6.7.8"
|
|
}
|
|
|
|
run_scenario "$SCENARIO" "$HERE" body
|