Skip to content

Commit

Permalink
Example (#30)
Browse files Browse the repository at this point in the history
Co-authored-by: Matteo Pace <[email protected]>
  • Loading branch information
jcchavezs and M4tteoP authored Oct 5, 2022
1 parent 0cfce2c commit 7a94d69
Show file tree
Hide file tree
Showing 10 changed files with 1,217 additions and 10 deletions.
7 changes: 7 additions & 0 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,13 @@ jobs:
ENVOY_IMAGE=$image go run mage.go e2e
done
- name: Spins up the example
run: go run mage.go runExample

- name: Run example tests
shell: bash
run: ./example/readme-tests.sh

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2

Expand Down
30 changes: 24 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -104,10 +104,28 @@ go run mage.go ftw
```
Take a look at its config file [ftw.yml](./ftw/ftw.yml) for details about tests currently excluded.

### Spinning up the coraza-proxy-wasm for manual tests

Via the commands `setup` and `teardown` you can spin up and tear down the test environment. Envoy with the coraza-wasm filter will be reachable at `localhost:8080`.
In order to monitor envoy logs while performing requests run:
```
docker-compose -f ./ftw/docker-compose.yml logs -f envoy-logs
## Example: Spinning up the coraza-wasm-filter for manual tests
Once the filter is built, via the commands `RunExample` and `teardownExample` you can spin up and tear down the test environment. Envoy with the coraza-wasm filter will be reachable at `localhost:8080`. The filter is configured with the CRS loaded working in Anomaly Scoring mode. For details and locally tweaking the configuration refer to [coraza-demo.conf](./rules/coraza-demo.conf) and [crs-setup-demo.conf](./rules/crs-setup-demo.conf).
In order to monitor envoy logs while performing requests you can run:
- Envoy logs: `docker-compose -f ./example/docker-compose.yml logs -f envoy-logs`.
- Critical wasm (audit) logs: `docker-compose -f ./example/docker-compose.yml logs -f wasm-logs`

### Manual requests
Run `./example/readme-tests.sh` in order to run the following requests against the just set up test environment, otherwise manually execute them on your own:
```bash
# True positive requests:
# XSS phase 1
curl -I 'http://localhost:8080/anything?arg=<script>alert(0)</script>'
# SQLI phase 2 (reading the body request)
curl -i -X POST 'http://localhost:8080/anything' --data "1%27%20ORDER%20BY%203--%2B"
# Triggers a CRS scanner detection rule (913100)
curl -I --user-agent "Grabber/0.1 (X11; U; Linux i686; en-US; rv:1.7)" -H "Host: localhost" -H "Accept: text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5" localhost:8080

# True negative requests:
# A GET request with a harmless argument
curl -I 'http://localhost:8080/anything?arg=arg_1'
# An usual user-agent
curl -I --user-agent "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.0.0 Safari/537.36" localhost:8080
# A payload (reading the body request)
curl -i -X POST 'http://localhost:8080/anything' --data "this is a payload"
```
2 changes: 1 addition & 1 deletion e2e/tests.sh
100755 → 100644
Original file line number Diff line number Diff line change
Expand Up @@ -48,4 +48,4 @@ if [[ "$status_code" -ne 403 ]] ; then
fi
echo "[Ok] Got status code $status_code, expected 403"

echo "[Done] All tests passed"
echo "[Done] All tests passed"
55 changes: 55 additions & 0 deletions example/docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
services:
httpbin:
image: kennethreitz/httpbin:latest
chown:
image: alpine:3.16
command:
- /bin/sh
- -c
- chown -R 101:101 /home/envoy/logs
volumes:
- logs:/home/envoy/logs:rw
envoy:
depends_on:
- chown
- httpbin
image: envoyproxy/envoy:v1.23-latest
command:
- -c
- /conf/envoy-config.yaml
- --log-level
- info
- --component-log-level
- wasm:debug
- --log-format [%Y-%m-%d %T.%f][%t][%l][%n] [%g:%#] %v
- --log-path
- /home/envoy/logs/envoy.log
volumes:
- ../build:/build
- .:/conf
- logs:/home/envoy/logs:rw
ports:
- 8080:80
wasm-logs:
depends_on:
- envoy
image: debian:11-slim
entrypoint: bash
command:
- -c
- tail -c +0 -f /home/envoy/logs/envoy.log | grep --line-buffered "[critical][wasm]" | tee /home/envoy/logs/ftw.log
volumes:
- logs:/home/envoy/logs:rw
envoy-logs:
depends_on:
- envoy
- wasm-logs
image: debian:11-slim
entrypoint: bash
command:
- -c
- tail -c +0 -f /home/envoy/logs/envoy.log
volumes:
- logs:/home/envoy/logs:ro
volumes:
logs:
68 changes: 68 additions & 0 deletions example/envoy-config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
static_resources:
listeners:
- address:
socket_address:
address: 0.0.0.0
port_value: 80
filter_chains:
- filters:
- name: envoy.filters.network.http_connection_manager
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
stat_prefix: ingress_http
codec_type: auto
http_protocol_options:
accept_http_10: true
route_config:
virtual_hosts:
- name: local_route
domains:
- "*"
routes:
- match:
prefix: "/"
route:
cluster: local_server
http_filters:
- name: envoy.filters.http.wasm
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.http.wasm.v3.Wasm
config:
name: "coraza-filter"
root_id: ""
configuration:
"@type": "type.googleapis.com/google.protobuf.StringValue"
value: |
{
"rules": [
{"inline": "Include coraza-demo.conf"},
{"inline": "Include crs-setup-demo.conf"},
{"inline": "SecDebugLogLevel 3"},
{"inline": "Include crs/*.conf"},
{"inline": "SecRule ARGS:id \"@eq 0\" \"id:1, phase:1,deny, status:403,msg:'Invalid id',log,auditlog\""}
]
}
vm_config:
runtime: "envoy.wasm.runtime.v8"
vm_id: "my_vm_id"
code:
local:
filename: "build/main.wasm"
- name: envoy.filters.http.router
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router

clusters:
- name: local_server
connect_timeout: 6000s
type: strict_dns
lb_policy: round_robin
load_assignment:
cluster_name: local_server
endpoints:
- lb_endpoints:
- endpoint:
address:
socket_address:
address: httpbin
port_value: 80
110 changes: 110 additions & 0 deletions example/readme-tests.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
#!/bin/bash
# Copyright 2022 The OWASP Coraza contributors
# SPDX-License-Identifier: Apache-2.0
ENVOY_HOST=${ENVOY_HOST:-"localhost:8080"}

[[ "${DEBUG}" == "true" ]] && set -x

envoy_url_echo="http://${ENVOY_HOST}/anything"

okayBodyPayload="hello"
maliciousBodyPayload="maliciouspayload"
bodyPayloadForResponseBodyTrueNegative="Hello world"
bodyPayloadForResponseBody="responsebodycode"

# wait_for_service waits until the given URL returns a 200 status code.
# $1: The URL to send requests to.
# $2: The max number of requests to send before giving up.
function wait_for_service() {
local status_code="000"
local url=${1}
local max=${2}
while [[ "${status_code}" -ne "200" ]]; do
status_code=$(curl --write-out "%{http_code}" --silent --output /dev/null "${url}")
sleep 1
echo -ne "[Wait] Waiting for response from ${url}. Timeout: ${max}s \r"
((max-=1))
if [[ "${max}" -eq 0 ]]; then
echo "[Fail] Timeout waiting for response from ${url}, make sure the server is running."
exit 1
fi
done
echo -e "\n[Ok] Got status code ${status_code}"
}

# check_status sends HTTP requests to the given URL and expects a given response code.
# $1: The URL to send requests to.
# $2: The expected status code.
# $3-N: The rest of the arguments will be passed to the curl command as additional arguments
# to customize the HTTP call.
function check_status() {
local url=${1}
local status=${2}
local args=("${@:3}" --write-out '%{http_code}' --silent --output /dev/null)
status_code=$(curl "${args[@]}" "${url}")
if [[ "${status_code}" -ne ${status} ]] ; then
echo "[Fail] Unexpected response with code ${status_code} from ${url}"
exit 1
fi
echo "[Ok] Got status code ${status_code}, expected ${status}"
}

# check_body sends the given HTTP request and checks the response body.
# $1: The URL to send requests to.
# $2: true/false indicating if an empty body is expected or not.
# $3-N: The rest of the arguments will be passed to the curl command as additional arguments
# to customize the HTTP call.
function check_body() {
local url=${1}
local empty=${2}
local args=("${@:3}" --silent)
response_body=$(curl "${args[@]}" "${url}")
if [[ "${empty}" == "true" ]] && [[ -n "${response_body}" ]]; then
echo -e "[Fail] Unexpected response with a body. Body dump:\n${response_body}"
exit 1
fi
if [[ "${empty}" != "true" ]] && [[ -z "${response_body}" ]]; then
echo -e "[Fail] Unexpected response with a body. Body dump:\n${response_body}"
exit 1
fi
echo "[Ok] Got response with an expected body (empty=${empty})"
}

step=1
total_steps=7

# Testing if the server is up
echo "[${step}/${total_steps}] Testing application reachability"
wait_for_service "${envoy_url_echo}" 20

# Testing XSS phase 1
((step+=1))
echo "[${step}/${total_steps}] Testing XSS at request headers"
check_status "${envoy_url_echo}?arg=<script>alert(0)</script>" 403

# Testing SQLI phase 2
((step+=1))
echo "[${step}/${total_steps}] Testing SQLi at request body"
check_status "${envoy_url_echo}" 403 -X POST --data "1%27%20ORDER%20BY%203--%2B"

# Triggers a CRS scanner detection rule (913100)
((step+=1))
echo "[${step}/${total_steps}] (onRequestBody) Testing CRS rule 913100"
check_status "${envoy_url_echo}" 403 --user-agent "Grabber/0.1 (X11; U; Linux i686; en-US; rv:1.7)" -H "Host: localhost" -H "Accept: text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5"

# True negative GET request
((step+=1))
echo "[${step}/${total_steps}] True negative GET request"
check_status "${envoy_url_echo}?arg=arg_1" 200

# True negative GET request with an usual user-agent
((step+=1))
echo "[${step}/${total_steps}] True negative GET request with user-agent"
check_status "${envoy_url_echo}" 200 --user-agent "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.0.0 Safari/537.36"

# True negative POST request with a payload
((step+=1))
echo "[${step}/${total_steps}] True negative POST request"
check_status "${envoy_url_echo}" 200 --data "this is a payload"

echo "[Done] All examples request worked as expected"
4 changes: 2 additions & 2 deletions ftw/tests.sh
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ cd /workspace

step=1
total_steps=3
max_retries=10 #seconds for the server reachability timeout
max_retries=15 #seconds for the server reachability timeout
host=${1:-envoy}
health_url="http://${host}:80"
unfiltered_url="http://${host}:80/home"
Expand All @@ -21,7 +21,7 @@ while [[ "$status_code" -eq "000" ]]; do
status_code=$(curl --write-out "%{http_code}" --silent --output /dev/null $health_url)
sleep 1
echo -ne "[Wait] Waiting for response from $health_url. Timeout: ${max_retries}s \r"
((max_retries-=1))
let "max_retries--"
if [[ "$max_retries" -eq 0 ]] ; then
echo "[Fail] Timeout waiting for response from $health_url, make sure the server is running."
exit 1
Expand Down
12 changes: 11 additions & 1 deletion magefile.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ func Lint() error {
return nil
}

// Test runs all tests.
// Test runs all unit tests.
func Test() error {
return sh.RunV("go", "test", "./...")
}
Expand Down Expand Up @@ -172,4 +172,14 @@ func Ftw() error {
return sh.RunWithV(env, "docker-compose", "--file", "ftw/docker-compose.yml", "run", "--rm", "ftw")
}

// RunExample spins up the test environment, access at http://localhost:8080. Requires docker-compose.
func RunExample() error {
return sh.RunV("docker-compose", "--file", "example/docker-compose.yml", "up", "-d", "envoy-logs")
}

// TeardownExample tears down the test environment. Requires docker-compose.
func TeardownExample() error {
return sh.RunV("docker-compose", "--file", "example/docker-compose.yml", "down")
}

var Default = Build
Loading

0 comments on commit 7a94d69

Please sign in to comment.