Skip to content

Latest commit

 

History

History

Folders and files

NameName
Last commit message
Last commit date

parent directory

..
 
 
 
 
 
 
 
 
 
 
 
 

F5 WAF for NGINX

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 .