* 🐛 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>
* ✨ 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>
The sequence diagrams used the keyword `Destroy` (capital D). Mermaid
keywords are case-sensitive, so the invalid token aborted parsing and
every diagram failed to render on GitHub.
Lowercase all `Destroy` -> `destroy` in README.md and the captcha
example README. The `create` keyword was already correct, and each
create/destroy is properly paired with an adjacent message.
Fixes#270
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
* 🐛 fix(user-agent) add version in ua to reduce warning logs from crowdsec LAPI
* 🐛 fix(user-agent) add version in ua to reduce warning logs from crowdsec LAPI
* 🐛 fix(user-agent) remove whitespaces
* 🐛 fix(user-agent) Add Cap for Bouncer
---------
Co-authored-by: maxlerebourg <maxlerebourg@gmail.com>
* ✨ Add ban html template
* 📝 Add doc for custom ban page
* ⚰️ Remove old code due to merge
* ✏️ Fix merge remaining in html
* ✨ render banTemplate from html/template to string
* 🚨 : fix lint
---------
Co-authored-by: max.lerebourg <max.lerebourg@monisnap.com>
* 📝 Clean documentation in Readme
* 📝 Update readme
* 📝 Place exemples in seperated README
* ⬆️ Update traefik to 2.9.6 and crowdsec to 1.4.3 in exemples
* 📝 Add exemple at the right place
* 📝 Add documentation exemple 4 for vm binary usage
* :hammer:Update binary version
* 🔨 update
* 🔨 update
* 🚧 Working Crowdsec with tls auth
* 🐛 Add changes to download the plugin from the service
* 🔧 Add config middle for https
* 🔧 Update config for the exemple
* 🔧 Add conf for certs, working example
* 📝 Add doc for binary vm Readme
* 📝 update documentation for exemple and make
* 🚨 Fix lint
* 🚨 Fix Lint End of File
* ✨ Add support for insecure tls connections to LAPI
* 📝 Add documentation for the TLS insecure parameter
* 🚧 Add tls authority certificate and checks for params
* 📝 Add example for tls communication in readme and folder
* 📝 Update documentation and example for tls
* 🚨 Fix easy lint errors
* 🦺 logic to fetch certificates
* 🚨 Fix lint on readme
* ♻️ Refactor validate to fix lint and clean
* 🚧 Add doc, cert gen for crowdsec example
* 🚧 Progress on setting up Crowdsec with tls
* 🚧 Update certs validation for example
* ♻️ Add load variable from file or value and get client cert
* ♻️ Refactor getting variables
* 🚨 Fix lint, no new line on new files
* 🐛 Fix bug on condition check lapi key cert
* ♻️ Update after review
* ♻️ Update after review
* 🍱 fix mathieu code
* ♻️ Refactor logic of loading tls certificates
* 🍱 clean code
* 🍱 last fix
* 🍱 fix lint
* ♻️ Add documentation in readme, fix lint, remove unfinished tests
* 🐛 Fix conditions logics
* 🚨 Fix Lint
* ♻️ simplify code on getVariable
Co-authored-by: Max Lerebourg <maxlerebourg@gmail.com>