Skip to content

Commit

Permalink
Allow running ftw (envoyproxy#13)
Browse files Browse the repository at this point in the history
  • Loading branch information
anuraaga authored Sep 1, 2022
1 parent d94cdb3 commit 9986677
Show file tree
Hide file tree
Showing 12 changed files with 391 additions and 81 deletions.
1 change: 1 addition & 0 deletions README_RULES.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
- 00-modsecurity.conf: `SecRequestBodyJsonDepthLimit` not supported
- 00-modsecurity.conf: `SecAuditLogRelevantStatus` uses syntax not supported with re2
- 00-modsecurity.conf: `SecStatusEngine` not supported
- REQUEST-912-DOS-PROTECTION: 912171 temporarily disabled since doesn't interact well with go-ftw
- REQUEST-920-PROTOCOL-ENFORCEMENT: 920120 not supported with re2
- REQUEST-942-APPLICATION-ATTACK-SQLI: 942130: not supported with re2
- REQUEST-942-APPLICATION-ATTACK-SQLI: 942480: regexp fails to compile in wasm with "out of bounds memory access"
Expand Down
2 changes: 1 addition & 1 deletion custom_rules/00-modsecurity.conf
Original file line number Diff line number Diff line change
Expand Up @@ -256,7 +256,7 @@ SecResponseBodyLimitAction ProcessPartial
# disabled by default
#SecDebugLog /opt/modsecurity/var/log/debug.log

SecDebugLogLevel 9
SecDebugLogLevel 3


# -- Audit log configuration -------------------------------------------------
Expand Down
2 changes: 1 addition & 1 deletion custom_rules/02-crs-test.conf
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,6 @@ SecRule REQUEST_HEADERS:X-CRS-Test "@rx ^.*$" \
"id:999999,\
phase:1,\
log,\
msg:'%{MATCHED_VAR}',\
msg:'X-CRS-Test %{MATCHED_VAR}',\
pass,\
t:none"
34 changes: 17 additions & 17 deletions custom_rules/REQUEST-912-DOS-PROTECTION.conf
Original file line number Diff line number Diff line change
Expand Up @@ -284,23 +284,23 @@ SecRule TX:EXECUTING_PARANOIA_LEVEL "@lt 2" "id:912019,phase:5,pass,nolog,skipAf
#
# This is a stricter sibling of rule 912170.
#
SecRule IP:DOS_BURST_COUNTER "@ge 1" \
"id:912171,\
phase:5,\
pass,\
t:none,\
log,\
msg:'Potential Denial of Service (DoS) Attack from %{tx.real_ip} - # of Request Bursts: %{ip.dos_burst_counter}',\
tag:'application-multi',\
tag:'language-multi',\
tag:'platform-multi',\
tag:'attack-dos',\
tag:'paranoia-level/2',\
tag:'OWASP_CRS',\
tag:'capec/1000/210/227/469',\
ver:'OWASP_CRS/3.3.2',\
setvar:'ip.dos_block=1',\
expirevar:'ip.dos_block=%{tx.dos_block_timeout}'"
# SecRule IP:DOS_BURST_COUNTER "@ge 1" \
# "id:912171,\
# phase:5,\
# pass,\
# t:none,\
# log,\
# msg:'Potential Denial of Service (DoS) Attack from %{tx.real_ip} - # of Request Bursts: %{ip.dos_burst_counter}',\
# tag:'application-multi',\
# tag:'language-multi',\
# tag:'platform-multi',\
# tag:'attack-dos',\
# tag:'paranoia-level/2',\
# tag:'OWASP_CRS',\
# tag:'capec/1000/210/227/469',\
# ver:'OWASP_CRS/3.3.2',\
# setvar:'ip.dos_block=1',\
# expirevar:'ip.dos_block=%{tx.dos_block_timeout}'"



Expand Down
19 changes: 19 additions & 0 deletions ftw/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Copyright 2022 The OWASP Coraza contributors
# SPDX-License-Identifier: Apache-2.0

FROM golang:1.19-alpine

WORKDIR /workspace

RUN apk update && apk add curl

RUN go install github.com/fzipi/go-ftw@f5f64a16b3d2bebea600ea070ffd5baa7f36213c

ADD https://github.com/coreruleset/coreruleset/archive/refs/tags/v4.0.0-rc1.tar.gz /workspace/coreruleset/
RUN cd coreruleset && tar -xf v4.0.0-rc1.tar.gz --strip-components 1

COPY ftw.yml /workspace/ftw.yml
COPY tests.sh /workspace/tests.sh

ENTRYPOINT ["sh"]
CMD ["-c", "/workspace/tests.sh"]
48 changes: 48 additions & 0 deletions ftw/docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
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
envoy-logs:
depends_on:
- envoy
image: alpine:3.16
command: tail -f /home/envoy/logs/envoy.log
volumes:
- logs:/home/envoy/logs:ro
ftw:
depends_on:
- envoy-logs
build: .
volumes:
- logs:/home/envoy/logs:ro

volumes:
logs:
62 changes: 62 additions & 0 deletions ftw/envoy-config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
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":"SecRuleEngine On \nSecRule REQUEST_URI \"@streq /admin\" \"id:101,phase:1,t:lowercase,deny\""
}
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
62 changes: 62 additions & 0 deletions ftw/ftw.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
---
# For caddy configuration see the next file, Caddyfile
# Just type: go-ftw run -d ./coreruleset/tests/regression --config .ftw.yaml
logfile: '/home/envoy/logs/envoy.log'
testoverride:
input:
dest_addr: envoy
ignore:
'920181-1': 'Invalid URL, Coraza stops this.'
'942490-17': 'Invalid URL, Coraza stops this.'
'942260-17': 'Invalid URL, Coraza stops this.'
'942260-6': 'Invalid URL, Coraza stops this.'
'942150-6': 'Invalid URL, Coraza stops this.'
'920240-1': 'Invalid URL, Coraza stops this.'
'920240-5': 'Invalid URL, Coraza stops this.'
'920240-6': 'Invalid URL, Coraza stops this.'
'941130-11': 'Invalid URL, Coraza stops this.'
'941130-2': 'Invalid URL, Coraza stops this.'
'941130-4': 'Invalid URL, Coraza stops this.'
'941130-6': 'Invalid URL, Coraza stops this.'
'941130-9': 'Invalid URL, Coraza stops this.'
'941130-10': 'Invalid URL, Coraza stops this.'
'941130-12': 'Invalid URL, Coraza stops this.'
'941130-14': 'Invalid URL, Coraza stops this.'
'941130-16': 'Invalid URL, Coraza stops this.'
'921150-1': 'Invalid URL, Coraza stops this.'
'921160-1': 'Invalid URL, Coraza stops this.'
'941110-6': 'Invalid URL, Coraza stops this.'
'942100-10': 'Invalid URL, Coraza stops this.'
'932140-3': 'Invalid URL, Coraza stops this.'
'941280-2': 'Invalid URL, Coraza stops this.'
'942100-13': 'Invalid URL, Coraza stops this.'
'920120-4': 'Rule bug'
'920120-6': 'Rule bug'
'920120-7': 'Rule bug'
'920170-7': 'TODO(anuraaga): HTTP/1.0 issue?'
'920460-1': 'Quadruple backslash issue'
'941330-1': 'Quadruple backslash issue'
'920460-2': 'Quadruple backslash issue'
'920460-3': 'Quadruple backslash issue'
'920460-4': 'Quadruple backslash issue'
'932180-2': 'Bad multipart'
'920100-4': 'Method connect is not valid for caddy'
'920100-5': 'Method connect is not valid for caddy'
'920100-8': 'Caddy doesnt respond to errors like apache'
'920170-3': 'Caddy doesnt accept HEAD payloads'
'920171-2': 'Caddy hides transfer-encoding from Coraza on file_server mode'
'920171-3': 'Caddy hides transfer-encoding from Coraza on file_server mode'
'920270-4': 'Caddy ignores the nullbyte'
'920272-5': 'Caddy can handle that invalid encoding'
'920280-3': 'Caddy returns error 505 instead'
'920290-1': 'Caddy ignores an empty host'
'920420-8': 'Caddy does not accept thatinvalid content-type'
'920430-3': 'Caddy returns error 505 instead'
'920430-5': 'Caddy will close the connection on close and return 400'
'920430-8': 'HTTP/3 is unsupported by caddy'
'920430-9': 'HTTP/0.8 is unsupported by caddy'
# Temporary:
'953120-0': 'Temporary, this works but the testing framework does not support it yet.'
'953120-2': 'Temporary, this works but the testing framework does not support it yet.'
'953120-4': 'Temporary, this works but the testing framework does not support it yet.'
'943110-4': 'Temporary, this works but the testing framework does not support it yet.'
34 changes: 34 additions & 0 deletions ftw/tests.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
#!/bin/sh
# Copyright 2022 The OWASP Coraza contributors
# SPDX-License-Identifier: Apache-2.0

