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-snippetto 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-nginxPods are Ready) - Ingress created for your Service;
app.localresolves - Normal traffic returns 200; attack string returns 403
-
artifacts/waf-test.txtsaved with both outcomes