diff --git a/Makefile b/Makefile index ed9a91eecd..c6febb797c 100644 --- a/Makefile +++ b/Makefile @@ -19,6 +19,7 @@ TARGET ?= local ## The target of the build. Possible valu override DOCKER_BUILD_OPTIONS += --build-arg IC_VERSION=$(VERSION) ## The options for the docker build command. For example, --pull ARCH ?= amd64 ## The architecture of the image or binary. For example: amd64, arm64, ppc64le, s390x. Not all architectures are supported for all targets GOOS ?= linux ## The OS of the binary. For example linux, darwin +NGINX_AGENT ?= true # final docker build command DOCKER_CMD = docker build --platform linux/$(strip $(ARCH)) $(strip $(DOCKER_BUILD_OPTIONS)) --target $(strip $(TARGET)) -f build/Dockerfile -t $(strip $(PREFIX)):$(strip $(TAG)) . @@ -136,7 +137,7 @@ alpine-image-plus-fips: build ## Create Docker image for Ingress Controller (Alp .PHONY: alpine-image-nap-plus-fips alpine-image-nap-plus-fips: build ## Create Docker image for Ingress Controller (Alpine with NGINX Plus, NGINX App Protect WAF and FIPS) - $(DOCKER_CMD) $(PLUS_ARGS) --build-arg BUILD_OS=alpine-plus-nap-fips + $(DOCKER_CMD) $(PLUS_ARGS) --build-arg BUILD_OS=alpine-plus-nap-fips --build-arg NGINX_AGENT=$(NGINX_AGENT) .PHONY: debian-image-plus debian-image-plus: build ## Create Docker image for Ingress Controller (Debian with NGINX Plus) @@ -144,7 +145,7 @@ debian-image-plus: build ## Create Docker image for Ingress Controller (Debian w .PHONY: debian-image-nap-plus debian-image-nap-plus: build ## Create Docker image for Ingress Controller (Debian with NGINX Plus and NGINX App Protect WAF) - $(DOCKER_CMD) $(PLUS_ARGS) --build-arg BUILD_OS=debian-plus-nap --build-arg NAP_MODULES=waf + $(DOCKER_CMD) $(PLUS_ARGS) --build-arg BUILD_OS=debian-plus-nap --build-arg NAP_MODULES=waf --build-arg NGINX_AGENT=$(NGINX_AGENT) .PHONY: debian-image-dos-plus debian-image-dos-plus: build ## Create Docker image for Ingress Controller (Debian with NGINX Plus and NGINX App Protect DoS) @@ -152,7 +153,7 @@ debian-image-dos-plus: build ## Create Docker image for Ingress Controller (Debi .PHONY: debian-image-nap-dos-plus debian-image-nap-dos-plus: build ## Create Docker image for Ingress Controller (Debian with NGINX Plus, NGINX App Protect WAF and DoS) - $(DOCKER_CMD) $(PLUS_ARGS) --build-arg BUILD_OS=debian-plus-nap --build-arg NAP_MODULES=waf,dos + $(DOCKER_CMD) $(PLUS_ARGS) --build-arg BUILD_OS=debian-plus-nap --build-arg NAP_MODULES=waf,dos --build-arg NGINX_AGENT=$(NGINX_AGENT) .PHONY: ubi-image ubi-image: build ## Create Docker image for Ingress Controller (UBI) @@ -164,7 +165,7 @@ ubi-image-plus: build ## Create Docker image for Ingress Controller (UBI with NG .PHONY: ubi-image-nap-plus ubi-image-nap-plus: build ## Create Docker image for Ingress Controller (UBI with NGINX Plus and NGINX App Protect WAF) - $(DOCKER_CMD) $(PLUS_ARGS) --secret id=rhel_license,src=rhel_license --build-arg BUILD_OS=ubi-9-plus-nap --build-arg NAP_MODULES=waf + $(DOCKER_CMD) $(PLUS_ARGS) --secret id=rhel_license,src=rhel_license --build-arg BUILD_OS=ubi-9-plus-nap --build-arg NAP_MODULES=waf --build-arg NGINX_AGENT=$(NGINX_AGENT) .PHONY: ubi-image-dos-plus ubi-image-dos-plus: build ## Create Docker image for Ingress Controller (UBI with NGINX Plus and NGINX App Protect DoS) @@ -172,7 +173,7 @@ ubi-image-dos-plus: build ## Create Docker image for Ingress Controller (UBI wit .PHONY: ubi-image-nap-dos-plus ubi-image-nap-dos-plus: build ## Create Docker image for Ingress Controller (UBI with NGINX Plus, NGINX App Protect WAF and DoS) - $(DOCKER_CMD) $(PLUS_ARGS) --secret id=rhel_license,src=rhel_license --build-arg BUILD_OS=ubi-8-plus-nap --build-arg NAP_MODULES=waf,dos + $(DOCKER_CMD) $(PLUS_ARGS) --secret id=rhel_license,src=rhel_license --build-arg BUILD_OS=ubi-8-plus-nap --build-arg NAP_MODULES=waf,dos --build-arg NGINX_AGENT=$(NGINX_AGENT) .PHONY: all-images ## Create all the Docker images for Ingress Controller all-images: alpine-image alpine-image-plus alpine-image-plus-fips alpine-image-nap-plus-fips debian-image debian-image-plus debian-image-nap-plus debian-image-dos-plus debian-image-nap-dos-plus ubi-image ubi-image-plus ubi-image-nap-plus ubi-image-dos-plus ubi-image-nap-dos-plus diff --git a/build/Dockerfile b/build/Dockerfile index 73803012fe..d068d39fc7 100644 --- a/build/Dockerfile +++ b/build/Dockerfile @@ -4,6 +4,7 @@ ARG NGINX_PLUS_VERSION=R31 ARG DOWNLOAD_TAG=edge ARG DEBIAN_FRONTEND=noninteractive ARG PREBUILT_BASE_IMG=nginx/nginx-ingress:${DOWNLOAD_TAG} +ARG NGINX_AGENT=false ############################################# Base images containing libs for Opentracing and FIPS ############################################# @@ -131,7 +132,8 @@ RUN --mount=type=bind,from=alpine-fips-3.17,target=/tmp/fips/ \ && printf "%s\n" "https://pkgs.nginx.com/app-protect-security-updates/alpine/v$(grep -E -o '^[0-9]+\.[0-9]+' /etc/alpine-release)/main" >> /etc/apk/repositories \ && printf "%s\n" "https://pkgs.nginx.com/nginx-agent/alpine/v$(grep -E -o '^[0-9]+\.[0-9]+' /etc/alpine-release)/main" >> /etc/apk/repositories \ && apk upgrade --no-cache -U \ - && apk add --no-cache libcap-utils libcurl nginx-plus nginx-plus-module-njs nginx-plus-module-opentracing nginx-plus-module-fips-check nginx-agent \ + && apk add --no-cache libcap-utils libcurl nginx-plus nginx-plus-module-njs nginx-plus-module-opentracing nginx-plus-module-fips-check \ + && if [ -z "${NGINX_AGENT##true}" ]; then apk add --no-cache nginx-agent; fi \ && mkdir -p /usr/ssl \ && cp -av /tmp/fips/usr/lib/ossl-modules/fips.so /usr/lib/ossl-modules/fips.so \ && cp -av /tmp/fips/usr/ssl/fipsmodule.cnf /usr/ssl/fipsmodule.cnf \ @@ -194,8 +196,9 @@ RUN --mount=type=secret,id=nginx-repo.crt,dst=/etc/ssl/nginx/nginx-repo.crt,mode && sq dearmor -o /usr/share/keyrings/nginx-archive-keyring.gpg /tmp/nginx_signing.key \ && sq dearmor -o /usr/share/keyrings/app-protect-archive-keyring.gpg /tmp/app-protect-security-updates.key \ && apt-get update \ - && apt-get install --no-install-recommends --no-install-suggests -y nginx-plus nginx-plus-module-njs nginx-plus-module-opentracing nginx-plus-module-fips-check libcap2-bin libcurl4 nginx-agent \ + && apt-get install --no-install-recommends --no-install-suggests -y nginx-plus nginx-plus-module-njs nginx-plus-module-opentracing nginx-plus-module-fips-check libcap2-bin libcurl4 \ ## end of duplicated code + && if [ -z "${NGINX_AGENT##true}" ]; then apt-get install --no-install-recommends --no-install-suggests -y nginx-agent; fi \ && if [ -z "${NAP_MODULES##*waf*}" ]; then \ apt-get install --no-install-recommends --no-install-suggests -y app-protect app-protect-attack-signatures app-protect-threat-campaigns; \ fi \ @@ -260,7 +263,8 @@ RUN --mount=type=secret,id=nginx-repo.crt,dst=/etc/ssl/nginx/nginx-repo.crt,mode && groupadd --system --gid 101 nginx \ && useradd --system --gid nginx --no-create-home --home-dir /nonexistent --comment "nginx user" --shell /bin/false --uid 101 nginx \ && rpm --import /tmp/nginx_signing.key \ - && dnf --nodocs install -y nginx-plus nginx-plus-module-njs nginx-plus-module-fips-check nginx-agent \ + && dnf --nodocs install -y nginx-plus nginx-plus-module-njs nginx-plus-module-fips-check \ + && if [ -z "${NGINX_AGENT##true}" ]; then dnf --nodocs install -y nginx-agent; fi \ ## end of duplicated code ## fix for CVEs && dnf upgrade -y curl dbus libcap libssh platform-python python3-requests libxml2 systemd sqlite-libs dnf-plugin-subscription-manager dmidecode subscription-manager-rhsm-certificates glibc subscription-manager \ @@ -307,7 +311,8 @@ RUN --mount=type=secret,id=nginx-repo.crt,dst=/etc/ssl/nginx/nginx-repo.crt,mode && groupadd --system --gid 101 nginx \ && useradd --system --gid nginx --no-create-home --home-dir /nonexistent --comment "nginx user" --shell /bin/false --uid 101 nginx \ && rpm --import /tmp/nginx_signing.key \ - && dnf --nodocs install -y nginx-plus nginx-plus-module-njs nginx-plus-module-fips-check nginx-agent \ + && dnf --nodocs install -y nginx-plus nginx-plus-module-njs nginx-plus-module-fips-check \ + && if [ -z "${NGINX_AGENT##true}" ]; then dnf --nodocs install -y nginx-agent; fi \ ## end of duplicated code ## fix for CVEs && dnf upgrade -y curl dbus libcap libssh platform-python python3-requests libxml2 systemd sqlite-libs dnf-plugin-subscription-manager dmidecode subscription-manager-rhsm-certificates glibc subscription-manager \ @@ -355,7 +360,11 @@ RUN --mount=type=bind,target=/tmp [ -n "${NAP_MODULES##*waf*}" ] && exit 0; mkdi && chown -R 101:0 /etc/app_protect /usr/share/ts /var/log/app_protect/ /opt/app_protect/ \ && chmod -R g=u /etc/app_protect /usr/share/ts /var/log/app_protect/ /opt/app_protect/ \ && touch /etc/nginx/waf/nac-usersigs/index.conf \ - && cp -a /tmp/build/log-default.json /etc/nginx + && cp -a /tmp/build/log-default.json /etc/nginx \ + && if [ -z "${NGINX_AGENT##true}" ]; then mkdir -p /etc/ssl/nms /opt/nms-nap-compiler \ + && chown -R 101:0 /etc/ssl/nms /opt/nms-nap-compiler \ + && chmod -R g=u /etc/ssl/nms /opt/nms-nap-compiler \ + && NAP_VERSION=$(cat /opt/app_protect/VERSION) && ln -s /opt/app_protect "/opt/nms-nap-compiler/app_protect-${NAP_VERSION}"; fi # run only on nap dos build RUN [ -n "${NAP_MODULES##*dos*}" ] && exit 0; mkdir -p /root/app_protect_dos /etc/nginx/dos/policies /etc/nginx/dos/logconfs /shared/cores /var/log/adm /var/run/adm \ diff --git a/charts/nginx-ingress/README.md b/charts/nginx-ingress/README.md index 403c5efd1c..c47d802ec4 100644 --- a/charts/nginx-ingress/README.md +++ b/charts/nginx-ingress/README.md @@ -489,6 +489,21 @@ The following tables lists the configurable parameters of the NGINX Ingress Cont |`serviceNameOverride` | Used to prevent cloud load balancers from being replaced due to service name change during helm upgrades. | "" | |`nginxServiceMesh.enable` | Enable integration with NGINX Service Mesh. See the NGINX Service Mesh [docs](https://docs.nginx.com/nginx-service-mesh/tutorials/kic/deploy-with-kic/) for more details. Requires `controller.nginxplus`. | false | |`nginxServiceMesh.enableEgress` | Enable NGINX Service Mesh workloads to route egress traffic through the Ingress Controller. See the NGINX Service Mesh [docs](https://docs.nginx.com/nginx-service-mesh/tutorials/kic/deploy-with-kic/#enabling-egress) for more details. Requires `nginxServiceMesh.enable`. | false | +|`nginxAgent.enable` | Enable NGINX Agent to integrate Security Monitoring and App Protect WAF modules. Requires `controller.appprotect.enable`. | false | +|`nginxAgent.instanceGroup` | Set a custom Instance Group name, shown when connected to NGINX Instance Manager. `nginx-ingress.controller.fullname` will be used if not set. | "" | +|`nginxAgent.logLevel` | Log level for NGINX Agent. | "error | +|`nginxAgent.instanceManager.host` | FQDN or IP for connecting to NGINX Ingress Controller. Required when `nginxAgent.enable` is set to `true` | "" | +|`nginxAgent.instanceManager.grpcPort` | Port for connecting to NGINX Ingress Controller. | 443 | +|`nginxAgent.instanceManager.sni` | Server Name Indication for NGINX Instance Manager. See the NGINX Agent [docs](https://docs.nginx.com/nginx-agent/configuration/encrypt-communication/) for more details. | "" | +|`nginxAgent.instanceManager.tls.enable` | Enable TLS for NGINX Instance Manager connection. | true | +|`nginxAgent.instanceManager.tls.skipVerify` | Skip certification verification for NGINX Instance Manager connection. | false | +|`nginxAgent.instanceManager.tls.caSecret` | Name of `nginx.org/ca` secret used for verification of NGINX Instance Manager TLS. | "" | +|`nginxAgent.instanceManager.tls.secret` | Name of `kubernetes.io/tls` secret with a TLS certificate and key for using mTLS between NGINX Agent and NGINX Instance Manager. See the NGINX Instance Manager [docs](https://docs.nginx.com/nginx-management-suite/admin-guides/configuration/secure-traffic/#mutual-client-certificate-auth-setup-mtls) and the NGINX Agent [docs](https://docs.nginx.com/nginx-agent/configuration/encrypt-communication/) for more details. | "" | +|`nginxAgent.syslog.host` | Address for NGINX Agent to run syslog listener. | 127.0.0.1 | +|`nginxAgent.syslog.port` | Port for NGINX Agent to run syslog listener. | 1514 | +|`nginxAgent.napMonitoring.collectorBufferSize` | Buffer size for collector. Will contain log lines and parsed log lines. | 50000 | +|`nginxAgent.napMonitoring.processorBufferSize` | Buffer size for processor. Will contain log lines and parsed log lines. | 50000 | +|`nginxAgent.customConfigMap` | The name of a custom ConfigMap to use instead of the one provided by default. | "" | ## Notes diff --git a/charts/nginx-ingress/templates/_helpers.tpl b/charts/nginx-ingress/templates/_helpers.tpl index 4b65c23082..0b55efd69a 100644 --- a/charts/nginx-ingress/templates/_helpers.tpl +++ b/charts/nginx-ingress/templates/_helpers.tpl @@ -70,6 +70,9 @@ nsm.nginx.com/enable-ingress: "true" nsm.nginx.com/enable-egress: "{{ .Values.nginxServiceMesh.enableEgress }}" nsm.nginx.com/{{ .Values.controller.kind }}: {{ include "nginx-ingress.controller.fullname" . }} {{- end }} +{{- if and .Values.nginxAgent.enable (eq (.Values.nginxAgent.customConfigMap | default "") "") }} +agent-configuration-revision-hash: {{ include "nginx-ingress.agentConfiguration" . | sha1sum | trunc 8 | quote }} +{{- end }} {{- if .Values.controller.pod.extraLabels }} {{ toYaml .Values.controller.pod.extraLabels }} {{- end }} @@ -98,6 +101,17 @@ Expand the name of the configmap. {{- end -}} {{- end -}} +{{/* +Expand the name of the configmap used for NGINX Agent. +*/}} +{{- define "nginx-ingress.agentConfigName" -}} +{{- if ne (.Values.nginxAgent.customConfigMap | default "") "" -}} +{{ .Values.nginxAgent.customConfigMap }} +{{- else -}} +{{- printf "%s-agent-config" (include "nginx-ingress.fullname" . | trunc 49 | trimSuffix "-") -}} +{{- end -}} +{{- end -}} + {{/* Expand leader election lock name. */}} @@ -264,15 +278,29 @@ Build the args for the service binary. - -enable-latency-metrics={{ .Values.controller.enableLatencyMetrics }} - -ssl-dynamic-reload={{ .Values.controller.enableSSLDynamicReload }} - -enable-telemetry-reporting={{ .Values.controller.enableTelemetryReporting}} +{{- if .Values.nginxAgent.enable }} +- -agent=true +- -agent-instance-group={{ default (include "nginx-ingress.controller.fullname" .) .Values.nginxAgent.instanceGroup }} +{{- end }} {{- end -}} {{/* Volumes for controller. */}} {{- define "nginx-ingress.volumes" -}} -{{- if or (eq (include "nginx-ingress.readOnlyRootFilesystem" .) "true" ) .Values.controller.volumes }} +{{- $volumesSet := "false" }} volumes: -{{- end }} +{{- if eq (include "nginx-ingress.volumeEntries" .) "" -}} +{{ toYaml list | printf " %s" }} +{{- else }} +{{ include "nginx-ingress.volumeEntries" . }} +{{- end -}} +{{- end -}} + +{{/* +List of volumes for controller. +*/}} +{{- define "nginx-ingress.volumeEntries" -}} {{- if eq (include "nginx-ingress.readOnlyRootFilesystem" .) "true" }} - name: nginx-etc emptyDir: {} @@ -286,15 +314,42 @@ volumes: {{- if .Values.controller.volumes }} {{ toYaml .Values.controller.volumes }} {{- end }} +{{- if .Values.nginxAgent.enable }} +- name: agent-conf + configMap: + name: {{ include "nginx-ingress.agentConfigName" . }} +- name: agent-dynamic + emptyDir: {} +{{- if and .Values.nginxAgent.instanceManager.tls (or (ne (.Values.nginxAgent.instanceManager.tls.secret | default "") "") (ne (.Values.nginxAgent.instanceManager.tls.caSecret | default "") "")) }} +- name: nginx-agent-tls + projected: + sources: +{{- if ne .Values.nginxAgent.instanceManager.tls.secret "" }} + - secret: + name: {{ .Values.nginxAgent.instanceManager.tls.secret }} +{{- end }} +{{- if ne .Values.nginxAgent.instanceManager.tls.caSecret "" }} + - secret: + name: {{ .Values.nginxAgent.instanceManager.tls.caSecret }} +{{- end }} +{{- end }} +{{- end -}} {{- end -}} {{/* Volume mounts for controller. */}} {{- define "nginx-ingress.volumeMounts" -}} -{{- if or ( eq (include "nginx-ingress.readOnlyRootFilesystem" .) "true" ) .Values.controller.volumeMounts }} +{{- $volumesSet := "false" }} volumeMounts: -{{- end }} +{{- if eq (include "nginx-ingress.volumeMountEntries" .) "" -}} +{{ toYaml list | printf " %s" }} +{{- else }} +{{ include "nginx-ingress.volumeMountEntries" . }} +{{- end -}} +{{- end -}} + +{{- define "nginx-ingress.volumeMountEntries" -}} {{- if eq (include "nginx-ingress.readOnlyRootFilesystem" .) "true" }} - mountPath: /etc/nginx name: nginx-etc @@ -305,7 +360,61 @@ volumeMounts: - mountPath: /var/log/nginx name: nginx-log {{- end }} -{{- if .Values.controller.volumeMounts}} +{{- if .Values.controller.volumeMounts }} {{ toYaml .Values.controller.volumeMounts }} {{- end }} +{{- if .Values.nginxAgent.enable }} +- name: agent-conf + mountPath: /etc/nginx-agent/nginx-agent.conf + subPath: nginx-agent.conf +- name: agent-dynamic + mountPath: /var/lib/nginx-agent +{{- if and .Values.nginxAgent.instanceManager.tls (or (ne (.Values.nginxAgent.instanceManager.tls.secret | default "") "") (ne (.Values.nginxAgent.instanceManager.tls.caSecret | default "") "")) }} +- name: nginx-agent-tls + mountPath: /etc/ssl/nms + readOnly: true +{{- end }} {{- end -}} +{{- end -}} + +{{- define "nginx-ingress.agentConfiguration" -}} +log: + level: {{ .Values.nginxAgent.logLevel }} + path: "" +server: + host: {{ required ".Values.nginxAgent.instanceManager.host is required when setting .Values.nginxAgent.enable to true" .Values.nginxAgent.instanceManager.host }} + grpcPort: {{ .Values.nginxAgent.instanceManager.grpcPort }} +{{- if ne (.Values.nginxAgent.instanceManager.sni | default "") "" }} + metrics: {{ .Values.nginxAgent.instanceManager.sni }} + command: {{ .Values.nginxAgent.instanceManager.sni }} +{{- end }} +{{- if .Values.nginxAgent.instanceManager.tls }} +tls: + enable: {{ .Values.nginxAgent.instanceManager.tls.enable | default true }} + skip_verify: {{ .Values.nginxAgent.instanceManager.tls.skipVerify | default false }} + {{- if ne .Values.nginxAgent.instanceManager.tls.caSecret "" }} + ca: "/etc/ssl/nms/ca.crt" + {{- end }} + {{- if ne .Values.nginxAgent.instanceManager.tls.secret "" }} + cert: "/etc/ssl/nms/tls.crt" + key: "/etc/ssl/nms/tls.key" + {{- end }} +{{- end }} +features: + - registration + - nginx-counting + - metrics-sender + - dataplane-status +extensions: + - nginx-app-protect + - nap-monitoring +nginx_app_protect: + report_interval: 15s + precompiled_publication: true +nap_monitoring: + collector_buffer_size: {{ .Values.nginxAgent.napMonitoring.collectorBufferSize }} + processor_buffer_size: {{ .Values.nginxAgent.napMonitoring.processorBufferSize }} + syslog_ip: {{ .Values.nginxAgent.syslog.host }} + syslog_port: {{ .Values.nginxAgent.syslog.port }} + +{{ end -}} diff --git a/charts/nginx-ingress/templates/controller-configmap.yaml b/charts/nginx-ingress/templates/controller-configmap.yaml index fd11991865..8f1d3e47bb 100644 --- a/charts/nginx-ingress/templates/controller-configmap.yaml +++ b/charts/nginx-ingress/templates/controller-configmap.yaml @@ -11,7 +11,22 @@ metadata: {{ toYaml .Values.controller.config.annotations | indent 4 }} {{- end }} data: -{{- if .Values.controller.config.entries }} -{{ toYaml .Values.controller.config.entries | indent 2 }} +{{ toYaml (default dict .Values.controller.config.entries) | indent 2 }} {{- end }} +--- +{{- if and .Values.nginxAgent.enable (eq (.Values.nginxAgent.customConfigMap | default "") "") }} +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ include "nginx-ingress.agentConfigName" . }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "nginx-ingress.labels" . | nindent 4 }} +{{- if .Values.controller.config.annotations }} + annotations: +{{ toYaml .Values.controller.config.annotations | indent 4 }} +{{- end }} +data: + nginx-agent.conf: |- +{{ include "nginx-ingress.agentConfiguration" . | indent 4 }} {{- end }} diff --git a/charts/nginx-ingress/values.schema.json b/charts/nginx-ingress/values.schema.json index 0b494baf60..53c9b7198d 100644 --- a/charts/nginx-ingress/values.schema.json +++ b/charts/nginx-ingress/values.schema.json @@ -1803,6 +1803,168 @@ "enableEgress": false } ] + }, + "nginxAgent": { + "type": "object", + "default": { + "enable": false + }, + "title": "Configuration for NGINX Agent.", + "required": [ + "enable" + ], + "properties": { + "enable": { + "type": "boolean", + "default": false, + "title": "Enable NGINX Agent", + "examples": [ + false + ] + }, + "instanceGroup": { + "type": "string", + "default": "", + "title": "Set the --instance-group argument for NGINX Agent", + "examples": [ + "my-instance-group" + ] + }, + "logLevel": { + "type": "string", + "default": "info", + "title": "Log level for NGINX Agent", + "enum": [ + "panic", + "fatal", + "error", + "info", + "debug", + "trace" + ], + "examples": [ + "error" + ] + }, + "instanceManager": { + "type": "object", + "default": {}, + "title": "Configuration for the connection to NGINX Instance Manager", + "examples": [], + "required": [ + "host" + ], + "properties": { + "host": { + "type": "string", + "title": "FQDN or IP for connecting to NGINX Instance Manager", + "examples": [ + "nim.example.com" + ] + }, + "grpcPort": { + "type": "integer", + "title": "Port for connecting to NGINX Instance Manager", + "default": 443, + "examples": [ + 443 + ] + }, + "sni": { + "type": "string", + "title": "Server Name Indication for NGINX Instance Manager", + "default": "", + "examples": [ + "nim.example.com" + ] + }, + "tls": { + "type": "object", + "default": {}, + "title": "TLS configuration for connection between NGINX Agent and NGINX Instance Manager", + "properties": { + "enable": { + "type": "boolean", + "default": "true", + "title": "enable TLS for NGINX Instance Manager connection" + }, + "secret": { + "type": "string", + "default": "", + "title": "kubernetes.io/tls secret with a TLS certificate and key for using mTLS between NGINX Agent and NGINX Instance Manager" + }, + "caSecret": { + "type": "string", + "default": "", + "title": "nginx.org/ca secret for verification of Instance Manager TLS" + }, + "skipVerify": { + "type": "boolean", + "default": "false", + "title": "skip certificate verification" + } + } + } + } + }, + "syslog": { + "type": "object", + "default": { + "host": "127.0.0.1", + "port": 1514 + }, + "title": "Syslog listener which NGINX Agent uses to accept messages from App Protect WAF", + "properties": { + "host": { + "type": "string", + "title": "Address for NGINX Agent to run syslog listener", + "default": "127.0.0.1", + "examples": [ + "127.0.0.1" + ] + }, + "port": { + "type": "integer", + "title": "Port for NGINX Agent to run syslog listener", + "default": 1514, + "examples": [ + 1514 + ] + } + } + }, + "napMonitoring": { + "type": "object", + "default": {}, + "title": "NGINX App Protect Monitoring config", + "properties": { + "collectorBufferSize": { + "type": "integer", + "default": 50000, + "title": "Buffer size for collector. Will contain log lines and parsed log lines", + "examples": [ + 50000 + ] + }, + "processorBufferSize": { + "type": "integer", + "default": 50000, + "title": "Buffer size for processor. Will contain log lines and parsed log lines", + "examples": [ + 50000 + ] + } + } + }, + "customConfigMap": { + "type": "string", + "title": "The name of a custom ConfigMap to use instead of the one provided by default", + "default": "", + "examples": [ + "my-custom-configmap" + ] + } + } } }, "examples": [ diff --git a/charts/nginx-ingress/values.yaml b/charts/nginx-ingress/values.yaml index 075a4521a5..dea57e4c50 100644 --- a/charts/nginx-ingress/values.yaml +++ b/charts/nginx-ingress/values.yaml @@ -551,3 +551,31 @@ nginxServiceMesh: ## Enables NGINX Service Mesh workload to route egress traffic through the Ingress Controller. ## Requires nginxServiceMesh.enable enableEgress: false + +nginxAgent: + ## Enables NGINX Agent. + enable: false + ## If nginxAgent.instanceGroup is not set the value of nginx-ingress.controller.fullname will be used + instanceGroup: "" + logLevel: "error" + ## Syslog listener which NGINX Agent uses to accept messages from App Protect WAF + syslog: + host: "127.0.0.1" + port: 1514 + napMonitoring: + collectorBufferSize: 50000 + processorBufferSize: 50000 + instanceManager: + # FQDN or IP for connecting to NGINX Instance Manager, e.g. nim.example.com + host: "" + grpcPort: 443 + sni: "" + tls: + enabled: true + skipVerify: false + ## kubernetes.io/tls secret with a TLS certificate and key for using mTLS between NGINX Agent and Instance Manager + secret: "" + ## nginx.org/ca secret for verification of Instance Manager TLS + caSecret: "" + ## The name of a custom ConfigMap to use instead of the one provided by default + customConfigMap: "" diff --git a/cmd/nginx-ingress/flags.go b/cmd/nginx-ingress/flags.go index 2d162b5e3c..257409ea75 100644 --- a/cmd/nginx-ingress/flags.go +++ b/cmd/nginx-ingress/flags.go @@ -66,6 +66,9 @@ var ( appProtectDosMaxWorkers = flag.Int("app-protect-dos-max-workers", 0, "Max number of nginx processes to support. Requires -nginx-plus and -enable-app-protect-dos.") appProtectDosMemory = flag.Int("app-protect-dos-memory", 0, "RAM memory size to consume in MB. Requires -nginx-plus and -enable-app-protect-dos.") + agent = flag.Bool("agent", false, "Enable NGINX Agent") + agentInstanceGroup = flag.String("agent-instance-group", "nginx-ingress-controller", "Grouping used to associate NGINX Ingress Controller instances") + ingressClass = flag.String("ingress-class", "nginx", `A class of the Ingress Controller. @@ -280,6 +283,10 @@ func parseFlags() { if *ingressLink != "" && *externalService != "" { glog.Fatal("ingresslink and external-service cannot both be set") } + + if *agent && !*appProtect { + glog.Fatal("NGINX Agent is used to enable the Security Monitoring dashboard and requires NGINX App Protect to be enabled") + } } func initialChecks() { diff --git a/cmd/nginx-ingress/main.go b/cmd/nginx-ingress/main.go index 67c389f3f9..4ccc8e0091 100644 --- a/cmd/nginx-ingress/main.go +++ b/cmd/nginx-ingress/main.go @@ -49,6 +49,7 @@ const ( nginxVersionLabel = "app.nginx.org/version" versionLabel = "app.kubernetes.io/version" appProtectVersionLabel = "appprotect.f5.com/version" + agentVersionLabel = "app.nginx.org/agent-version" appProtectVersionPath = "/opt/app_protect/VERSION" ) @@ -81,7 +82,12 @@ func main() { appProtectVersion = getAppProtectVersionInfo() } - go updateSelfWithVersionInfo(kubeClient, version, appProtectVersion, nginxVersion, 10, time.Second*5) + var agentVersion string + if *agent { + agentVersion = getAgentVersionInfo(nginxManager) + } + + go updateSelfWithVersionInfo(kubeClient, version, appProtectVersion, agentVersion, nginxVersion, 10, time.Second*5) templateExecutor, templateExecutorV2 := createTemplateExecutors() @@ -427,12 +433,18 @@ func getAppProtectVersionInfo() string { return version } +func getAgentVersionInfo(nginxManager nginx.Manager) string { + return nginxManager.AgentVersion() +} + type childProcessConfig struct { nginxDone chan error aPPluginEnable bool aPPluginDone chan error aPDosEnable bool aPDosDone chan error + agentEnable bool + agentDone chan error } func startChildProcesses(nginxManager nginx.Manager) childProcessConfig { @@ -453,12 +465,20 @@ func startChildProcesses(nginxManager nginx.Manager) childProcessConfig { nginxDone := make(chan error, 1) nginxManager.Start(nginxDone) + var agentDone chan error + if *agent { + agentDone = make(chan error, 1) + nginxManager.AgentStart(agentDone, *agentInstanceGroup) + } + return childProcessConfig{ nginxDone: nginxDone, aPPluginEnable: *appProtect, aPPluginDone: aPPluginDone, aPDosEnable: *appProtectDos, aPDosDone: aPPDosAgentDone, + agentEnable: *agent, + agentDone: agentDone, } } @@ -778,7 +798,7 @@ func processConfigMaps(kubeClient *kubernetes.Clientset, cfgParams *configs.Conf return cfgParams } -func updateSelfWithVersionInfo(kubeClient *kubernetes.Clientset, version, appProtectVersion string, nginxVersion nginx.Version, maxRetries int, waitTime time.Duration) { +func updateSelfWithVersionInfo(kubeClient *kubernetes.Clientset, version, appProtectVersion, agentVersion string, nginxVersion nginx.Version, maxRetries int, waitTime time.Duration) { podUpdated := false for i := 0; (i < maxRetries || maxRetries == 0) && !podUpdated; i++ { @@ -803,6 +823,9 @@ func updateSelfWithVersionInfo(kubeClient *kubernetes.Clientset, version, appPro if appProtectVersion != "" { labels[appProtectVersionLabel] = appProtectVersion } + if agentVersion != "" { + labels[agentVersionLabel] = agentVersion + } newPod.ObjectMeta.Labels = labels _, err = kubeClient.CoreV1().Pods(newPod.ObjectMeta.Namespace).Update(context.TODO(), newPod, meta_v1.UpdateOptions{}) diff --git a/docs/content/configuration/global-configuration/command-line-arguments.md b/docs/content/configuration/global-configuration/command-line-arguments.md index 93e14eea02..de3f47cb4f 100644 --- a/docs/content/configuration/global-configuration/command-line-arguments.md +++ b/docs/content/configuration/global-configuration/command-line-arguments.md @@ -535,3 +535,17 @@ Used to activate or deactivate lazy loading for SSL Certificates. The default value is `true`. + +### -agent + +Enable NGINX Agent which can used with `-enable-app-protect` to send events to Security Monitoring. + +The default value is `false`. + + + +### -agent-instance-group + +Specify the instance group name to use for the NGINX Ingress Controller deployment when using `-agent`. + + diff --git a/docs/content/installation/installing-nic/installation-with-helm.md b/docs/content/installation/installing-nic/installation-with-helm.md index 7eda35bb3b..52670a7941 100644 --- a/docs/content/installation/installing-nic/installation-with-helm.md +++ b/docs/content/installation/installing-nic/installation-with-helm.md @@ -451,6 +451,21 @@ The following tables lists the configurable parameters of the NGINX Ingress Cont | **serviceNameOverride** | Used to prevent cloud load balancers from being replaced due to service name change during helm upgrades. | "" | | **nginxServiceMesh.enable** | Enable integration with NGINX Service Mesh. See the NGINX Service Mesh [docs](https://docs.nginx.com/nginx-service-mesh/tutorials/kic/deploy-with-kic/) for more details. Requires `controller.nginxplus`. | false | | **nginxServiceMesh.enableEgress** | Enable NGINX Service Mesh workloads to route egress traffic through the Ingress Controller. See the NGINX Service Mesh [docs](https://docs.nginx.com/nginx-service-mesh/tutorials/kic/deploy-with-kic/#enabling-egress) for more details. Requires `nginxServiceMesh.enable`. | false | +|**nginxAgent.enable** | Enable NGINX Agent to integrate the Security Monitoring and App Protect WAF modules. Requires `controller.appprotect.enable`. | false | +|**nginxAgent.instanceGroup** | Set a custom Instance Group name for the deployment, shown when connected to NGINX Instance Manager. `nginx-ingress.controller.fullname` will be used if not set. | "" | +|**nginxAgent.logLevel** | Log level for NGINX Agent. | "error | +|**nginxAgent.instanceManager.host** | FQDN or IP for connecting to NGINX Ingress Controller. Required when `nginxAgent.enable` is set to `true` | "" | +|**nginxAgent.instanceManager.grpcPort** | Port for connecting to NGINX Ingress Controller. | 443 | +|**nginxAgent.instanceManager.sni** | Server Name Indication for Instance Manager. See the NGINX Agent [docs](https://docs.nginx.com/nginx-agent/configuration/encrypt-communication/) for more details. | "" | +|**nginxAgent.instanceManager.tls.enable** | Enable TLS for Instance Manager connection. | true | +|**nginxAgent.instanceManager.tls.skipVerify** | Skip certification verification for Instance Manager connection. | false | +|**nginxAgent.instanceManager.tls.caSecret** | Name of `nginx.org/ca` secret used for verification of Instance Manager TLS. | "" | +|**nginxAgent.instanceManager.tls.secret** | Name of `kubernetes.io/tls` secret with a TLS certificate and key for using mTLS between NGINX Agent and Instance Manager. See the NGINX Instance Manager [docs](https://docs.nginx.com/nginx-management-suite/admin-guides/configuration/secure-traffic/#mutual-client-certificate-auth-setup-mtls) and the NGINX Agent [docs](https://docs.nginx.com/nginx-agent/configuration/encrypt-communication/) for more details. | "" | +|**nginxAgent.syslog.host** | Address for NGINX Agent to run syslog listener. | 127.0.0.1 | +|**nginxAgent.syslog.port** | Port for NGINX Agent to run syslog listener. | 1514 | +|**nginxAgent.napMonitoring.collectorBufferSize** | Buffer size for collector. Will contain log lines and parsed log lines. | 50000 | +|**nginxAgent.napMonitoring.processorBufferSize** | Buffer size for processor. Will contain log lines and parsed log lines. | 50000 | +|**nginxAgent.customConfigMap** | The name of a custom ConfigMap to use instead of the one provided by default. | "" | {{}} {{< /table >}} diff --git a/examples/custom-resources/security-monitoring/README.md b/examples/custom-resources/security-monitoring/README.md new file mode 100644 index 0000000000..5a970ae08d --- /dev/null +++ b/examples/custom-resources/security-monitoring/README.md @@ -0,0 +1,105 @@ +# WAF + +This example describes how to deploy the NGINX Plus Ingress Controller with [NGINX App +Protect](https://www.nginx.com/products/nginx-app-protect/) and [NGINX Agent](https://docs.nginx.com/nginx-agent/overview/) in order to integrate with [NGINX Management Suite Security Monitoring](https://docs.nginx.com/nginx-management-suite/security/). It involves deploying a simple web application, then configure load balancing and WAF protection for the application using the VirtualServer resource. Afterwards, we configure NGINX App Protect to send logs to the NGINX Agent syslog listener, which is then sent to the Security Monitoring dashboard in NGINX Instance Manager. + +## Prerequisites + +1. Follow the installation [instructions](https://docs.nginx.com/nginx-ingress-controller/installation) to deploy NGINX + Ingress Controller with NGINX App Protect and NGINX Agent. Configure NGINX Agent to connect to a deployment of NGINX Instance Manager with Security Monitoring, and verify your NGINX Ingress Controller deployment is online in NGINX Instance Manager. + +1. Save the public IP address of the Ingress Controller into a shell variable: + + ```console + IC_IP=XXX.YYY.ZZZ.III + ``` + +1. Save the HTTP port of NGINX Ingress Controller into a shell variable: + + ```console + IC_HTTP_PORT= + ``` + +## Step 1. Deploy a web application + +Create the application deployment and service: + +```console +kubectl apply -f webapp.yaml +``` + +## Step 2 - Deploy the AP Policy + +1. Create the User Defined Signature, App Protect policy and log configuration: + + ```console + kubectl apply -f ap-apple-uds.yaml + kubectl apply -f ap-dataguard-alarm-policy.yaml + kubectl apply -f ap-logconf.yaml + ``` + +Note the log configuration in `ap-logconf.yaml` is a specific format required by NGINX Agent for integration with Security Monitoring. + +## Step 3 - Deploy the WAF Policy + +1. Create the WAF policy + + ```console + kubectl apply -f waf.yaml + ``` + +Note the App Protect configuration settings in the Policy resource. They enable WAF protection by configuring App +Protect with the policy and log configuration created in the previous step. + +## Step 4 - Configure Load Balancing + +1. Create the VirtualServer Resource: + + ```console + kubectl apply -f virtual-server.yaml + ``` + +Note that the VirtualServer references the policy `waf-policy` created in Step 3. + +## Step 5 - Test the Application + +To access the application, **curl`** the coffee and the tea services. Use the --resolve option to set the Host header +of a request with `webapp.example.com` + +1. Send a request to the application: + + ```console + curl --resolve webapp.example.com:$IC_HTTP_PORT:$IC_IP http://webapp.example.com:$IC_HTTP_PORT/ + ``` + + ```text + Server address: 10.12.0.18:80 + Server name: webapp-7586895968-r26zn + ... + ``` + +1. Send a request with a suspicious URL: + + ```console + curl --resolve webapp.example.com:$IC_HTTP_PORT:$IC_IP "http://webapp.example.com:$IC_HTTP_PORT/