Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
231 changes: 231 additions & 0 deletions test/e2e/trafficprotectionpolicy-enforce-attack/chainsaw-test.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,231 @@
# yaml-language-server: $schema=https://raw.githubusercontent.com/kyverno/chainsaw/main/.schemas/json/test-chainsaw-v1alpha1.json
apiVersion: chainsaw.kyverno.io/v1alpha1
kind: Test
metadata:
name: trafficprotectionpolicy-enforce-blocks-attack
# Mechanism A enforcement-correctness coverage (network-services-operator#242).
# A valid Enforce TrafficProtectionPolicy (detection >= blocking) attached to a
# gateway must, end to end on the real Coraza edge data plane:
# 1. pass benign traffic through to the backend with the body intact, and
# 2. block a CRS-tripping attack payload with HTTP 403.
#
# Body integrity is asserted on the benign 200 (not just the status code): a WAF
# translation bug can leave the listener up yet corrupt or replace the response
# body (infra#3321). The branded local_reply_config only maps status >= 500, so a
# WAF block returns Coraza's raw 403 (unbranded) — this test asserts the 403 and
# that the attack never reached the backend, not a branded body.
#
# Precondition: the downstream (nso-infra) must have the WAF data plane wired up
# (extension-server + the extensionManager Envoy Gateway registered on the
# `datum-downstream-gateway` GatewayClass, with the Coraza filter).
# `make prepare-infra-cluster` installs this via `make downstream-waf-dataplane`.
spec:
cluster: nso-infra
# EG only reconciles namespaces carrying this label.
namespaceTemplate:
metadata:
labels:
meta.datumapis.com/upstream-cluster-name: e2e
steps:
- name: Deploy a backend
try:
- apply:
resource:
apiVersion: apps/v1
kind: Deployment
metadata:
name: echo
spec:
replicas: 1
selector:
matchLabels:
app: echo
template:
metadata:
labels:
app: echo
spec:
containers:
- name: echo
image: hashicorp/http-echo:1.0
args: ["-text=hello from backend", "-listen=:8080"]
ports:
- containerPort: 8080
- apply:
resource:
apiVersion: v1
kind: Service
metadata:
name: echo
spec:
selector:
app: echo
ports:
- port: 80
targetPort: 8080
- assert:
resource:
apiVersion: apps/v1
kind: Deployment
metadata:
name: echo
status:
availableReplicas: 1

- name: Route through the WAF gateway with a valid Enforce policy
bindings:
- name: hostname
value: (join('.', [$namespace, 'e2e.test']))
try:
- apply:
resource:
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: waf-gw
spec:
gatewayClassName: datum-downstream-gateway
listeners:
- name: http
protocol: HTTP
port: 80
hostname: ($hostname)
allowedRoutes:
namespaces:
from: Same
- apply:
resource:
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: echo
spec:
parentRefs:
- name: waf-gw
hostnames:
- ($hostname)
rules:
- matches:
- path:
type: PathPrefix
value: /
backendRefs:
- name: echo
port: 80
- apply:
resource:
apiVersion: networking.datumapis.com/v1alpha
kind: TrafficProtectionPolicy
metadata:
name: enforce-waf
spec:
mode: Enforce
targetRefs:
- group: gateway.networking.k8s.io
kind: Gateway
name: waf-gw
ruleSets:
- type: OWASPCoreRuleSet
owaspCoreRuleSet: {}
- assert:
timeout: 3m
resource:
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: waf-gw
status:
(conditions[?type == 'Programmed']):
- status: "True"
catch:
- script:
timeout: 60s
content: |
set -x
kubectl get gateway -A -o yaml
kubectl describe gateway -A
kubectl get gatewayclass datum-downstream-gateway -o yaml
kubectl -n datum-downstream-gateway get pods -o wide
kubectl -n datum-downstream-gateway describe pods
kubectl -n datum-downstream-gateway logs deploy/envoy-gateway --tail=-1
kubectl -n datum-downstream-gateway logs -l gateway.envoyproxy.io/owning-gateway-namespace --all-containers --tail=200 || true
Comment on lines +141 to +151

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this just for debugging?


- name: Benign traffic reaches the backend with the body intact
description: >
Probe the downstream Envoy via the kind hostPort (30080). Assert the
benign GET returns 200 AND the backend body ("hello from backend") is
served intact — a WAF/listener translation bug can keep the listener up
while corrupting or replacing the body (infra#3321), which a status-only
check would miss.
bindings:
- name: hostname
value: (join('.', [$namespace, 'e2e.test']))
try:
- script:
env:
- name: HOSTNAME
value: ($hostname)
content: |
set -u
for i in $(seq 1 40); do
resp=$(curl -s -w '\n%{http_code}' --max-time 5 \
-H "Host: ${HOSTNAME}" http://localhost:30080/) || resp=$'\n000'
code=$(printf '%s' "$resp" | tail -n1)
body=$(printf '%s' "$resp" | sed '$d')
echo "attempt ${i}: HTTP ${code}"
if [ "${code}" = "200" ]; then
case "${body}" in
*"hello from backend"*)
echo "benign body intact"
exit 0
;;
*)
echo "benign GET returned 200 but body was corrupted: [${body}]"
exit 1
;;
esac
fi
sleep 3
done
echo "benign request never returned 200"
exit 1

- name: A CRS-tripping attack payload is blocked with 403
description: >
A path-traversal payload in a query parameter trips CRS rule 930110
(severity CRITICAL, score 5) which meets the default inbound anomaly
threshold at paranoia level 1, so Coraza denies the request with 403.
Assert the 403 and that the attack never reached the backend.
bindings:
- name: hostname
value: (join('.', [$namespace, 'e2e.test']))
try:
- script:
env:
- name: HOSTNAME
value: ($hostname)
content: |
set -u
url='http://localhost:30080/?file=../../../../etc/passwd'
for i in $(seq 1 20); do
resp=$(curl -s -w '\n%{http_code}' --max-time 5 \
-H "Host: ${HOSTNAME}" "${url}") || resp=$'\n000'
code=$(printf '%s' "$resp" | tail -n1)
body=$(printf '%s' "$resp" | sed '$d')
echo "attempt ${i}: HTTP ${code}"
if [ "${code}" = "403" ]; then
case "${body}" in
*"hello from backend"*)
echo "attack was blocked with 403 but backend body leaked"
exit 1
;;
*)
echo "attack blocked with 403, backend not reached"
exit 0
;;
esac
fi
sleep 3
done
echo "attack payload was not blocked with 403"
exit 1
Loading