diff --git a/.github/workflows/container-image.yaml b/.github/workflows/container-image.yaml index 52e71ef..2964e2e 100644 --- a/.github/workflows/container-image.yaml +++ b/.github/workflows/container-image.yaml @@ -35,7 +35,8 @@ jobs: run: > for image in $HAPROXY_IMAGES; do echo "Running e2e with Haproxy image $image" - HAPROXY_IMAGE=$image docker compose -f docker-compose.e2e.yaml up --abort-on-container-exit tests + HAPROXY_IMAGE=$image + go run mage.go e2e done - name: Set up Docker Buildx diff --git a/.gitignore b/.gitignore index f84664f..16a9611 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,9 @@ # Test binary, built with `go test -c` *.test +# Binaries generated by make build +coraza-spoa_* + # Output of the go coverage tool, specifically when used with LiteIDE *.out vendor/ @@ -16,4 +19,4 @@ vendor/ # local files config.yaml logs/ -rules/ \ No newline at end of file +rules/ diff --git a/docker-compose.e2e.yaml b/docker-compose.e2e.yaml index 5d1c5e9..956c063 100644 --- a/docker-compose.e2e.yaml +++ b/docker-compose.e2e.yaml @@ -1,10 +1,13 @@ version: "3.9" services: httpbin: - image: mccutchen/go-httpbin:v2.5.0 + restart: unless-stopped + image: mccutchen/go-httpbin:v2.9.0 + command: [ "/bin/go-httpbin", "-port", "8080" ] ports: - 8080:8080 coraza: + restart: unless-stopped build: context: . target: coreruleset @@ -21,7 +24,7 @@ services: "-c", "haproxy -f /usr/local/etc/haproxy/haproxy.cfg | tee /var/lib/haproxy/hap.log" ] - ports: [ "4000:80" ] + ports: [ "4000:80"] links: - "coraza:coraza" - "httpbin:httpbin" @@ -30,17 +33,3 @@ services: source: ./docker/haproxy target: /usr/local/etc/haproxy - hap:/var/lib/haproxy - tests: - depends_on: - - haproxy - - coraza - links: - - "haproxy:haproxy" - - "httpbin:httpbin" - build: - context: docker/e2e - dockerfile: ./Dockerfile.curl - volumes: - - hap:/haproxy -volumes: - hap: diff --git a/docker/e2e/docker-compose.e2e.yaml b/docker/e2e/docker-compose.e2e.yaml new file mode 100644 index 0000000..54d8916 --- /dev/null +++ b/docker/e2e/docker-compose.e2e.yaml @@ -0,0 +1,38 @@ +version: "3.9" +services: + httpbin: + image: mccutchen/go-httpbin:v2.9.0 + command: [ "/bin/go-httpbin", "-port", "8081" ] + ports: + - 8081:8081 + + coraza: + build: + context: ../../ + target: coreruleset + volumes: + - ./docker/e2e/e2e-rules.conf:/etc/coraza-spoa/rules/000-e2e-rules.conf + + haproxy: + depends_on: + - coraza + - httpbin + image: ${HAPROXY_IMAGE:-haproxy:2.7-alpine} + command: + [ + "sh", + "-c", + "haproxy -f /usr/local/etc/haproxy/haproxy.cfg | tee /var/lib/haproxy/hap.log" + ] + ports: [ "4000:80" ] + links: + - "coraza:coraza" + - "httpbin:httpbin" + volumes: + - type: bind + source: ../haproxy + target: /usr/local/etc/haproxy + - hap:/var/lib/haproxy + +volumes: + hap: diff --git a/docker/e2e/e2e-rules.conf b/docker/e2e/e2e-rules.conf index aae10b6..e542534 100644 --- a/docker/e2e/e2e-rules.conf +++ b/docker/e2e/e2e-rules.conf @@ -1,9 +1,16 @@ +# See https://github.com/corazawaf/coraza/blob/main/http/e2e/cmd/httpe2e/main.go SecRuleEngine On SecRequestBodyAccess On -SecRule REQUEST_URI "/e2e-deny" "id:101,phase:1,t:lowercase,log,deny" -SecRule REQUEST_URI "/e2e-drop" "id:102,phase:1,t:lowercase,log,drop" -SecRule REQUEST_URI "/e2e-redirect" "id:103,phase:1,t:lowercase,log,redirect:http://www.example.org/denied" -SecRule REQUEST_BODY "@rx maliciouspayload" "id:104,phase:2,t:lowercase,log,deny" -SecRule RESPONSE_STATUS "@streq 406" "id:105,phase:3,t:lowercase,log,deny" -SecRule RESPONSE_HEADERS::e2eblock "true" "id:106,phase:4,t:lowercase,log,deny" -SecRule RESPONSE_BODY "@contains responsebodycode" "id:107,phase:4,t:lowercase,log,deny" +SecResponseBodyAccess On +SecResponseBodyMimeType application/json +# Custom rule for Coraza config check (ensuring that these configs are used) +SecRule &REQUEST_HEADERS:coraza-e2e "@eq 0" "id:100,phase:1,deny,status:424,log,msg:'Coraza E2E - Missing header'" +# Custom rules for e2e testing +SecRule REQUEST_URI "@streq /admin" "id:101,phase:1,t:lowercase,log,deny" +SecRule REQUEST_BODY "@rx maliciouspayload" "id:102,phase:2,t:lowercase,log,deny" +SecRule RESPONSE_HEADERS:pass "@rx leak" "id:103,phase:3,t:lowercase,log,deny" +SecRule RESPONSE_BODY "@contains responsebodycode" "id:104,phase:4,t:lowercase,log,deny" +# Custom rules mimicking the following CRS rules: 941100, 942100, 913100 +SecRule ARGS_NAMES|ARGS "@detectXSS" "id:9411,phase:2,t:none,t:utf8toUnicode,t:urlDecodeUni,t:htmlEntityDecode,t:jsDecode,t:cssDecode,t:removeNulls,log,deny" +SecRule ARGS_NAMES|ARGS "@detectSQLi" "id:9421,phase:2,t:none,t:utf8toUnicode,t:urlDecodeUni,t:removeNulls,multiMatch,log,deny" +SecRule REQUEST_HEADERS:User-Agent "@pm grabber masscan" "id:9131,phase:1,t:none,log,deny" diff --git a/magefile.go b/magefile.go index 857119c..75b8079 100644 --- a/magefile.go +++ b/magefile.go @@ -19,8 +19,8 @@ import ( var addLicenseVersion = "v1.0.0" // https://github.com/google/addlicense // TODO: Use recent version (for example v1.53.2) to run on Go 1.20 (https://github.com/golangci/golangci-lint/pull/3414) -var golangCILintVer = "v1.48.0" // https://github.com/golangci/golangci-lint/releases -var gosImportsVer = "v0.1.5" // https://github.com/rinchsan/gosimports/releases/tag/v0.1.5 +var golangCILintVer = "v1.48.0" // https://github.com/golangci/golangci-lint/releases +var gosImportsVer = "v0.1.5" // https://github.com/rinchsan/gosimports/releases/tag/v0.1.5 var errRunGoModTidy = errors.New("go.mod/sum not formatted, commit changes") var errNoGitDir = errors.New("no .git directory found") @@ -73,6 +73,31 @@ func Test() error { return nil } +// E2e runs e2e tests with a built plugin against the e2e deployment. Requires docker-compose. +func E2e() error { + var err error + if err = sh.RunV("docker-compose", "-f", "docker-compose.e2e.yaml", "up", "-d", "haproxy"); err != nil { + return err + } + defer func() { + _ = sh.RunV("docker-compose", "--file", "docker-compose.e2e.yaml", "down", "-v") + }() + + haproxyHost := os.Getenv("HAPROXY_HOST") + if haproxyHost == "" { + haproxyHost = "localhost:4000" + } + httpbinHost := os.Getenv("HTTPBIN_HOST") + if httpbinHost == "" { + httpbinHost = "localhost:8080" + } + + if err = sh.RunV("go", "run", "github.com/corazawaf/coraza/v3/http/e2e/cmd/httpe2e@main", "--proxy-hostport", "http://"+haproxyHost, "--httpbin-hostport", "http://"+httpbinHost); err != nil { + sh.RunV("docker-compose", "-f", "docker-compose.e2e.yaml", "logs", "haproxy") + } + return err +} + // Precommit installs a git hook to run check when committing func Precommit() error { if _, err := os.Stat(filepath.Join(".git", "hooks")); os.IsNotExist(err) {