mirror of
https://github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin.git
synced 2026-07-20 11:09:00 +02:00
* 🐛 fall back to system trust store when no custom TLS CA is set Closes #327. Until now, configuring `crowdsecLapiScheme=https` forced the operator to either provide `crowdsecLapiTLSCertificateAuthority` (a custom CA) or set `crowdsecLapiTLSInsecureVerify=true` — there was no way to rely on the host's system trust store, which is the expected setup when the LAPI sits behind a reverse proxy with a publicly trusted (e.g. Let's Encrypt) certificate. Two contributing bugs: - `validateParamsTLS` rejected an empty CA up-front. - `getTLSConfig` always allocated an empty `tls.Config.RootCAs`, which silently disabled the standard library's fall-back to `x509.SystemCertPool()`. Fix: drop the validation error for the empty-CA case and only allocate `RootCAs` when a custom CA is actually provided. Same change applies symmetrically to the AppSec path since the helper is shared. Add unit tests covering the four meaningful states (HTTP, HTTPS with system CA, HTTPS with custom CA, HTTPS with insecure verify) plus the malformed-PEM rejection. README updated to document the system trust store as an explicit option for both LAPI and AppSec HTTPS. * 📝 fix gofmt alignment in TLS test struct * ✅ update existing test: https without CA is now accepted * ♻️ tests: hoist shared validPEM to package level, rename cfgGarbage Address review on #331: - the self-signed validPEM block was duplicated in two test funcs; declare it once at package level and drop both local copies. - rename cfgGarbage -> cfgInvalidCA (and its test case) for a descriptive name. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * ✅ e2e mock: add tls-system-ca scenario (HTTPS LAPI via system trust store) CI regression coverage for this PR: with no custom CA configured, the bouncer must fall back to the OS/system trust store for an HTTPS LAPI. In the binary suite the "system trust store" is whatever Go's x509.SystemCertPool() reads, which honours SSL_CERT_FILE on the Traefik process. The scenario mints a throwaway CA, serves the mock LAPI over HTTPS with a cert signed by it, and runs the stack twice: - positive: SSL_CERT_FILE = our CA -> LAPI trusted -> 200 - negative: SSL_CERT_FILE = empty bundle -> not trusted -> 403 (fail-closed) The negative run proves the patch still VERIFIES (not an insecure skip). - mocklapi: optional --lapi-tls-cert/--lapi-tls-key to serve the LAPI over TLS. - common.sh: opt-in LAPI_TLS_CERT/KEY (HTTPS mock) and TRAEFIK_SSL_CERT_FILE (inject SSL_CERT_FILE into Traefik); both default-empty, other scenarios unaffected. - adds openssl as a scenario-only dependency. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
92 lines
4.0 KiB
Markdown
92 lines
4.0 KiB
Markdown
# 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/`](mocklapi/main.go), 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`](../../../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).
|
|
|
|
```bash
|
|
# 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`.
|