NGINX Gateway Fabric: F5 WAF 정책 Override 검증
NGINX Gateway Fabric에서 Gateway-level WAFPolicy를 적용하면 하나의 Gateway에 연결된 여러 HTTPRoute에 동일한 WAF 정책을 공통으로 적용할 수 있습니다. 하지만 실제 서비스 환경에서는 애플리케이션별 특성에 따라 서로 다른 보안 정책이 필요한 경우가 있습니다.
예를 들어 공통 서비스에는 공격 요청을 차단하는 정책을 적용하면서도, 특정 서비스에는 운영 영향도를 줄이기 위해 동일한 공격을 탐지하되 차단하지 않는 정책을 적용할 수 있습니다
이전 구성에서는 hared-gateway에 Gateway-level WAFPolicy를 적용하고, 해당 Gateway에 연결된 app1-route와 app2-route가 동일한 WAF 정책을 적용받는 구성을 확인했습니다.
이전 포스트에서는 이 구성에서 app2-route에 별도의 Route-level WAFPolicy를 적용합니다. app1-route는 기존 Gateway-level 정책을 그대로 적용받도록 유지하고, app2-route에는 다른 WAF 정책을 직접 적용하여 Route-level 정책이 Gateway-level 정책보다 우선하여 동작하는지 검증합니다.
이를 통해 Gateway에서는 여러 Route에 적용할 공통 보안 정책을 관리하고, 특정 애플리케이션에 필요한 예외 정책은 HTTPRoute 단위로 분리하여 적용하는 구성을 확인합니다.
목차
1. 검증 목표
2. 환경 구성
3. 기존 리소스 상태 확인
4. Route-level override용 WAF policy definition 추가
5. transparent 정책 bundle 생성 및 bundle-server 업데이트
6. app2-route에 Route-level WAFPolicy 생성
7. 동일 공격 요청으로 Route-level WAFPolicy override 검증
8. 결론
1. 검증 목표
이번 구성에서는 Gateway-level WAFPolicy가 적용된 환경에서 특정 HTTPRoute에 별도의 Route-level WAFPolicy를 적용하고, 두 정책의 우선순위에 따라 서로 다른 동작이 발생하는지 확인합니다.
검증 대상은 다음과 같습니다.
app1-route는 별도의 Route-levelWAFPolicy를 구성하지 않고 기존 Gateway-level 정책을 그대로 적용받습니다.app2-route에는 별도의 Route-levelWAFPolicy를 적용합니다.- Gateway-level 정책은 공격 요청을 차단하는
blocking정책으로 유지합니다. - Route-level 정책은 동일한 공격 Signature를 탐지하되 요청을 차단하지 않는
transparent정책으로 구성합니다. - 동일한 공격 요청을 두 HTTPRoute에 전달하여
app1-route는 차단되고app2-route는 허용되는지 확인합니다.
최종적으로 확인하려는 구조는 다음과 같습니다.
Gateway├── HTTPRoute1│ └── Gateway-level WAFPolicy 상속│ └── 공격 요청 차단│└── HTTPRoute2 └── Route-level WAFPolicy 적용 └── Gateway-level WAFPolicy override └── 동일 공격 요청에 대해 다른 동작 수행
기대하는 결과는 다음과 같습니다.
| 요청 대상 | 적용되는 WAFPolicy | 정책 모드 | 기대 동작 |
app1.example.com | Gateway-level WAFPolicy | blocking | 공격 요청 차단 |
app2.example.com | Route-level WAFPolicy | transparent | 공격 요청 탐지 후 허용 |
2. 환경 구성
이번 구성은 이전 포스트에서 사용한 NGING Gateway Fabric 환경을 그대로 사용합니다.
| 구성 요소 | 버전 |
| NGINX Gateway Fabric | 2.6.5 |
| NGINX Plus | r37.0.2 |
| Kubernetes | v1.36.2 |
추가로 이번 구성에서는 다음 리소스가 이미 생성되어 있는 상태를 전제로 진행합니다.
shared-gatewayapp1-routeapp2-route- Gateway-level
gateway-waf-policy bundle-serverwaf-policy-definitionsConfigMap
이후 app2-route에만 별도의 Route-level WAFPolicy를 추가하여 Gateway-level 정책과 서로 다른 동작을 구성합니다.
3. 기존 리소스 상태 확인
이번 구성은 Gateway-level WAFPolicy가 이미 적용된 상태에서 시작합니다.
현재 shared-gateway에는 app1-route와 app2-route가 연결되어 있으며, 두 HTTPRoute 모두 별도의 Route-level WAFPolicy 없이 Gateway-level gateway-waf-policy를 적용받는 구조입니다.
shared-gateway├── app1-route│ └── Gateway-level WAFPolicy 상속│└── app2-route └── Gateway-level WAFPolicy 상속
먼저 테스트 애플리케이션과 Service 상태를 확인합니다.
$ kubectl get deployment,pod,svc -n default
정상적으로 구성된 경우 두 Deployment와 Pod가 실행되고, 각 애플리케이션에 대한 ClusterIP Service가 생성되어 있어야 합니다.
NAME READY UP-TO-DATE AVAILABLE AGEdeployment.apps/app1 1/1 1 1 10ddeployment.apps/app2 1/1 1 1 10dNAME READY STATUS RESTARTS AGEpod/app1-8455b5554c-tqrwx 1/1 Running 0 10dpod/app2-657c5d4769-2lml5 1/1 Running 0 10dNAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGEservice/app1 ClusterIP 10.111.215.35 <none> 80/TCP 10dservice/app2 ClusterIP 10.96.139.192 <none> 80/TCP 10d
다음으로 Gateway 상태를 확인합니다.
$ kubectl get gateway shared-gateway$ kubectl describe gateway shared-gateway
Programmed=True와 Accepted=True가 확인되면 Gateway가 정상적으로 처리되고 있는 상태입니다.
NAME CLASS ADDRESS PROGRAMMED AGEshared-gateway nginx True 10d...Status: Conditions: Message: The Gateway is accepted Reason: Accepted Status: True Type: Accepted Message: The Gateway is programmed Reason: Programmed Status: True Type: Programmed...
HTTPRoute 상태도 확인합니다.
$ kubectl get httproute
NAME HOSTNAMES AGEapp1-route ["app1.example.com"] 9dapp2-route ["app2.example.com"] 9d
두 HTTPRoute가 모두 생성되어 있고 각각 shared-gateway에 연결되어 있어야 합니다.
기존 Gateway-level WAFPolicy도 확인합니다.
$ kubectl get wafpolicy gateway-waf-policy
NAME AGEgateway-waf-policy 9d
마지막으로 WAF Policy Bundle을 제공하는 bundle-server 상태를 확인합니다.
$ kubectl get deployment bundle-server$ kubectl get pods -l app=bundle-server$ kubectl get svc bundle-server
NAME READY UP-TO-DATE AVAILABLE AGEbundle-server 1/1 1 1 9dNAME READY STATUS RESTARTS AGEbundle-server-6f8dc8b6b6-bskql 1/1 Running 0 5d23hNAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGEbundle-server ClusterIP 10.97.208.228 <none> 80/TCP 9d
여기까지 정상이라면 Gateway-level WAFPolicy가 적용된 기존 구성이 준비된 상태입니다. 다음 단계에서는 app2-route에 적용할 별도의 Transparent Mode WAF Policy Definition을 추가합니다.
4. Route-level override용 WAF policy definition 추가
app2-route에 Gateway-level WAFPolicy와 다른 동작을 적용하기 위해 별도의 WAF Policy Definition을 추가합니다.
Gateway-level 정책은 blocking Mode로 구성되어 있으며, All Signatures Signature Set에 매칭되는 요청을 탐지하고 차단합니다. 반면 app2-route에는 동일한 공격 Signature를 탐지하되 요청은 차단하지 않는 transparent Mode 정책을 적용합니다.
기존 waf-policy-definitions ConfigMap에 attack-signatures-transparent.json을 추가합니다.
apiVersion: v1kind: ConfigMapmetadata: name: waf-policy-definitionsdata: attack-signatures-blocking.json: | { "policy": { "name": "attack-signatures-blocking", "template": { "name": "POLICY_TEMPLATE_NGINX_BASE" }, "applicationLanguage": "utf-8", "enforcementMode": "blocking", "signature-sets": [ { "name": "All Signatures", "block": true, "alarm": true } ] } } attack-signatures-transparent.json: | { "policy": { "name": "attack-signatures-transparent", "template": { "name": "POLICY_TEMPLATE_NGINX_BASE" }, "applicationLanguage": "utf-8", "enforcementMode": "transparent", "signature-sets": [ { "name": "All Signatures", "block": true, "alarm": true } ] } }
두 정책의 차이는 enforcementMode입니다.
| 정책 | enforcementMode | 동작 |
attack-signatures-blocking | blocking | 공격 Signature 탐지 시 요청 차단 |
attack-signatures-transparent | transparent | 공격 Signature를 탐지하지만 요청은 차단하지 않음 |
transparent Mode에서는 위반 사항과 Attack Signature를 탐지하고 Security Log에 기록하지만, 해당 위반으로 요청을 차단하지 않습니다.
작성한 ConfigMap을 적용합니다.
$ kubectl apply -f waf-policy-definitions.yaml
정상적으로 반영되었는지 확인합니다.
$ kubectl get configmap waf-policy-definitionsNAME DATA AGEwaf-policy-definitions 2 3d20h
DATA가 2로 표시되면 attack-signatures-blocking.json과 attack-signatures-transparent.json 두 개의 Policy Definition이 ConfigMap에 포함된 상태입니다.
다음 단계에서는 새로 추가한 attack-signatures-transparent.json을 WAF Compiler로 컴파일하여 Route-level WAFPolicy에서 사용할 Policy Bundle을 생성합니다.
5. transparent 정책 bundle 생성 및 bundle-server 업데이트
앞에서 추가한 attack-signatures-transparent.json을 Route-level WAFPolicy에서 사용하려면 WAF Compiler를 통해 .tgz 형식의 Policy Bundle로 변환해야 합니다.
WAFPolicy는 JSON 형식의 Policy Definition을 직접 참조하지 않고, 컴파일된 Policy Bundle의 HTTP URL을 policySource.httpSource.url에 지정하여 사용합니다. 따라서 기존 bundle-server에서 Blocking 정책과 Transparent 정책의 Bundle을 함께 제공하도록 구성을 변경합니다.
bundle-server.yaml을 다음과 같이 수정합니다.
apiVersion: apps/v1kind: Deploymentmetadata: name: bundle-serverspec: replicas: 1 selector: matchLabels: app: bundle-server template: metadata: labels: app: bundle-server spec: imagePullSecrets: - name: nginx-plus-registry-secret initContainers: - name: compile-attack-signatures-blocking image: private-registry.nginx.com/nap/waf-compiler:5.13.1 args: - -p - /policies/attack-signatures-blocking.json - -o - /bundles/attack-signatures-blocking.tgz volumeMounts: - name: policies mountPath: /policies - name: bundles mountPath: /bundles - name: compile-attack-signatures-transparent image: private-registry.nginx.com/nap/waf-compiler:5.13.1 args: - -p - /policies/attack-signatures-transparent.json - -o - /bundles/attack-signatures-transparent.tgz volumeMounts: - name: policies mountPath: /policies - name: bundles mountPath: /bundles containers: - name: bundle-server image: nginx:alpine ports: - containerPort: 80 volumeMounts: - name: bundles mountPath: /usr/share/nginx/html volumes: - name: policies configMap: name: waf-policy-definitions - name: bundles emptyDir: {}apiVersion: v1kind: Servicemetadata: name: bundle-serverspec: selector: app: bundle-server ports: - name: http port: 80 targetPort: 80 protocol: TCP
두 개의 initContainer는 각각 다른 Policy Definition을 컴파일합니다.
| initContainer | 입력 Policy | 생성 Bundle |
compile-attack-signatures-blocking | attack-signatures-blocking.json | attack-signatures-blocking.tgz |
compile-attack-signatures-transparent | attack-signatures-transparent.json | attack-signatures-transparent.tgz |
두 initContainer가 생성한 Bundle은 동일한 bundles 볼륨에 저장되며, 이후 bundle-server 컨테이너가 해당 볼륨을 /usr/share/nginx/html에 마운트하여 HTTP로 제공합니다.
수정한 리소스를 적용합니다.
$ kubectl apply -f bundle-server.yaml
Deployment가 정상적으로 업데이트되었는지 확인합니다.
$ kubectl rollout status deployment/bundle-serverdeployment "bundle-server" successfully rolled out
Pod 상태를 확인합니다.
$ kubectl get pods -l app=bundle-serverNAME READY STATUS RESTARTS AGEbundle-server-6f8dc8b6b6-bskql 1/1 Running 0 59m
Pod가 Running 상태라면 두 initContainer의 Policy Bundle 생성이 완료되고, bundle-server에서 Blocking 및 Transparent Policy Bundle을 제공할 수 있는 상태입니다.
다음 단계에서는 attack-signatures-transparent.tgz를 참조하는 Route-level WAFPolicy를 생성하여 app2-route에 적용합니다.
6. app2-route에 Route-level WAFPolicy 생성
이제 app2-route에만 별도의 Route-level WAFPolicy를 적용합니다.
WAFPolicy는 Gateway 또는 HTTPRoute를 대상으로 지정할 수 있으며, 특정 HTTPRoute에 직접 적용된 Route-level 정책은 해당 Route에서 Gateway-level 정책보다 우선합니다. WAFPolicy는 병합되지 않으므로 app2-route에서는 Route-level 정책이 Gateway-level 정책을 대체합니다.
app2-route-waf-policy.yaml 파일을 생성하고 다음 내용을 작성합니다.
apiVersion: gateway.nginx.org/v1alpha1kind: WAFPolicymetadata: name: app2-route-waf-policyspec: type: HTTP targetRefs: - group: gateway.networking.k8s.io kind: HTTPRoute name: app2-route policySource: httpSource: url: http://bundle-server.default.svc.cluster.local/attack-signatures-transparent.tgz securityLogs: - destination: type: stderr logSource: defaultProfile: log_all
주요 설정은 다음과 같습니다.
| 항목 | 설정 | 설명 |
type | HTTP | HTTP URL에서 Policy Bundle을 가져오는 방식 |
targetRefs.kind | HTTPRoute | 정책 적용 대상을 HTTPRoute로 지정 |
targetRefs.name | app2-route | Route-level 정책을 적용할 HTTPRoute |
policySource.httpSource.url | attack-signatures-transparent.tgz | Transparent Mode로 컴파일한 Policy Bundle |
securityLogs.destination.type | stderr | Security Log 출력 위치 |
defaultProfile | log_all | 탐지된 요청을 포함한 Security Log 기록 |
targetRefs에서 HTTPRoute와 app2-route를 지정했으므로 이 정책은 app2-route에만 적용됩니다. HTTP URL을 통해 컴파일된 Bundle을 참조하는 type: HTTP 구성도 WAFPolicy에서 지원되는 Policy Source 방식입니다.
작성한 WAFPolicy를 적용합니다.
$ kubectl apply -f app2-route-waf-policy.yaml
생성된 WAFPolicy를 확인합니다.
$ kubectl get wafpolicyNAME AGEapp2-route-waf-policy 43sgateway-waf-policy 3d21h
두 개의 WAFPolicy가 존재하지만 적용 대상은 서로 다릅니다.
| WAFPolicy | 적용 대상 | Policy |
gateway-waf-policy | shared-gateway | Blocking |
app2-route-waf-policy | app2-route | Transparent |
app2-route-waf-policy 상세 상태를 확인합니다.
$ kubectl describe wafpolicy app2-route-waf-policy
정상적으로 적용된 경우 Target Refs에서 HTTPRoute와 app2-route를 확인할 수 있습니다.
...Spec: Policy Source: Http Source: URL: http://bundle-server.default.svc.cluster.local/attack-signatures-transparent.tgz Retry Attempts: 3 Security Logs: Destination: Type: stderr Log Source: Default Profile: log_all Retry Attempts: 3 Target Refs: Group: gateway.networking.k8s.io Kind: HTTPRoute Name: app2-route Type: HTTP...
이제 정책 적용 구조는 다음과 같습니다.
shared-gateway├── app1-route│ └── Gateway-level WAFPolicy│ └── Blocking│└── app2-route └── Route-level WAFPolicy └── Transparent
따라서 app1-route는 계속 Gateway-level WAFPolicy를 적용받고, app2-route에서는 Route-level app2-route-waf-policy가 Gateway-level 정책을 Override합니다. 다른 Route들은 기존 Gateway-level 정책을 계속 적용받습니다.
7. 동일 공격 요청으로 Route-level WAFPolicy override 검증
이제 동일한 공격 요청을 app1-route와 app2-route에 각각 전달하여 Route-level WAFPolicy의 Override 동작을 확인합니다.
먼저 현재 생성된 WAFPolicy를 확인합니다.
$ kubectl get wafpolicyNAME AGEapp2-route-waf-policy 8dgateway-waf-policy 11d
현재 두 개의 WAFPolicy가 존재하며 각각 전용 대상이 다릅니다.
| WAFPolicy | 적용 대상 | 정책 |
gateway-waf-policy | shared-gateway | Blocking |
app2-route-waf-policy | app2-route | Transparent |
Gateway-level WAFPolicy의 적용 대상을 확인합니다.
$ kubectl describe wafpolicy gateway-waf-policy
Target Refs에서 shared-gateway가 지정되어 있는지 확인합니다.
Target Refs: Group: gateway.networking.k8s.io Kind: Gateway Name: shared-gateway
gateway-waf-policy는 shared-gateway를 대상으로 하는 Gateway-level 정책입니다.
다음으로 Route-level WAFPolicy의 적용 대상을 확인합니다.
$ kubectl describe wafpolicy app2-route-waf-policy
Target Refs:
Group: gateway.networking.k8s.io
Kind: HTTPRoute
Name: app2-route
Type: HTTP
app2-route-waf-policy는 app2-route를 직접 대상으로 하는 Route-level 정책입니다.
두 HTTPRoute가 동일한 Gateway에 연결되어 있는지도 확인합니다.
$ kubectl describe httproute app1-route$ kubectl describe httproute app2-route
두 Route의 Parent Refs에서 다음과 같이 shared-gateway가 확인되어야 합니다.
Parent Refs:
Group: gateway.networking.k8s.io
Kind: Gateway
Name: shared-gateway
따라서 현재 정책 적용 구조는 다음과 같습니다.
shared-gateway├── app1-route│ └── Gateway-level WAFPolicy│ └── Blocking│└── app2-route└── Route-level WAFPolicy└── Transparent
먼저 app1-route에 XSS 패턴이 포함된 요청을 전달합니다.
$ curl -H "Host: app1.example.com" \ "http://127.0.0.1:8080/?q=<script>alert(1)</script>"
app1-route에는 별도의 Route-level WAFPolicy가 없으므로 shared-gateway에 적용된 Blocking 정책을 그대로 적용받습니다.
정상적으로 동작한다면 요청이 차단되고 다음과 같은 응답이 반환됩니다.
<html><head><title>Request Rejected</title></head><body>The requested URL was rejected. Please consult with your administrator.Your support ID is: 17701616346470225537</body></html>
실제 검증에서도 app1-route는 Gateway-level WAFPolicy에 의해 공격 요청이 차단되었습니다.
다음으로 app2-route에 동일한 요청을 전달합니다.
$ curl -H "Host: app2.example.com" \ "http://127.0.0.1:8080/?q=<script>alert(1)</script>"
app2-route에는 Transparent Mode로 구성된 app2-route-waf-policy가 직접 적용되어 있습니다. 따라서 공격 Signature는 탐지하지만 요청을 차단하지 않고 Backend Service로 전달합니다.
정상적으로 동작한다면 다음과 같이 Backend 애플리케이션의 응답을 확인할 수 있습니다.
Server address: 10.244.49.163:8080Server name: app2-657c5d4769-2lml5Date: 24/Jun/2026:04:09:19 +0000URI: /?q=<script>alert(1)</script>Request ID: 3c2171298d1bc101a1bd706ac0ee14d8
최종 결과는 다음과 같습니다.
| 요청 대상 | 적용 정책 | 결과 |
app1.example.com | Gateway-level / Blocking | 공격 요청 차단 |
app2.example.com | Route-level / Transparent | 공격 탐지 후 Backend로 전달 |
동일한 Gateway에 연결된 두 HTTPRoute에 동일한 공격 요청을 전달했지만 서로 다른 결과가 발생한 것을 통해 app2-route에서는 Route-level WAFPolicy가 Gateway-level 정책을 Override하여 동작하는 것을 확인할 수 있습니다.
8. 결론
이번 구성에서는 Gateway-level WAFPolicy가 적용된 환경에서 특정 HTTPRoute에 별도의 Route-level WAFPolicy를 추가하고, 두 정책의 적용 우선순위를 검증했습니다.
app1-route는 별도의 Route-level 정책이 없기 때문에 shared-gateway에 적용된 Blocking Mode의 Gateway-level WAFPolicy를 그대로 적용받아 공격 요청을 차단했습니다. 반면 app2-route에는 Transparent Mode의 app2-route-waf-policy를 직접 적용하여 동일한 공격 요청을 탐지하되 Backend 애플리케이션까지 정상적으로 전달되는 것을 확인했습니다.
이를 통해 특정 HTTPRoute에 Route-level WAFPolicy가 직접 적용된 경우 해당 Route에서는 Gateway-level 정책이 아닌 Route-level 정책을 기준으로 동작하는 것을 확인할 수 있습니다.
이러한 구성은 여러 Route에 공통으로 적용할 보안 정책을 Gateway 단위에서 관리하면서도, 서비스 특성에 따라 별도의 정책이 필요한 경우 HTTPRoute 단위로 세부 정책을 적용할 수 있다는 장점이 있습니다. 따라서 공통 보안 기준과 서비스별 예외 정책을 분리하여 관리해야 하는 Kubernetes Gateway API 환경에서 보다 유연한 WAF 정책 구성이 가능합니다.
NGINX STORE에 문의하여 더 자세한 정보를 확인하세요.