This use case applies WAF protection to a sample application exposed through NGINX Ingress Controller
NGINX Ingress Controller needs to be deployed with the WAF in precompiled mode, see DEPLOYING-WAFv5.md
Get NGINX Ingress Controller Node IP, HTTP and HTTPS NodePorts and pod name
export NIC_IP=`kubectl get pod -l app.kubernetes.io/instance=nic -n nginx-ingress -o json|jq '.items[0].status.hostIP' -r`
export HTTP_PORT=`kubectl get svc nic-nginx-ingress-controller -n nginx-ingress -o jsonpath='{.spec.ports[0].nodePort}'`
export HTTPS_PORT=`kubectl get svc nic-nginx-ingress-controller -n nginx-ingress -o jsonpath='{.spec.ports[1].nodePort}'`
export NIC_PODNAME=`kubectl get pod -l app.kubernetes.io/instance=nic -n nginx-ingress -o json|jq '.items[0].metadata.name' -r`
Check NGINX Ingress Controller IP address, HTTP and HTTPS ports and pod name
echo -e "NIC address: $NIC_IP\nHTTP port : $HTTP_PORT\nHTTPS port : $HTTPS_PORT\nPod name : $NIC_PODNAME"
cd into the lab directory
cd ~/NGINX-Ingress-Controller-Lab/labs/8.waf-precompiled
Compile the policy bundle
cd artifacts
chmod 777 .
docker run --rm \
-v $(pwd):$(pwd) \
waf-compiler-5.11.2:custom \
-include-source -full-export -g $(pwd)/global_settings.json -p $(pwd)/waf_policy.json -o $(pwd)/waf_policy.tgz
The output should be similar to
{
"completed_successfully": true,
"compiler_engine": "express",
"compiler_version": "11.608.0",
"filename": "/home/f5/work/NGINX-Ingress-Controller-Lab/labs/8.waf-precompiled/artifacts/waf_policy.tgz",
"file_size": 1862742,
"attack_signatures_package": {
"version": "2026.02.11",
"revision_datetime": "2026-02-11T14:34:04Z"
},
"bot_signatures_package": {
"version": "2026.02.11",
"revision_datetime": "2026-02-11T15:20:49Z"
},
"threat_campaigns_package": {
"version": "2026.02.16",
"revision_datetime": "2026-02-16T10:33:29Z"
}
}
The policy has been compiled into waf_policy.tgz
Compile the WAF log profile
docker run \
-v $(pwd):$(pwd) \
waf-compiler-5.11.2:custom \
-l $(pwd)/log_profile.json -o $(pwd)/log_profile.tgz
The output should be similar to
{
"filename": "/home/f5/work/NGINX-Ingress-Controller-Lab/labs/8.waf-precompiled/artifacts/log_profile.tgz",
"file_size": 1691,
"completed_successfully": true,
"compiler_engine": "full",
"compiler_version": "11.608.0"
}
The log profile has been compiled into log_profile.tgz
Copy the compiled policy bundle and log profile into the NGINX Ingress Controller pod:
kubectl cp waf_policy.tgz $NIC_PODNAME:/etc/app_protect/bundles/ -c nginx-ingress -n nginx-ingress
kubectl cp log_profile.tgz $NIC_PODNAME:/etc/app_protect/bundles/ -c nginx-ingress -n nginx-ingress
Change directory
cd ..
Copy the compiled policy bundle and the compiled log profile to the relevant pods
Deploy the sample web applications
kubectl apply -f 0.webapp.yaml
Deploy the syslog service to receive NGINX App Protect security violations logs
kubectl apply -f 1.syslog.yaml
Deploy the WAF policy
kubectl apply -f 2.waf.yaml
Describe the WAF policy
kubectl describe policy waf-policy
The output should be similar to
Name: waf-policy
Namespace: default
Labels: <none>
Annotations: <none>
API Version: k8s.nginx.org/v1
Kind: Policy
Metadata:
Creation Timestamp: 2026-02-25T16:08:51Z
Generation: 1
Resource Version: 98780287
UID: 663e319b-17e8-41f5-80c9-7076f40ff6d1
Spec:
Waf:
Ap Bundle: waf_policy.tgz
Enable: true
Security Logs:
Ap Log Bundle: log_profile.tgz
Enable: true
Log Dest: syslog:server=syslog-svc.default:514
Status:
Message: Policy default/waf-policy was added or updated
Reason: AddedOrUpdated
State: Valid
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal AddedOrUpdated 26s nginx-ingress-controller Policy default/waf-policy was added or updated
Publish the application through NGINX Ingress Controller applying the WAF policy
kubectl apply -f 3.virtual-server.yaml
Check the newly created VirtualServer resource
kubectl get vs -o wide
Output should be similar to
NAME STATE HOST IP EXTERNALHOSTNAME PORTS AGE
webapp Valid webapp.example.com 9m49s
Describe the webapp virtualserver
kubectl describe vs webapp
Output should be similar to
Name: webapp
Namespace: default
Labels: <none>
Annotations: <none>
API Version: k8s.nginx.org/v1
Kind: VirtualServer
Metadata:
Creation Timestamp: 2025-04-03T21:03:22Z
Generation: 1
Resource Version: 251235
UID: 5e08b717-01b0-482d-8e20-10de3374a8f7
Spec:
Host: webapp.example.com
Policies:
Name: waf-policy
Routes:
Action:
Pass: webapp
Path: /
Upstreams:
Name: webapp
Port: 80
Service: webapp-svc
Status:
Message: Configuration for default/webapp was added or updated
Reason: AddedOrUpdated
State: Valid
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal AddedOrUpdated 1s nginx-ingress-controller Configuration for default/webapp was added or updated
Access the application using a legitimate request
curl -i -H "Host: webapp.example.com" http://$NIC_IP:$HTTP_PORT
Output should be similar to
HTTP/1.1 200 OK
Date: Thu, 03 Apr 2025 21:03:43 GMT
Content-Type: text/plain
Content-Length: 158
Connection: keep-alive
Expires: Thu, 03 Apr 2025 21:03:42 GMT
Cache-Control: no-cache
Server address: 192.168.36.103:8080
Server name: webapp-6db59b8dcc-l5dsk
Date: 03/Apr/2025:21:03:43 +0000
URI: /
Request ID: 204ea07975ae1618b29728b25f129498
Access the application using a suspicious URL
curl -i -H "Host: webapp.example.com" "http://$NIC_IP:$HTTP_PORT/<script>alert();</script>"
Output should be similar to
HTTP/1.1 200 OK
Content-Type: text/html; charset=utf-8
Connection: close
Cache-Control: no-cache
Pragma: no-cache
Content-Length: 247
<html><head><title>Request Rejected</title></head><body>The requested URL was rejected. Please consult with your administrator.<br><br>Your support ID is: 15024425679859283163<br><br><a href='javascript:history.back();'>[Go Back]</a></body></html>
Check the security violation logs in the syslog pod
export SYSLOG_POD_NAME=`kubectl get pods -l app=syslog -o jsonpath='{.items[0].metadata.name}'`
kubectl exec -it $SYSLOG_POD_NAME -- cat /var/log/messages
Delete the lab
kubectl delete -f .