diff --git a/Makefile b/Makefile index c9265b7..648a034 100644 --- a/Makefile +++ b/Makefile @@ -1,10 +1,9 @@ -.PHONY: lint test vendor clean e2e e2e_mock +.PHONY: lint test vendor clean e2e_mock export GO111MODULE=on -# Docker suite (real Traefik + Crowdsec). Kept for local debugging. -E2E_SCENARIOS := stream-mode live-mode none-mode trusted-ips custom-ban-page captcha appsec # Binary/mock suite (Traefik binary + mock LAPI). This is what CI runs. +# The local Docker suite (make e2e) lives in a separate PR/branch. E2E_MOCK_SCENARIOS := stream-mode live-mode none-mode trusted-ips custom-ban-page captcha default: lint test @@ -18,11 +17,6 @@ test: yaegi_test: yaegi test -v . -e2e: $(addprefix e2e_,$(E2E_SCENARIOS)) - -e2e_%: - ./tests/e2e/scenarios/$*/run.sh - e2e_mock: $(addprefix e2e_mock_,$(E2E_MOCK_SCENARIOS)) e2e_mock_%: diff --git a/tests/e2e/README.md b/tests/e2e/README.md deleted file mode 100644 index 258a1ef..0000000 --- a/tests/e2e/README.md +++ /dev/null @@ -1,62 +0,0 @@ -# End-to-end test suite - -There are two suites: - -- **`scenarios/` (this one)** — real Traefik + Crowdsec **Docker** containers. - High fidelity, kept for **local debugging**. Run with `make e2e`. -- **[`mock/`](mock/README.md)** — Traefik **binary** + a **mock LAPI**, no - Docker. This is what **CI runs** (`make e2e_mock`). It validates the - plugin's own behaviour; Crowdsec / AppSec correctness is out of scope there - (that is the upstream maintainer's responsibility). - -The rest of this document covers the Docker suite. - -These tests spin up real Traefik + Crowdsec containers and exercise the -plugin in the same conditions Traefik uses in production: loaded from a -local path, no module download, no mocking. - -Each scenario lives in its own directory under `scenarios/` and owns: - -- `docker-compose.yml` — the stack to spin up -- `run.sh` — orchestration + assertions -- optional fixtures (`acquis.yaml`, `ban.html`, ...) - -## Running locally - -Prerequisites: `docker`, `docker compose`, `curl`, `bash`. - -Run a single scenario: - -```bash -./tests/e2e/scenarios/stream-mode/run.sh -# or -make e2e_stream-mode -``` - -Run everything: - -```bash -make e2e -``` - -Scenarios run **sequentially** on a single host: they share the canonical -`crowdsec` container name (so `cscli` commands work uniformly) and the same -`8000:80` port. Each scenario uses its own Docker Compose project -(`-p e2e-`) and tears its stack down on exit, so the next one -starts clean. `make e2e` runs them one after another; Docker reuses the -images pulled by the first scenario, so the Traefik / Crowdsec / whoami -images are downloaded only once for the whole suite. - -## Writing a new scenario - -1. Copy `scenarios/stream-mode/` as a template. -2. Rename `container_name`s (keep `crowdsec` for the LAPI container). -3. Edit `run.sh` to express the behavior under test. -4. Add the scenario name to `E2E_SCENARIOS` in the `Makefile`. - -## CI - -This Docker suite is **not** run in CI — it is meant for local debugging. -`.github/workflows/e2e.yml` runs the [`mock/`](mock/README.md) suite -(`make -k e2e_mock`) instead, which needs neither Docker nor a real Crowdsec. -Run this suite locally with `make e2e` (or `make e2e_`). diff --git a/tests/e2e/lib/common.sh b/tests/e2e/lib/common.sh deleted file mode 100755 index 64e4cfd..0000000 --- a/tests/e2e/lib/common.sh +++ /dev/null @@ -1,78 +0,0 @@ -#!/usr/bin/env bash -# Shared helpers for end-to-end scenarios. -# Dependencies: bash, curl, docker. - -# Wait until the Crowdsec LAPI reports ready, or fail after timeout. -# Usage: wait_crowdsec_ready [container_name] [timeout_seconds] -wait_crowdsec_ready() { - local container="${1:-crowdsec}" - local timeout="${2:-90}" - local elapsed=0 - while (( elapsed < timeout )); do - if docker exec "$container" cscli lapi status >/dev/null 2>&1; then - return 0 - fi - sleep 1 - ((elapsed++)) - done - echo "wait_crowdsec_ready: timed out after ${timeout}s waiting for $container" >&2 - return 1 -} - -# Poll URL until it returns the expected status code, or fail. -# Usage: wait_for_status URL CODE [TIMEOUT_SECONDS] [curl args...] -wait_for_status() { - local url="$1" expected="$2" timeout="${3:-30}" - shift 3 || true - local elapsed=0 - local got="" - while (( elapsed < timeout )); do - got=$(curl -s -o /dev/null -w '%{http_code}' "$@" "$url" || true) - if [[ "$got" == "$expected" ]]; then - return 0 - fi - sleep 1 - ((elapsed++)) - done - echo "wait_for_status: $url expected $expected, last seen ${got:-}" >&2 - return 1 -} - -# Assert a single curl returns the expected status code. -# Usage: assert_status URL CODE [curl args...] -assert_status() { - local url="$1" expected="$2" - shift 2 || true - local got - got=$(curl -s -o /dev/null -w '%{http_code}' "$@" "$url") - if [[ "$got" != "$expected" ]]; then - echo "assert_status: $url expected $expected, got $got" >&2 - return 1 - fi -} - -# Assert a response header matches a value (case-insensitive name). -# Usage: assert_header URL HEADER VALUE [curl args...] -assert_header() { - local url="$1" header="$2" expected="$3" - shift 3 || true - local got - got=$(curl -s -D - -o /dev/null "$@" "$url" | tr -d '\r' \ - | awk -v h="${header,,}" -F': ' 'tolower($1) == h { print $2; exit }') - if [[ "$got" != "$expected" ]]; then - echo "assert_header: $url header $header expected \"$expected\", got \"$got\"" >&2 - return 1 - fi -} - -# Dump diagnostic info for a compose project; called on failure. -# Usage: dump_diagnostics PROJECT COMPOSE_FILE -dump_diagnostics() { - local project="$1" compose_file="$2" - echo "=== docker compose ps ===" - docker compose -p "$project" -f "$compose_file" ps || true - echo "=== docker compose logs ===" - docker compose -p "$project" -f "$compose_file" logs --no-color || true - echo "=== cscli decisions list ===" - docker exec crowdsec cscli decisions list 2>/dev/null || true -} diff --git a/tests/e2e/mock/README.md b/tests/e2e/mock/README.md index 574bef6..40465b7 100644 --- a/tests/e2e/mock/README.md +++ b/tests/e2e/mock/README.md @@ -5,9 +5,10 @@ the local source tree, and replaces Crowdsec with a small **HTTP mock** ([`mocklapi/`](mocklapi/main.go), a stdlib-only Go command). No Docker, no real Crowdsec. -It is what **CI runs** (`make e2e_mock`). The Docker suite in -[`../scenarios`](../scenarios) is kept for local debugging against a real -Crowdsec, but is not exercised in CI. +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 does and does NOT test diff --git a/tests/e2e/scenarios/appsec/acquis.yaml b/tests/e2e/scenarios/appsec/acquis.yaml deleted file mode 100644 index 97d9bc5..0000000 --- a/tests/e2e/scenarios/appsec/acquis.yaml +++ /dev/null @@ -1,6 +0,0 @@ -listen_addr: 0.0.0.0:7422 -appsec_config: crowdsecurity/crs-inband -name: e2eAppSec -source: appsec -labels: - type: appsec diff --git a/tests/e2e/scenarios/appsec/docker-compose.yml b/tests/e2e/scenarios/appsec/docker-compose.yml deleted file mode 100644 index b3d2a87..0000000 --- a/tests/e2e/scenarios/appsec/docker-compose.yml +++ /dev/null @@ -1,55 +0,0 @@ -services: - traefik: - image: traefik:v3.7.1 - container_name: e2e-appsec-traefik - command: - - "--log.level=INFO" - - "--accesslog" - - "--api.insecure=true" - - "--providers.docker=true" - - "--providers.docker.exposedbydefault=false" - - "--entryPoints.web.address=:80" - - "--entryPoints.web.forwardedHeaders.insecure=true" - - "--experimental.localplugins.bouncer.modulename=github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin" - volumes: - - /var/run/docker.sock:/var/run/docker.sock:ro - - ../../../..:/plugins-local/src/github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin - ports: - - "8000:80" - depends_on: - crowdsec: - condition: service_healthy - - whoami: - image: traefik/whoami - container_name: e2e-appsec-whoami - labels: - - "traefik.enable=true" - - "traefik.http.routers.r.rule=PathPrefix(`/foo`)" - - "traefik.http.routers.r.entrypoints=web" - - "traefik.http.routers.r.middlewares=bouncer@docker" - - "traefik.http.services.s.loadbalancer.server.port=80" - - "traefik.http.middlewares.bouncer.plugin.bouncer.enabled=true" - # IP bouncing disabled — we only test AppSec body inspection here. - - "traefik.http.middlewares.bouncer.plugin.bouncer.crowdsecmode=none" - - "traefik.http.middlewares.bouncer.plugin.bouncer.crowdseclapikey=40796d93c2958f9e58345514e67740e5" - - "traefik.http.middlewares.bouncer.plugin.bouncer.crowdsecappsecenabled=true" - - "traefik.http.middlewares.bouncer.plugin.bouncer.crowdsecappsechost=crowdsec:7422" - - "traefik.http.middlewares.bouncer.plugin.bouncer.forwardedheaderstrustedips=172.16.0.0/12" - - crowdsec: - image: crowdsecurity/crowdsec:v1.7.8 - container_name: crowdsec - environment: - COLLECTIONS: "crowdsecurity/appsec-crs-inband" - BOUNCER_KEY_TRAEFIK: 40796d93c2958f9e58345514e67740e5 - CUSTOM_HOSTNAME: crowdsec - CROWDSEC_BYPASS_DB_VOLUME_CHECK: "true" - volumes: - - ./acquis.yaml:/etc/crowdsec/acquis.yaml:ro - healthcheck: - test: ["CMD", "cscli", "lapi", "status"] - interval: 2s - timeout: 3s - retries: 60 - start_period: 15s diff --git a/tests/e2e/scenarios/appsec/run.sh b/tests/e2e/scenarios/appsec/run.sh deleted file mode 100755 index c0c6b80..0000000 --- a/tests/e2e/scenarios/appsec/run.sh +++ /dev/null @@ -1,40 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -HERE="$(cd "$(dirname "$0")" && pwd)" -# shellcheck source=../../lib/common.sh -source "$HERE/../../lib/common.sh" - -SCENARIO=appsec -PROJECT="e2e-${SCENARIO}" -COMPOSE_FILE="$HERE/docker-compose.yml" -LOG_FILE="/tmp/e2e-${SCENARIO}.log" - -cleanup() { - local rc=$? - if (( rc != 0 )); then - dump_diagnostics "$PROJECT" "$COMPOSE_FILE" > "$LOG_FILE" 2>&1 || true - echo "Scenario failed. Logs written to $LOG_FILE" - fi - docker compose -p "$PROJECT" -f "$COMPOSE_FILE" down -v --remove-orphans >/dev/null 2>&1 || true - exit $rc -} -trap cleanup EXIT - -echo "[$SCENARIO] starting stack (AppSec collections download — may take ~30s on first boot)..." -docker compose -p "$PROJECT" -f "$COMPOSE_FILE" up -d --wait - -echo "[$SCENARIO] waiting for crowdsec readiness..." -wait_crowdsec_ready crowdsec 180 - -echo "[$SCENARIO] waiting for traefik readiness..." -wait_for_status http://localhost:8000/foo 200 30 - -echo "[$SCENARIO] benign request must pass" -assert_status http://localhost:8000/foo 200 - -echo "[$SCENARIO] SQL-injection-like query string must be blocked by OWASP CRS (inband)" -# Classic SQLi probe: ?id=1' OR '1'='1 — caught by CRS rule 942100/942130 (paranoia 1). -assert_status "http://localhost:8000/foo?id=1%27%20OR%20%271%27%3D%271" 403 - -echo "[$SCENARIO] OK" diff --git a/tests/e2e/scenarios/captcha/captcha.html b/tests/e2e/scenarios/captcha/captcha.html deleted file mode 100644 index 94736cc..0000000 --- a/tests/e2e/scenarios/captcha/captcha.html +++ /dev/null @@ -1,9 +0,0 @@ - - -E2E captcha marker - -