cd /workspace

# Copied from https://github.com/jcchavezs/modsecurity-wasm-filter-e2e/blob/master/tests.sh

step=1
total_steps=3
max_retries=10 #seconds for the server reachability timeout
host=${1:-envoy}
health_url="http://${host}:80"
unfiltered_url="http://${host}:80/home"
filtered_url="http://${host}:80/admin"

# Testing if the server is up
echo "[$step/$total_steps] Testing application reachability"
status_code="000"
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))
if [[ "$max_retries" -eq 0 ]] ; then
echo "[Fail] Timeout waiting for response from $health_url, make sure the server is running."
exit 1
fi
done
echo -e "\n[Ok] Got status code $status_code, expected 200. Ready to start."

# Protocol violations often get treated by Envoy itself, exclude them for now while investigating
# what works. Also currently HTTP/1.0 seems to have an issue so we exclude any tests using it.
go-ftw run -d coreruleset/tests/regression/tests --config ftw.yml --exclude '920.*|921.*|93.*|True.*|94.*|95.*'
8 changes: 8 additions & 0 deletions magefile.go
Original file line number Diff line number Diff line change
Expand Up @@ -139,4 +139,12 @@ func E2e() error {
return sh.RunV("docker-compose", "--file", "e2e/docker-compose.yml", "up", "--abort-on-container-exit")
}

