Consider 502, 503 and 504 as unavaible for appsec (#338)

* Consider 502, 503 and 504 as unavaible for appsec

Fixes #337

*  add test and the function isReverseProxyError

---------

Co-authored-by: maxlerebourg <maxlerebourg@gmail.com>
This commit is contained in:
Daniel Berteaud
2026-06-30 22:03:06 +02:00
committed by GitHub
co-authored by maxlerebourg
parent 21895fbb9d
commit 1c1672c856
4 changed files with 45 additions and 6 deletions
+8 -2
View File
@@ -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)
+19 -1
View File
@@ -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
}
})))
}()
@@ -23,6 +23,9 @@ http:
crowdsecLapiHost: "@@LAPI_HOST@@"
crowdsecLapiKey: "@@APIKEY@@"
crowdsecAppsecEnabled: "true"
crowdsecAppsecFailureBlock: "true"
crowdsecAppsecBodyLimit: 4
crowdsecAppsecUnreachableBlock: "false"
crowdsecAppsecScheme: http
crowdsecAppsecHost: "@@APPSEC_HOST@@"
forwardedHeadersTrustedIps:
+15 -3
View File
@@ -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