Skip to content

Commit 173fcf2

Browse files
authored
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 0bbb602 commit 173fcf2

22 files changed

+2497
-215
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
@@ -485,6 +520,40 @@ type ContainerSpec struct {
485520
VolumeMounts []corev1.VolumeMount `json:"volumeMounts,omitempty"`
486521
}
487522

523+
// WAFContainerSpec defines the container specifications for NGINX App Protect WAF v5.
524+
// NAP v5 requires two additional containers: waf-enforcer and waf-config-mgr.
525+
type WAFContainerSpec struct {
526+
// Enforcer defines the configuration for the WAF enforcer container.
527+
// This container performs the actual WAF enforcement and policy application.
528+
//
529+
// +optional
530+
Enforcer *WAFContainerConfig `json:"enforcer,omitempty"`
531+
532+
// ConfigManager defines the configuration for the WAF configuration manager container.
533+
// This container manages policy configuration and communication with the enforcer.
534+
//
535+
// +optional
536+
ConfigManager *WAFContainerConfig `json:"configManager,omitempty"`
537+
}
538+
539+
// WAFContainerConfig defines the configuration for a single WAF container.
540+
type WAFContainerConfig struct {
541+
// Image is the container image to use for this WAF container.
542+
//
543+
// +optional
544+
Image *Image `json:"image,omitempty"`
545+
546+
// Resources describes the compute resource requirements for this WAF container.
547+
//
548+
// +optional
549+
Resources *corev1.ResourceRequirements `json:"resources,omitempty"`
550+
551+
// VolumeMounts describe the mounting of Volumes within the WAF container.
552+
//
553+
// +optional
554+
VolumeMounts []corev1.VolumeMount `json:"volumeMounts,omitempty"`
555+
}
556+
488557
// Image is the NGINX image to use.
489558
type Image struct {
490559
// 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: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,20 @@
11
# syntax=docker/dockerfile:1.16
2+
3+
# renovate: datasource=docker depName=alpine
4+
ARG ALPINE_VERSION=3.21
5+
26
FROM scratch AS nginx-files
37

48
# the following links can be replaced with local files if needed, i.e. ADD --chown=101:1001 <local_file> <container_file>
59
ADD --link --chown=101:1001 https://cs.nginx.com/static/keys/nginx_signing.rsa.pub nginx_signing.rsa.pub
610

7-
FROM alpine:3.21
11+
FROM alpine:${ALPINE_VERSION}
812

913
ARG NGINX_PLUS_VERSION=R34
1014
# renovate: datasource=github-tags depName=nginx/agent extractVersion=^v?(?<version>.*)$
1115
ARG NGINX_AGENT_VERSION=3.0.0
16+
ARG APP_PROTECT_VERSION=34.5.342
17+
ARG INCLUDE_NAP_WAF=false
1218
ARG NJS_DIR
1319
ARG NGINX_CONF_DIR
1420
ARG BUILD_AGENT
@@ -20,6 +26,10 @@ RUN --mount=type=secret,id=nginx-repo.crt,dst=/etc/apk/cert.pem,mode=0644 \
2026
&& adduser -S -D -H -u 101 -h /var/cache/nginx -s /sbin/nologin -G nginx -g nginx nginx \
2127
&& 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 \
2228
&& printf "%s\n" "https://pkgs.nginx.com/nginx-agent/alpine/v$(egrep -o '^[0-9]+\.[0-9]+' /etc/alpine-release)/main" >> /etc/apk/repositories \
29+
&& if [ "${INCLUDE_NAP_WAF}" = "true" ]; then \
30+
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 \
31+
&& apk add --no-cache app-protect-module-plus~=${APP_PROTECT_VERSION}; \
32+
fi \
2333
&& apk add --no-cache nginx-plus nginx-plus-module-njs nginx-plus-module-otel nginx-agent=${NGINX_AGENT_VERSION}
2434

2535
RUN apk add --no-cache libcap bash \
@@ -45,4 +55,5 @@ USER 101:1001
4555

4656
LABEL org.nginx.ngf.image.build.agent="${BUILD_AGENT}"
4757

58+
ENV USE_NAP_WAF=${INCLUDE_NAP_WAF}
4859
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
@@ -259,7 +259,7 @@ The following table lists the configurable parameters of the NGINX Gateway Fabri
259259
| `certGenerator.serverTLSSecretName` | The name of the Secret containing TLS CA, certificate, and key for the NGINX Gateway Fabric control plane to securely communicate with the NGINX Agent. Must exist in the same namespace that the NGINX Gateway Fabric control plane is running in (default namespace: nginx-gateway). | string | `"server-tls"` |
260260
| `clusterDomain` | The DNS cluster domain of your Kubernetes cluster. | string | `"cluster.local"` |
261261
| `gateways` | A list of Gateway objects. View https://gateway-api.sigs.k8s.io/reference/spec/#gateway for full Gateway reference. | list | `[]` |
262-
| `nginx` | The nginx section contains the configuration for all NGINX data plane deployments installed by the NGINX Gateway Fabric control plane. | object | `{"config":{},"container":{},"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}}` |
262+
| `nginx` | The nginx section contains the configuration for all NGINX data plane deployments installed by the NGINX Gateway Fabric control plane. | object | `{"config":{},"container":{},"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":{}}` |
263263
| `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 | `{}` |
264264
| `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 | `{}` |
265265
| `nginx.debug` | Enable debugging for NGINX. Uses the nginx-debug binary. The NGINX error log level should be set to debug in the NginxProxy resource. | bool | `false` |
@@ -283,6 +283,7 @@ The following table lists the configurable parameters of the NGINX Gateway Fabri
283283
| `nginx.usage.resolver` | The nameserver used to resolve the NGINX Plus usage reporting endpoint. Used with NGINX Instance Manager. | string | `""` |
284284
| `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"` |
285285
| `nginx.usage.skipVerify` | Disable client verification of the NGINX Plus usage reporting server certificate. | bool | `false` |
286+
| `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 | `{}` |
286287
| `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},"nodeSelector":{},"podAnnotations":{},"productTelemetry":{"enable":true},"readinessProbe":{"enable":true,"initialDelaySeconds":3,"port":8081},"replicas":1,"resources":{},"service":{"annotations":{}},"serviceAccount":{"annotations":{},"imagePullSecret":"","imagePullSecrets":[],"name":""},"snippetsFilters":{"enable":false},"terminationGracePeriodSeconds":30,"tolerations":[],"topologySpreadConstraints":[]}` |
287288
| `nginxGateway.affinity` | The affinity of the NGINX Gateway Fabric control plane pod. | object | `{}` |
288289
| `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
@@ -26,6 +26,10 @@ spec:
2626
{{- if .Values.nginx.debug }}
2727
debug: {{ .Values.nginx.debug }}
2828
{{- end }}
29+
{{- if and .Values.nginx.wafContainers }}
30+
wafContainers:
31+
{{- toYaml .Values.nginx.wafContainers | nindent 8 }}
32+
{{- end }}
2933
{{- end }}
3034
{{- if eq .Values.nginx.kind "daemonSet" }}
3135
daemonSet:
@@ -42,6 +46,10 @@ spec:
4246
{{- if .Values.nginx.debug }}
4347
debug: {{ .Values.nginx.debug }}
4448
{{- end }}
49+
{{- if and .Values.nginx.wafContainers }}
50+
wafContainers:
51+
{{- toYaml .Values.nginx.wafContainers | nindent 8 }}
52+
{{- end }}
4553
{{- end }}
4654
{{- if .Values.nginx.service }}
4755
service:

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

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -268,6 +268,15 @@
268268
},
269269
"required": [],
270270
"type": "object"
271+
},
272+
"waf": {
273+
"description": "WAF enables NGINX App Protect WAF functionality.",
274+
"enum": [
275+
"enabled",
276+
"disabled"
277+
],
278+
"required": [],
279+
"type": "string"
271280
}
272281
},
273282
"required": [],
@@ -488,6 +497,12 @@
488497
"required": [],
489498
"title": "usage",
490499
"type": "object"
500+
},
501+
"wafContainers": {
502+
"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.",
503+
"required": [],
504+
"title": "wafContainers",
505+
"type": "object"
491506
}
492507
},
493508
"required": [],

