🐛 forward client HTTP version to AppSec via X-Crowdsec-Appsec-Http-Version (#400)

This commit is contained in:
ffluegel authored and GitHub committed 2026-10-11 13:47:02 +02:00
1 parent 710e88808b
commit bd94fb0c2b
2 files changed
+90 -18

No files matched your search

+22 -18
View File
@@ -27,24 +27,25 @@ import (
)
const (
crowdsecAppsecIPHeader = "X-Crowdsec-Appsec-Ip"
crowdsecAppsecURIHeader = "X-Crowdsec-Appsec-Uri"
crowdsecAppsecHostHeader = "X-Crowdsec-Appsec-Host"
crowdsecAppsecVerbHeader = "X-Crowdsec-Appsec-Verb"
crowdsecAppsecHeader = "X-Crowdsec-Appsec-Api-Key"
crowdsecAppsecUserAgent = "X-Crowdsec-Appsec-User-Agent"
crowdsecLapiHeader = "X-Api-Key"
crowdsecLapiRoute = "v1/decisions"
crowdsecLapiStreamRoute = "v1/decisions/stream"
crowdsecLapiMetricsRoute = "v1/usage-metrics"
crowdsecCapiHost = "api.crowdsec.net"
crowdsecCapiHeader = "Authorization"
crowdsecCapiLoginRoute = "v2/watchers/login"
crowdsecCapiStreamRoute = "v2/decisions/stream"
cacheTimeoutKey = "updated"
appsecAllowAction = "allow"
appsecChallengeAction = "challenge"
appsecResponseBodyLimit = 1 << 20 // 1 MiB
crowdsecAppsecIPHeader = "X-Crowdsec-Appsec-Ip"
crowdsecAppsecURIHeader = "X-Crowdsec-Appsec-Uri"
crowdsecAppsecHostHeader = "X-Crowdsec-Appsec-Host"
crowdsecAppsecVerbHeader = "X-Crowdsec-Appsec-Verb"
crowdsecAppsecHeader = "X-Crowdsec-Appsec-Api-Key"
crowdsecAppsecUserAgent = "X-Crowdsec-Appsec-User-Agent"
crowdsecAppsecHTTPVersionHeader = "X-Crowdsec-Appsec-Http-Version"
crowdsecLapiHeader = "X-Api-Key"
crowdsecLapiRoute = "v1/decisions"
crowdsecLapiStreamRoute = "v1/decisions/stream"
crowdsecLapiMetricsRoute = "v1/usage-metrics"
crowdsecCapiHost = "api.crowdsec.net"
crowdsecCapiHeader = "Authorization"
crowdsecCapiLoginRoute = "v2/watchers/login"
crowdsecCapiStreamRoute = "v2/decisions/stream"
cacheTimeoutKey = "updated"
appsecAllowAction = "allow"
appsecChallengeAction = "challenge"
appsecResponseBodyLimit = 1 << 20 // 1 MiB
)
// ##############################################################
@@ -841,6 +842,9 @@ func appsecQuery(bouncer *Bouncer, ip string, httpReq *http.Request) (*AppSecRes
req.Header.Set(crowdsecAppsecHostHeader, httpReq.Host)
req.Header.Set(crowdsecAppsecURIHeader, httpReq.URL.String())
req.Header.Set(crowdsecAppsecUserAgent, httpReq.Header.Get("User-Agent"))
if httpReq.ProtoMajor > 0 {
req.Header.Set(crowdsecAppsecHTTPVersionHeader, fmt.Sprintf("%d%d", httpReq.ProtoMajor, httpReq.ProtoMinor))
}
req.Header.Set("User-Agent", "Crowdsec-Bouncer-Traefik-Plugin/"+pluginVersion)
res, err := bouncer.httpAppsecClient.Do(req)
+68
View File
@@ -885,3 +885,71 @@ func Test_appsecQuery_unreadableBodyMethods(t *testing.T) {
})
}
}
// Test_appsecQuery_forwardsHTTPVersion verifies that the client HTTP version
// is forwarded to CrowdSec AppSec via X-Crowdsec-Appsec-Http-Version.
func Test_appsecQuery_forwardsHTTPVersion(t *testing.T) {
tests := []struct {
name string
protoMajor int
protoMinor int
expectedHdr string
}{
{
name: "HTTP/1.0",
protoMajor: 1,
protoMinor: 0,
expectedHdr: "10",
},
{
name: "HTTP/1.1",
protoMajor: 1,
protoMinor: 1,
expectedHdr: "11",
},
{
name: "HTTP/2.0",
protoMajor: 2,
protoMinor: 0,
expectedHdr: "20",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
var capturedVersion string
appsecServer := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
capturedVersion = r.Header.Get(crowdsecAppsecHTTPVersionHeader)
rw.WriteHeader(http.StatusOK)
if _, err := rw.Write([]byte(`{"action":"allow"}`)); err != nil {
t.Errorf("write response: %v", err)
}
}))
defer appsecServer.Close()
appsecURL, _ := url.Parse(appsecServer.URL)
bouncer := &Bouncer{
appsecScheme: appsecURL.Scheme,
appsecHost: appsecURL.Host,
appsecPath: "/",
appsecBodyLimit: 10485760,
appsecFailureBlock: false,
httpAppsecClient: appsecServer.Client(),
log: logger.New("INFO", ""),
}
req, _ := http.NewRequest(http.MethodGet, "http://localhost/", nil)
req.ProtoMajor = tt.protoMajor
req.ProtoMinor = tt.protoMinor
_, err := appsecQuery(bouncer, "1.2.3.4", req)
if err != nil {
t.Fatalf("appsecQuery() unexpected error: %v", err)
}
if capturedVersion != tt.expectedHdr {
t.Errorf("expected %s header %q, got %q", crowdsecAppsecHTTPVersionHeader, tt.expectedHdr, capturedVersion)
}
})
}
}