E2E_CAPTCHA_PAGE_MARKER

- -
- - diff --git a/tests/e2e/scenarios/captcha/docker-compose.yml b/tests/e2e/scenarios/captcha/docker-compose.yml deleted file mode 100644 index 9b0306c..0000000 --- a/tests/e2e/scenarios/captcha/docker-compose.yml +++ /dev/null @@ -1,59 +0,0 @@ -services: - traefik: - image: traefik:v3.7.1 - container_name: e2e-captcha-traefik - command: - - "--log.level=INFO" - - "--accesslog" - - "--api.insecure=true" - - "--providers.docker=true" - - "--providers.docker.exposedbydefault=false" - - "--entryPoints.web.address=:80" - - "--entryPoints.web.forwardedHeaders.insecure=true" - - "--experimental.localplugins.bouncer.modulename=github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin" - volumes: - - /var/run/docker.sock:/var/run/docker.sock:ro - - ../../../..:/plugins-local/src/github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin - - ./captcha.html:/captcha.html:ro - ports: - - "8000:80" - depends_on: - crowdsec: - condition: service_healthy - - whoami: - image: traefik/whoami - container_name: e2e-captcha-whoami - labels: - - "traefik.enable=true" - - "traefik.http.routers.r.rule=PathPrefix(`/foo`)" - - "traefik.http.routers.r.entrypoints=web" - - "traefik.http.routers.r.middlewares=bouncer@docker" - - "traefik.http.services.s.loadbalancer.server.port=80" - - "traefik.http.middlewares.bouncer.plugin.bouncer.enabled=true" - - "traefik.http.middlewares.bouncer.plugin.bouncer.crowdsecmode=stream" - - "traefik.http.middlewares.bouncer.plugin.bouncer.updateintervalseconds=3" - - "traefik.http.middlewares.bouncer.plugin.bouncer.crowdseclapikey=40796d93c2958f9e58345514e67740e5" - - "traefik.http.middlewares.bouncer.plugin.bouncer.forwardedheaderstrustedips=172.16.0.0/12" - - "traefik.http.middlewares.bouncer.plugin.bouncer.captchaprovider=turnstile" - # Cloudflare Turnstile public test keys (always pass / always fail variants exist). - # These render a valid widget without contacting any real API key. - - "traefik.http.middlewares.bouncer.plugin.bouncer.captchasitekey=1x00000000000000000000AA" - - "traefik.http.middlewares.bouncer.plugin.bouncer.captchasecretkey=1x0000000000000000000000000000000AA" - - "traefik.http.middlewares.bouncer.plugin.bouncer.captchahtmlfilepath=/captcha.html" - - "traefik.http.middlewares.bouncer.plugin.bouncer.captchagraceperiodseconds=10" - - crowdsec: - image: crowdsecurity/crowdsec:v1.7.8 - container_name: crowdsec - environment: - DISABLE_ONLINE_API: "true" - BOUNCER_KEY_TRAEFIK: 40796d93c2958f9e58345514e67740e5 - CUSTOM_HOSTNAME: crowdsec - CROWDSEC_BYPASS_DB_VOLUME_CHECK: "true" - healthcheck: - test: ["CMD", "cscli", "lapi", "status"] - interval: 2s - timeout: 3s - retries: 30 - start_period: 5s diff --git a/tests/e2e/scenarios/captcha/run.sh b/tests/e2e/scenarios/captcha/run.sh deleted file mode 100755 index 581aa65..0000000 --- a/tests/e2e/scenarios/captcha/run.sh +++ /dev/null @@ -1,53 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -HERE="$(cd "$(dirname "$0")" && pwd)" -# shellcheck source=../../lib/common.sh -source "$HERE/../../lib/common.sh" - -SCENARIO=captcha -PROJECT="e2e-${SCENARIO}" -COMPOSE_FILE="$HERE/docker-compose.yml" -LOG_FILE="/tmp/e2e-${SCENARIO}.log" - -cleanup() { - local rc=$? - if (( rc != 0 )); then - dump_diagnostics "$PROJECT" "$COMPOSE_FILE" > "$LOG_FILE" 2>&1 || true - echo "Scenario failed. Logs written to $LOG_FILE" - fi - docker compose -p "$PROJECT" -f "$COMPOSE_FILE" down -v --remove-orphans >/dev/null 2>&1 || true - exit $rc -} -trap cleanup EXIT - -echo "[$SCENARIO] starting stack..." -docker compose -p "$PROJECT" -f "$COMPOSE_FILE" up -d --wait - -echo "[$SCENARIO] waiting for crowdsec readiness..." -wait_crowdsec_ready - -echo "[$SCENARIO] waiting for traefik readiness..." -wait_for_status http://localhost:8000/foo 200 30 - -echo "[$SCENARIO] adding captcha decision for 1.2.3.4" -docker exec crowdsec cscli decisions add --ip 1.2.3.4 --type captcha --duration 5m - -echo "[$SCENARIO] waiting one stream tick + buffer..." -sleep 6 - -echo "[$SCENARIO] captcha response must be HTTP 200 (the captcha page itself, not a 403)" -assert_status http://localhost:8000/foo 200 -H "X-Forwarded-For: 1.2.3.4" - -echo "[$SCENARIO] captcha response body must contain the captcha template marker" -body=$(curl -s http://localhost:8000/foo -H "X-Forwarded-For: 1.2.3.4") -if ! grep -q "E2E_CAPTCHA_PAGE_MARKER" <<<"$body"; then - echo "Expected response body to contain E2E_CAPTCHA_PAGE_MARKER, got:" >&2 - echo "$body" >&2 - exit 1 -fi - -echo "[$SCENARIO] non-flagged IP must still pass through to the backend" -assert_status http://localhost:8000/foo 200 -H "X-Forwarded-For: 5.6.7.8" - -echo "[$SCENARIO] OK" diff --git a/tests/e2e/scenarios/custom-ban-page/ban.html b/tests/e2e/scenarios/custom-ban-page/ban.html deleted file mode 100644 index 62c0fe3..0000000 --- a/tests/e2e/scenarios/custom-ban-page/ban.html +++ /dev/null @@ -1,8 +0,0 @@ - - -E2E ban marker - -

