e2e mock: add AppSec scenario + custom remediation header assertion

Address review feedback on the binary e2e suite:

- mocklapi: add an AppSec WAF stand-in (--appsec-addr) that blocks any URI
  containing "rpc2" — the exact probe from examples/appsec-enabled — and allows
  the rest. Lets the suite exercise the plugin's AppSec wiring (header
  forwarding + allow/block enforcement) without the real CRS engine.
- new scenarios/appsec: benign request passes, /foo/rpc2 is 403.
- custom-ban-page: assert the banned response carries the custom remediation
  header (remediationHeadersCustomName), per review.
- README: drop the "don't open issues / AppSec intentionally absent" framing;
  describe what the suite actually covers, including AppSec wiring.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
mhx
2026-06-06 21:33:52 +02:00
co-authored by Claude Opus 4.8
parent 070a82992a
commit 8497f7de75
8 changed files with 98 additions and 19 deletions
+21 -4
View File
@@ -4,9 +4,10 @@
// test drive decisions through /admin instead of `cscli`. It also serves the
// stub upstream that Traefik proxies allowed requests to.
//
// It is deliberately NOT a Crowdsec/AppSec conformance harness: Crowdsec's own
// correctness is the upstream maintainer's responsibility, not this plugin's.
// See the suite README.
// It is NOT a Crowdsec/AppSec conformance harness — the real WAF engine (OWASP
// CRS, virtual patching) is out of scope. The AppSec endpoint here emulates a
// single deterministic rule so the suite can exercise the plugin's AppSec
// wiring (header forwarding, allow/block handling) end to end. See the README.
package main
import (
@@ -14,6 +15,7 @@ import (
"flag"
"log"
"net/http"
"strings"
"sync"
)
@@ -48,6 +50,8 @@ func main() {
// The stub upstream Traefik proxies allowed requests to — the binary-suite
// equivalent of the traefik/whoami container. Not AppSec.
backendAddr := flag.String("backend-addr", "127.0.0.1:8091", "address for the stub upstream service")
// AppSec WAF stand-in (the real engine listens on :7422). Not a CRS engine.
appsecAddr := flag.String("appsec-addr", "127.0.0.1:8092", "address for the AppSec mock")
flag.Parse()
go func() {
@@ -56,6 +60,19 @@ func main() {
})))
}()
// AppSec mock: the plugin forwards the request metadata in X-Crowdsec-Appsec-*
// headers and reads our status — 200 allows, 403 blocks. We emulate one
// deterministic virtual-patching rule (block any URI containing "rpc2", the
// exact probe from examples/appsec-enabled) so the plugin's AppSec path is
// 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") {
w.WriteHeader(http.StatusForbidden)
}
})))
}()
mux := http.NewServeMux()
// Readiness probe for the test harness (empty body, 200).
@@ -113,6 +130,6 @@ func main() {
}
})
log.Printf("mocklapi: LAPI on %s, backend on %s", *lapiAddr, *backendAddr)
log.Printf("mocklapi: LAPI on %s, backend on %s, appsec on %s", *lapiAddr, *backendAddr, *appsecAddr)
log.Fatal(http.ListenAndServe(*lapiAddr, mux))
}