errcheck flagged fmt.Fprint in the new test. Use rw.Write with the error
checked, and drop the now-unused fmt import.
Verified with golangci-lint v1.63.4 and go test under go1.22 in containers
matching CI, rather than the local go1.26 toolchain that made the linter
unusable.
Fix#384.
appsecQuery never read the appsec response body. net/http only returns a
connection to the idle pool once its body has reached EOF, so closing early
discarded it: every request opened a fresh TCP connection to the appsec host and
left it in TIME_WAIT. crowdsecQuery, two functions above, does call io.ReadAll,
which is why the LAPI path in the same process pools correctly.
Measured with an httptest appsec server over 10 calls:
empty response body -> 1 connection (already at EOF, reuse worked)
non-empty response body -> 10 connections (one per request)
The drain goes in the defer rather than before the final return, because the 500
and non-200 paths return earlier. Draining only at the end fixes 200 and leaves
the other two leaking:
status 200 -> 1 connection status 403 -> 10 status 500 -> 10
which is the wrong half to fix: 403 is what appsec produces for a site under
attack, and 500 is what a wedged appsec produces.
Also removes the unreachable `if err != nil { ... appsecQuery:readBody }` left
over from a version that did read the body -- err is always nil there -- and sets
MaxIdleConnsPerHost on the three transports. They each talk to a single host, so
the unset default of 2 (DefaultMaxIdleConnsPerHost) capped the pool well below
the configured MaxIdleConns of 10.
Adds Test_appsecQuery_reusesConnection, which asserts one connection for ten
calls across 200/403/500 and fails on the previous code.
* Do not consider body unreadable when it's http.NoBody
Fix#351
* 🐛 appsec: only drop unreadable bodies for methods that carry one
Gate the crowdsecAppsecUnreadableBodyBlock drop on the request method,
mirroring the reference lua-cs-bouncer METHODS_WITH_BODY list
(POST/PUT/PATCH/DELETE). Browser GETs over HTTP/3 never carry a
Content-Length (quic-go always wraps the stream in a non-nil body with
ContentLength -1), so they were all wrongly dropped; they are now
forwarded to appsec headers-only, while gRPC streams (always POST) stay
droppable.
Fix#351
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* 🍱 add test and remove useless comment
* 🍱 add test for GET with unreadable body
* 🍱 fix log
* 🍱 fix test
---------
Co-authored-by: mhx <mathieu@hanotaux.fr>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: maxlerebourg <maxlerebourg@gmail.com>
* 🐛 fix appsec silently 403-ing gRPC streams with unreadable body
A bidirectional gRPC stream is an HTTP/2 request with no Content-Length
whose body never reaches EOF. Since #321 removed the ContentLength guard,
appsecQuery buffered it with io.ReadAll, which blocked until the request
timed out and was turned into a 403 (issue #323). The backend was never
reached (OriginStatus:0).
Mirror the reference lua-cs-bouncer behaviour: detect an unreadable body
(ProtoMajor >= 2 && ContentLength < 0) and, instead of buffering it,
forward the request to Appsec with headers only. Add a new
CrowdsecAppsecDropUnreadableBody option (default false) that mirrors the
reference APPSEC_DROP_UNREADABLE_BODY: when true, such requests are
blocked outright instead of forwarded without their body.
Readable HTTP/1.1 bodies are still buffered and inspected, so the bypass
closed by #321 stays closed.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* 🚨 appsec: satisfy linters (gocritic ifElseChain, misspell)
Rewrite the body-handling if/else chain in appsecQuery as a switch
(gocritic) and use US spelling "behavior" (misspell).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* 🔇 appsec: drop redundant unreadable-body debug log
Address review on #332: the caller (handleNextServeHTTP) already logs the
returned error with the request IP, so the inner Debug line duplicated it.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* 🍱 increase gocyclo
* 🍱 fix lint
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: maxlerebourg <maxlerebourg@gmail.com>
* Add parameter to configure Ban Response Content-Type
* Add testing for new BanResponseContentType parameter
* Ensure there is a fallback to default Content-Type is user provided empty value
* Set Content-Type even if banTemplate is nil
* Add more edge cases for testing ban response Content-Type
* Add CR/LF validation for BanResponseContentType
* Add CaptchaResponseContentType to allow separate Content-Type configuration for captcha responses
* Add testing for new CaptchaResponseContentType
* Update README
* Split nil and CR/LF response Content-Type value validation into separate function
* Throw error instead of setting the default in case of empty parameter declaration
* Update testing accordingly
* ✨ remove HTML from var name, add tests and infer content type from filePath
* 🍱 fix lint ?
* 🍱 fix lint
* 🍱 fix lint
* 🍱 fix lint + naming
* 🍱 fix lint
* 🍱 fuck lint
---------
Co-authored-by: maxlerebourg <maxlerebourg@gmail.com>
* ✨ 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>