Skip to content

Commit 7a94d69

Browse files
jcchavezsM4tteoP
andauthored
Example (#30)
Co-authored-by: Matteo Pace <[email protected]>
1 parent 0cfce2c commit 7a94d69

File tree

10 files changed

+1217
-10
lines changed

10 files changed

+1217
-10
lines changed

.github/workflows/ci.yaml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,13 @@ jobs:
7676
ENVOY_IMAGE=$image go run mage.go e2e
7777
done
7878
79+
- name: Spins up the example
80+
run: go run mage.go runExample
81+
82+
- name: Run example tests
83+
shell: bash
84+
run: ./example/readme-tests.sh
85+
7986
- name: Set up Docker Buildx
8087
uses: docker/setup-buildx-action@v2
8188

README.md

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -104,10 +104,28 @@ go run mage.go ftw
104104
```
105105
Take a look at its config file [ftw.yml](./ftw/ftw.yml) for details about tests currently excluded.
106106

107-
### Spinning up the coraza-proxy-wasm for manual tests
108-
109-
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`.
110-
In order to monitor envoy logs while performing requests run:
111-
```
112-
docker-compose -f ./ftw/docker-compose.yml logs -f envoy-logs
107+
## Example: Spinning up the coraza-wasm-filter for manual tests
108+
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).
109+
In order to monitor envoy logs while performing requests you can run:
110+
- Envoy logs: `docker-compose -f ./example/docker-compose.yml logs -f envoy-logs`.
111+
- Critical wasm (audit) logs: `docker-compose -f ./example/docker-compose.yml logs -f wasm-logs`
112+
113+
### Manual requests
114+
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:
115+
```bash
116+
# True positive requests:
117+
# XSS phase 1
118+
curl -I 'http://localhost:8080/anything?arg=<script>alert(0)</script>'
119+
# SQLI phase 2 (reading the body request)
120+
curl -i -X POST 'http://localhost:8080/anything' --data "1%27%20ORDER%20BY%203--%2B"
121+
# Triggers a CRS scanner detection rule (913100)
122+
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
123+
124+
# True negative requests:
125+
# A GET request with a harmless argument
126+
curl -I 'http://localhost:8080/anything?arg=arg_1'
127+
# An usual user-agent
128+
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
129+
# A payload (reading the body request)
130+
curl -i -X POST 'http://localhost:8080/anything' --data "this is a payload"
113131
```

e2e/tests.sh

100755100644
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,4 +48,4 @@ if [[ "$status_code" -ne 403 ]] ; then
4848
fi
4949
echo "[Ok] Got status code $status_code, expected 403"
5050

51-
echo "[Done] All tests passed"
51+
echo "[Done] All tests passed"

example/docker-compose.yml

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
services:
2+
httpbin:
3+
image: kennethreitz/httpbin:latest
4+
chown:
5+
image: alpine:3.16
6+
command:
7+
- /bin/sh
8+
- -c
9+
- chown -R 101:101 /home/envoy/logs
10+
volumes:
11+
- logs:/home/envoy/logs:rw
12+
envoy:
13+
depends_on:
14+
- chown
15+
- httpbin
16+
image: envoyproxy/envoy:v1.23-latest
17+
command:
18+
- -c
19+
- /conf/envoy-config.yaml
20+
- --log-level
21+
- info
22+
- --component-log-level
23+
- wasm:debug
24+
- --log-format [%Y-%m-%d %T.%f][%t][%l][%n] [%g:%#] %v
25+
- --log-path
26+
- /home/envoy/logs/envoy.log
27+
volumes:
28+
- ../build:/build
29+
- .:/conf
30+
- logs:/home/envoy/logs:rw
31+
ports:
32+
- 8080:80
33+
wasm-logs:
34+
depends_on:
35+
- envoy
36+
image: debian:11-slim
37+
entrypoint: bash
38+
command:
39+
- -c
40+
- tail -c +0 -f /home/envoy/logs/envoy.log | grep --line-buffered "[critical][wasm]" | tee /home/envoy/logs/ftw.log
41+
volumes:
42+
- logs:/home/envoy/logs:rw
43+
envoy-logs:
44+
depends_on:
45+
- envoy
46+
- wasm-logs
47+
image: debian:11-slim
48+
entrypoint: bash
49+
command:
50+
- -c
51+
- tail -c +0 -f /home/envoy/logs/envoy.log
52+
volumes:
53+
- logs:/home/envoy/logs:ro
54+
volumes:
55+
logs:

example/envoy-config.yaml

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
static_resources:
2+
listeners:
3+
- address:
4+
socket_address:
5+
address: 0.0.0.0
6+
port_value: 80
7+
filter_chains:
8+
- filters:
9+
- name: envoy.filters.network.http_connection_manager
10+
typed_config:
11+
"@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
12+
stat_prefix: ingress_http
13+
codec_type: auto
14+
http_protocol_options:
15+
accept_http_10: true
16+
route_config:
17+
virtual_hosts:
18+
- name: local_route
19+
domains:
20+
- "*"
21+
routes:
22+
- match:
23+
prefix: "/"
24+
route:
25+
cluster: local_server
26+
http_filters:
27+
- name: envoy.filters.http.wasm
28+
typed_config:
29+
"@type": type.googleapis.com/envoy.extensions.filters.http.wasm.v3.Wasm
30+
config:
31+
name: "coraza-filter"
32+
root_id: ""
33+
configuration:
34+
"@type": "type.googleapis.com/google.protobuf.StringValue"
35+
value: |
36+
{
37+
"rules": [
38+
{"inline": "Include coraza-demo.conf"},
39+
{"inline": "Include crs-setup-demo.conf"},
40+
{"inline": "SecDebugLogLevel 3"},
41+
{"inline": "Include crs/*.conf"},
42+
{"inline": "SecRule ARGS:id \"@eq 0\" \"id:1, phase:1,deny, status:403,msg:'Invalid id',log,auditlog\""}
43+
]
44+
}
45+
vm_config:
46+
runtime: "envoy.wasm.runtime.v8"
47+
vm_id: "my_vm_id"
48+
code:
49+
local:
50+
filename: "build/main.wasm"
51+
- name: envoy.filters.http.router
52+
typed_config:
53+
"@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
54+
55+
clusters:
56+
- name: local_server
57+
connect_timeout: 6000s
58+
type: strict_dns
59+
lb_policy: round_robin
60+
load_assignment:
61+
cluster_name: local_server
62+
endpoints:
63+
- lb_endpoints:
64+
- endpoint:
65+
address:
66+
socket_address:
67+
address: httpbin
68+
port_value: 80

example/readme-tests.sh

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
#!/bin/bash
2+
# Copyright 2022 The OWASP Coraza contributors
3+
# SPDX-License-Identifier: Apache-2.0
4+
ENVOY_HOST=${ENVOY_HOST:-"localhost:8080"}
5+
6+
[[ "${DEBUG}" == "true" ]] && set -x
7+
8+
envoy_url_echo="http://${ENVOY_HOST}/anything"
9+
10+
okayBodyPayload="hello"
11+
maliciousBodyPayload="maliciouspayload"
12+
bodyPayloadForResponseBodyTrueNegative="Hello world"
13+
bodyPayloadForResponseBody="responsebodycode"
14+
15+
# wait_for_service waits until the given URL returns a 200 status code.
16+
# $1: The URL to send requests to.
17+
# $2: The max number of requests to send before giving up.
18+
function wait_for_service() {
19+
local status_code="000"
20+
local url=${1}
21+
local max=${2}
22+
while [[ "${status_code}" -ne "200" ]]; do
23+
status_code=$(curl --write-out "%{http_code}" --silent --output /dev/null "${url}")
24+
sleep 1
25+
echo -ne "[Wait] Waiting for response from ${url}. Timeout: ${max}s \r"
26+
((max-=1))
27+
if [[ "${max}" -eq 0 ]]; then
28+
echo "[Fail] Timeout waiting for response from ${url}, make sure the server is running."
29+
exit 1
30+
fi
31+
done
32+
echo -e "\n[Ok] Got status code ${status_code}"
33+
}
34+
35+
# check_status sends HTTP requests to the given URL and expects a given response code.
36+
# $1: The URL to send requests to.
37+
# $2: The expected status code.
38+
# $3-N: The rest of the arguments will be passed to the curl command as additional arguments
39+
# to customize the HTTP call.
40+
function check_status() {
41+
local url=${1}
42+
local status=${2}
43+
local args=("${@:3}" --write-out '%{http_code}' --silent --output /dev/null)
44+
status_code=$(curl "${args[@]}" "${url}")
45+
if [[ "${status_code}" -ne ${status} ]] ; then
46+
echo "[Fail] Unexpected response with code ${status_code} from ${url}"
47+
exit 1
48+
fi
49+
echo "[Ok] Got status code ${status_code}, expected ${status}"
50+
}
51+
52+
# check_body sends the given HTTP request and checks the response body.
53+
# $1: The URL to send requests to.
54+
# $2: true/false indicating if an empty body is expected or not.
55+
# $3-N: The rest of the arguments will be passed to the curl command as additional arguments
56+
# to customize the HTTP call.
57+
function check_body() {
58+
local url=${1}
59+
local empty=${2}
60+
local args=("${@:3}" --silent)
61+
response_body=$(curl "${args[@]}" "${url}")
62+
if [[ "${empty}" == "true" ]] && [[ -n "${response_body}" ]]; then
63+
echo -e "[Fail] Unexpected response with a body. Body dump:\n${response_body}"
64+
exit 1
65+
fi
66+
if [[ "${empty}" != "true" ]] && [[ -z "${response_body}" ]]; then
67+
echo -e "[Fail] Unexpected response with a body. Body dump:\n${response_body}"
68+
exit 1
69+
fi
70+
echo "[Ok] Got response with an expected body (empty=${empty})"
71+
}
72+
73+
step=1
74+
total_steps=7
75+
76+
# Testing if the server is up
77+
echo "[${step}/${total_steps}] Testing application reachability"
78+
wait_for_service "${envoy_url_echo}" 20
79+
80+
# Testing XSS phase 1
81+
((step+=1))
82+
echo "[${step}/${total_steps}] Testing XSS at request headers"
83+
check_status "${envoy_url_echo}?arg=<script>alert(0)</script>" 403
84+
85+
# Testing SQLI phase 2
86+
((step+=1))
87+
echo "[${step}/${total_steps}] Testing SQLi at request body"
88+
check_status "${envoy_url_echo}" 403 -X POST --data "1%27%20ORDER%20BY%203--%2B"
89+
90+
# Triggers a CRS scanner detection rule (913100)
91+
((step+=1))
92+
echo "[${step}/${total_steps}] (onRequestBody) Testing CRS rule 913100"
93+
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"
94+
95+
# True negative GET request
96+
((step+=1))
97+
echo "[${step}/${total_steps}] True negative GET request"
98+
check_status "${envoy_url_echo}?arg=arg_1" 200
99+
100+
# True negative GET request with an usual user-agent
101+
((step+=1))
102+
echo "[${step}/${total_steps}] True negative GET request with user-agent"
103+
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"
104+
105+
# True negative POST request with a payload
106+
((step+=1))
107+
echo "[${step}/${total_steps}] True negative POST request"
108+
check_status "${envoy_url_echo}" 200 --data "this is a payload"
109+
110+
echo "[Done] All examples request worked as expected"

ftw/tests.sh

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ cd /workspace
88

99
step=1
1010
total_steps=3
11-
max_retries=10 #seconds for the server reachability timeout
11+
max_retries=15 #seconds for the server reachability timeout
1212
host=${1:-envoy}
1313
health_url="http://${host}:80"
1414
unfiltered_url="http://${host}:80/home"
@@ -21,7 +21,7 @@ while [[ "$status_code" -eq "000" ]]; do
2121
status_code=$(curl --write-out "%{http_code}" --silent --output /dev/null $health_url)
2222
sleep 1
2323
echo -ne "[Wait] Waiting for response from $health_url. Timeout: ${max_retries}s \r"
24-
((max_retries-=1))
24+
let "max_retries--"
2525
if [[ "$max_retries" -eq 0 ]] ; then
2626
echo "[Fail] Timeout waiting for response from $health_url, make sure the server is running."
2727
exit 1

magefile.go

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ func Lint() error {
6363
return nil
6464
}
6565

66-
// Test runs all tests.
66+
// Test runs all unit tests.
6767
func Test() error {
6868
return sh.RunV("go", "test", "./...")
6969
}
@@ -172,4 +172,14 @@ func Ftw() error {
172172
return sh.RunWithV(env, "docker-compose", "--file", "ftw/docker-compose.yml", "run", "--rm", "ftw")
173173
}
174174

175+
// RunExample spins up the test environment, access at http://localhost:8080. Requires docker-compose.
176+
func RunExample() error {
177+
return sh.RunV("docker-compose", "--file", "example/docker-compose.yml", "up", "-d", "envoy-logs")
178+
}
179+
180+
// TeardownExample tears down the test environment. Requires docker-compose.
181+
func TeardownExample() error {
182+
return sh.RunV("docker-compose", "--file", "example/docker-compose.yml", "down")
183+
}
184+
175185
var Default = Build

0 commit comments

Comments
 (0)