-
Notifications
You must be signed in to change notification settings - Fork 3
test: add inverted-paranoia WAF enforcement e2e #272
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
231 changes: 231 additions & 0 deletions
231
test/e2e/trafficprotectionpolicy-enforce-attack/chainsaw-test.yaml
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 | ||
|
|
||
| - 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 | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
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?