🐛 fall back to system trust store when no custom TLS CA is set - #331
Merged
Conversation
maxlerebourg
reviewed
Jun 5, 2026
maxlerebourg
reviewed
Jun 5, 2026
mathieuHa
force-pushed
the
fix/327-tls-system-ca
branch
from
June 6, 2026 10:04
993a415 to
a2a21ae
Compare
mathieuHa
added a commit
that referenced
this pull request
Jun 6, 2026
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>
maxlerebourg
previously approved these changes
Jun 14, 2026
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.
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>
…tore) 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>
mathieuHa
force-pushed
the
fix/327-tls-system-ca
branch
from
June 15, 2026 16:21
02de09f to
a48498e
Compare
maxlerebourg
approved these changes
Jun 21, 2026
renovate Bot
added a commit
to sdwilsh/ansible-playbooks
that referenced
this pull request
Jul 29, 2026
…plugin to v1.7.0 ##### [\`v1.7.0\`](https://github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin/releases/tag/v1.7.0) #### Deprecated `BanHTMLFilePath` become `BanFilePath` `CaptchaHTMLFilePath` become `CaptchaFilePath` #### What's Changed - 🍱 Header.Add -> Set, log Info -> Debug, version bump by [@maxlerebourg](https://github.com/maxlerebourg) in [#324](maxlerebourg/crowdsec-bouncer-traefik-plugin#324) - ✨️ Do not block middleware startup due to stream intialization by [@maxlerebourg](https://github.com/maxlerebourg) in [#281](maxlerebourg/crowdsec-bouncer-traefik-plugin#281) - 📝 docs: fix Mermaid diagrams not rendering (lowercase destroy keyword) by [@mathieuHa](https://github.com/mathieuHa) in [#335](maxlerebourg/crowdsec-bouncer-traefik-plugin#335) - ✨ tests: CI end-to-end suite — Traefik binary + mock LAPI by [@mathieuHa](https://github.com/mathieuHa) in [#329](maxlerebourg/crowdsec-bouncer-traefik-plugin#329) - ⬆️ Bump actions/upload-artifact from 4 to 7 by [@dependabot](https://github.com/dependabot)\[bot] in [#336](maxlerebourg/crowdsec-bouncer-traefik-plugin#336) - ⬆️ Bump actions/checkout from 6 to 7 by [@dependabot](https://github.com/dependabot)\[bot] in [#341](maxlerebourg/crowdsec-bouncer-traefik-plugin#341) - 🐛 fall back to system trust store when no custom TLS CA is set by [@mathieuHa](https://github.com/mathieuHa) in [#331](maxlerebourg/crowdsec-bouncer-traefik-plugin#331) - Add parameter to configure the ban page Content-Type response header by [@highpingblorg](https://github.com/highpingblorg) in [#325](maxlerebourg/crowdsec-bouncer-traefik-plugin#325) - ⬆️ Bump actions/cache from 5 to 6 by [@dependabot](https://github.com/dependabot)\[bot] in [#344](maxlerebourg/crowdsec-bouncer-traefik-plugin#344) - Consider 502, 503 and 504 as unavaible for appsec by [@dani](https://github.com/dani) in [#338](maxlerebourg/crowdsec-bouncer-traefik-plugin#338) - 🐛 appsec: do not buffer unreadable (gRPC/HTTP2) request bodies by [@mathieuHa](https://github.com/mathieuHa) in [#332](maxlerebourg/crowdsec-bouncer-traefik-plugin#332) - 🤖 chore: adopt Renovate for automated dependency updates by [@mathieuHa](https://github.com/mathieuHa) in [#330](maxlerebourg/crowdsec-bouncer-traefik-plugin#330) - ⬆️ renovate: Update all (major) by [@maxlerebourg](https://github.com/maxlerebourg) in [#345](maxlerebourg/crowdsec-bouncer-traefik-plugin#345) - 🐛 ci(renovate): fix OOM crash and stop using Mend's default gitAuthor by [@mathieuHa](https://github.com/mathieuHa) in [#347](maxlerebourg/crowdsec-bouncer-traefik-plugin#347) - ⬆️ renovate: Update all by [@maxlerebourg](https://github.com/maxlerebourg) in [#349](maxlerebourg/crowdsec-bouncer-traefik-plugin#349) - Do not consider body unreadable when it's http.NoBody by [@dani](https://github.com/dani) in [#352](maxlerebourg/crowdsec-bouncer-traefik-plugin#352) - feat: Allow cache reading from replicas by [@samtoxie](https://github.com/samtoxie) in [#342](maxlerebourg/crowdsec-bouncer-traefik-plugin#342) - ✨ Renovate update version.go by [@maxlerebourg](https://github.com/maxlerebourg) in [#360](maxlerebourg/crowdsec-bouncer-traefik-plugin#360) - ⬆️ renovate: Update all by [@maxlerebourg](https://github.com/maxlerebourg) in [#350](maxlerebourg/crowdsec-bouncer-traefik-plugin#350) #### New Contributors - [@dani](https://github.com/dani) made their first contribution in [#338](maxlerebourg/crowdsec-bouncer-traefik-plugin#338) - [@samtoxie](https://github.com/samtoxie) made their first contribution in [#342](maxlerebourg/crowdsec-bouncer-traefik-plugin#342) **Full Changelog**: <maxlerebourg/crowdsec-bouncer-traefik-plugin@v1.6.0...v1.7.0>
renovate Bot
added a commit
to sdwilsh/ansible-playbooks
that referenced
this pull request
Jul 29, 2026
…plugin to v1.7.0 ##### [\`v1.7.0\`](https://github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin/releases/tag/v1.7.0) #### Deprecated `BanHTMLFilePath` become `BanFilePath` `CaptchaHTMLFilePath` become `CaptchaFilePath` #### What's Changed - 🍱 Header.Add -> Set, log Info -> Debug, version bump by [@maxlerebourg](https://github.com/maxlerebourg) in [#324](maxlerebourg/crowdsec-bouncer-traefik-plugin#324) - ✨️ Do not block middleware startup due to stream intialization by [@maxlerebourg](https://github.com/maxlerebourg) in [#281](maxlerebourg/crowdsec-bouncer-traefik-plugin#281) - 📝 docs: fix Mermaid diagrams not rendering (lowercase destroy keyword) by [@mathieuHa](https://github.com/mathieuHa) in [#335](maxlerebourg/crowdsec-bouncer-traefik-plugin#335) - ✨ tests: CI end-to-end suite — Traefik binary + mock LAPI by [@mathieuHa](https://github.com/mathieuHa) in [#329](maxlerebourg/crowdsec-bouncer-traefik-plugin#329) - ⬆️ Bump actions/upload-artifact from 4 to 7 by [@dependabot](https://github.com/dependabot)\[bot] in [#336](maxlerebourg/crowdsec-bouncer-traefik-plugin#336) - ⬆️ Bump actions/checkout from 6 to 7 by [@dependabot](https://github.com/dependabot)\[bot] in [#341](maxlerebourg/crowdsec-bouncer-traefik-plugin#341) - 🐛 fall back to system trust store when no custom TLS CA is set by [@mathieuHa](https://github.com/mathieuHa) in [#331](maxlerebourg/crowdsec-bouncer-traefik-plugin#331) - Add parameter to configure the ban page Content-Type response header by [@highpingblorg](https://github.com/highpingblorg) in [#325](maxlerebourg/crowdsec-bouncer-traefik-plugin#325) - ⬆️ Bump actions/cache from 5 to 6 by [@dependabot](https://github.com/dependabot)\[bot] in [#344](maxlerebourg/crowdsec-bouncer-traefik-plugin#344) - Consider 502, 503 and 504 as unavaible for appsec by [@dani](https://github.com/dani) in [#338](maxlerebourg/crowdsec-bouncer-traefik-plugin#338) - 🐛 appsec: do not buffer unreadable (gRPC/HTTP2) request bodies by [@mathieuHa](https://github.com/mathieuHa) in [#332](maxlerebourg/crowdsec-bouncer-traefik-plugin#332) - 🤖 chore: adopt Renovate for automated dependency updates by [@mathieuHa](https://github.com/mathieuHa) in [#330](maxlerebourg/crowdsec-bouncer-traefik-plugin#330) - ⬆️ renovate: Update all (major) by [@maxlerebourg](https://github.com/maxlerebourg) in [#345](maxlerebourg/crowdsec-bouncer-traefik-plugin#345) - 🐛 ci(renovate): fix OOM crash and stop using Mend's default gitAuthor by [@mathieuHa](https://github.com/mathieuHa) in [#347](maxlerebourg/crowdsec-bouncer-traefik-plugin#347) - ⬆️ renovate: Update all by [@maxlerebourg](https://github.com/maxlerebourg) in [#349](maxlerebourg/crowdsec-bouncer-traefik-plugin#349) - Do not consider body unreadable when it's http.NoBody by [@dani](https://github.com/dani) in [#352](maxlerebourg/crowdsec-bouncer-traefik-plugin#352) - feat: Allow cache reading from replicas by [@samtoxie](https://github.com/samtoxie) in [#342](maxlerebourg/crowdsec-bouncer-traefik-plugin#342) - ✨ Renovate update version.go by [@maxlerebourg](https://github.com/maxlerebourg) in [#360](maxlerebourg/crowdsec-bouncer-traefik-plugin#360) - ⬆️ renovate: Update all by [@maxlerebourg](https://github.com/maxlerebourg) in [#350](maxlerebourg/crowdsec-bouncer-traefik-plugin#350) #### New Contributors - [@dani](https://github.com/dani) made their first contribution in [#338](maxlerebourg/crowdsec-bouncer-traefik-plugin#338) - [@samtoxie](https://github.com/samtoxie) made their first contribution in [#342](maxlerebourg/crowdsec-bouncer-traefik-plugin#342) **Full Changelog**: <maxlerebourg/crowdsec-bouncer-traefik-plugin@v1.6.0...v1.7.0>
renovate Bot
added a commit
to sdwilsh/ansible-playbooks
that referenced
this pull request
Jul 29, 2026
…plugin to v1.7.0 ##### [\`v1.7.0\`](https://github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin/releases/tag/v1.7.0) #### Deprecated `BanHTMLFilePath` become `BanFilePath` `CaptchaHTMLFilePath` become `CaptchaFilePath` #### What's Changed - 🍱 Header.Add -> Set, log Info -> Debug, version bump by [@maxlerebourg](https://github.com/maxlerebourg) in [#324](maxlerebourg/crowdsec-bouncer-traefik-plugin#324) - ✨️ Do not block middleware startup due to stream intialization by [@maxlerebourg](https://github.com/maxlerebourg) in [#281](maxlerebourg/crowdsec-bouncer-traefik-plugin#281) - 📝 docs: fix Mermaid diagrams not rendering (lowercase destroy keyword) by [@mathieuHa](https://github.com/mathieuHa) in [#335](maxlerebourg/crowdsec-bouncer-traefik-plugin#335) - ✨ tests: CI end-to-end suite — Traefik binary + mock LAPI by [@mathieuHa](https://github.com/mathieuHa) in [#329](maxlerebourg/crowdsec-bouncer-traefik-plugin#329) - ⬆️ Bump actions/upload-artifact from 4 to 7 by [@dependabot](https://github.com/dependabot)\[bot] in [#336](maxlerebourg/crowdsec-bouncer-traefik-plugin#336) - ⬆️ Bump actions/checkout from 6 to 7 by [@dependabot](https://github.com/dependabot)\[bot] in [#341](maxlerebourg/crowdsec-bouncer-traefik-plugin#341) - 🐛 fall back to system trust store when no custom TLS CA is set by [@mathieuHa](https://github.com/mathieuHa) in [#331](maxlerebourg/crowdsec-bouncer-traefik-plugin#331) - Add parameter to configure the ban page Content-Type response header by [@highpingblorg](https://github.com/highpingblorg) in [#325](maxlerebourg/crowdsec-bouncer-traefik-plugin#325) - ⬆️ Bump actions/cache from 5 to 6 by [@dependabot](https://github.com/dependabot)\[bot] in [#344](maxlerebourg/crowdsec-bouncer-traefik-plugin#344) - Consider 502, 503 and 504 as unavaible for appsec by [@dani](https://github.com/dani) in [#338](maxlerebourg/crowdsec-bouncer-traefik-plugin#338) - 🐛 appsec: do not buffer unreadable (gRPC/HTTP2) request bodies by [@mathieuHa](https://github.com/mathieuHa) in [#332](maxlerebourg/crowdsec-bouncer-traefik-plugin#332) - 🤖 chore: adopt Renovate for automated dependency updates by [@mathieuHa](https://github.com/mathieuHa) in [#330](maxlerebourg/crowdsec-bouncer-traefik-plugin#330) - ⬆️ renovate: Update all (major) by [@maxlerebourg](https://github.com/maxlerebourg) in [#345](maxlerebourg/crowdsec-bouncer-traefik-plugin#345) - 🐛 ci(renovate): fix OOM crash and stop using Mend's default gitAuthor by [@mathieuHa](https://github.com/mathieuHa) in [#347](maxlerebourg/crowdsec-bouncer-traefik-plugin#347) - ⬆️ renovate: Update all by [@maxlerebourg](https://github.com/maxlerebourg) in [#349](maxlerebourg/crowdsec-bouncer-traefik-plugin#349) - Do not consider body unreadable when it's http.NoBody by [@dani](https://github.com/dani) in [#352](maxlerebourg/crowdsec-bouncer-traefik-plugin#352) - feat: Allow cache reading from replicas by [@samtoxie](https://github.com/samtoxie) in [#342](maxlerebourg/crowdsec-bouncer-traefik-plugin#342) - ✨ Renovate update version.go by [@maxlerebourg](https://github.com/maxlerebourg) in [#360](maxlerebourg/crowdsec-bouncer-traefik-plugin#360) - ⬆️ renovate: Update all by [@maxlerebourg](https://github.com/maxlerebourg) in [#350](maxlerebourg/crowdsec-bouncer-traefik-plugin#350) #### New Contributors - [@dani](https://github.com/dani) made their first contribution in [#338](maxlerebourg/crowdsec-bouncer-traefik-plugin#338) - [@samtoxie](https://github.com/samtoxie) made their first contribution in [#342](maxlerebourg/crowdsec-bouncer-traefik-plugin#342) **Full Changelog**: <maxlerebourg/crowdsec-bouncer-traefik-plugin@v1.6.0...v1.7.0>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #327.
Problem
Setting
crowdsecLapiScheme=httpsagainst a LAPI exposed behind a reverse proxy with a publicly trusted certificate (e.g. Let's Encrypt) currently forces the operator to pick one of two suboptimal options:crowdsecLapiTLSInsecureVerify=true(skip verification — defeats the point), orcrowdsecLapiTLSCertificateAuthorityto a CA bundle (extra config nobody should need for a public CA).The expected behavior — "use the system trust store" — wasn't reachable.
Root cause
Two contributing bugs in
pkg/configuration/configuration.go:validateParamsTLSreturned an error whenCrowdsecLapiTLSCertificateAuthoritywas empty.getTLSConfigalways initializedtls.Config.RootCAs = x509.NewCertPool()(an empty pool), which silently disables the stdlib's fall-back tox509.SystemCertPool(). So even if validation didn't fire, verification of a Let's Encrypt cert would still fail.The Go idiom for "use the system trust store" is to leave
tls.Config.RootCAsasnil. Thetraefikimage (alpine +ca-certificates) ships a CA bundle that already trusts the public roots.Fix
validateParamsTLS: drop the empty-CA error; still rejects garbage PEM.getTLSConfig: only allocateRootCAswhen a custom CA is provided; otherwise leave itnil.Tests
Test_validateParamsTLSandTest_GetTLSConfigCrowdsecwere scaffolds (// TODO: Add test cases.) — now filled in.Test_validateParamsTLScovers:Test_GetTLSConfigCrowdseccovers:tls.ConfigRootCAs == nil(system trust store)RootCAspopulatedInsecureSkipVerify == trueDocs
README.md— replaced the "you need to either accept any certificates or add the CA" wording with three explicit options (system trust store / custom CA / skip verification), for both the LAPI and the AppSec sections. Per-field descriptions ofCrowdsec{Lapi,Appsec}TlsCertificateAuthoritynow mention the system trust store as the default fallback.Compatibility
No behavior change for existing setups: configs that explicitly set a custom CA or insecure verify continue to work identically. The new behavior is only reachable by omitting both — which was rejected before.