Skip to content

Commit ac78eaf

Browse files
committed
Deploy WAF containers when enabled in NGINXProxy (#3481)
* Add WAF dockerfile and make targets * Add WAF parameters to NGINXProxy resource * Review feedback * Add plus image path; add readOnlyRootFS to waf containers * Capitalise WAF
1 parent 69e2642 commit ac78eaf

File tree

22 files changed

+2503
-225
lines changed

22 files changed

+2503
-225
lines changed

Makefile

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@ CHART_DIR = $(SELF_DIR)charts/nginx-gateway-fabric
55
NGINX_CONF_DIR = internal/controller/nginx/conf
66
NJS_DIR = internal/controller/nginx/modules/src
77
KIND_CONFIG_FILE = $(SELF_DIR)config/cluster/kind-cluster.yaml
8+
NAP_WAF_ALPINE_VERSION = 3.19
89
NGINX_DOCKER_BUILD_PLUS_ARGS = --secret id=nginx-repo.crt,src=$(SELF_DIR)nginx-repo.crt --secret id=nginx-repo.key,src=$(SELF_DIR)nginx-repo.key
10+
NGINX_DOCKER_BUILD_NAP_WAF_ARGS = --build-arg ALPINE_VERSION=$(NAP_WAF_ALPINE_VERSION) --build-arg INCLUDE_NAP_WAF=true
911
BUILD_AGENT = local
1012

1113
PROD_TELEMETRY_ENDPOINT = oss.edge.df.f5.com:443
@@ -77,6 +79,9 @@ build-images: build-ngf-image build-nginx-image ## Build the NGF and nginx docke
7779
.PHONY: build-images-with-plus
7880
build-images-with-plus: build-ngf-image build-nginx-plus-image ## Build the NGF and NGINX Plus docker images
7981

82+
.PHONY: build-images-nap-waf
83+
build-images-with-nap-waf: build-ngf-image build-nginx-plus-image-with-nap-waf ## Build the NGF and NGINX Plus with WAF docker images
84+
8085
.PHONY: build-prod-ngf-image
8186
build-prod-ngf-image: TELEMETRY_ENDPOINT=$(PROD_TELEMETRY_ENDPOINT)
8287
build-prod-ngf-image: build-ngf-image ## Build the NGF docker image for production
@@ -99,6 +104,13 @@ build-prod-nginx-plus-image: build-nginx-plus-image ## Build the custom nginx pl
99104
build-nginx-plus-image: check-for-docker ## Build the custom nginx plus image
100105
docker build --platform linux/$(GOARCH) $(strip $(NGINX_DOCKER_BUILD_OPTIONS)) $(strip $(NGINX_DOCKER_BUILD_PLUS_ARGS)) -f $(SELF_DIR)build/Dockerfile.nginxplus -t $(strip $(NGINX_PLUS_PREFIX)):$(strip $(TAG)) $(strip $(SELF_DIR))
101106

107+
.PHONY: build-nginx-plus-image-with-nap-waf
108+
build-nginx-plus-image-with-nap-waf: check-for-docker ## Build the custom nginx plus image with NAP WAF. Note that arm is NOT supported.
109+
@if [ $(GOARCH) = "arm64" ]; then \
110+
echo "\033[0;31mIMPORTANT:\033[0m The nginx-plus-waf image cannot be built for arm64 architecture and will be built for amd64."; \
111+
fi
112+
docker build --platform linux/amd64 $(strip $(NGINX_DOCKER_BUILD_OPTIONS)) $(strip $(NGINX_DOCKER_BUILD_PLUS_ARGS)) $(strip $(NGINX_DOCKER_BUILD_NAP_WAF_ARGS)) -f $(SELF_DIR)build/Dockerfile.nginxplus -t $(strip $(NGINX_PLUS_PREFIX)):$(strip $(TAG)) $(strip $(SELF_DIR))
113+
102114
.PHONY: check-for-docker
103115
check-for-docker: ## Check if Docker is installed
104116
@docker -v || (code=$$?; printf "\033[0;31mError\033[0m: there was a problem with Docker\n"; exit $$code)

apis/v1alpha2/nginxproxy_types.go

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,12 +72,35 @@ type NginxProxySpec struct {
7272
//
7373
// +optional
7474
DisableHTTP2 *bool `json:"disableHTTP2,omitempty"`
75+
// WAF enables NGINX App Protect WAF functionality.
76+
// When enabled, NGINX Gateway Fabric will deploy additional WAF containers
77+
// (waf-enforcer and waf-config-mgr) alongside the main NGINX container.
78+
// Default is "disabled".
79+
//
80+
// +optional
81+
// +kubebuilder:default:=disabled
82+
WAF *WAFState `json:"waf,omitempty"`
7583
// Kubernetes contains the configuration for the NGINX Deployment and Service Kubernetes objects.
7684
//
7785
// +optional
7886
Kubernetes *KubernetesSpec `json:"kubernetes,omitempty"`
7987
}
8088

89+
// WAFState defines the state of WAF functionality.
90+
//
91+
// +kubebuilder:validation:Enum=enabled;disabled
92+
type WAFState string
93+
94+
const (
95+
// WAFEnabled enables NGINX App Protect WAF functionality.
96+
// This will deploy additional containers for WAF enforcement and configuration management.
97+
WAFEnabled WAFState = "enabled"
98+
99+
// WAFDisabled disables NGINX App Protect WAF functionality.
100+
// Only the standard NGINX container will be deployed.
101+
WAFDisabled WAFState = "disabled"
102+
)
103+
81104
// Telemetry specifies the OpenTelemetry configuration.
82105
type Telemetry struct {
83106
// DisabledFeatures specifies OpenTelemetry features to be disabled.
@@ -388,6 +411,12 @@ type DeploymentSpec struct {
388411
// +optional
389412
Replicas *int32 `json:"replicas,omitempty"`
390413

414+
// WAFContainers defines container specifications for NGINX App Protect WAF v5 containers.
415+
// These containers are only deployed when WAF is enabled in the NginxProxy spec.
416+
//
417+
// +optional
418+
WAFContainers *WAFContainerSpec `json:"wafContainers,omitempty"`
419+
391420
// Pod defines Pod-specific fields.
392421
//
393422
// +optional
@@ -401,6 +430,12 @@ type DeploymentSpec struct {
401430

402431
// DaemonSet is the configuration for the NGINX DaemonSet.
403432
type DaemonSetSpec struct {
433+
// WAFContainers defines container specifications for NGINX App Protect WAF v5 containers.
434+
// These containers are only deployed when WAF is enabled in the NginxProxy spec.
435+
//
436+
// +optional
437+
WAFContainers *WAFContainerSpec `json:"wafContainers,omitempty"`
438+
404439
// Pod defines Pod-specific fields.
405440
//
406441
// +optional
@@ -490,6 +525,40 @@ type ContainerSpec struct {
490525
VolumeMounts []corev1.VolumeMount `json:"volumeMounts,omitempty"`
491526
}
492527

528+
// WAFContainerSpec defines the container specifications for NGINX App Protect WAF v5.
529+
// NAP v5 requires two additional containers: waf-enforcer and waf-config-mgr.
530+
type WAFContainerSpec struct {
531+
// Enforcer defines the configuration for the WAF enforcer container.
532+
// This container performs the actual WAF enforcement and policy application.
533+
//
534+
// +optional
535+
Enforcer *WAFContainerConfig `json:"enforcer,omitempty"`
536+
537+
// ConfigManager defines the configuration for the WAF configuration manager container.
538+
// This container manages policy configuration and communication with the enforcer.
539+
//
540+
// +optional
541+
ConfigManager *WAFContainerConfig `json:"configManager,omitempty"`
542+
}
543+
544+
// WAFContainerConfig defines the configuration for a single WAF container.
545+
type WAFContainerConfig struct {
546+
// Image is the container image to use for this WAF container.
547+
//
548+
// +optional
549+
Image *Image `json:"image,omitempty"`
550+
551+
// Resources describes the compute resource requirements for this WAF container.
552+
//
553+
// +optional
554+
Resources *corev1.ResourceRequirements `json:"resources,omitempty"`
555+
556+
// VolumeMounts describe the mounting of Volumes within the WAF container.
557+
//
558+
// +optional
559+
VolumeMounts []corev1.VolumeMount `json:"volumeMounts,omitempty"`
560+
}
561+
493562
// Image is the NGINX image to use.
494563
type Image struct {
495564
// Repository is the image path.

apis/v1alpha2/zz_generated.deepcopy.go

Lines changed: 72 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

build/Dockerfile.nginxplus

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,13 @@ FROM scratch AS nginx-files
44
# the following links can be replaced with local files if needed, i.e. ADD --chown=101:1001 <local_file> <container_file>
55
ADD --link --chown=101:1001 https://cs.nginx.com/static/keys/nginx_signing.rsa.pub nginx_signing.rsa.pub
66

7-
FROM alpine:3.21
7+
FROM alpine:${ALPINE_VERSION}
88

99
ARG NGINX_PLUS_VERSION=R34
1010
# renovate: datasource=github-tags depName=nginx/agent
1111
ARG NGINX_AGENT_VERSION=v3.0.3
12+
ARG APP_PROTECT_VERSION=34.5.342
13+
ARG INCLUDE_NAP_WAF=false
1214
ARG NJS_DIR
1315
ARG NGINX_CONF_DIR
1416
ARG BUILD_AGENT
@@ -20,6 +22,10 @@ RUN --mount=type=secret,id=nginx-repo.crt,dst=/etc/apk/cert.pem,mode=0644 \
2022
&& adduser -S -D -H -u 101 -h /var/cache/nginx -s /sbin/nologin -G nginx -g nginx nginx \
2123
&& printf "%s\n" "https://pkgs.nginx.com/plus/${NGINX_PLUS_VERSION}/alpine/v$(grep -E -o '^[0-9]+\.[0-9]+' /etc/alpine-release)/main" >> /etc/apk/repositories \
2224
&& printf "%s\n" "https://pkgs.nginx.com/nginx-agent/alpine/v$(egrep -o '^[0-9]+\.[0-9]+' /etc/alpine-release)/main" >> /etc/apk/repositories \
25+
&& if [ "${INCLUDE_NAP_WAF}" = "true" ]; then \
26+
printf "%s\n" "https://pkgs.nginx.com/app-protect-x-plus/alpine/v$(grep -E -o '^[0-9]+\.[0-9]+' /etc/alpine-release)/main" >> /etc/apk/repositories \
27+
&& apk add --no-cache app-protect-module-plus~=${APP_PROTECT_VERSION}; \
28+
fi \
2329
&& apk add --no-cache nginx-plus nginx-plus-module-njs nginx-plus-module-otel nginx-agent=${NGINX_AGENT_VERSION#v}
2430

2531
RUN apk add --no-cache bash \
@@ -40,4 +46,5 @@ USER 101:1001
4046

4147
LABEL org.nginx.ngf.image.build.agent="${BUILD_AGENT}"
4248

49+
ENV USE_NAP_WAF=${INCLUDE_NAP_WAF}
4350
ENTRYPOINT ["/agent/entrypoint.sh"]

build/entrypoint.sh

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,11 @@ trap 'handle_quit' QUIT
2727

2828
rm -rf /var/run/nginx/*.sock
2929

30+
# Bootstrap the necessary app protect files
31+
if [ "${USE_NAP_WAF:-false}" = "true" ]; then
32+
touch /opt/app_protect/bd_config/policy_path.map
33+
fi
34+
3035
# Launch nginx
3136
echo "starting nginx ..."
3237

charts/nginx-gateway-fabric/README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -264,7 +264,7 @@ The following table lists the configurable parameters of the NGINX Gateway Fabri
264264
| `certGenerator.ttlSecondsAfterFinished` | How long to wait after the cert generator job has finished before it is removed by the job controller. | int | `30` |
265265
| `clusterDomain` | The DNS cluster domain of your Kubernetes cluster. | string | `"cluster.local"` |
266266
| `gateways` | A list of Gateway objects. View https://gateway-api.sigs.k8s.io/reference/spec/#gateway for full Gateway reference. | list | `[]` |
267-
| `nginx` | The nginx section contains the configuration for all NGINX data plane deployments installed by the NGINX Gateway Fabric control plane. | object | `{"config":{},"container":{"hostPorts":[],"lifecycle":{},"resources":{},"volumeMounts":[]},"debug":false,"image":{"pullPolicy":"Always","repository":"ghcr.io/nginx/nginx-gateway-fabric/nginx","tag":"edge"},"imagePullSecret":"","imagePullSecrets":[],"kind":"deployment","plus":false,"pod":{},"replicas":1,"service":{"externalTrafficPolicy":"Local","loadBalancerClass":"","loadBalancerIP":"","loadBalancerSourceRanges":[],"nodePorts":[],"type":"LoadBalancer"},"usage":{"caSecretName":"","clientSSLSecretName":"","endpoint":"","resolver":"","secretName":"nplus-license","skipVerify":false}}` |
267+
| `nginx` | The nginx section contains the configuration for all NGINX data plane deployments installed by the NGINX Gateway Fabric control plane. | object | `{"config":{},"container":{"hostPorts":[],"lifecycle":{},"resources":{},"volumeMounts":[]},"debug":false,"image":{"pullPolicy":"Always","repository":"ghcr.io/nginx/nginx-gateway-fabric/nginx","tag":"edge"},"imagePullSecret":"","imagePullSecrets":[],"kind":"deployment","plus":false,"pod":{},"replicas":1,"service":{"externalTrafficPolicy":"Local","loadBalancerClass":"","loadBalancerIP":"","loadBalancerSourceRanges":[],"nodePorts":[],"type":"LoadBalancer"},"usage":{"caSecretName":"","clientSSLSecretName":"","endpoint":"","resolver":"","secretName":"nplus-license","skipVerify":false},"wafContainers":{}}` |
268268
| `nginx.config` | The configuration for the data plane that is contained in the NginxProxy resource. This is applied globally to all Gateways managed by this instance of NGINX Gateway Fabric. | object | `{}` |
269269
| `nginx.container` | The container configuration for the NGINX container. This is applied globally to all Gateways managed by this instance of NGINX Gateway Fabric. | object | `{"hostPorts":[],"lifecycle":{},"resources":{},"volumeMounts":[]}` |
270270
| `nginx.container.hostPorts` | A list of HostPorts to expose on the host. This configuration allows containers to bind to a specific port on the host node, enabling external network traffic to reach the container directly through the host's IP address and port. Use this option when you need to expose container ports on the host for direct access, such as for debugging, legacy integrations, or when NodePort/LoadBalancer services are not suitable. Note: Using hostPort may have security and scheduling implications, as it ties pods to specific nodes and ports. | list | `[]` |
@@ -292,6 +292,7 @@ The following table lists the configurable parameters of the NGINX Gateway Fabri
292292
| `nginx.usage.resolver` | The nameserver used to resolve the NGINX Plus usage reporting endpoint. Used with NGINX Instance Manager. | string | `""` |
293293
| `nginx.usage.secretName` | The name of the Secret containing the JWT for NGINX Plus usage reporting. Must exist in the same namespace that the NGINX Gateway Fabric control plane is running in (default namespace: nginx-gateway). | string | `"nplus-license"` |
294294
| `nginx.usage.skipVerify` | Disable client verification of the NGINX Plus usage reporting server certificate. | bool | `false` |
295+
| `nginx.wafContainers` | Configuration for NGINX App Protect WAF v5 containers. These containers are only deployed when WAF is enabled via nginx.config.waf: "Enabled". All settings are optional overrides - defaults are provided by NGF. | object | `{}` |
295296
| `nginxGateway` | The nginxGateway section contains configuration for the NGINX Gateway Fabric control plane deployment. | object | `{"affinity":{},"config":{"logging":{"level":"info"}},"configAnnotations":{},"extraVolumeMounts":[],"extraVolumes":[],"gatewayClassAnnotations":{},"gatewayClassName":"nginx","gatewayControllerName":"gateway.nginx.org/nginx-gateway-controller","gwAPIExperimentalFeatures":{"enable":false},"image":{"pullPolicy":"Always","repository":"ghcr.io/nginx/nginx-gateway-fabric","tag":"edge"},"kind":"deployment","labels":{},"leaderElection":{"enable":true,"lockName":""},"lifecycle":{},"metrics":{"enable":true,"port":9113,"secure":false},"name":"","nodeSelector":{},"podAnnotations":{},"productTelemetry":{"enable":true},"readinessProbe":{"enable":true,"initialDelaySeconds":3,"port":8081},"replicas":1,"resources":{},"service":{"annotations":{},"labels":{}},"serviceAccount":{"annotations":{},"imagePullSecret":"","imagePullSecrets":[],"name":""},"snippetsFilters":{"enable":false},"terminationGracePeriodSeconds":30,"tolerations":[],"topologySpreadConstraints":[]}` |
296297
| `nginxGateway.affinity` | The affinity of the NGINX Gateway Fabric control plane pod. | object | `{}` |
297298
| `nginxGateway.config.logging.level` | Log level. | string | `"info"` |

charts/nginx-gateway-fabric/templates/nginxproxy.yaml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,10 @@ spec:
2929
{{- if .Values.nginx.debug }}
3030
debug: {{ .Values.nginx.debug }}
3131
{{- end }}
32+
{{- if and .Values.nginx.wafContainers }}
33+
wafContainers:
34+
{{- toYaml .Values.nginx.wafContainers | nindent 8 }}
35+
{{- end }}
3236
{{- end }}
3337
{{- if eq .Values.nginx.kind "daemonSet" }}
3438
daemonSet:
@@ -48,6 +52,10 @@ spec:
4852
{{- if .Values.nginx.debug }}
4953
debug: {{ .Values.nginx.debug }}
5054
{{- end }}
55+
{{- if and .Values.nginx.wafContainers }}
56+
wafContainers:
57+
{{- toYaml .Values.nginx.wafContainers | nindent 8 }}
58+
{{- end }}
5159
{{- end }}
5260
{{- if .Values.nginx.service }}
5361
service:

charts/nginx-gateway-fabric/values.schema.json

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -305,6 +305,15 @@
305305
},
306306
"required": [],
307307
"type": "object"
308+
},
309+
"waf": {
310+
"description": "WAF enables NGINX App Protect WAF functionality.",
311+
"enum": [
312+
"enabled",
313+
"disabled"
314+
],
315+
"required": [],
316+
"type": "string"
308317
}
309318
},
310319
"required": [],
@@ -572,6 +581,12 @@
572581
"required": [],
573582
"title": "usage",
574583
"type": "object"
584+
},
585+
"wafContainers": {
586+
"description": "Configuration for NGINX App Protect WAF v5 containers.\nThese containers are only deployed when WAF is enabled via nginx.config.waf: \"Enabled\".\nAll settings are optional overrides - defaults are provided by NGF.",
587+
"required": [],
588+
"title": "wafContainers",
589+
"type": "object"
575590
}
576591
},
577592
"required": [],

charts/nginx-gateway-fabric/values.yaml

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -371,6 +371,12 @@ nginx:
371371
# - IPAddress
372372
# value:
373373
# type: string
374+
# waf:
375+
# type: string
376+
# description: WAF enables NGINX App Protect WAF functionality.
377+
# enum:
378+
# - enabled
379+
# - disabled
374380
# @schema
375381
# -- The configuration for the data plane that is contained in the NginxProxy resource. This is applied globally to all Gateways
376382
# managed by this instance of NGINX Gateway Fabric.
@@ -436,6 +442,33 @@ nginx:
436442
# -- volumeMounts are the additional volume mounts for the NGINX container.
437443
volumeMounts: []
438444

445+
# -- Configuration for NGINX App Protect WAF v5 containers.
446+
# These containers are only deployed when WAF is enabled via nginx.config.waf: "Enabled".
447+
# All settings are optional overrides - defaults are provided by NGF.
448+
wafContainers: {}
449+
# -- WAF Enforcer container configuration.
450+
# enforcer:
451+
# image: {}
452+
453+
# -- The resource requirements of the WAF Enforcer container.
454+
# resources: {}
455+
#
456+
# # -- Additional volume mounts for the WAF enforcer container.
457+
# # NAP v5 shared volumes are automatically configured by NGF.
458+
# volumeMounts: []
459+
460+
# -- WAF Configuration Manager container configuration.
461+
# configManager:
462+
# # -- Container image configuration
463+
# image: {}
464+
#
465+
# # -- The resource requirements of the WAF config manager container.
466+
# resources: {}
467+
468+
# # -- Additional volume mounts for the WAF config manager container.
469+
# # NAP v5 shared volumes are automatically configured by NGF.
470+
# volumeMounts: []
471+
439472
# -- The service configuration for the NGINX data plane. This is applied globally to all Gateways managed by this
440473
# instance of NGINX Gateway Fabric.
441474
service:

0 commit comments

Comments
 (0)