// Ftw runs ftw tests with a built plugin and Envoy. Requires docker-compose.
func Ftw() error {
if err := sh.RunV("docker-compose", "--file", "ftw/docker-compose.yml", "build"); err != nil {
return err
}
return sh.RunV("docker-compose", "--file", "ftw/docker-compose.yml", "run", "--rm", "ftw")
}

var Default = Build
25 changes: 11 additions & 14 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,7 @@ func (ctx *httpContext) OnHttpRequestHeaders(numHeaders int, endOfStream bool) t
return types.ActionContinue
}

tx.ProcessURI(path, method, "2.0") // TODO use the right HTTP version
tx.ProcessURI(path, method, "HTTP/2.0") // TODO use the right HTTP version

hs, err := proxywasm.GetHttpRequestHeaders()
if err != nil {
Expand All @@ -182,8 +182,7 @@ func (ctx *httpContext) OnHttpRequestHeaders(numHeaders int, endOfStream bool) t

interruption := tx.ProcessRequestHeaders()
if interruption != nil {
ctx.handleInterruption(interruption)
return types.ActionContinue
return ctx.handleInterruption(interruption)
}

return types.ActionContinue
Expand Down Expand Up @@ -216,8 +215,7 @@ func (ctx *httpContext) OnHttpRequestBody(bodySize int, endOfStream bool) types.
return types.ActionContinue
}
if interruption != nil {
ctx.handleInterruption(interruption)
return types.ActionContinue
return ctx.handleInterruption(interruption)
}

return types.ActionContinue
Expand Down Expand Up @@ -246,10 +244,9 @@ func (ctx *httpContext) OnHttpResponseHeaders(numHeaders int, endOfStream bool)
tx.AddResponseHeader(h[0], h[1])
}

interruption := tx.ProcessResponseHeaders(code, "2.0")
interruption := tx.ProcessResponseHeaders(code, "HTTP/2.0")
if interruption != nil {
ctx.handleInterruption(interruption)
return types.ActionContinue
return ctx.handleInterruption(interruption)
}

return types.ActionContinue
Expand All @@ -276,15 +273,13 @@ func (ctx *httpContext) OnHttpResponseBody(bodySize int, endOfStream bool) types
return types.ActionContinue
}

interruption, err := tx.ProcessResponseBody()
// We have already sent response headers so cannot now send an unauthorized response.
// The error will have been logged by Coraza though.
_, err := tx.ProcessResponseBody()
if err != nil {
proxywasm.LogCriticalf("failed to process response body: %v", err)
return types.ActionContinue
}
if interruption != nil {
ctx.handleInterruption(interruption)
return types.ActionContinue
}

return types.ActionContinue
}
Expand All @@ -296,7 +291,7 @@ func (ctx *httpContext) OnHttpStreamDone() {
proxywasm.LogInfof("%d finished", ctx.contextID)
}

func (ctx *httpContext) handleInterruption(interruption *ctypes.Interruption) {
func (ctx *httpContext) handleInterruption(interruption *ctypes.Interruption) types.Action {
proxywasm.LogInfof("%d interrupted, action %q", ctx.contextID, interruption.Action)
statusCode := interruption.Status
if statusCode == 0 {
Expand All @@ -306,6 +301,8 @@ func (ctx *httpContext) handleInterruption(interruption *ctypes.Interruption) {
if err := proxywasm.SendHttpResponse(uint32(statusCode), nil, nil, -1); err != nil {
panic(err)
}

return types.ActionPause
}

func logError(error ctypes.MatchedRule) {
Expand Down
Loading

0 comments on commit 9986677

Please sign in to comment.