diff --git a/bouncer.go b/bouncer.go index 65e5db4..b5ac73b 100644 --- a/bouncer.go +++ b/bouncer.go @@ -677,6 +677,12 @@ func handleStreamCache(bouncer *Bouncer) error { return nil } +func isReverseProxyError(statusCode int) bool { + return statusCode == http.StatusBadGateway || + statusCode == http.StatusServiceUnavailable || + statusCode == http.StatusGatewayTimeout +} + func crowdsecQuery(bouncer *Bouncer, stringURL string, data []byte) ([]byte, error) { var req *http.Request if len(data) > 0 { @@ -688,7 +694,7 @@ func crowdsecQuery(bouncer *Bouncer, stringURL string, data []byte) ([]byte, err req.Header.Set("User-Agent", "Crowdsec-Bouncer-Traefik-Plugin/"+pluginVersion) res, err := bouncer.httpClient.Do(req) - if err != nil { + if err != nil || isReverseProxyError(res.StatusCode) { return nil, fmt.Errorf("crowdsecQuery:unreachable url:%s %w", stringURL, err) } defer func() { @@ -752,7 +758,7 @@ func appsecQuery(bouncer *Bouncer, ip string, httpReq *http.Request) error { req.Header.Set("User-Agent", "Crowdsec-Bouncer-Traefik-Plugin/"+pluginVersion) res, err := bouncer.httpAppsecClient.Do(req) - if err != nil { + if err != nil || isReverseProxyError(res.StatusCode) { bouncer.log.Error("appsecQuery:unreachable") if bouncer.appsecUnreachableBlock { return fmt.Errorf("appsecQuery:unreachable %w", err) diff --git a/tests/e2e/mock/mocklapi/main.go b/tests/e2e/mock/mocklapi/main.go index a2804bd..cb0ba11 100644 --- a/tests/e2e/mock/mocklapi/main.go +++ b/tests/e2e/mock/mocklapi/main.go @@ -13,6 +13,7 @@ package main import ( "encoding/json" "flag" + "io" "log" "net/http" "strings" @@ -72,9 +73,26 @@ func main() { // exercised without standing up the real WAF. go func() { log.Fatal(http.ListenAndServe(*appsecAddr, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - if strings.Contains(r.Header.Get("X-Crowdsec-Appsec-Uri"), "rpc2") { + if strings.Contains(r.Header.Get("X-Crowdsec-Appsec-Uri"), "403") { w.WriteHeader(http.StatusForbidden) } + if strings.Contains(r.Header.Get("X-Crowdsec-Appsec-Uri"), "500") { + w.WriteHeader(http.StatusInternalServerError) + } + if strings.Contains(r.Header.Get("X-Crowdsec-Appsec-Uri"), "502") { + w.WriteHeader(http.StatusBadGateway) + } + // Read body + body, err := io.ReadAll(r.Body) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + return + } + defer r.Body.Close() + if strings.Contains(string(body), "a=0") { + w.WriteHeader(http.StatusForbidden) + return + } }))) }() diff --git a/tests/e2e/mock/scenarios/appsec/dynamic.yml b/tests/e2e/mock/scenarios/appsec/dynamic.yml index 05fa8d9..0bb38de 100644 --- a/tests/e2e/mock/scenarios/appsec/dynamic.yml +++ b/tests/e2e/mock/scenarios/appsec/dynamic.yml @@ -23,6 +23,9 @@ http: crowdsecLapiHost: "@@LAPI_HOST@@" crowdsecLapiKey: "@@APIKEY@@" crowdsecAppsecEnabled: "true" + crowdsecAppsecFailureBlock: "true" + crowdsecAppsecBodyLimit: 4 + crowdsecAppsecUnreachableBlock: "false" crowdsecAppsecScheme: http crowdsecAppsecHost: "@@APPSEC_HOST@@" forwardedHeadersTrustedIps: diff --git a/tests/e2e/mock/scenarios/appsec/run.sh b/tests/e2e/mock/scenarios/appsec/run.sh index 1e1d2bd..7b92113 100755 --- a/tests/e2e/mock/scenarios/appsec/run.sh +++ b/tests/e2e/mock/scenarios/appsec/run.sh @@ -13,11 +13,23 @@ SCENARIO=appsec # plugin's AppSec path end to end (header forwarding + allow/block handling); it # does not test the real WAF's detection accuracy. body() { - echo "[$SCENARIO] benign request must pass (AppSec allows)" + echo "[$SCENARIO] benign request must pass (AppSec 200)" assert_status "http://127.0.0.1:${WEB_PORT}/foo" 200 -H "X-Forwarded-For: 1.2.3.4" - echo "[$SCENARIO] request whose URI contains 'rpc2' must be blocked (AppSec 403)" - assert_status "http://127.0.0.1:${WEB_PORT}/foo/rpc2" 403 -H "X-Forwarded-For: 1.2.3.4" + echo "[$SCENARIO] request that return 403 must be blocked (AppSec 403)" + assert_status "http://127.0.0.1:${WEB_PORT}/foo/403" 403 -H "X-Forwarded-For: 1.2.3.4" + + echo "[$SCENARIO] request that return 500 must be blocked (because CrowdsecAppsecFailureBlock = true) (AppSec 500)" + assert_status "http://127.0.0.1:${WEB_PORT}/foo/500" 403 -H "X-Forwarded-For: 1.2.3.4" + + echo "[$SCENARIO] request that return 502 must pass (because CrowdsecAppsecUnreachableBlock = false) (Proxy error 502)" + assert_status "http://127.0.0.1:${WEB_PORT}/foo/502" 200 -H "X-Forwarded-For: 1.2.3.4" + + echo "[$SCENARIO] request that send bad body after crowdsecAppsecBodyLimit must pass (AppSec 200)" + assert_status "http://127.0.0.1:${WEB_PORT}/foo" 200 -H "X-Forwarded-For: 1.2.3.4" -X POST -d "______&a=0" + + echo "[$SCENARIO] request that send bad body before crowdsecAppsecBodyLimit must pass (AppSec 403)" + assert_status "http://127.0.0.1:${WEB_PORT}/foo" 403 -H "X-Forwarded-For: 1.2.3.4" -X POST -d "a=0&______" } run_scenario "$SCENARIO" "$HERE" body