charts/nginx-gateway-fabric/values.yaml

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -367,6 +367,12 @@ nginx:
367367
# - IPAddress
368368
# value:
369369
# type: string
370+
# waf:
371+
# type: string
372+
# description: WAF enables NGINX App Protect WAF functionality.
373+
# enum:
374+
# - enabled
375+
# - disabled
370376
# @schema
371377
# -- The configuration for the data plane that is contained in the NginxProxy resource. This is applied globally to all Gateways
372378
# managed by this instance of NGINX Gateway Fabric.
@@ -406,6 +412,33 @@ nginx:
406412
# -- extraVolumeMounts are the additional volume mounts for the NGINX container.
407413
# extraVolumeMounts: []
408414

415+
# -- Configuration for NGINX App Protect WAF v5 containers.
416+
# These containers are only deployed when WAF is enabled via nginx.config.waf: "Enabled".
417+
# All settings are optional overrides - defaults are provided by NGF.
418+
wafContainers: {}
419+
# -- WAF Enforcer container configuration.
420+
# enforcer:
421+
# image: {}
422+
423+
# -- The resource requirements of the WAF Enforcer container.
424+
# resources: {}
425+
#
426+
# # -- Additional volume mounts for the WAF enforcer container.
427+
# # NAP v5 shared volumes are automatically configured by NGF.
428+
# volumeMounts: []
429+
430+
# -- WAF Configuration Manager container configuration.
431+
# configManager:
432+
# # -- Container image configuration
433+
# image: {}
434+
#
435+
# # -- The resource requirements of the WAF config manager container.
436+
# resources: {}
437+
438+
# # -- Additional volume mounts for the WAF config manager container.
439+
# # NAP v5 shared volumes are automatically configured by NGF.
440+
# volumeMounts: []
441+
409442
# -- The service configuration for the NGINX data plane. This is applied globally to all Gateways managed by this
410443
# instance of NGINX Gateway Fabric.
411444
service:

0 commit comments

Comments
 (0)