mirror of
https://github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin.git
synced 2026-07-21 11:38:59 +02:00
🐛 appsec: do not buffer unreadable (gRPC/HTTP2) request bodies (#332)
* 🐛 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>
This commit is contained in:
co-authored by
Claude Opus 4.8
maxlerebourg
parent
1c1672c856
commit
d32f271195
@@ -63,6 +63,7 @@ type Config struct {
|
||||
CrowdsecAppsecTLSCertificateBouncerKeyFile string `json:"crowdsecAppsecTlsCertificateBouncerKeyFile,omitempty"`
|
||||
CrowdsecAppsecFailureBlock bool `json:"crowdsecAppsecFailureBlock,omitempty"`
|
||||
CrowdsecAppsecUnreachableBlock bool `json:"crowdsecAppsecUnreachableBlock,omitempty"`
|
||||
CrowdsecAppsecUnreadableBodyBlock bool `json:"crowdsecAppsecUnreadableBodyBlock,omitempty"`
|
||||
CrowdsecAppsecBodyLimit int64 `json:"crowdsecAppsecBodyLimit,omitempty"`
|
||||
CrowdsecLapiScheme string `json:"crowdsecLapiScheme,omitempty"`
|
||||
CrowdsecLapiHost string `json:"crowdsecLapiHost,omitempty"`
|
||||
@@ -127,52 +128,53 @@ func contains(source []string, target string) bool {
|
||||
// New creates the default plugin configuration.
|
||||
func New() *Config {
|
||||
return &Config{
|
||||
Enabled: false,
|
||||
LogLevel: LogINFO,
|
||||
LogFormat: "common",
|
||||
LogFilePath: "",
|
||||
CrowdsecMode: LiveMode,
|
||||
CrowdsecAppsecEnabled: false,
|
||||
CrowdsecAppsecFailureBlock: true,
|
||||
CrowdsecAppsecUnreachableBlock: true,
|
||||
CrowdsecAppsecBodyLimit: 10485760,
|
||||
CrowdsecAppsecScheme: "",
|
||||
CrowdsecAppsecHost: "crowdsec:7422",
|
||||
CrowdsecAppsecPath: "/",
|
||||
CrowdsecAppsecKey: "",
|
||||
CrowdsecAppsecTLSInsecureVerify: false,
|
||||
CrowdsecLapiScheme: HTTP,
|
||||
CrowdsecLapiHost: "crowdsec:8080",
|
||||
CrowdsecLapiPath: "/",
|
||||
CrowdsecLapiKey: "",
|
||||
CrowdsecLapiTLSInsecureVerify: false,
|
||||
UpdateIntervalSeconds: 60,
|
||||
MetricsUpdateIntervalSeconds: 600,
|
||||
UpdateMaxFailure: 0,
|
||||
StreamStartupBlock: true,
|
||||
DefaultDecisionSeconds: 60,
|
||||
RemediationStatusCode: http.StatusForbidden,
|
||||
HTTPTimeoutSeconds: 10,
|
||||
CaptchaProvider: "",
|
||||
CaptchaCustomJsURL: "",
|
||||
CaptchaCustomValidateURL: "",
|
||||
CaptchaCustomKey: "",
|
||||
CaptchaCustomResponse: "",
|
||||
CaptchaSiteKey: "",
|
||||
CaptchaSecretKey: "",
|
||||
CaptchaGracePeriodSeconds: 1800,
|
||||
CaptchaFilePath: "/captcha.html",
|
||||
BanFilePath: "",
|
||||
TraceHeadersCustomName: "",
|
||||
RemediationHeadersCustomName: "",
|
||||
ForwardedHeadersCustomName: "X-Forwarded-For",
|
||||
ForwardedHeadersTrustedIPs: []string{},
|
||||
ClientTrustedIPs: []string{},
|
||||
RedisCacheEnabled: false,
|
||||
RedisCacheHost: "redis:6379",
|
||||
RedisCachePassword: "",
|
||||
RedisCacheDatabase: "",
|
||||
RedisCacheUnreachableBlock: true,
|
||||
Enabled: false,
|
||||
LogLevel: LogINFO,
|
||||
LogFormat: "common",
|
||||
LogFilePath: "",
|
||||
CrowdsecMode: LiveMode,
|
||||
CrowdsecAppsecEnabled: false,
|
||||
CrowdsecAppsecFailureBlock: true,
|
||||
CrowdsecAppsecUnreachableBlock: true,
|
||||
CrowdsecAppsecUnreadableBodyBlock: true,
|
||||
CrowdsecAppsecBodyLimit: 10485760,
|
||||
CrowdsecAppsecScheme: "",
|
||||
CrowdsecAppsecHost: "crowdsec:7422",
|
||||
CrowdsecAppsecPath: "/",
|
||||
CrowdsecAppsecKey: "",
|
||||
CrowdsecAppsecTLSInsecureVerify: false,
|
||||
CrowdsecLapiScheme: HTTP,
|
||||
CrowdsecLapiHost: "crowdsec:8080",
|
||||
CrowdsecLapiPath: "/",
|
||||
CrowdsecLapiKey: "",
|
||||
CrowdsecLapiTLSInsecureVerify: false,
|
||||
UpdateIntervalSeconds: 60,
|
||||
MetricsUpdateIntervalSeconds: 600,
|
||||
UpdateMaxFailure: 0,
|
||||
StreamStartupBlock: true,
|
||||
DefaultDecisionSeconds: 60,
|
||||
RemediationStatusCode: http.StatusForbidden,
|
||||
HTTPTimeoutSeconds: 10,
|
||||
CaptchaProvider: "",
|
||||
CaptchaCustomJsURL: "",
|
||||
CaptchaCustomValidateURL: "",
|
||||
CaptchaCustomKey: "",
|
||||
CaptchaCustomResponse: "",
|
||||
CaptchaSiteKey: "",
|
||||
CaptchaSecretKey: "",
|
||||
CaptchaGracePeriodSeconds: 1800,
|
||||
CaptchaFilePath: "/captcha.html",
|
||||
BanFilePath: "",
|
||||
TraceHeadersCustomName: "",
|
||||
RemediationHeadersCustomName: "",
|
||||
ForwardedHeadersCustomName: "X-Forwarded-For",
|
||||
ForwardedHeadersTrustedIPs: []string{},
|
||||
ClientTrustedIPs: []string{},
|
||||
RedisCacheEnabled: false,
|
||||
RedisCacheHost: "redis:6379",
|
||||
RedisCachePassword: "",
|
||||
RedisCacheDatabase: "",
|
||||
RedisCacheUnreachableBlock: true,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user