E2E_CUSTOM_BAN_PAGE_MARKER

-

IP: {{ .ClientIP }} reason: {{ .RemediationReason }}

- - diff --git a/tests/e2e/scenarios/custom-ban-page/docker-compose.yml b/tests/e2e/scenarios/custom-ban-page/docker-compose.yml deleted file mode 100644 index d07397d..0000000 --- a/tests/e2e/scenarios/custom-ban-page/docker-compose.yml +++ /dev/null @@ -1,53 +0,0 @@ -services: - traefik: - image: traefik:v3.7.1 - container_name: e2e-banpage-traefik - command: - - "--log.level=INFO" - - "--accesslog" - - "--api.insecure=true" - - "--providers.docker=true" - - "--providers.docker.exposedbydefault=false" - - "--entryPoints.web.address=:80" - - "--entryPoints.web.forwardedHeaders.insecure=true" - - "--experimental.localplugins.bouncer.modulename=github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin" - volumes: - - /var/run/docker.sock:/var/run/docker.sock:ro - - ../../../..:/plugins-local/src/github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin - - ./ban.html:/ban.html:ro - ports: - - "8000:80" - depends_on: - crowdsec: - condition: service_healthy - - whoami: - image: traefik/whoami - container_name: e2e-banpage-whoami - labels: - - "traefik.enable=true" - - "traefik.http.routers.r.rule=PathPrefix(`/foo`)" - - "traefik.http.routers.r.entrypoints=web" - - "traefik.http.routers.r.middlewares=bouncer@docker" - - "traefik.http.services.s.loadbalancer.server.port=80" - - "traefik.http.middlewares.bouncer.plugin.bouncer.enabled=true" - - "traefik.http.middlewares.bouncer.plugin.bouncer.crowdsecmode=stream" - - "traefik.http.middlewares.bouncer.plugin.bouncer.updateintervalseconds=3" - - "traefik.http.middlewares.bouncer.plugin.bouncer.crowdseclapikey=40796d93c2958f9e58345514e67740e5" - - "traefik.http.middlewares.bouncer.plugin.bouncer.forwardedheaderstrustedips=172.16.0.0/12" - - "traefik.http.middlewares.bouncer.plugin.bouncer.banhtmlfilepath=/ban.html" - - crowdsec: - image: crowdsecurity/crowdsec:v1.7.8 - container_name: crowdsec - environment: - DISABLE_ONLINE_API: "true" - BOUNCER_KEY_TRAEFIK: 40796d93c2958f9e58345514e67740e5 - CUSTOM_HOSTNAME: crowdsec - CROWDSEC_BYPASS_DB_VOLUME_CHECK: "true" - healthcheck: - test: ["CMD", "cscli", "lapi", "status"] - interval: 2s - timeout: 3s - retries: 30 - start_period: 5s diff --git a/tests/e2e/scenarios/custom-ban-page/run.sh b/tests/e2e/scenarios/custom-ban-page/run.sh deleted file mode 100755 index 02ca143..0000000 --- a/tests/e2e/scenarios/custom-ban-page/run.sh +++ /dev/null @@ -1,53 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -HERE="$(cd "$(dirname "$0")" && pwd)" -# shellcheck source=../../lib/common.sh -source "$HERE/../../lib/common.sh" - -SCENARIO=custom-ban-page -PROJECT="e2e-${SCENARIO}" -COMPOSE_FILE="$HERE/docker-compose.yml" -LOG_FILE="/tmp/e2e-${SCENARIO}.log" - -cleanup() { - local rc=$? - if (( rc != 0 )); then - dump_diagnostics "$PROJECT" "$COMPOSE_FILE" > "$LOG_FILE" 2>&1 || true - echo "Scenario failed. Logs written to $LOG_FILE" - fi - docker compose -p "$PROJECT" -f "$COMPOSE_FILE" down -v --remove-orphans >/dev/null 2>&1 || true - exit $rc -} -trap cleanup EXIT - -echo "[$SCENARIO] starting stack..." -docker compose -p "$PROJECT" -f "$COMPOSE_FILE" up -d --wait - -echo "[$SCENARIO] waiting for crowdsec readiness..." -wait_crowdsec_ready - -echo "[$SCENARIO] waiting for traefik readiness..." -wait_for_status http://localhost:8000/foo 200 30 - -echo "[$SCENARIO] adding ban decision" -docker exec crowdsec cscli decisions add --ip 1.2.3.4 --type ban --duration 5m - -echo "[$SCENARIO] waiting one stream tick + buffer..." -sleep 6 - -echo "[$SCENARIO] banned response status is 403" -assert_status http://localhost:8000/foo 403 -H "X-Forwarded-For: 1.2.3.4" - -echo "[$SCENARIO] banned response Content-Type is HTML" -assert_header http://localhost:8000/foo Content-Type "text/html; charset=utf-8" -H "X-Forwarded-For: 1.2.3.4" - -echo "[$SCENARIO] banned response body contains the custom marker" -body=$(curl -s http://localhost:8000/foo -H "X-Forwarded-For: 1.2.3.4") -if ! grep -q "E2E_CUSTOM_BAN_PAGE_MARKER" <<<"$body"; then - echo "Expected response body to contain E2E_CUSTOM_BAN_PAGE_MARKER, got:" >&2 - echo "$body" >&2 - exit 1 -fi - -echo "[$SCENARIO] OK" diff --git a/tests/e2e/scenarios/live-mode/docker-compose.yml b/tests/e2e/scenarios/live-mode/docker-compose.yml deleted file mode 100644 index e6fe8a0..0000000 --- a/tests/e2e/scenarios/live-mode/docker-compose.yml +++ /dev/null @@ -1,51 +0,0 @@ -services: - traefik: - image: traefik:v3.7.1 - container_name: e2e-live-traefik - command: - - "--log.level=INFO" - - "--accesslog" - - "--api.insecure=true" - - "--providers.docker=true" - - "--providers.docker.exposedbydefault=false" - - "--entryPoints.web.address=:80" - - "--entryPoints.web.forwardedHeaders.insecure=true" - - "--experimental.localplugins.bouncer.modulename=github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin" - volumes: - - /var/run/docker.sock:/var/run/docker.sock:ro - - ../../../..:/plugins-local/src/github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin - ports: - - "8000:80" - depends_on: - crowdsec: - condition: service_healthy - - whoami: - image: traefik/whoami - container_name: e2e-live-whoami - labels: - - "traefik.enable=true" - - "traefik.http.routers.r.rule=PathPrefix(`/foo`)" - - "traefik.http.routers.r.entrypoints=web" - - "traefik.http.routers.r.middlewares=bouncer@docker" - - "traefik.http.services.s.loadbalancer.server.port=80" - - "traefik.http.middlewares.bouncer.plugin.bouncer.enabled=true" - - "traefik.http.middlewares.bouncer.plugin.bouncer.crowdsecmode=live" - - "traefik.http.middlewares.bouncer.plugin.bouncer.defaultdecisionseconds=2" - - "traefik.http.middlewares.bouncer.plugin.bouncer.crowdseclapikey=40796d93c2958f9e58345514e67740e5" - - "traefik.http.middlewares.bouncer.plugin.bouncer.forwardedheaderstrustedips=172.16.0.0/12" - - crowdsec: - image: crowdsecurity/crowdsec:v1.7.8 - container_name: crowdsec - environment: - DISABLE_ONLINE_API: "true" - BOUNCER_KEY_TRAEFIK: 40796d93c2958f9e58345514e67740e5 - CUSTOM_HOSTNAME: crowdsec - CROWDSEC_BYPASS_DB_VOLUME_CHECK: "true" - healthcheck: - test: ["CMD", "cscli", "lapi", "status"] - interval: 2s - timeout: 3s - retries: 30 - start_period: 5s diff --git a/tests/e2e/scenarios/live-mode/run.sh b/tests/e2e/scenarios/live-mode/run.sh deleted file mode 100755 index 2324105..0000000 --- a/tests/e2e/scenarios/live-mode/run.sh +++ /dev/null @@ -1,48 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -HERE="$(cd "$(dirname "$0")" && pwd)" -# shellcheck source=../../lib/common.sh -source "$HERE/../../lib/common.sh" - -SCENARIO=live-mode -PROJECT="e2e-${SCENARIO}" -COMPOSE_FILE="$HERE/docker-compose.yml" -LOG_FILE="/tmp/e2e-${SCENARIO}.log" - -cleanup() { - local rc=$? - if (( rc != 0 )); then - dump_diagnostics "$PROJECT" "$COMPOSE_FILE" > "$LOG_FILE" 2>&1 || true - echo "Scenario failed. Logs written to $LOG_FILE" - fi - docker compose -p "$PROJECT" -f "$COMPOSE_FILE" down -v --remove-orphans >/dev/null 2>&1 || true - exit $rc -} -trap cleanup EXIT - -echo "[$SCENARIO] starting stack..." -docker compose -p "$PROJECT" -f "$COMPOSE_FILE" up -d --wait - -echo "[$SCENARIO] waiting for crowdsec readiness..." -wait_crowdsec_ready - -echo "[$SCENARIO] waiting for traefik readiness..." -wait_for_status http://localhost:8000/foo 200 30 - -echo "[$SCENARIO] no decision -> first hit queries LAPI, returns 200, caches 'allowed' for 2s" -assert_status http://localhost:8000/foo 200 -H "X-Forwarded-For: 1.2.3.4" - -echo "[$SCENARIO] adding ban decision for 1.2.3.4" -docker exec crowdsec cscli decisions add --ip 1.2.3.4 --type ban --duration 5m - -echo "[$SCENARIO] waiting for defaultDecisionSeconds cache to expire..." -sleep 3 - -echo "[$SCENARIO] next hit must re-query LAPI and now see the ban" -assert_status http://localhost:8000/foo 403 -H "X-Forwarded-For: 1.2.3.4" - -echo "[$SCENARIO] another non-banned IP must still pass" -assert_status http://localhost:8000/foo 200 -H "X-Forwarded-For: 5.6.7.8" - -echo "[$SCENARIO] OK" diff --git a/tests/e2e/scenarios/none-mode/docker-compose.yml b/tests/e2e/scenarios/none-mode/docker-compose.yml deleted file mode 100644 index af14153..0000000 --- a/tests/e2e/scenarios/none-mode/docker-compose.yml +++ /dev/null @@ -1,50 +0,0 @@ -services: - traefik: - image: traefik:v3.7.1 - container_name: e2e-none-traefik - command: - - "--log.level=INFO" - - "--accesslog" - - "--api.insecure=true" - - "--providers.docker=true" - - "--providers.docker.exposedbydefault=false" - - "--entryPoints.web.address=:80" - - "--entryPoints.web.forwardedHeaders.insecure=true" - - "--experimental.localplugins.bouncer.modulename=github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin" - volumes: - - /var/run/docker.sock:/var/run/docker.sock:ro - - ../../../..:/plugins-local/src/github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin - ports: - - "8000:80" - depends_on: - crowdsec: - condition: service_healthy - - whoami: - image: traefik/whoami - container_name: e2e-none-whoami - labels: - - "traefik.enable=true" - - "traefik.http.routers.r.rule=PathPrefix(`/foo`)" - - "traefik.http.routers.r.entrypoints=web" - - "traefik.http.routers.r.middlewares=bouncer@docker" - - "traefik.http.services.s.loadbalancer.server.port=80" - - "traefik.http.middlewares.bouncer.plugin.bouncer.enabled=true" - - "traefik.http.middlewares.bouncer.plugin.bouncer.crowdsecmode=none" - - "traefik.http.middlewares.bouncer.plugin.bouncer.crowdseclapikey=40796d93c2958f9e58345514e67740e5" - - "traefik.http.middlewares.bouncer.plugin.bouncer.forwardedheaderstrustedips=172.16.0.0/12" - - crowdsec: - image: crowdsecurity/crowdsec:v1.7.8 - container_name: crowdsec - environment: - DISABLE_ONLINE_API: "true" - BOUNCER_KEY_TRAEFIK: 40796d93c2958f9e58345514e67740e5 - CUSTOM_HOSTNAME: crowdsec - CROWDSEC_BYPASS_DB_VOLUME_CHECK: "true" - healthcheck: - test: ["CMD", "cscli", "lapi", "status"] - interval: 2s - timeout: 3s - retries: 30 - start_period: 5s diff --git a/tests/e2e/scenarios/none-mode/run.sh b/tests/e2e/scenarios/none-mode/run.sh deleted file mode 100755 index 328ce71..0000000 --- a/tests/e2e/scenarios/none-mode/run.sh +++ /dev/null @@ -1,48 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -HERE="$(cd "$(dirname "$0")" && pwd)" -# shellcheck source=../../lib/common.sh -source "$HERE/../../lib/common.sh" - -SCENARIO=none-mode -PROJECT="e2e-${SCENARIO}" -COMPOSE_FILE="$HERE/docker-compose.yml" -LOG_FILE="/tmp/e2e-${SCENARIO}.log" - -cleanup() { - local rc=$? - if (( rc != 0 )); then - dump_diagnostics "$PROJECT" "$COMPOSE_FILE" > "$LOG_FILE" 2>&1 || true - echo "Scenario failed. Logs written to $LOG_FILE" - fi - docker compose -p "$PROJECT" -f "$COMPOSE_FILE" down -v --remove-orphans >/dev/null 2>&1 || true - exit $rc -} -trap cleanup EXIT - -echo "[$SCENARIO] starting stack..." -docker compose -p "$PROJECT" -f "$COMPOSE_FILE" up -d --wait - -echo "[$SCENARIO] waiting for crowdsec readiness..." -wait_crowdsec_ready - -echo "[$SCENARIO] waiting for traefik readiness..." -wait_for_status http://localhost:8000/foo 200 30 - -echo "[$SCENARIO] no decision -> request passes (LAPI queried per request)" -assert_status http://localhost:8000/foo 200 -H "X-Forwarded-For: 1.2.3.4" - -echo "[$SCENARIO] adding ban decision" -docker exec crowdsec cscli decisions add --ip 1.2.3.4 --type ban --duration 5m - -echo "[$SCENARIO] none mode has no cache -> next request must be blocked immediately" -assert_status http://localhost:8000/foo 403 -H "X-Forwarded-For: 1.2.3.4" - -echo "[$SCENARIO] deleting decision" -docker exec crowdsec cscli decisions delete --ip 1.2.3.4 - -echo "[$SCENARIO] previously banned IP must pass again immediately" -assert_status http://localhost:8000/foo 200 -H "X-Forwarded-For: 1.2.3.4" - -echo "[$SCENARIO] OK" diff --git a/tests/e2e/scenarios/stream-mode/docker-compose.yml b/tests/e2e/scenarios/stream-mode/docker-compose.yml deleted file mode 100644 index 718650e..0000000 --- a/tests/e2e/scenarios/stream-mode/docker-compose.yml +++ /dev/null @@ -1,51 +0,0 @@ -services: - traefik: - image: traefik:v3.7.1 - container_name: e2e-stream-traefik - command: - - "--log.level=INFO" - - "--accesslog" - - "--api.insecure=true" - - "--providers.docker=true" - - "--providers.docker.exposedbydefault=false" - - "--entryPoints.web.address=:80" - - "--entryPoints.web.forwardedHeaders.insecure=true" - - "--experimental.localplugins.bouncer.modulename=github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin" - volumes: - - /var/run/docker.sock:/var/run/docker.sock:ro - - ../../../..:/plugins-local/src/github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin - ports: - - "8000:80" - depends_on: - crowdsec: - condition: service_healthy - - whoami: - image: traefik/whoami - container_name: e2e-stream-whoami - labels: - - "traefik.enable=true" - - "traefik.http.routers.r.rule=PathPrefix(`/foo`)" - - "traefik.http.routers.r.entrypoints=web" - - "traefik.http.routers.r.middlewares=bouncer@docker" - - "traefik.http.services.s.loadbalancer.server.port=80" - - "traefik.http.middlewares.bouncer.plugin.bouncer.enabled=true" - - "traefik.http.middlewares.bouncer.plugin.bouncer.crowdsecmode=stream" - - "traefik.http.middlewares.bouncer.plugin.bouncer.updateintervalseconds=3" - - "traefik.http.middlewares.bouncer.plugin.bouncer.crowdseclapikey=40796d93c2958f9e58345514e67740e5" - - "traefik.http.middlewares.bouncer.plugin.bouncer.forwardedheaderstrustedips=172.16.0.0/12" - - crowdsec: - image: crowdsecurity/crowdsec:v1.7.8 - container_name: crowdsec - environment: - DISABLE_ONLINE_API: "true" - BOUNCER_KEY_TRAEFIK: 40796d93c2958f9e58345514e67740e5 - CUSTOM_HOSTNAME: crowdsec - CROWDSEC_BYPASS_DB_VOLUME_CHECK: "true" - healthcheck: - test: ["CMD", "cscli", "lapi", "status"] - interval: 2s - timeout: 3s - retries: 30 - start_period: 5s diff --git a/tests/e2e/scenarios/stream-mode/run.sh b/tests/e2e/scenarios/stream-mode/run.sh deleted file mode 100755 index 1988c23..0000000 --- a/tests/e2e/scenarios/stream-mode/run.sh +++ /dev/null @@ -1,57 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -HERE="$(cd "$(dirname "$0")" && pwd)" -# shellcheck source=../../lib/common.sh -source "$HERE/../../lib/common.sh" - -SCENARIO=stream-mode -PROJECT="e2e-${SCENARIO}" -COMPOSE_FILE="$HERE/docker-compose.yml" -LOG_FILE="/tmp/e2e-${SCENARIO}.log" - -cleanup() { - local rc=$? - if (( rc != 0 )); then - dump_diagnostics "$PROJECT" "$COMPOSE_FILE" > "$LOG_FILE" 2>&1 || true - echo "Scenario failed. Logs written to $LOG_FILE" - fi - docker compose -p "$PROJECT" -f "$COMPOSE_FILE" down -v --remove-orphans >/dev/null 2>&1 || true - exit $rc -} -trap cleanup EXIT - -echo "[$SCENARIO] starting stack..." -docker compose -p "$PROJECT" -f "$COMPOSE_FILE" up -d --wait - -echo "[$SCENARIO] waiting for crowdsec readiness..." -wait_crowdsec_ready - -echo "[$SCENARIO] waiting for traefik readiness..." -wait_for_status http://localhost:8000/foo 200 30 - -echo "[$SCENARIO] no decision yet -> request allowed" -assert_status http://localhost:8000/foo 200 -H "X-Forwarded-For: 1.2.3.4" - -echo "[$SCENARIO] adding ban decision for 1.2.3.4" -docker exec crowdsec cscli decisions add --ip 1.2.3.4 --type ban --duration 5m - -echo "[$SCENARIO] waiting one stream tick + buffer..." -sleep 6 - -echo "[$SCENARIO] banned IP must be blocked (HTTP 403)" -assert_status http://localhost:8000/foo 403 -H "X-Forwarded-For: 1.2.3.4" - -echo "[$SCENARIO] non-banned IP must still pass (HTTP 200)" -assert_status http://localhost:8000/foo 200 -H "X-Forwarded-For: 5.6.7.8" - -echo "[$SCENARIO] deleting ban decision" -docker exec crowdsec cscli decisions delete --ip 1.2.3.4 - -echo "[$SCENARIO] waiting one stream tick + buffer..." -sleep 6 - -echo "[$SCENARIO] previously banned IP must pass again" -assert_status http://localhost:8000/foo 200 -H "X-Forwarded-For: 1.2.3.4" - -echo "[$SCENARIO] OK" diff --git a/tests/e2e/scenarios/trusted-ips/docker-compose.yml b/tests/e2e/scenarios/trusted-ips/docker-compose.yml deleted file mode 100644 index 39d6042..0000000 --- a/tests/e2e/scenarios/trusted-ips/docker-compose.yml +++ /dev/null @@ -1,52 +0,0 @@ -services: - traefik: - image: traefik:v3.7.1 - container_name: e2e-trusted-traefik - command: - - "--log.level=INFO" - - "--accesslog" - - "--api.insecure=true" - - "--providers.docker=true" - - "--providers.docker.exposedbydefault=false" - - "--entryPoints.web.address=:80" - - "--entryPoints.web.forwardedHeaders.insecure=true" - - "--experimental.localplugins.bouncer.modulename=github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin" - volumes: - - /var/run/docker.sock:/var/run/docker.sock:ro - - ../../../..:/plugins-local/src/github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin - ports: - - "8000:80" - depends_on: - crowdsec: - condition: service_healthy - - whoami: - image: traefik/whoami - container_name: e2e-trusted-whoami - labels: - - "traefik.enable=true" - - "traefik.http.routers.r.rule=PathPrefix(`/foo`)" - - "traefik.http.routers.r.entrypoints=web" - - "traefik.http.routers.r.middlewares=bouncer@docker" - - "traefik.http.services.s.loadbalancer.server.port=80" - - "traefik.http.middlewares.bouncer.plugin.bouncer.enabled=true" - - "traefik.http.middlewares.bouncer.plugin.bouncer.crowdsecmode=stream" - - "traefik.http.middlewares.bouncer.plugin.bouncer.updateintervalseconds=3" - - "traefik.http.middlewares.bouncer.plugin.bouncer.crowdseclapikey=40796d93c2958f9e58345514e67740e5" - - "traefik.http.middlewares.bouncer.plugin.bouncer.forwardedheaderstrustedips=172.16.0.0/12" - - "traefik.http.middlewares.bouncer.plugin.bouncer.clienttrustedips=1.2.3.4/32" - - crowdsec: - image: crowdsecurity/crowdsec:v1.7.8 - container_name: crowdsec - environment: - DISABLE_ONLINE_API: "true" - BOUNCER_KEY_TRAEFIK: 40796d93c2958f9e58345514e67740e5 - CUSTOM_HOSTNAME: crowdsec - CROWDSEC_BYPASS_DB_VOLUME_CHECK: "true" - healthcheck: - test: ["CMD", "cscli", "lapi", "status"] - interval: 2s - timeout: 3s - retries: 30 - start_period: 5s diff --git a/tests/e2e/scenarios/trusted-ips/run.sh b/tests/e2e/scenarios/trusted-ips/run.sh deleted file mode 100755 index 479bc5e..0000000 --- a/tests/e2e/scenarios/trusted-ips/run.sh +++ /dev/null @@ -1,46 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -HERE="$(cd "$(dirname "$0")" && pwd)" -# shellcheck source=../../lib/common.sh -source "$HERE/../../lib/common.sh" - -SCENARIO=trusted-ips -PROJECT="e2e-${SCENARIO}" -COMPOSE_FILE="$HERE/docker-compose.yml" -LOG_FILE="/tmp/e2e-${SCENARIO}.log" - -cleanup() { - local rc=$? - if (( rc != 0 )); then - dump_diagnostics "$PROJECT" "$COMPOSE_FILE" > "$LOG_FILE" 2>&1 || true - echo "Scenario failed. Logs written to $LOG_FILE" - fi - docker compose -p "$PROJECT" -f "$COMPOSE_FILE" down -v --remove-orphans >/dev/null 2>&1 || true - exit $rc -} -trap cleanup EXIT - -echo "[$SCENARIO] starting stack..." -docker compose -p "$PROJECT" -f "$COMPOSE_FILE" up -d --wait - -echo "[$SCENARIO] waiting for crowdsec readiness..." -wait_crowdsec_ready - -echo "[$SCENARIO] waiting for traefik readiness..." -wait_for_status http://localhost:8000/foo 200 30 - -echo "[$SCENARIO] banning the trusted IP 1.2.3.4 and an untrusted IP 5.6.7.8" -docker exec crowdsec cscli decisions add --ip 1.2.3.4 --type ban --duration 5m -docker exec crowdsec cscli decisions add --ip 5.6.7.8 --type ban --duration 5m - -echo "[$SCENARIO] waiting one stream tick + buffer..." -sleep 6 - -echo "[$SCENARIO] trusted IP must bypass the bouncer even though it is banned" -assert_status http://localhost:8000/foo 200 -H "X-Forwarded-For: 1.2.3.4" - -echo "[$SCENARIO] untrusted banned IP must be blocked (control: proves the bouncer is active)" -assert_status http://localhost:8000/foo 403 -H "X-Forwarded-For: 5.6.7.8" - -echo "[$SCENARIO] OK"