Chapter 5. WAF Quickstart

Why this

When a code fix needs a sprint, a reverse-proxy filter can block a whole class of bad inputs as a stopgap. We’ll use the community ingress-nginx controller that ships with Minikube and add a basic rule via annotations on your Ingress.

Note: These steps use the Minikube ingress addon. They don’t require separate ingress images or cloud ingress.

1) Enable the Ingress controller

minikube -p ship addons enable ingress
kubectl -n ingress-nginx get pods

2) Create an Ingress for your app

Assuming your Service is app in namespace ship on port 8080, create infra/k8s/ingress.yaml:

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: app
  namespace: ship
  annotations:
    # Turn on basic request filtering in the ingress (see next section for rule)
    nginx.ingress.kubernetes.io/server-snippet: |
      # Block very basic script injection in query/path
      if ($request_uri ~* "<script") { return 403; }
spec:
  ingressClassName: nginx
  rules:
    - host: app.local
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: app
                port:
                  number: 8080

Apply it:

kubectl apply -f infra/k8s/ingress.yaml

We use server-snippet to add a minimal block rule at the NGINX layer. For a richer rule set, you can layer ModSecurity/CRS if your ingress build enables it; start with the simple rule to prove the workflow.

3) Get a local URL for the Ingress

minikube -p ship tunnel >/dev/null 2>&1 &
# /etc/hosts entry for convenience (macOS/Linux):
echo "127.0.0.1 app.local" | sudo tee -a /etc/hosts

Verify:

curl -i http://app.local/healthz

4) Prove the block (virtual patch)

Try a normal request and a malicious one; capture both results:

{
  echo "--- OK request $(date) ---"
  curl -sSi "http://app.local/?input=hello" | head -n 1

  echo "--- BLOCK attempt $(date) ---"
  curl -sSi "http://app.local/?input=<script>alert(1)</script>" | head -n 1
} | tee -a artifacts/waf-test.txt

Expected:

  • Normal request → HTTP/1.1 200 OK
  • Attack string → HTTP/1.1 403 Forbidden (blocked by the server snippet)

5) Optional: move the rule to a dedicated ConfigMap

If you prefer not to inline snippets in the Ingress, you can mount a global config via the ingress-nginx ConfigMap (cluster-wide) and reference it. For the local lab, the per-Ingress annotation is sufficient and keeps everything in your repo.

6) Clean up (optional)

# remove hosts entry if added
sudo sed -i.bak '/app\.local/d' /etc/hosts
# stop the tunnel process if left running in the background
pkill -f "minikube.*tunnel" || true

Makefile helper (optional)

waf-apply:
	kubectl apply -f infra/k8s/ingress.yaml
	@echo "Ingress applied. If needed, run: minikube -p ship tunnel"

waf-test:
	@bash -lc '\
	mkdir -p artifacts; \
	echo "--- OK request $$(
	  date) ---" >> artifacts/waf-test.txt; \
	curl -sSi "http://app.local/?input=hello" | head -n 1 >> artifacts/waf-test.txt || true; \
	echo "--- BLOCK attempt $$(
	  date) ---" >> artifacts/waf-test.txt; \
	curl -sSi "http://app.local/?input=<script>alert(1)</script>" | head -n 1 >> artifacts/waf-test.txt || true; \
	echo "Wrote artifacts/waf-test.txt"'

Checklist

  • Ingress controller running (ingress-nginx Pods are Ready)
  • Ingress created for your Service; app.local resolves
  • Normal traffic returns 200; attack string returns 403
  • artifacts/waf-test.txt saved with both outcomes