From ddbd3316a212f59f4167483c50d0eb2db0d0e79c Mon Sep 17 00:00:00 2001 From: Travis Martin Date: Tue, 28 Jul 2026 14:18:33 -0700 Subject: [PATCH 01/50] Added "Get started with F5 WAF for NGINX with PLM" --- .../{get-started.md => get-started-http.md} | 4 +- .../ngf/waf-integration/get-started-plm.md | 37 +++++++++++++++++++ content/ngf/waf-integration/overview.md | 2 +- content/ngf/waf-integration/policy-sources.md | 4 +- 4 files changed, 43 insertions(+), 4 deletions(-) rename content/ngf/waf-integration/{get-started.md => get-started-http.md} (99%) create mode 100644 content/ngf/waf-integration/get-started-plm.md diff --git a/content/ngf/waf-integration/get-started.md b/content/ngf/waf-integration/get-started-http.md similarity index 99% rename from content/ngf/waf-integration/get-started.md rename to content/ngf/waf-integration/get-started-http.md index 78ffd4493..a34bdddce 100644 --- a/content/ngf/waf-integration/get-started.md +++ b/content/ngf/waf-integration/get-started-http.md @@ -1,5 +1,5 @@ --- -title: Get started with F5 WAF for NGINX +title: Get started with F5 WAF for NGINX (HTTP) weight: 200 toc: true f5-content-type: how-to @@ -8,6 +8,8 @@ f5-description: Quick start guide for protecting traffic with F5 WAF for NGINX i f5-summary: > Deploy a sample application, compile a WAF policy, and apply it to a Gateway using NGINX Gateway Fabric. This walkthrough uses the HTTP source type to demonstrate the full flow from policy compilation to attack blocking. +aliases: + - /nginx-gateway-fabric/waf-integration/get-started/ --- This guide walks through the complete flow of protecting traffic with F5 WAF for NGINX: deploy a sample application, compile a WAF policy, apply it to a Gateway, and verify that attacks are blocked. diff --git a/content/ngf/waf-integration/get-started-plm.md b/content/ngf/waf-integration/get-started-plm.md new file mode 100644 index 000000000..8466a6c37 --- /dev/null +++ b/content/ngf/waf-integration/get-started-plm.md @@ -0,0 +1,37 @@ +--- +title: Get started with F5 WAF for NGINX (PLM) +weight: 199 +toc: true +f5-content-type: tutorial +f5-docs: DOCS-000 +f5-product: F5 NGINX Gateway Fabric +f5-description: Tutorial for setting up F5 WAF for NGINX with a PLM-based policy workflow in F5 NGINX Gateway Fabric. +f5-keywords: "F5 NGINX Gateway Fabric, F5 WAF for NGINX, policy lifecycle manager, tutorial, step by step, beginner" +f5-summary: > + Set up and validate a policy lifecycle workflow for F5 WAF for NGINX in F5 NGINX Gateway Fabric. + Learn how to deploy a sample app, attach a WAFPolicy, and verify policy enforcement. + This tutorial is for platform engineers and operators with basic Kubernetes and Gateway API knowledge. +f5-audience: operator +--- + +Introduction text goes here. + +## Before you begin + +## Deploy PLM infrastructure + +## Configure NGF to connect to PLM storage + +## Enable WAF in the NginxProxy resource + +## Configure security logging (optional) + +## Define the WAF policy + +## Deploy the Gateway and attach WAFPolicy + +## Configure HTTPRoutes + +## Validate policy compilation and application + +## Test deployment and policy enforcement \ No newline at end of file diff --git a/content/ngf/waf-integration/overview.md b/content/ngf/waf-integration/overview.md index f686d0ff4..e4ab13502 100644 --- a/content/ngf/waf-integration/overview.md +++ b/content/ngf/waf-integration/overview.md @@ -141,7 +141,7 @@ Route-level WAFPolicy → Overrides Gateway-level for that route only ## See also -- [Get started with F5 WAF for NGINX]({{< ref "/ngf/waf-integration/get-started.md" >}}) +- [Get started with F5 WAF for NGINX]({{< ref "/ngf/waf-integration/get-started-http.md" >}}) - [Configure policy sources (NGINX Instance Manager, NGINX One Console, and HTTP)]({{< ref "/ngf/waf-integration/policy-sources.md" >}}) - [Configure WAF settings]({{< ref "/ngf/waf-integration/configuration.md" >}}) - [WAFPolicy and NginxProxy API reference]({{< ref "/ngf/reference/api.md" >}}) diff --git a/content/ngf/waf-integration/policy-sources.md b/content/ngf/waf-integration/policy-sources.md index 4208ed5ff..75b879626 100644 --- a/content/ngf/waf-integration/policy-sources.md +++ b/content/ngf/waf-integration/policy-sources.md @@ -7,7 +7,7 @@ f5-product: NGINX Gateway Fabric f5-description: Configure WAFPolicy to fetch compiled bundles from F5 NGINX Instance Manager, F5 NGINX One Console, or an HTTP server. --- -NGINX Gateway Fabric supports three policy source types for fetching compiled WAF bundles: F5 NGINX Instance Manager, F5 NGINX One Console, and direct HTTP/HTTPS URLs. For a quick start walkthrough using the HTTP source, see [Get started with F5 WAF for NGINX]({{< ref "/ngf/waf-integration/get-started.md" >}}). +NGINX Gateway Fabric supports three policy source types for fetching compiled WAF bundles: F5 NGINX Instance Manager, F5 NGINX One Console, and direct HTTP/HTTPS URLs. For a quick start walkthrough using the HTTP source, see [Get started with F5 WAF for NGINX]({{< ref "/ngf/waf-integration/get-started-http.md" >}}). Before configuring a policy source, ensure that WAF is [enabled on the NginxProxy]({{< ref "/ngf/waf-integration/overview.md#enable-waf-on-the-nginxproxy" >}}) — either per Gateway or globally via Helm values. @@ -210,7 +210,7 @@ Replace `` with your NGINX One Console tenant hostname. The `namespace` Use this option when you compile WAF policies using the F5 WAF compiler CLI or a CI/CD pipeline and host the resulting bundle on an HTTP/HTTPS server. For details on using the compiler, see [Build and use the compiler tool]({{< ref "/waf/configure/compiler.md" >}}). -For a complete walkthrough including policy compilation and a bundle server deployment, see [Get started with F5 WAF for NGINX]({{< ref "/ngf/waf-integration/get-started.md" >}}). +For a complete walkthrough including policy compilation and a bundle server deployment, see [Get started with F5 WAF for NGINX]({{< ref "/ngf/waf-integration/get-started-http.md" >}}). For production environments, you would typically host compiled bundles on an HTTPS server with authentication. See [Configure WAF settings]({{< ref "/ngf/waf-integration/configuration.md" >}}) for details on adding credentials, custom CA certificates, and checksum verification to your `policySource`. From 9bd5a7647c06a02c7b900a2a80b2664c1e07ceb6 Mon Sep 17 00:00:00 2001 From: Travis Martin Date: Mon, 10 Aug 2026 11:04:53 -0700 Subject: [PATCH 02/50] docs: Add PLM infrastructure and WAF policy sections for NGF tutorial MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Satisfies TECHDOCS-5343 (Story 3) and TECHDOCS-5347 (Story 7) for the NGF + F5 WAF for NGINX with PLM tutorial. Story 3 — Deploy PLM infrastructure (shared include): - Add content/includes/waf/plm-deploy-infrastructure.md - Covers PLM architecture (Policy Controller → gRPC → compiler → SeaweedFS/S3), CRD installation, registry pull secret creation, Helm install with plm-values.yaml, and deployment verification - No NGF-specific content; consumable by the NIC tutorial unmodified - Flags open item: registry token credential (TECHDOCS-5342) Story 7 — Define the WAF policy (NGF content + shared include): - Add content/includes/waf/plm-define-policy-bundle-method.md (precompiled-bundle method; product-neutral shared include) - Add inline and Git-reference methods directly to get-started-plm.md as NGF-specific content - Documents correct recompile trigger (spec/annotation change required; controller does not poll Git or Artifactory) - Documents bundle.state values: pending | processing | ready | invalid - Documents isCompiled: false for precompiled bundles Also updates get-started-plm.md with TODO placeholders for all remaining sections (Stories 2, 4, 5, 6, 8, 9, 10, 11). --- .../waf/plm-define-policy-bundle-method.md | 74 ++++++++ .../includes/waf/plm-deploy-infrastructure.md | 150 ++++++++++++++++ .../ngf/waf-integration/get-started-plm.md | 166 +++++++++++++++++- 3 files changed, 388 insertions(+), 2 deletions(-) create mode 100644 content/includes/waf/plm-define-policy-bundle-method.md create mode 100644 content/includes/waf/plm-deploy-infrastructure.md diff --git a/content/includes/waf/plm-define-policy-bundle-method.md b/content/includes/waf/plm-define-policy-bundle-method.md new file mode 100644 index 000000000..26f630e5d --- /dev/null +++ b/content/includes/waf/plm-define-policy-bundle-method.md @@ -0,0 +1,74 @@ +--- +f5-product: F5 WAF for NGINX +f5-files: +- content/ngf/waf-integration/get-started-plm.md +--- + +The precompiled-bundle method lets you reference a `.tgz` policy bundle stored in an artifact registry (for example, Artifactory or Nexus). The Policy Controller imports the bundle and stores it in the SeaweedFS object store without recompiling it. + +Use this method when: + +- Your security team compiles and publishes bundles through an external pipeline. +- You want to decouple policy compilation from cluster operations. + +Create an `APPolicy` resource that references your bundle: + +```yaml +apiVersion: appprotect.f5.com/v1 +kind: APPolicy +metadata: + name: + namespace: +spec: + policy: + $ref: "https:///.tgz" +``` + +Replace ``, ``, ``, and `` with your values. + +Apply the resource: + +```shell +kubectl apply -f .yaml +``` + +{{< call-out class="note" title="Note" >}} +The Policy Controller must be able to reach the artifact registry host over HTTPS. If the registry uses a private certificate authority, configure the Policy Controller to trust that CA. +{{< /call-out >}} + +#### Confirm the policy is ready + +The Policy Controller processes the bundle and updates the `APPolicy` status. Check the `bundle.state` field: + +```shell +kubectl get appolicy \ + --namespace \ + --output jsonpath='State: {.status.bundle.state}{"\n"}Bundle: {.status.bundle.location}{"\n"}isCompiled: {.status.processing.isCompiled}{"\n"}' +``` + +When the bundle is ready, the output looks like this: + +```text +State: ready +Bundle: s3:///bundles/_imported_.tgz +isCompiled: false +``` + +`isCompiled: false` confirms the bundle was imported as-is and not recompiled. + +`bundle.state` can be one of: + +| State | Meaning | +|-------|---------| +| `pending` | The Policy Controller has not yet processed the resource. | +| `processing` | The Policy Controller is importing or storing the bundle. | +| `ready` | The bundle is stored and ready to use. `bundle.location` is populated. | +| `invalid` | The bundle could not be imported. Check the status for error detail. | + +#### Update a precompiled bundle + +The Policy Controller does not poll the artifact registry for changes. To pick up a new version of a bundle, update the `APPolicy` resource to reference the new bundle URL (or bump its revision annotation) and re-apply it: + +```shell +kubectl apply -f .yaml +``` diff --git a/content/includes/waf/plm-deploy-infrastructure.md b/content/includes/waf/plm-deploy-infrastructure.md new file mode 100644 index 000000000..90c3c21f9 --- /dev/null +++ b/content/includes/waf/plm-deploy-infrastructure.md @@ -0,0 +1,150 @@ +--- +f5-product: F5 WAF for NGINX +f5-files: +- content/ngf/waf-integration/get-started-plm.md +--- + +The Policy Lifecycle Manager (PLM) backend runs as a Kubernetes operator. It watches WAF custom resources and compiles WAF policies into bundles. The Policy Controller delegates compilation to a separate compiler service over gRPC. The resulting bundles are stored in an embedded SeaweedFS S3-compatible object store. + +F5 WAF for NGINX and NGINX Gateway Fabric are installed using separate Helm charts. The steps in this section install only the F5 WAF for NGINX PLM components and do not affect your existing NGINX Gateway Fabric installation. + +### Install the CRDs + +Install the four custom resource definitions (CRDs) that the Policy Controller manages: + +```shell +kubectl apply -f https://raw.githubusercontent.com/nginx/waf-policy-controller/main/manifests/1-deploy-crds.yaml +``` + +Confirm all four CRDs are present: + +```shell +kubectl get crd | grep appprotect.f5.com +``` + +Expected output: + +```text +appolicies.appprotect.f5.com +aplogconfs.appprotect.f5.com +apsignatures.appprotect.f5.com +apusersigs.appprotect.f5.com +``` + +### Create the registry pull secret + +Create a namespace for the PLM components, then create the registry pull secret using the credentials from the previous section. + +Replace `` with your chosen namespace name, and `` with your F5 WAF for NGINX JWT. + + + +```shell +kubectl create namespace + +kubectl create secret docker-registry regcred \ + --namespace \ + --docker-server=private-registry.nginx.com \ + --docker-username= \ + --docker-password=none \ + --dry-run=client --output yaml | kubectl apply -f - +``` + +### Install the Policy Controller + +Create a values file for the Helm installation. Replace ``, ``, ``, ``, and `` with your values. `` is the Helm chart version (for example, `5.14.0`). `` is the container image tag, which typically matches the chart version. + +`` and `` are the base64-encoded contents of your `nginx-repo.crt` and `nginx-repo.key` files. To encode them, run: + +```shell +base64 --wrap=0 < nginx-repo.crt +base64 --wrap=0 < nginx-repo.key +``` + +Create `/tmp/plm-values.yaml`: + +```yaml +imagePullSecrets: + - name: regcred +securityUpdatesRepo: + cert: "" + key: "" +policyController: + image: + tag: "" +compiler: + image: + tag: "" +seaweedfsOperatorConfig: + seaweedfs: + image: + tag: "" +seaweedfs-operator: + image: + tag: "" + pullSecrets: regcred +``` + +Add the NGINX Helm repository and install the chart: + +```shell +helm repo add nginx-stable https://helm.nginx.com/stable +helm repo update nginx-stable + +helm upgrade --install nginx-stable/f5-waf-policy-controller \ + --version \ + --namespace \ + --values /tmp/plm-values.yaml +``` + +Replace `` with a name for the Helm release (for example, `plm`). + +### Verify the deployment + +Wait for all PLM components to become ready. The Policy Controller's init container waits for both the compiler service and the SeaweedFS S3 endpoint to be available before it starts, so the controller pod will show `Init:0/1` until SeaweedFS is ready. + +Wait for the SeaweedFS storage backend: + +```shell +kubectl rollout status deployment/-seaweedfs-operator \ + --namespace --timeout=120s + +kubectl wait pods \ + --selector app.kubernetes.io/name=seaweedfs \ + --for=condition=Ready \ + --namespace \ + --timeout=180s +``` + +Wait for the Policy Controller: + +```shell +kubectl rollout status deployment/-f5-waf-policy-controller \ + --namespace --timeout=180s +``` + +Confirm all eight pods are running: + +```shell +kubectl get pods --namespace +``` + +Expected output: + +```text +NAME READY STATUS RESTARTS +-f5-waf-compiler-service-xxxxx 1/1 Running 0 +-f5-waf-policy-controller-xxxxx 1/1 Running 0 +-seaweedfs-operator-xxxxx 1/1 Running 0 +-f5-waf-seaweed-master-0 1/1 Running 0 +-f5-waf-seaweed-filer-0 1/1 Running 0 +-f5-waf-seaweed-volume-0 1/1 Running 0 +-f5-waf-seaweed-volume-1 1/1 Running 0 +-f5-waf-seaweed-volume-2 1/1 Running 0 +``` + +Confirm the four CRDs are present: + +```shell +kubectl get crd | grep appprotect.f5.com +``` diff --git a/content/ngf/waf-integration/get-started-plm.md b/content/ngf/waf-integration/get-started-plm.md index 8466a6c37..1390cb839 100644 --- a/content/ngf/waf-integration/get-started-plm.md +++ b/content/ngf/waf-integration/get-started-plm.md @@ -14,24 +14,186 @@ f5-summary: > f5-audience: operator --- -Introduction text goes here. + ## Before you begin + + ## Deploy PLM infrastructure +{{< include "waf/plm-deploy-infrastructure.md" >}} + ## Configure NGF to connect to PLM storage + + ## Enable WAF in the NginxProxy resource + + ## Configure security logging (optional) + + ## Define the WAF policy +The `APPolicy` custom resource defines what F5 WAF for NGINX enforces. The `APLogConf` custom resource defines what it logs — see [Configure security logging](#configure-security-logging-optional) for details. The Policy Controller watches both resources and compiles them into bundles stored in the SeaweedFS object store. + +This section covers three ways to define a WAF policy. Choose the method that fits your workflow: + +| Method | When to use | +|--------|-------------| +| [Inline](#inline-method) | You want to define the policy directly in the Kubernetes resource. Good for getting started or for simple policies. | +| [Git reference](#git-reference-method) | Your policy JSON lives in a Git repository and you want the Policy Controller to fetch and compile it. | +| [Precompiled bundle](#precompiled-bundle-method) | Your security team publishes compiled bundles to an artifact registry. The Policy Controller imports the bundle without recompiling. | + +### Inline method + +The inline method embeds the full policy definition in the `APPolicy` resource. The Policy Controller converts and compiles it. + +Create an `APPolicy` resource with the policy embedded under `spec.policy`: + +```yaml +apiVersion: appprotect.f5.com/v1 +kind: APPolicy +metadata: + name: + namespace: +spec: + policy: + name: + template: + name: POLICY_TEMPLATE_NGINX_BASE + applicationLanguage: utf-8 + enforcementMode: blocking +``` + +Replace `` and `` with your values. Extend `spec.policy` with the policy fields you want to enforce. + +Apply the resource: + +```shell +kubectl apply -f .yaml +``` + +#### Confirm the policy is ready + +Check the `bundle.state` field to confirm the Policy Controller compiled the policy: + +```shell +kubectl get appolicy \ + --namespace \ + --output jsonpath='State: {.status.bundle.state}{"\n"}Bundle: {.status.bundle.location}{"\n"}Compiler: {.status.bundle.compilerVersion}{"\n"}' +``` + +When compilation succeeds, the output looks like this: + +```text +State: ready +Bundle: s3:///bundles/.tgz +Compiler: +``` + +`bundle.state` can be one of: + +| State | Meaning | +|-------|---------| +| `pending` | The Policy Controller has not yet processed the resource. | +| `processing` | The Policy Controller is compiling the policy. | +| `ready` | Compilation succeeded. `bundle.location` is populated. | +| `invalid` | Compilation failed. Check the status for error detail. | + +To update an inline policy, edit the `APPolicy` resource and re-apply it. The Policy Controller recompiles the policy when the resource spec changes. + +### Git reference method + +The Git reference method lets you store your policy JSON in a Git repository. The Policy Controller fetches the file and compiles it. + +#### Public repository + +Create an `APPolicy` resource that references the policy file by path: + +```yaml +apiVersion: appprotect.f5.com/v1 +kind: APPolicy +metadata: + name: + namespace: +spec: + policy: + $ref: + externalReferenceDetails: + repositoryDetails: + repository: https://github.com//.git + ref: "" +``` + +Replace ``, ``, ``, ``, ``, and `` with your values. + +{{< call-out class="note" title="Note" >}} +Pin `ref` to a tag or commit SHA rather than a branch name in production environments. +{{< /call-out >}} + +Apply the resource: + +```shell +kubectl apply -f .yaml +``` + +#### Private repository + +For private repositories, create a Kubernetes secret with your personal access token (PAT): + +```shell +kubectl create secret generic git-token-secret \ + --namespace \ + --from-literal=token= +``` + +Then reference the secret in the `APPolicy` resource: + +```yaml +apiVersion: appprotect.f5.com/v1 +kind: APPolicy +metadata: + name: + namespace: +spec: + policy: + $ref: + externalReferenceDetails: + repositoryDetails: + repository: https://github.com//.git + ref: "" + authentication: + token: git-token-secret +``` + +#### Confirm the policy is ready + +Check `bundle.state` as described in [Confirm the policy is ready](#confirm-the-policy-is-ready). + +#### Update a Git-referenced policy + +The Policy Controller does not poll the Git repository for changes. To pick up changes to the referenced policy file, re-apply the `APPolicy` resource (or update its revision annotation) after you push changes to the repository. + +### Precompiled bundle method + +{{< include "waf/plm-define-policy-bundle-method.md" >}} + ## Deploy the Gateway and attach WAFPolicy + + ## Configure HTTPRoutes + + ## Validate policy compilation and application -## Test deployment and policy enforcement \ No newline at end of file + + +## Test deployment and policy enforcement + + \ No newline at end of file From f0536be31bcc4ccb991cbfe7abff3e8c6b459779 Mon Sep 17 00:00:00 2001 From: Travis Martin Date: Mon, 10 Aug 2026 11:38:39 -0700 Subject: [PATCH 03/50] style: Make PLM includes product-agnostic for NGF/NIC reuse Remove the hard-coded NGINX Gateway Fabric product name from the deploy-infrastructure include. The separate-Helm-charts note now refers to the NGINX data plane generically so both the NGF and NIC tutorials can consume the include unmodified. Also adds the NIC target file to f5-files front matter in both includes. --- content/includes/waf/plm-define-policy-bundle-method.md | 1 + content/includes/waf/plm-deploy-infrastructure.md | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/content/includes/waf/plm-define-policy-bundle-method.md b/content/includes/waf/plm-define-policy-bundle-method.md index 26f630e5d..1f5e37dd4 100644 --- a/content/includes/waf/plm-define-policy-bundle-method.md +++ b/content/includes/waf/plm-define-policy-bundle-method.md @@ -2,6 +2,7 @@ f5-product: F5 WAF for NGINX f5-files: - content/ngf/waf-integration/get-started-plm.md +- content/nic/waf-integration/get-started-plm.md --- The precompiled-bundle method lets you reference a `.tgz` policy bundle stored in an artifact registry (for example, Artifactory or Nexus). The Policy Controller imports the bundle and stores it in the SeaweedFS object store without recompiling it. diff --git a/content/includes/waf/plm-deploy-infrastructure.md b/content/includes/waf/plm-deploy-infrastructure.md index 90c3c21f9..fa04651b4 100644 --- a/content/includes/waf/plm-deploy-infrastructure.md +++ b/content/includes/waf/plm-deploy-infrastructure.md @@ -2,11 +2,12 @@ f5-product: F5 WAF for NGINX f5-files: - content/ngf/waf-integration/get-started-plm.md +- content/nic/waf-integration/get-started-plm.md --- The Policy Lifecycle Manager (PLM) backend runs as a Kubernetes operator. It watches WAF custom resources and compiles WAF policies into bundles. The Policy Controller delegates compilation to a separate compiler service over gRPC. The resulting bundles are stored in an embedded SeaweedFS S3-compatible object store. -F5 WAF for NGINX and NGINX Gateway Fabric are installed using separate Helm charts. The steps in this section install only the F5 WAF for NGINX PLM components and do not affect your existing NGINX Gateway Fabric installation. +F5 WAF for NGINX is installed using a separate Helm chart from your NGINX data plane. The steps in this section install only the F5 WAF for NGINX PLM components and do not affect your existing NGINX installation. ### Install the CRDs From 04fd34913ec08c00d980ad1546a8e6838dd3b593 Mon Sep 17 00:00:00 2001 From: Travis Martin Date: Mon, 10 Aug 2026 11:49:13 -0700 Subject: [PATCH 04/50] style: Remove headings from PLM includes Headings in include files cause TOC and hierarchy issues depending on the level of the section they are embedded in. Replace all ### and #### headings with bold lead-in sentences. --- .../waf/plm-define-policy-bundle-method.md | 8 ++------ .../includes/waf/plm-deploy-infrastructure.md | 16 ++++------------ 2 files changed, 6 insertions(+), 18 deletions(-) diff --git a/content/includes/waf/plm-define-policy-bundle-method.md b/content/includes/waf/plm-define-policy-bundle-method.md index 1f5e37dd4..43caa352e 100644 --- a/content/includes/waf/plm-define-policy-bundle-method.md +++ b/content/includes/waf/plm-define-policy-bundle-method.md @@ -37,9 +37,7 @@ kubectl apply -f .yaml The Policy Controller must be able to reach the artifact registry host over HTTPS. If the registry uses a private certificate authority, configure the Policy Controller to trust that CA. {{< /call-out >}} -#### Confirm the policy is ready - -The Policy Controller processes the bundle and updates the `APPolicy` status. Check the `bundle.state` field: +**Confirm the policy is ready.** The Policy Controller processes the bundle and updates the `APPolicy` status. Check the `bundle.state` field: ```shell kubectl get appolicy \ @@ -66,9 +64,7 @@ isCompiled: false | `ready` | The bundle is stored and ready to use. `bundle.location` is populated. | | `invalid` | The bundle could not be imported. Check the status for error detail. | -#### Update a precompiled bundle - -The Policy Controller does not poll the artifact registry for changes. To pick up a new version of a bundle, update the `APPolicy` resource to reference the new bundle URL (or bump its revision annotation) and re-apply it: +**Update a precompiled bundle.** The Policy Controller does not poll the artifact registry for changes. To pick up a new version of a bundle, update the `APPolicy` resource to reference the new bundle URL (or bump its revision annotation) and re-apply it: ```shell kubectl apply -f .yaml diff --git a/content/includes/waf/plm-deploy-infrastructure.md b/content/includes/waf/plm-deploy-infrastructure.md index fa04651b4..5c81f676b 100644 --- a/content/includes/waf/plm-deploy-infrastructure.md +++ b/content/includes/waf/plm-deploy-infrastructure.md @@ -9,9 +9,7 @@ The Policy Lifecycle Manager (PLM) backend runs as a Kubernetes operator. It wat F5 WAF for NGINX is installed using a separate Helm chart from your NGINX data plane. The steps in this section install only the F5 WAF for NGINX PLM components and do not affect your existing NGINX installation. -### Install the CRDs - -Install the four custom resource definitions (CRDs) that the Policy Controller manages: +**Install the CRDs.** Install the four custom resource definitions (CRDs) that the Policy Controller manages: ```shell kubectl apply -f https://raw.githubusercontent.com/nginx/waf-policy-controller/main/manifests/1-deploy-crds.yaml @@ -32,9 +30,7 @@ apsignatures.appprotect.f5.com apusersigs.appprotect.f5.com ``` -### Create the registry pull secret - -Create a namespace for the PLM components, then create the registry pull secret using the credentials from the previous section. +**Create the registry pull secret.** Create a namespace for the PLM components, then create the registry pull secret using the credentials from the previous section. Replace `` with your chosen namespace name, and `` with your F5 WAF for NGINX JWT. @@ -51,9 +47,7 @@ kubectl create secret docker-registry regcred \ --dry-run=client --output yaml | kubectl apply -f - ``` -### Install the Policy Controller - -Create a values file for the Helm installation. Replace ``, ``, ``, ``, and `` with your values. `` is the Helm chart version (for example, `5.14.0`). `` is the container image tag, which typically matches the chart version. +**Install the Policy Controller.** Create a values file for the Helm installation. Replace ``, ``, ``, ``, and `` with your values. `` is the Helm chart version (for example, `5.14.0`). `` is the container image tag, which typically matches the chart version. `` and `` are the base64-encoded contents of your `nginx-repo.crt` and `nginx-repo.key` files. To encode them, run: @@ -100,9 +94,7 @@ helm upgrade --install nginx-stable/f5-waf-policy-controller \ Replace `` with a name for the Helm release (for example, `plm`). -### Verify the deployment - -Wait for all PLM components to become ready. The Policy Controller's init container waits for both the compiler service and the SeaweedFS S3 endpoint to be available before it starts, so the controller pod will show `Init:0/1` until SeaweedFS is ready. +**Verify the deployment.** Wait for all PLM components to become ready. The Policy Controller's init container waits for both the compiler service and the SeaweedFS S3 endpoint to be available before it starts, so the controller pod will show `Init:0/1` until SeaweedFS is ready. Wait for the SeaweedFS storage backend: From 5604ab8193123255780876bbbaaeefb190a192ee Mon Sep 17 00:00:00 2001 From: Travis Martin Date: Mon, 10 Aug 2026 11:52:48 -0700 Subject: [PATCH 05/50] style: Restore headings in PLM includes at correct levels Match heading levels to the consuming section in each case: - plm-deploy-infrastructure.md: ### (consumed under ##) - plm-define-policy-bundle-method.md: #### (consumed under ###) Ensures TOC entries and visual hierarchy are consistent with the parallel method subsections in get-started-plm.md. --- .../waf/plm-define-policy-bundle-method.md | 8 ++++++-- .../includes/waf/plm-deploy-infrastructure.md | 16 ++++++++++++---- 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/content/includes/waf/plm-define-policy-bundle-method.md b/content/includes/waf/plm-define-policy-bundle-method.md index 43caa352e..1f5e37dd4 100644 --- a/content/includes/waf/plm-define-policy-bundle-method.md +++ b/content/includes/waf/plm-define-policy-bundle-method.md @@ -37,7 +37,9 @@ kubectl apply -f .yaml The Policy Controller must be able to reach the artifact registry host over HTTPS. If the registry uses a private certificate authority, configure the Policy Controller to trust that CA. {{< /call-out >}} -**Confirm the policy is ready.** The Policy Controller processes the bundle and updates the `APPolicy` status. Check the `bundle.state` field: +#### Confirm the policy is ready + +The Policy Controller processes the bundle and updates the `APPolicy` status. Check the `bundle.state` field: ```shell kubectl get appolicy \ @@ -64,7 +66,9 @@ isCompiled: false | `ready` | The bundle is stored and ready to use. `bundle.location` is populated. | | `invalid` | The bundle could not be imported. Check the status for error detail. | -**Update a precompiled bundle.** The Policy Controller does not poll the artifact registry for changes. To pick up a new version of a bundle, update the `APPolicy` resource to reference the new bundle URL (or bump its revision annotation) and re-apply it: +#### Update a precompiled bundle + +The Policy Controller does not poll the artifact registry for changes. To pick up a new version of a bundle, update the `APPolicy` resource to reference the new bundle URL (or bump its revision annotation) and re-apply it: ```shell kubectl apply -f .yaml diff --git a/content/includes/waf/plm-deploy-infrastructure.md b/content/includes/waf/plm-deploy-infrastructure.md index 5c81f676b..fa04651b4 100644 --- a/content/includes/waf/plm-deploy-infrastructure.md +++ b/content/includes/waf/plm-deploy-infrastructure.md @@ -9,7 +9,9 @@ The Policy Lifecycle Manager (PLM) backend runs as a Kubernetes operator. It wat F5 WAF for NGINX is installed using a separate Helm chart from your NGINX data plane. The steps in this section install only the F5 WAF for NGINX PLM components and do not affect your existing NGINX installation. -**Install the CRDs.** Install the four custom resource definitions (CRDs) that the Policy Controller manages: +### Install the CRDs + +Install the four custom resource definitions (CRDs) that the Policy Controller manages: ```shell kubectl apply -f https://raw.githubusercontent.com/nginx/waf-policy-controller/main/manifests/1-deploy-crds.yaml @@ -30,7 +32,9 @@ apsignatures.appprotect.f5.com apusersigs.appprotect.f5.com ``` -**Create the registry pull secret.** Create a namespace for the PLM components, then create the registry pull secret using the credentials from the previous section. +### Create the registry pull secret + +Create a namespace for the PLM components, then create the registry pull secret using the credentials from the previous section. Replace `` with your chosen namespace name, and `` with your F5 WAF for NGINX JWT. @@ -47,7 +51,9 @@ kubectl create secret docker-registry regcred \ --dry-run=client --output yaml | kubectl apply -f - ``` -**Install the Policy Controller.** Create a values file for the Helm installation. Replace ``, ``, ``, ``, and `` with your values. `` is the Helm chart version (for example, `5.14.0`). `` is the container image tag, which typically matches the chart version. +### Install the Policy Controller + +Create a values file for the Helm installation. Replace ``, ``, ``, ``, and `` with your values. `` is the Helm chart version (for example, `5.14.0`). `` is the container image tag, which typically matches the chart version. `` and `` are the base64-encoded contents of your `nginx-repo.crt` and `nginx-repo.key` files. To encode them, run: @@ -94,7 +100,9 @@ helm upgrade --install nginx-stable/f5-waf-policy-controller \ Replace `` with a name for the Helm release (for example, `plm`). -**Verify the deployment.** Wait for all PLM components to become ready. The Policy Controller's init container waits for both the compiler service and the SeaweedFS S3 endpoint to be available before it starts, so the controller pod will show `Init:0/1` until SeaweedFS is ready. +### Verify the deployment + +Wait for all PLM components to become ready. The Policy Controller's init container waits for both the compiler service and the SeaweedFS S3 endpoint to be available before it starts, so the controller pod will show `Init:0/1` until SeaweedFS is ready. Wait for the SeaweedFS storage backend: From ee9508ad28f3bdac168cefec835ea9396f86e393 Mon Sep 17 00:00:00 2001 From: Saylor Berman Date: Mon, 10 Aug 2026 13:49:24 -0600 Subject: [PATCH 06/50] Add NGF PLM docs --- content/ngf/reference/cli-help.md | 5 + .../ngf/waf-integration/get-started-http.md | 1 + .../ngf/waf-integration/get-started-plm.md | 571 ++++++++++++++++-- content/ngf/waf-integration/overview.md | 46 +- content/ngf/waf-integration/policy-sources.md | 12 +- 5 files changed, 572 insertions(+), 63 deletions(-) diff --git a/content/ngf/reference/cli-help.md b/content/ngf/reference/cli-help.md index 6e51e3d5e..f10aee54c 100644 --- a/content/ngf/reference/cli-help.md +++ b/content/ngf/reference/cli-help.md @@ -44,6 +44,11 @@ This command runs the NGINX Gateway Fabric control plane. | _leader-election-lock-name_ | _string_ | The name of the leader election lock. A lease object with this name will be created in the same namespace as the controller (Default: `"nginx-gateway-leader-election-lock"`). | | _product-telemetry-disable_ | _bool_ | Disable the collection of product telemetry (Default: `false`). | | _nginx-docker-secret_ | _list_ | The name of the NGINX docker registry Secret(s). Must exist in the same namespace that the NGINX Gateway Fabric control plane is running in (default namespace: nginx-gateway). | +| _plm-storage-url_ | _string_ | PLM storage service URL (HTTP or HTTPS). | +| _plm-storage-credentials-secret_ | _string_ | The name of the Secret containing the PLM storage S3 secret access key (`seaweedfs_admin_secret`). Must exist in the same namespace that the NGINX Gateway Fabric control plane is running in (default namespace: nginx-gateway), unless prefixed with `/`. | +| _plm-storage-ca-secret_ | _string_ | The name of the Secret containing the CA certificate (`ca.crt`) for PLM storage TLS server verification. Must exist in the same namespace that the NGINX Gateway Fabric control plane is running in (default namespace: nginx-gateway), unless prefixed with `/`. | +| _plm-storage-client-ssl-secret_ | _string_ | The name of the Secret containing the client certificate and key (`tls.crt`/`tls.key`) for mutual TLS to PLM storage. Must exist in the same namespace that the NGINX Gateway Fabric control plane is running in (default namespace: nginx-gateway), unless prefixed with `/`. | +| _plm-storage-skip-verify_ | _bool_ | Disable TLS certificate verification for PLM storage connections. Use for testing only. | | _usage-report-secret_ | _string_ | 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) | | _usage-report-endpoint_ | _string_ | The endpoint of the NGINX Plus usage reporting server. | | _usage-report-resolver_ | _string_ | The nameserver used to resolve the NGINX Plus usage reporting endpoint. Used with NGINX Instance Manager. | diff --git a/content/ngf/waf-integration/get-started-http.md b/content/ngf/waf-integration/get-started-http.md index a34bdddce..c82656fe6 100644 --- a/content/ngf/waf-integration/get-started-http.md +++ b/content/ngf/waf-integration/get-started-http.md @@ -530,6 +530,7 @@ SSN: *******6789 ## Next steps +- [Get started with F5 WAF for NGINX using PLM]({{< ref "/ngf/waf-integration/get-started-plm.md" >}}) for a Kubernetes-native policy workflow using PLM. - [Configure policy sources (NIM and NGINX One Console)]({{< ref "/ngf/waf-integration/policy-sources.md" >}}) for managed policy workflows. - [Configure WAF settings]({{< ref "/ngf/waf-integration/configuration.md" >}}) for polling, TLS, authentication, security logging, and fail-open behavior. - [Troubleshoot WAFPolicy status]({{< ref "/ngf/waf-integration/troubleshooting.md" >}}) if a condition is `False`. diff --git a/content/ngf/waf-integration/get-started-plm.md b/content/ngf/waf-integration/get-started-plm.md index 1390cb839..ae9d9d45b 100644 --- a/content/ngf/waf-integration/get-started-plm.md +++ b/content/ngf/waf-integration/get-started-plm.md @@ -14,11 +14,15 @@ f5-summary: > f5-audience: operator --- - +This guide walks through the complete flow of protecting traffic with F5 WAF for NGINX using Policy Lifecycle Management (PLM): connect NGINX Gateway Fabric to PLM storage, define a WAF policy as Kubernetes custom resources, apply it to a Gateway, and verify that attacks are blocked. + +PLM is one of four WAF policy source types. With PLM, you define your security posture as `APPolicy` and `APLogConf` custom resources instead of compiling and hosting bundles yourself. For a comparison with the other source types, see [PLM (Policy Lifecycle Management)]({{< ref "/ngf/waf-integration/overview.md#plm-policy-lifecycle-management" >}}). ## Before you begin - +- Have `kubectl` access to a Kubernetes cluster. +- Have a valid F5 WAF for NGINX subscription. F5 WAF for NGINX is a separate add-on to NGINX Plus and isn't included with the NGINX Plus license. +- Have your private registry credentials Secret for `private-registry.nginx.com` available. You'll reference this Secret when you install NGINX Gateway Fabric. ## Deploy PLM infrastructure @@ -26,89 +30,238 @@ f5-audience: operator ## Configure NGF to connect to PLM storage - +NGINX Gateway Fabric fetches compiled bundles from PLM's in-cluster storage. You set up storage access once, cluster-wide, at install time — it applies to every `WAFPolicy` that uses `type: PLM`. + +Create a `values.yaml` file that enables WAF and sets the PLM storage connection details under `nginxGateway.plmStorage`: + +```yaml +# values.yaml +nginx: + image: + repository: private-registry.nginx.com/nginx-gateway-fabric/nginx-plus-f5waf + plus: true + config: + waf: + enable: true + imagePullSecret: nginx-plus-registry-secret +nginxGateway: + plmStorage: + url: "https://plm-storage-service.plm-system.svc.cluster.local" + credentialsSecretName: "plm-storage-credentials" # contains the seaweedfs_admin_secret field + tls: + caSecretName: "plm-ca-secret" # Secret with ca.crt for verifying the storage service + clientSSLSecretName: "plm-client-secret" # Secret with tls.crt/tls.key for mutual TLS + insecureSkipVerify: false # use only for testing +``` + +{{< call-out "caution" >}} Always use HTTPS with TLS verification (`caSecretName`) in production. Add `clientSSLSecretName` for mutual TLS in high-security environments, and never set `insecureSkipVerify: true`. {{< /call-out >}} + +{{< call-out "note" >}} `credentialsSecretName` and `caSecretName` must reference Secrets in the NGINX Gateway Fabric control plane namespace, unless you prefix them with `/`. {{< /call-out >}} + +Install NGINX Gateway Fabric by following [the installation guide]({{< ref "/ngf/install/helm.md" >}}) and using the **NGINX Plus with WAF** tab, and apply this `values.yaml` file in your install or upgrade command, specifying `--values values.yaml`. + +The PLM installation creates the credentials Secret automatically, containing the S3 secret access key in the `seaweedfs_admin_secret` field (access key ID `admin` by default): + +```yaml +apiVersion: v1 +kind: Secret +metadata: + name: plm-storage-credentials + namespace: nginx-gateway +type: Opaque +data: + seaweedfs_admin_secret: +``` + +NGINX Gateway Fabric watches the PLM credentials and TLS Secrets and rebuilds its storage client when they change, so you can rotate credentials without restarting the pod. + +--- -## Enable WAF in the NginxProxy resource +## Deploy the sample application - +Deploy the `customers` and `orders` sample applications. The `customers` app returns a response containing fake sensitive data (credit card number and SSN), which you'll use later to demonstrate data guard masking: + +```yaml +kubectl apply -f - < +PLM security logging profiles are defined as `APLogConf` custom resources. Create a namespace to hold your security resources, then define a log profile that logs illegal requests: -## Define the WAF policy +```shell +kubectl create namespace security +``` -The `APPolicy` custom resource defines what F5 WAF for NGINX enforces. The `APLogConf` custom resource defines what it logs — see [Configure security logging](#configure-security-logging-optional) for details. The Policy Controller watches both resources and compiles them into bundles stored in the SeaweedFS object store. +```yaml +kubectl apply -f - <}} + +{{%tab name="Inline"%}} + +Create an `APPolicy` resource with an inline policy that blocks all attack signatures: ```yaml +kubectl apply -f - < - namespace: + name: attack-signatures + namespace: security spec: policy: - name: + name: attack-signatures-blocking template: name: POLICY_TEMPLATE_NGINX_BASE applicationLanguage: utf-8 enforcementMode: blocking + signature-sets: + - name: All Signatures + block: true + alarm: true + cookies: + - name: "*" + attackSignaturesCheck: true + enforcementType: enforce + maskValueInLogs: false +EOF ``` -Replace `` and `` with your values. Extend `spec.policy` with the policy fields you want to enforce. - -Apply the resource: +Wait for the bundle to become ready: ```shell -kubectl apply -f .yaml +kubectl wait --for=jsonpath='{.status.bundle.state}'=ready appolicy/attack-signatures -n security --timeout=60s ``` -#### Confirm the policy is ready - -Check the `bundle.state` field to confirm the Policy Controller compiled the policy: +Because the `APPolicy` and `APLogConf` live in the `security` namespace but the `WAFPolicy` you create next targets a Gateway in the `default` namespace, create a `ReferenceGrant` in the `security` namespace to permit the cross-namespace reference: -```shell -kubectl get appolicy \ - --namespace \ - --output jsonpath='State: {.status.bundle.state}{"\n"}Bundle: {.status.bundle.location}{"\n"}Compiler: {.status.bundle.compilerVersion}{"\n"}' -``` - -When compilation succeeds, the output looks like this: - -```text -State: ready -Bundle: s3:///bundles/.tgz -Compiler: +```yaml +kubectl apply -f - <}} Without a matching `ReferenceGrant`, the `WAFPolicy` is rejected with `ResolvedRefs=False` and reason `RefNotPermitted`. If you put the `APPolicy` and `APLogConf` in the same namespace as the `WAFPolicy`, you can skip the `ReferenceGrant`. See [Troubleshoot WAFPolicy status]({{< ref "/ngf/waf-integration/troubleshooting.md" >}}) for details. {{< /call-out >}} -To update an inline policy, edit the `APPolicy` resource and re-apply it. The Policy Controller recompiles the policy when the resource spec changes. +{{% /tab %}} -### Git reference method +{{%tab name="Git reference"%}} -The Git reference method lets you store your policy JSON in a Git repository. The Policy Controller fetches the file and compiles it. +Store your policy JSON in a Git repository and reference it from `APPolicy`. #### Public repository @@ -172,28 +325,334 @@ spec: #### Confirm the policy is ready -Check `bundle.state` as described in [Confirm the policy is ready](#confirm-the-policy-is-ready). +Check `bundle.state`: + +```shell +kubectl get appolicy \ + --namespace \ + --output jsonpath='State: {.status.bundle.state}{"\n"}Bundle: {.status.bundle.location}{"\n"}Compiler: {.status.bundle.compilerVersion}{"\n"}' +``` #### Update a Git-referenced policy The Policy Controller does not poll the Git repository for changes. To pick up changes to the referenced policy file, re-apply the `APPolicy` resource (or update its revision annotation) after you push changes to the repository. -### Precompiled bundle method +{{% /tab %}} + +{{%tab name="Precompiled bundle"%}} {{< include "waf/plm-define-policy-bundle-method.md" >}} +{{% /tab %}} + +{{}} + ## Deploy the Gateway and attach WAFPolicy - +Create a Gateway. WAF is already enabled globally, so NGINX Gateway Fabric automatically deploys the WAF sidecar containers alongside the NGINX Pod: + +```yaml +kubectl apply -f - < +Create two HTTPRoutes — `customers` and `orders` — attached to the Gateway. Because the `WAFPolicy` targets the Gateway, both routes inherit WAF protection automatically: + +```yaml +kubectl apply -f - < +Confirm the `APPolicy` and `APLogConf` bundles compiled successfully: + +```shell +kubectl get appolicy attack-signatures -n security -o jsonpath='{.status.bundle.state}{"\n"}' +kubectl get aplogconf log-illegal -n security -o jsonpath='{.status.bundle.state}{"\n"}' +``` + +Both commands should print `ready`. + +Verify the `WAFPolicy` has been accepted and programmed: + +```shell +kubectl describe wafpolicy gateway-base-protection +``` + +Look for three conditions in the output: + +```text +Status: + Conditions: + Message: The Policy is accepted + Observed Generation: 1 + Reason: Accepted + Status: True + Type: Accepted + Message: All references are resolved + Observed Generation: 1 + Reason: ResolvedRefs + Status: True + Type: ResolvedRefs + Message: Policy is programmed in the data plane + Observed Generation: 1 + Reason: Programmed + Status: True + Type: Programmed +``` + +If any condition is `False`, the message field describes the problem. See [Troubleshoot WAFPolicy status]({{< ref "/ngf/waf-integration/troubleshooting.md" >}}) for guidance. + +Verify that the NGINX Pod has all three containers running: + +```shell +kubectl get pods -l app.kubernetes.io/name=gateway-nginx +``` + +Each NGINX Pod should show `3/3` in the `READY` column, indicating the main NGINX container, `waf-enforcer`, and `waf-config-mgr` are all running: + +```text +NAME READY STATUS RESTARTS AGE +gateway-nginx-7f9b8d6c4d-xxxxx 3/3 Running 0 2m +``` + +--- ## Test deployment and policy enforcement - \ No newline at end of file +Confirm the Gateway was assigned an IP address and reports `Programmed=True`: + +```shell +kubectl describe gateways.gateway.networking.k8s.io gateway +``` + +```text +Addresses: + Type: IPAddress + Value: 10.96.20.187 +``` + +Save the public IP address and port of the Gateway into shell variables: + +```text +GW_IP=XXX.YYY.ZZZ.III +GW_PORT= +``` + +**Verify normal traffic flows.** Send a request to the `customers` route — the response contains the fake sensitive data from the `customers` backend: + +{{< call-out "note" >}} If you have a DNS record allocated for `cafe.example.com`, you can send the request directly to that hostname, without needing to resolve. {{< /call-out >}} + +```shell +curl --resolve cafe.example.com:$GW_PORT:$GW_IP http://cafe.example.com:$GW_PORT/customers +``` + +```text +Customer List: + +Name: John Doe +Credit Card: 4111-1111-1111-1111 +SSN: 123-45-6789 +``` + +The sensitive data passes through because the gateway-level `attack-signatures` policy only inspects inbound requests for attack patterns — it doesn't mask outbound response data. + +**Verify attacks are blocked.** Send a request with a cross-site scripting (XSS) payload: + +```shell +curl --resolve cafe.example.com:$GW_PORT:$GW_IP "http://cafe.example.com:$GW_PORT/customers?x=" +``` + +The WAF detects the attack signature and rejects the request: + +```text + +Request Rejected +... +``` + +**Verify the `orders` route is also protected.** Since the policy targets the Gateway, all attached routes inherit protection: + +```shell +curl --resolve cafe.example.com:$GW_PORT:$GW_IP "http://cafe.example.com:$GW_PORT/orders?x=" +``` + +```text + +Request Rejected +... +``` + +{{< call-out "note" >}} The exact blocking response depends on your WAF policy configuration. Check the security log for a corresponding blocked event using `kubectl logs -c waf-enforcer`. {{< /call-out >}} + +--- + +## Apply a route-level override (optional) + +In the previous step, you saw that the `customers` route returns sensitive data (credit card numbers and SSNs) in the response body. The gateway-level policy blocks inbound attacks, but doesn't inspect outbound responses. + +To protect sensitive data in responses, define a **data guard** `APPolicy` and apply it as a route-level override on the `customers` route: + +```yaml +kubectl apply -f - <}}) for architecture and policy lifecycle concepts. +- [Configure policy sources]({{< ref "/ngf/waf-integration/policy-sources.md" >}}) for the other policy source types. +- [Configure WAF settings]({{< ref "/ngf/waf-integration/configuration.md" >}}) for TLS, authentication, fail-open behavior, and WAF container settings. +- [Troubleshoot WAFPolicy status]({{< ref "/ngf/waf-integration/troubleshooting.md" >}}) if a condition is `False`. diff --git a/content/ngf/waf-integration/overview.md b/content/ngf/waf-integration/overview.md index e4ab13502..432bac8e6 100644 --- a/content/ngf/waf-integration/overview.md +++ b/content/ngf/waf-integration/overview.md @@ -108,16 +108,49 @@ WAF policies must be compiled before they can be applied. Compilation takes a JS ### Source types -The following policy source types are supported, selected via the `spec.type` field on the `WAFPolicy` resource: +Set the source type using the `spec.type` field on the `WAFPolicy` resource: -| Type | Description | -|--------|--------------------------------------------------------------------------------------| -| `NIM` | NGINX Instance Manager — fetched by policy name or UID via NGINX Instance Manager API| -| `N1C` | NGINX One Console — fetched by policy name or object ID via NGINX One Console API | -| `HTTP` | Direct HTTP/HTTPS URL to a compiled bundle file | +| Type | Description | +|--------|---------------------------------------------------------------------------------------------------| +| `NIM` | NGINX Instance Manager — fetched by policy name or UID via NGINX Instance Manager API | +| `N1C` | NGINX One Console — fetched by policy name or object ID via NGINX One Console API | +| `HTTP` | Direct HTTP/HTTPS URL to a compiled bundle file | +| `PLM` | Policy Lifecycle Management — `APPolicy`/`APLogConf` CRDs, fetched from in-cluster storage | + +The `NIM`, `N1C`, and `HTTP` source types reference an externally compiled bundle through `policySource` (and `logSource` for log profiles). They detect updates by polling. The `PLM` source type is Kubernetes-native and event-driven: it references `APPolicy` and `APLogConf` custom resources through `policyRef` (and `logRef`), and doesn't require polling. See [PLM (Policy Lifecycle Management)](#plm-policy-lifecycle-management) below. For details on configuring each source type, see [Configure policy sources]({{< ref "/ngf/waf-integration/policy-sources.md" >}}). +### PLM (Policy Lifecycle Management) + +Policy Lifecycle Management (PLM) is a Kubernetes-native policy source. Instead of pointing NGINX Gateway Fabric at an externally compiled bundle, you define your WAF security posture as `APPolicy` and `APLogConf` custom resources in the cluster. The PLM controller watches these resources, compiles them automatically, and stores the resulting bundles in in-cluster S3-compatible storage. NGINX Gateway Fabric fetches the bundles from that storage and deploys them to the data plane. + +The following table summarizes how PLM differs from the HTTP, NGINX Instance Manager, and NGINX One Console source types: + +| Aspect | HTTP / NIM / N1C | PLM | +|--------------------|-------------------------------------------------|---------------------------------------------------------| +| Policy definition | Authored externally (file/Git, NIM, or N1C) | Authored in-cluster as `APPolicy`/`APLogConf` CRDs | +| Compilation | External (compiler CLI/CI-CD, NIM, or N1C) | Automatic, by the PLM controller | +| Bundle storage | HTTP server, NIM, or N1C | In-cluster S3-compatible storage | +| `WAFPolicy` fields | `policySource` / `logSource` | `policyRef.apPolicyRef` / `logRef.apLogConfRef` | +| Update detection | Polling (checksum or conditional GET) | Event-driven Kubernetes watch (no polling) | +| Authentication | Per-`WAFPolicy` credentials Secret | Cluster-wide PLM storage credentials, set at install | +| Network access | External egress to the policy source | Fully in-cluster | + +At runtime, the flow is: + +```text +Create APPolicy/APLogConf → PLM compiles and sets status.bundle.state: ready → +NGINX Gateway Fabric detects the ready status via watch → fetches the bundle from +in-cluster storage → deploys to the data plane +``` + +Changes to an `APPolicy` or `APLogConf` spec trigger recompilation and an automatic re-fetch — no polling required, and no change to the `WAFPolicy` resource. + +When a `WAFPolicy` references an `APPolicy` or `APLogConf` in a different namespace, create a [ReferenceGrant](https://gateway-api.sigs.k8s.io/api-types/referencegrant/) in the target namespace to permit the reference. + +{{< call-out "note" >}} PLM requires the PLM system to be installed in the cluster and PLM storage access to be set up on NGINX Gateway Fabric at install time. For a complete walkthrough, see [Get started with F5 WAF for NGINX using PLM]({{< ref "/ngf/waf-integration/get-started-plm.md" >}}). {{< /call-out >}} + --- ## Policy attachment @@ -142,6 +175,7 @@ Route-level WAFPolicy → Overrides Gateway-level for that route only ## See also - [Get started with F5 WAF for NGINX]({{< ref "/ngf/waf-integration/get-started-http.md" >}}) +- [Get started with F5 WAF for NGINX using PLM]({{< ref "/ngf/waf-integration/get-started-plm.md" >}}) - [Configure policy sources (NGINX Instance Manager, NGINX One Console, and HTTP)]({{< ref "/ngf/waf-integration/policy-sources.md" >}}) - [Configure WAF settings]({{< ref "/ngf/waf-integration/configuration.md" >}}) - [WAFPolicy and NginxProxy API reference]({{< ref "/ngf/reference/api.md" >}}) diff --git a/content/ngf/waf-integration/policy-sources.md b/content/ngf/waf-integration/policy-sources.md index 75b879626..9eec47c0d 100644 --- a/content/ngf/waf-integration/policy-sources.md +++ b/content/ngf/waf-integration/policy-sources.md @@ -7,7 +7,7 @@ f5-product: NGINX Gateway Fabric f5-description: Configure WAFPolicy to fetch compiled bundles from F5 NGINX Instance Manager, F5 NGINX One Console, or an HTTP server. --- -NGINX Gateway Fabric supports three policy source types for fetching compiled WAF bundles: F5 NGINX Instance Manager, F5 NGINX One Console, and direct HTTP/HTTPS URLs. For a quick start walkthrough using the HTTP source, see [Get started with F5 WAF for NGINX]({{< ref "/ngf/waf-integration/get-started-http.md" >}}). +NGINX Gateway Fabric supports four policy source types for fetching compiled WAF bundles: F5 NGINX Instance Manager, F5 NGINX One Console, direct HTTP/HTTPS URLs, and Policy Lifecycle Management (PLM). For a quick start walkthrough using the HTTP source, see [Get started with F5 WAF for NGINX]({{< ref "/ngf/waf-integration/get-started-http.md" >}}). For a walkthrough using PLM, see [Get started with F5 WAF for NGINX using PLM]({{< ref "/ngf/waf-integration/get-started-plm.md" >}}). Before configuring a policy source, ensure that WAF is [enabled on the NginxProxy]({{< ref "/ngf/waf-integration/overview.md#enable-waf-on-the-nginxproxy" >}}) — either per Gateway or globally via Helm values. @@ -216,6 +216,14 @@ For production environments, you would typically host compiled bundles on an HTT --- +## Policy Lifecycle Management (PLM) + +Use this option when you manage WAF policies as Kubernetes resources with Policy Lifecycle Management (PLM). With PLM, you define your security posture as `APPolicy` and `APLogConf` custom resources instead of `policySource`/`logSource`; the PLM controller compiles them automatically and stores the resulting bundles in in-cluster storage. NGINX Gateway Fabric fetches those bundles and deploys them to the data plane. Because PLM is event-driven, no per-`WAFPolicy` credentials Secret or polling configuration is needed. + +For a comparison of PLM with the other source types, see [PLM (Policy Lifecycle Management)]({{< ref "/ngf/waf-integration/overview.md#plm-policy-lifecycle-management" >}}). For a complete walkthrough — including PLM storage setup, defining `APPolicy`/`APLogConf` resources, and applying a `WAFPolicy` — see [Get started with F5 WAF for NGINX using PLM]({{< ref "/ngf/waf-integration/get-started-plm.md" >}}). + +--- + ## Management console visibility When using NGINX Instance Manager or NGINX One Console as your policy source, be aware that neither management console currently displays WAF policy deployments to NGINX Gateway Fabric, nor does it show which compiled bundle versions NGINX Gateway Fabric has fetched. @@ -279,6 +287,8 @@ The `localhost:1514` syslog destination points to the NGINX Agent's OpenTelemetr ## See also - [F5 WAF for NGINX overview]({{< ref "/ngf/waf-integration/overview.md" >}}) +- [Get started with F5 WAF for NGINX]({{< ref "/ngf/waf-integration/get-started-http.md" >}}) +- [Get started with F5 WAF for NGINX using PLM]({{< ref "/ngf/waf-integration/get-started-plm.md" >}}) - [Configure WAF settings]({{< ref "/ngf/waf-integration/configuration.md" >}}) - [Troubleshoot WAFPolicy status]({{< ref "/ngf/waf-integration/troubleshooting.md" >}}) - [WAFPolicy and NginxProxy API reference]({{< ref "/ngf/reference/api.md" >}}) From 51b563fb4c597b3543d7241ef2804c597a0da62a Mon Sep 17 00:00:00 2001 From: Travis Martin Date: Mon, 10 Aug 2026 13:11:12 -0700 Subject: [PATCH 07/50] fix: Use concrete example values in PLM infrastructure include Address PR feedback (sjberman): avoid placeholder values where possible so commands are copy-pasteable. - Namespace: plm-system (matches plm-storage-service.plm-system URL already used in the NGF guide) - Release name: plm - Version: {{< version-waf-policy-controller >}} shortcode (already exists at 5.14.0; auto-updates with future releases) Sensitive values retained as placeholders: , , . --- .../includes/waf/plm-deploy-infrastructure.md | 58 +++++++++---------- 1 file changed, 27 insertions(+), 31 deletions(-) diff --git a/content/includes/waf/plm-deploy-infrastructure.md b/content/includes/waf/plm-deploy-infrastructure.md index fa04651b4..692009dc0 100644 --- a/content/includes/waf/plm-deploy-infrastructure.md +++ b/content/includes/waf/plm-deploy-infrastructure.md @@ -34,17 +34,15 @@ apusersigs.appprotect.f5.com ### Create the registry pull secret -Create a namespace for the PLM components, then create the registry pull secret using the credentials from the previous section. - -Replace `` with your chosen namespace name, and `` with your F5 WAF for NGINX JWT. +Create a namespace for the PLM components, then create the registry pull secret using the credentials from the previous section. Replace `` with your F5 WAF for NGINX JWT. ```shell -kubectl create namespace +kubectl create namespace plm-system kubectl create secret docker-registry regcred \ - --namespace \ + --namespace plm-system \ --docker-server=private-registry.nginx.com \ --docker-username= \ --docker-password=none \ @@ -53,9 +51,7 @@ kubectl create secret docker-registry regcred \ ### Install the Policy Controller -Create a values file for the Helm installation. Replace ``, ``, ``, ``, and `` with your values. `` is the Helm chart version (for example, `5.14.0`). `` is the container image tag, which typically matches the chart version. - -`` and `` are the base64-encoded contents of your `nginx-repo.crt` and `nginx-repo.key` files. To encode them, run: +Create a values file for the Helm installation. Replace `` and `` with the base64-encoded contents of your `nginx-repo.crt` and `nginx-repo.key` files. To encode them, run: ```shell base64 --wrap=0 < nginx-repo.crt @@ -72,17 +68,17 @@ securityUpdatesRepo: key: "" policyController: image: - tag: "" + tag: "{{< version-waf-policy-controller >}}" compiler: image: - tag: "" + tag: "{{< version-waf-policy-controller >}}" seaweedfsOperatorConfig: seaweedfs: image: - tag: "" + tag: "{{< version-waf-policy-controller >}}" seaweedfs-operator: image: - tag: "" + tag: "{{< version-waf-policy-controller >}}" pullSecrets: regcred ``` @@ -92,14 +88,12 @@ Add the NGINX Helm repository and install the chart: helm repo add nginx-stable https://helm.nginx.com/stable helm repo update nginx-stable -helm upgrade --install nginx-stable/f5-waf-policy-controller \ - --version \ - --namespace \ +helm upgrade --install plm nginx-stable/f5-waf-policy-controller \ + --version {{< version-waf-policy-controller >}} \ + --namespace plm-system \ --values /tmp/plm-values.yaml ``` -Replace `` with a name for the Helm release (for example, `plm`). - ### Verify the deployment Wait for all PLM components to become ready. The Policy Controller's init container waits for both the compiler service and the SeaweedFS S3 endpoint to be available before it starts, so the controller pod will show `Init:0/1` until SeaweedFS is ready. @@ -107,41 +101,41 @@ Wait for all PLM components to become ready. The Policy Controller's init contai Wait for the SeaweedFS storage backend: ```shell -kubectl rollout status deployment/-seaweedfs-operator \ - --namespace --timeout=120s +kubectl rollout status deployment/plm-seaweedfs-operator \ + --namespace plm-system --timeout=120s kubectl wait pods \ --selector app.kubernetes.io/name=seaweedfs \ --for=condition=Ready \ - --namespace \ + --namespace plm-system \ --timeout=180s ``` Wait for the Policy Controller: ```shell -kubectl rollout status deployment/-f5-waf-policy-controller \ - --namespace --timeout=180s +kubectl rollout status deployment/plm-f5-waf-policy-controller \ + --namespace plm-system --timeout=180s ``` Confirm all eight pods are running: ```shell -kubectl get pods --namespace +kubectl get pods --namespace plm-system ``` Expected output: ```text NAME READY STATUS RESTARTS --f5-waf-compiler-service-xxxxx 1/1 Running 0 --f5-waf-policy-controller-xxxxx 1/1 Running 0 --seaweedfs-operator-xxxxx 1/1 Running 0 --f5-waf-seaweed-master-0 1/1 Running 0 --f5-waf-seaweed-filer-0 1/1 Running 0 --f5-waf-seaweed-volume-0 1/1 Running 0 --f5-waf-seaweed-volume-1 1/1 Running 0 --f5-waf-seaweed-volume-2 1/1 Running 0 +plm-f5-waf-compiler-service-xxxxx 1/1 Running 0 +plm-f5-waf-policy-controller-xxxxx 1/1 Running 0 +plm-seaweedfs-operator-xxxxx 1/1 Running 0 +plm-f5-waf-seaweed-master-0 1/1 Running 0 +plm-f5-waf-seaweed-filer-0 1/1 Running 0 +plm-f5-waf-seaweed-volume-0 1/1 Running 0 +plm-f5-waf-seaweed-volume-1 1/1 Running 0 +plm-f5-waf-seaweed-volume-2 1/1 Running 0 ``` Confirm the four CRDs are present: @@ -149,3 +143,5 @@ Confirm the four CRDs are present: ```shell kubectl get crd | grep appprotect.f5.com ``` + +All eight pods running and all four CRDs present confirms the PLM backend is ready. From 10c6283bbdfa1ffc776c43ef103fc4df60d91400 Mon Sep 17 00:00:00 2001 From: Travis Martin Date: Mon, 10 Aug 2026 13:36:40 -0700 Subject: [PATCH 08/50] style: Copy edit NGF WAF integration docs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Apply F5 style guide rules across four files: - get-started-plm.md - overview.md - policy-sources.md - cli-help.md (lines 47-51, PLM storage flags) Changes applied: - em-dash: replace all em dashes with commas, periods, colons, or parentheses; use -- only where unavoidable - latin-abbreviations: via → through/using; since (causal) → because - ensure-vs-make-sure: ensure → make sure - contractions: does not/cannot → doesn't/can't per Modern Voice - possessives: PLM's in-cluster storage → in-cluster PLM storage - word-list: ensures that → means; enables → results in - placeholders: / → / per formatting rules - sensitive-information: non-RFC-5737 IP replaced with 192.0.2.1 - f5-product-names: PLM storage flag descriptions spell out Policy Lifecycle Manager on first mention - Removed redundant heading: NGINX Instance Manager (NGINX Instance Manager) → NGINX Instance Manager - Updated f5-description in policy-sources.md to include PLM --- content/ngf/reference/cli-help.md | 10 +++---- .../ngf/waf-integration/get-started-plm.md | 16 ++++++------ content/ngf/waf-integration/overview.md | 26 +++++++++---------- content/ngf/waf-integration/policy-sources.md | 20 +++++++------- 4 files changed, 36 insertions(+), 36 deletions(-) diff --git a/content/ngf/reference/cli-help.md b/content/ngf/reference/cli-help.md index f10aee54c..d0a575b21 100644 --- a/content/ngf/reference/cli-help.md +++ b/content/ngf/reference/cli-help.md @@ -44,11 +44,11 @@ This command runs the NGINX Gateway Fabric control plane. | _leader-election-lock-name_ | _string_ | The name of the leader election lock. A lease object with this name will be created in the same namespace as the controller (Default: `"nginx-gateway-leader-election-lock"`). | | _product-telemetry-disable_ | _bool_ | Disable the collection of product telemetry (Default: `false`). | | _nginx-docker-secret_ | _list_ | The name of the NGINX docker registry Secret(s). Must exist in the same namespace that the NGINX Gateway Fabric control plane is running in (default namespace: nginx-gateway). | -| _plm-storage-url_ | _string_ | PLM storage service URL (HTTP or HTTPS). | -| _plm-storage-credentials-secret_ | _string_ | The name of the Secret containing the PLM storage S3 secret access key (`seaweedfs_admin_secret`). Must exist in the same namespace that the NGINX Gateway Fabric control plane is running in (default namespace: nginx-gateway), unless prefixed with `/`. | -| _plm-storage-ca-secret_ | _string_ | The name of the Secret containing the CA certificate (`ca.crt`) for PLM storage TLS server verification. Must exist in the same namespace that the NGINX Gateway Fabric control plane is running in (default namespace: nginx-gateway), unless prefixed with `/`. | -| _plm-storage-client-ssl-secret_ | _string_ | The name of the Secret containing the client certificate and key (`tls.crt`/`tls.key`) for mutual TLS to PLM storage. Must exist in the same namespace that the NGINX Gateway Fabric control plane is running in (default namespace: nginx-gateway), unless prefixed with `/`. | -| _plm-storage-skip-verify_ | _bool_ | Disable TLS certificate verification for PLM storage connections. Use for testing only. | +| _plm-storage-url_ | _string_ | The URL of the Policy Lifecycle Manager (PLM) storage service (HTTP or HTTPS). | +| _plm-storage-credentials-secret_ | _string_ | The name of the Secret containing the PLM storage S3 secret access key (`seaweedfs_admin_secret`). Must exist in the same namespace as the NGINX Gateway Fabric control plane (default: `nginx-gateway`), unless prefixed with `/`. | +| _plm-storage-ca-secret_ | _string_ | The name of the Secret containing the CA certificate (`ca.crt`) for verifying the PLM storage TLS server certificate. Must exist in the same namespace as the NGINX Gateway Fabric control plane (default: `nginx-gateway`), unless prefixed with `/`. | +| _plm-storage-client-ssl-secret_ | _string_ | The name of the Secret containing the client certificate and key (`tls.crt`/`tls.key`) for mutual TLS with PLM storage. Must exist in the same namespace as the NGINX Gateway Fabric control plane (default: `nginx-gateway`), unless prefixed with `/`. | +| _plm-storage-skip-verify_ | _bool_ | Disable TLS certificate verification for PLM storage connections. Use for testing only (Default: `false`). | | _usage-report-secret_ | _string_ | 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) | | _usage-report-endpoint_ | _string_ | The endpoint of the NGINX Plus usage reporting server. | | _usage-report-resolver_ | _string_ | The nameserver used to resolve the NGINX Plus usage reporting endpoint. Used with NGINX Instance Manager. | diff --git a/content/ngf/waf-integration/get-started-plm.md b/content/ngf/waf-integration/get-started-plm.md index ae9d9d45b..edeb777b3 100644 --- a/content/ngf/waf-integration/get-started-plm.md +++ b/content/ngf/waf-integration/get-started-plm.md @@ -30,7 +30,7 @@ PLM is one of four WAF policy source types. With PLM, you define your security p ## Configure NGF to connect to PLM storage -NGINX Gateway Fabric fetches compiled bundles from PLM's in-cluster storage. You set up storage access once, cluster-wide, at install time — it applies to every `WAFPolicy` that uses `type: PLM`. +NGINX Gateway Fabric fetches compiled bundles from in-cluster PLM storage. You set up storage access once, cluster-wide, at install time. It applies to every `WAFPolicy` that uses `type: PLM`. Create a `values.yaml` file that enables WAF and sets the PLM storage connection details under `nginxGateway.plmStorage`: @@ -56,7 +56,7 @@ nginxGateway: {{< call-out "caution" >}} Always use HTTPS with TLS verification (`caSecretName`) in production. Add `clientSSLSecretName` for mutual TLS in high-security environments, and never set `insecureSkipVerify: true`. {{< /call-out >}} -{{< call-out "note" >}} `credentialsSecretName` and `caSecretName` must reference Secrets in the NGINX Gateway Fabric control plane namespace, unless you prefix them with `/`. {{< /call-out >}} +{{< call-out "note" >}} `credentialsSecretName` and `caSecretName` must reference Secrets in the NGINX Gateway Fabric control plane namespace, unless you prefix them with `/`. {{< /call-out >}} Install NGINX Gateway Fabric by following [the installation guide]({{< ref "/ngf/install/helm.md" >}}) and using the **NGINX Plus with WAF** tab, and apply this `values.yaml` file in your install or upgrade command, specifying `--values values.yaml`. @@ -335,7 +335,7 @@ kubectl get appolicy \ #### Update a Git-referenced policy -The Policy Controller does not poll the Git repository for changes. To pick up changes to the referenced policy file, re-apply the `APPolicy` resource (or update its revision annotation) after you push changes to the repository. +The Policy Controller doesn't poll the Git repository for changes. To pick up changes to the referenced policy file, re-apply the `APPolicy` resource (or update its revision annotation) after you push changes to the repository. {{% /tab %}} @@ -397,13 +397,13 @@ spec: EOF ``` -This `WAFPolicy` protects every route attached to the Gateway. Later changes to the `APPolicy` or `APLogConf` spec trigger recompilation and an automatic re-fetch — no change to the `WAFPolicy` is required. +This `WAFPolicy` protects every route attached to the Gateway. Later changes to the `APPolicy` or `APLogConf` spec trigger recompilation and an automatic re-fetch. No change to the `WAFPolicy` is required. --- ## Configure HTTPRoutes -Create two HTTPRoutes — `customers` and `orders` — attached to the Gateway. Because the `WAFPolicy` targets the Gateway, both routes inherit WAF protection automatically: +Create two HTTPRoutes (`customers` and `orders`) attached to the Gateway. Because the `WAFPolicy` targets the Gateway, both routes inherit WAF protection automatically: ```yaml kubectl apply -f - <" diff --git a/content/ngf/waf-integration/overview.md b/content/ngf/waf-integration/overview.md index 432bac8e6..5ea6ff0f2 100644 --- a/content/ngf/waf-integration/overview.md +++ b/content/ngf/waf-integration/overview.md @@ -7,7 +7,7 @@ f5-product: NGINX Gateway Fabric f5-description: Architecture, setup, and concepts for F5 WAF for NGINX integration in NGINX Gateway Fabric. --- -F5 NGINX Gateway Fabric integrates with F5 WAF for NGINX to provide enterprise-grade web application firewall protection. WAF policies are compiled externally and deployed to the data plane via the `WAFPolicy` custom resource. +F5 NGINX Gateway Fabric integrates with F5 WAF for NGINX to provide web application firewall protection. WAF policies are compiled externally and deployed to the data plane using the `WAFPolicy` custom resource. {{< call-out class="note" >}} F5 WAF for NGINX requires NGINX Plus and a separate F5 WAF for NGINX subscription. Contact your F5 sales representative for licensing details. {{< /call-out >}} @@ -42,22 +42,22 @@ WAF is enabled by setting `waf.enable: true` on an `NginxProxy` resource. This i You can enable WAF at two levels: -- **All Gateways** — Set WAF on the GatewayClass-level `NginxProxy` so that every Gateway managed by this NGINX Gateway Fabric instance gets WAF sidecars by default. A per-Gateway `NginxProxy` can override this (for example, to disable WAF on a specific Gateway). -- **Per Gateway** — Create an `NginxProxy` and reference it from a Gateway's `spec.infrastructure.parametersRef`. Only that Gateway gets WAF sidecars. +- **All Gateways** -- Set WAF on the GatewayClass-level `NginxProxy` so that every Gateway managed by this NGINX Gateway Fabric instance gets WAF sidecars by default. A per-Gateway `NginxProxy` can override this (for example, to disable WAF on a specific Gateway). +- **Per Gateway** -- Create an `NginxProxy` and reference it from a Gateway's `spec.infrastructure.parametersRef`. Only that Gateway gets WAF sidecars. For details on how GatewayClass and Gateway-level NginxProxy settings are merged, see [Data plane configuration]({{< ref "/ngf/how-to/data-plane-configuration.md" >}}). ### Enable WAF for all Gateways -To enable WAF at install time use the **NGINX Plus with WAF** tab in the [Helm install guide]({{< ref "/ngf/install/helm.md" >}}). This sets the WAF-enabled NGINX Plus image (`nginx-plus-f5waf`) and enables WAF on the GatewayClass-level `NginxProxy`, so every Gateway gets WAF sidecars by default. +To enable WAF at install time, use the **NGINX Plus with WAF** tab in the [Helm install guide]({{< ref "/ngf/install/helm.md" >}}). This sets the WAF-enabled NGINX Plus image (`nginx-plus-f5waf`) and enables WAF on the GatewayClass-level `NginxProxy`, so every Gateway gets WAF sidecars by default. To disable WAF for a specific Gateway, create a per-Gateway `NginxProxy` with `waf.enable: false` and reference it from that Gateway. -{{< call-out class="note" >}} For additional WAF-related NginxProxy settings — including `disableCookieSeed`, `bundleFailOpen`, and custom WAF container images — see [Configure WAF settings]({{< ref "/ngf/waf-integration/configuration.md" >}}). {{< /call-out >}} +{{< call-out class="note" >}} For additional WAF-related NginxProxy settings (including `disableCookieSeed`, `bundleFailOpen`, and custom WAF container images), see [Configure WAF settings]({{< ref "/ngf/waf-integration/configuration.md" >}}). {{< /call-out >}} ### Enable WAF per Gateway -If you installed with the standard NGINX Plus image and want WAF on a specific Gateway only, create a per-Gateway `NginxProxy`. You must also set the NGINX image to `nginx-plus-f5waf`, since the standard `nginx-plus` image inherited from the GatewayClass does not include the WAF module: +If you installed with the standard NGINX Plus image and want WAF on a specific Gateway only, create a per-Gateway `NginxProxy`. You must also set the NGINX image to `nginx-plus-f5waf`, because the standard `nginx-plus` image inherited from the GatewayClass doesn't include the WAF module: ```yaml apiVersion: gateway.nginx.org/v1alpha2 @@ -100,11 +100,11 @@ For the full list of available images, see [Supported container images]({{< ref ### Bundles -A WAF bundle is a compiled policy package produced by the [F5 WAF for NGINX compiler]({{< ref "/waf/configure/compiler.md" >}}). It contains the security policy, optional logging profile, [attack signatures]({{< ref "/waf/policies/attack-signatures.md" >}}), [threat campaign]({{< ref "/waf/policies/threat-campaigns.md" >}}) data, [bot signatures]({{< ref "/waf/policies/bot-signatures.md" >}}), and related metadata in a format that the WAF engine can load and enforce at runtime. Pre-compiling policies into bundles enables faster, more reliable WAF startup — policies are resolved and validated at build time rather than on the running data plane. +A WAF bundle is a compiled policy package produced by the [F5 WAF for NGINX compiler]({{< ref "/waf/configure/compiler.md" >}}). It contains the security policy, optional logging profile, [attack signatures]({{< ref "/waf/policies/attack-signatures.md" >}}), [threat campaign]({{< ref "/waf/policies/threat-campaigns.md" >}}) data, [bot signatures]({{< ref "/waf/policies/bot-signatures.md" >}}), and related metadata in a format that the WAF engine can load and enforce at runtime. Pre-compiling policies into bundles results in faster, more reliable WAF startup: policies are resolved and validated at build time rather than on the running data plane. ### Compilation -WAF policies must be compiled before they can be applied. Compilation takes a JSON policy definition (and optionally [global settings]({{< ref "/waf/configure/compiler.md" >}}) such as a cookie seed and [user-defined signatures]({{< ref "/waf/policies/user-signatures.md" >}})) and produces a `.tgz` bundle. NGINX Gateway Fabric does not compile policies — its role begins at fetching a compiled bundle and deploying it to the data plane. +WAF policies must be compiled before they can be applied. Compilation takes a JSON policy definition (and optionally [global settings]({{< ref "/waf/configure/compiler.md" >}}) such as a cookie seed and [user-defined signatures]({{< ref "/waf/policies/user-signatures.md" >}})) and produces a `.tgz` bundle. NGINX Gateway Fabric doesn't compile policies. Its role begins at fetching a compiled bundle and deploying it to the data plane. ### Source types @@ -112,10 +112,10 @@ Set the source type using the `spec.type` field on the `WAFPolicy` resource: | Type | Description | |--------|---------------------------------------------------------------------------------------------------| -| `NIM` | NGINX Instance Manager — fetched by policy name or UID via NGINX Instance Manager API | -| `N1C` | NGINX One Console — fetched by policy name or object ID via NGINX One Console API | +| `NIM` | NGINX Instance Manager -- fetched by policy name or UID using the NGINX Instance Manager API | +| `N1C` | NGINX One Console -- fetched by policy name or object ID using the NGINX One Console API | | `HTTP` | Direct HTTP/HTTPS URL to a compiled bundle file | -| `PLM` | Policy Lifecycle Management — `APPolicy`/`APLogConf` CRDs, fetched from in-cluster storage | +| `PLM` | Policy Lifecycle Management -- `APPolicy`/`APLogConf` CRDs, fetched from in-cluster storage | The `NIM`, `N1C`, and `HTTP` source types reference an externally compiled bundle through `policySource` (and `logSource` for log profiles). They detect updates by polling. The `PLM` source type is Kubernetes-native and event-driven: it references `APPolicy` and `APLogConf` custom resources through `policyRef` (and `logRef`), and doesn't require polling. See [PLM (Policy Lifecycle Management)](#plm-policy-lifecycle-management) below. @@ -145,7 +145,7 @@ NGINX Gateway Fabric detects the ready status via watch → fetches the bundle f in-cluster storage → deploys to the data plane ``` -Changes to an `APPolicy` or `APLogConf` spec trigger recompilation and an automatic re-fetch — no polling required, and no change to the `WAFPolicy` resource. +Changes to an `APPolicy` or `APLogConf` spec trigger recompilation and an automatic re-fetch. No polling is required, and no change to the `WAFPolicy` resource is needed. When a `WAFPolicy` references an `APPolicy` or `APLogConf` in a different namespace, create a [ReferenceGrant](https://gateway-api.sigs.k8s.io/api-types/referencegrant/) in the target namespace to permit the reference. @@ -159,7 +159,7 @@ When a `WAFPolicy` references an `APPolicy` or `APLogConf` in a different namesp - A **Gateway-level** `WAFPolicy` protects all HTTPRoutes and GRPCRoutes attached to that Gateway automatically. New routes inherit protection without any additional configuration. - A **Route-level** `WAFPolicy` can be applied to a specific HTTPRoute or GRPCRoute to override the Gateway-level policy for that route. -- More specific (route-level) policies take precedence over less specific (gateway-level) policies. The route-level policy completely replaces the gateway-level policy for that route — there is no merging. +- More specific (route-level) policies take precedence over less specific (gateway-level) policies. The route-level policy completely replaces the gateway-level policy for that route. There's no merging. - Only one `WAFPolicy` may target a given resource at a given level. If two policies target the same Gateway or Route, the second is rejected with `Accepted=False` and reason `Conflicted`. ```text diff --git a/content/ngf/waf-integration/policy-sources.md b/content/ngf/waf-integration/policy-sources.md index 9eec47c0d..2b14363b5 100644 --- a/content/ngf/waf-integration/policy-sources.md +++ b/content/ngf/waf-integration/policy-sources.md @@ -4,24 +4,24 @@ weight: 300 toc: true f5-content-type: how-to f5-product: NGINX Gateway Fabric -f5-description: Configure WAFPolicy to fetch compiled bundles from F5 NGINX Instance Manager, F5 NGINX One Console, or an HTTP server. +f5-description: Configure WAFPolicy to fetch compiled bundles from F5 NGINX Instance Manager, F5 NGINX One Console, an HTTP server, or Policy Lifecycle Management. --- NGINX Gateway Fabric supports four policy source types for fetching compiled WAF bundles: F5 NGINX Instance Manager, F5 NGINX One Console, direct HTTP/HTTPS URLs, and Policy Lifecycle Management (PLM). For a quick start walkthrough using the HTTP source, see [Get started with F5 WAF for NGINX]({{< ref "/ngf/waf-integration/get-started-http.md" >}}). For a walkthrough using PLM, see [Get started with F5 WAF for NGINX using PLM]({{< ref "/ngf/waf-integration/get-started-plm.md" >}}). -Before configuring a policy source, ensure that WAF is [enabled on the NginxProxy]({{< ref "/ngf/waf-integration/overview.md#enable-waf-on-the-nginxproxy" >}}) — either per Gateway or globally via Helm values. +Before configuring a policy source, make sure WAF is [enabled on the NginxProxy]({{< ref "/ngf/waf-integration/overview.md#enable-waf-on-the-nginxproxy" >}}), either per Gateway or globally through Helm values. {{< call-out class="tip" >}} By default, NGINX Gateway Fabric retries transient fetch failures up to 3 times with exponential backoff, and each fetch attempt times out after 30 seconds. You can tune these using the `retryAttempts` and `timeout` fields on `policySource` or `logSource`. {{< /call-out >}} --- -## NGINX Instance Manager (NGINX Instance Manager) +## NGINX Instance Manager Use this option when you manage WAF policies through NGINX Instance Manager. For details on creating and compiling policies in NGINX Instance Manager, see [How WAF policy management works]({{< ref "/nim/waf-integration/overview.md" >}}) and [Create a security policy bundle]({{< ref "/nim/waf-integration/policies-and-logs/bundles/create-bundle.md" >}}). **Workflow:** -1. Author and compile a policy in NGINX Instance Manager using the NGINX Instance Manager console or API. Verify that compilation succeeded before proceeding — NGINX Gateway Fabric cannot detect compilation failures in NGINX Instance Manager. +1. Author and compile a policy in NGINX Instance Manager using the NGINX Instance Manager console or API. Verify that compilation succeeded before proceeding. NGINX Gateway Fabric can't detect compilation failures in NGINX Instance Manager. 2. Create a Secret with your NGINX Instance Manager credentials. 3. Create a `WAFPolicy` referencing the compiled policy by name. @@ -98,7 +98,7 @@ Replace `https://nim.example.com` with your NGINX Instance Manager base URL, and ### Apply a route-level override (optional) -To apply a different policy to a specific route — for example, a data-guard policy — create a route-level `WAFPolicy`: +To apply a different policy to a specific route (for example, a data-guard policy), create a route-level `WAFPolicy`: ```yaml kubectl apply -f - <}}). For a complete walkthrough — including PLM storage setup, defining `APPolicy`/`APLogConf` resources, and applying a `WAFPolicy` — see [Get started with F5 WAF for NGINX using PLM]({{< ref "/ngf/waf-integration/get-started-plm.md" >}}). +For a comparison of PLM with the other source types, see [PLM (Policy Lifecycle Management)]({{< ref "/ngf/waf-integration/overview.md#plm-policy-lifecycle-management" >}}). For a complete walkthrough (including PLM storage setup, defining `APPolicy`/`APLogConf` resources, and applying a `WAFPolicy`), see [Get started with F5 WAF for NGINX using PLM]({{< ref "/ngf/waf-integration/get-started-plm.md" >}}). --- @@ -228,19 +228,19 @@ For a comparison of PLM with the other source types, see [PLM (Policy Lifecycle When using NGINX Instance Manager or NGINX One Console as your policy source, be aware that neither management console currently displays WAF policy deployments to NGINX Gateway Fabric, nor does it show which compiled bundle versions NGINX Gateway Fabric has fetched. -This is by design: NGINX Gateway Fabric pulls compiled bundles from the management plane using a pull model and deploys them directly in Kubernetes using native Kubernetes manifests, rather than through the NGINX Instance Manager or NGINX One Console console. This workflow ensures that policies can be created, compiled, and made available to NGINX Gateway Fabric via API without requiring console-managed deployment flows. +This is by design: NGINX Gateway Fabric pulls compiled bundles from the management plane using a pull model and deploys them directly in Kubernetes using native Kubernetes manifests, rather than through the NGINX Instance Manager or NGINX One Console console. This workflow means policies can be created, compiled, and made available to NGINX Gateway Fabric through an API without requiring console-managed deployment flows. Policy association visibility for NGINX Instance Manager and NGINX One Console will be added in a future release. In the meantime, use `kubectl describe wafpolicy ` to check deployment status. ### Connect NGINX Gateway Fabric to F5 NGINX One Console -Ensure that NGINX Gateway Fabric is configured to connect to NGINX One Console. Follow the guidance at [Connect NGINX Gateway Fabric with Helm]({{< ref "/nginx-one-console/k8s/add-ngf-helm.md" >}}) or [Connect NGINX Gateway Fabric with Manifests]({{< ref "/nginx-one-console/k8s/add-ngf-manifests.md" >}}) before continuing. +Make sure NGINX Gateway Fabric is configured to connect to NGINX One Console. Follow the guidance at [Connect NGINX Gateway Fabric with Helm]({{< ref "/nginx-one-console/k8s/add-ngf-helm.md" >}}) or [Connect NGINX Gateway Fabric with Manifests]({{< ref "/nginx-one-console/k8s/add-ngf-manifests.md" >}}) before continuing. ### Export security logs to F5 NGINX One Console Although the NGINX One Console console does not display which policies are deployed to NGINX Gateway Fabric data planes, you can export WAF security events to the NGINX One Console security dashboard. This gives your security operations team visibility into blocked attacks, violations, and traffic patterns directly in the console. -To enable this, configure a `securityLogs` entry that sends events to the NGINX Agent's built-in OpenTelemetry collector, which forwards them to NGINX One Console. Use a log profile compiled for the NGINX One Console security dashboard: +To set this up, configure a `securityLogs` entry that sends events to the NGINX Agent's built-in OpenTelemetry collector, which forwards them to NGINX One Console. Use a log profile compiled for the NGINX One Console security dashboard: ```yaml kubectl apply -f - < Date: Mon, 10 Aug 2026 14:00:48 -0700 Subject: [PATCH 09/50] style: Second copy edit pass on NGF WAF integration docs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - possessives: fix PLM's, NGINX Agent's (product name possessives recasted using 'of' or restructured) - em-dash: fix reverted em dash in get-started-plm.md - sentence-length: split long Bundles sentence in overview.md - grammar: 'begins at fetching' → 'begins with fetching' - active-voice: recast passive 'to be set up' → 'to be configured' in overview.md call-out - possessives: Gateway's parametersRef → parametersRef field on a Gateway (inanimate object restructured) --- content/ngf/waf-integration/get-started-plm.md | 12 ++++++------ content/ngf/waf-integration/overview.md | 8 ++++---- content/ngf/waf-integration/policy-sources.md | 4 ++-- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/content/ngf/waf-integration/get-started-plm.md b/content/ngf/waf-integration/get-started-plm.md index edeb777b3..1b97c044a 100644 --- a/content/ngf/waf-integration/get-started-plm.md +++ b/content/ngf/waf-integration/get-started-plm.md @@ -335,7 +335,7 @@ kubectl get appolicy \ #### Update a Git-referenced policy -The Policy Controller doesn't poll the Git repository for changes. To pick up changes to the referenced policy file, re-apply the `APPolicy` resource (or update its revision annotation) after you push changes to the repository. +The Policy Controller does not poll the Git repository for changes. To pick up changes to the referenced policy file, re-apply the `APPolicy` resource (or update its revision annotation) after you push changes to the repository. {{% /tab %}} @@ -397,13 +397,13 @@ spec: EOF ``` -This `WAFPolicy` protects every route attached to the Gateway. Later changes to the `APPolicy` or `APLogConf` spec trigger recompilation and an automatic re-fetch. No change to the `WAFPolicy` is required. +This `WAFPolicy` protects every route attached to the Gateway. Later changes to the `APPolicy` or `APLogConf` spec trigger recompilation and an automatic re-fetch — no change to the `WAFPolicy` is required. --- ## Configure HTTPRoutes -Create two HTTPRoutes (`customers` and `orders`) attached to the Gateway. Because the `WAFPolicy` targets the Gateway, both routes inherit WAF protection automatically: +Create two HTTPRoutes — `customers` and `orders` — attached to the Gateway. Because the `WAFPolicy` targets the Gateway, both routes inherit WAF protection automatically: ```yaml kubectl apply -f - <" diff --git a/content/ngf/waf-integration/overview.md b/content/ngf/waf-integration/overview.md index 5ea6ff0f2..b825330f6 100644 --- a/content/ngf/waf-integration/overview.md +++ b/content/ngf/waf-integration/overview.md @@ -43,7 +43,7 @@ WAF is enabled by setting `waf.enable: true` on an `NginxProxy` resource. This i You can enable WAF at two levels: - **All Gateways** -- Set WAF on the GatewayClass-level `NginxProxy` so that every Gateway managed by this NGINX Gateway Fabric instance gets WAF sidecars by default. A per-Gateway `NginxProxy` can override this (for example, to disable WAF on a specific Gateway). -- **Per Gateway** -- Create an `NginxProxy` and reference it from a Gateway's `spec.infrastructure.parametersRef`. Only that Gateway gets WAF sidecars. +- **Per Gateway** -- Create an `NginxProxy` and reference it from the `spec.infrastructure.parametersRef` field on a Gateway. Only that Gateway gets WAF sidecars. For details on how GatewayClass and Gateway-level NginxProxy settings are merged, see [Data plane configuration]({{< ref "/ngf/how-to/data-plane-configuration.md" >}}). @@ -100,11 +100,11 @@ For the full list of available images, see [Supported container images]({{< ref ### Bundles -A WAF bundle is a compiled policy package produced by the [F5 WAF for NGINX compiler]({{< ref "/waf/configure/compiler.md" >}}). It contains the security policy, optional logging profile, [attack signatures]({{< ref "/waf/policies/attack-signatures.md" >}}), [threat campaign]({{< ref "/waf/policies/threat-campaigns.md" >}}) data, [bot signatures]({{< ref "/waf/policies/bot-signatures.md" >}}), and related metadata in a format that the WAF engine can load and enforce at runtime. Pre-compiling policies into bundles results in faster, more reliable WAF startup: policies are resolved and validated at build time rather than on the running data plane. +A WAF bundle is a compiled policy package produced by the [F5 WAF for NGINX compiler]({{< ref "/waf/configure/compiler.md" >}}). It contains the security policy, optional logging profile, [attack signatures]({{< ref "/waf/policies/attack-signatures.md" >}}), [threat campaign]({{< ref "/waf/policies/threat-campaigns.md" >}}) data, [bot signatures]({{< ref "/waf/policies/bot-signatures.md" >}}), and related metadata. The format lets the WAF engine load and enforce the policy at runtime. Pre-compiling policies into bundles results in faster, more reliable WAF startup: policies are resolved and validated at build time rather than on the running data plane. ### Compilation -WAF policies must be compiled before they can be applied. Compilation takes a JSON policy definition (and optionally [global settings]({{< ref "/waf/configure/compiler.md" >}}) such as a cookie seed and [user-defined signatures]({{< ref "/waf/policies/user-signatures.md" >}})) and produces a `.tgz` bundle. NGINX Gateway Fabric doesn't compile policies. Its role begins at fetching a compiled bundle and deploying it to the data plane. +WAF policies must be compiled before they can be applied. Compilation takes a JSON policy definition (and optionally [global settings]({{< ref "/waf/configure/compiler.md" >}}) such as a cookie seed and [user-defined signatures]({{< ref "/waf/policies/user-signatures.md" >}})) and produces a `.tgz` bundle. NGINX Gateway Fabric doesn't compile policies. Its role begins with fetching a compiled bundle and deploying it to the data plane. ### Source types @@ -149,7 +149,7 @@ Changes to an `APPolicy` or `APLogConf` spec trigger recompilation and an automa When a `WAFPolicy` references an `APPolicy` or `APLogConf` in a different namespace, create a [ReferenceGrant](https://gateway-api.sigs.k8s.io/api-types/referencegrant/) in the target namespace to permit the reference. -{{< call-out "note" >}} PLM requires the PLM system to be installed in the cluster and PLM storage access to be set up on NGINX Gateway Fabric at install time. For a complete walkthrough, see [Get started with F5 WAF for NGINX using PLM]({{< ref "/ngf/waf-integration/get-started-plm.md" >}}). {{< /call-out >}} +{{< call-out "note" >}} PLM requires the PLM system to be installed in the cluster and PLM storage to be configured on NGINX Gateway Fabric at install time. For a complete walkthrough, see [Get started with F5 WAF for NGINX using PLM]({{< ref "/ngf/waf-integration/get-started-plm.md" >}}). {{< /call-out >}} --- diff --git a/content/ngf/waf-integration/policy-sources.md b/content/ngf/waf-integration/policy-sources.md index 2b14363b5..260cd3050 100644 --- a/content/ngf/waf-integration/policy-sources.md +++ b/content/ngf/waf-integration/policy-sources.md @@ -240,7 +240,7 @@ Make sure NGINX Gateway Fabric is configured to connect to NGINX One Console. Fo Although the NGINX One Console console does not display which policies are deployed to NGINX Gateway Fabric data planes, you can export WAF security events to the NGINX One Console security dashboard. This gives your security operations team visibility into blocked attacks, violations, and traffic patterns directly in the console. -To set this up, configure a `securityLogs` entry that sends events to the NGINX Agent's built-in OpenTelemetry collector, which forwards them to NGINX One Console. Use a log profile compiled for the NGINX One Console security dashboard: +To set this up, configure a `securityLogs` entry that sends events to the built-in OpenTelemetry collector in NGINX Agent, which forwards them to NGINX One Console. Use a log profile compiled for the NGINX One Console security dashboard: ```yaml kubectl apply -f - <}} The `profileName: "secops_dashboard"` log profile must exist in your NGINX One Console namespace. This profile is required for events to appear correctly in the NGINX One Console security dashboard. {{< /call-out >}} From fa66d6e1fc2d0a44e1a30a83f40db80fc9a423dd Mon Sep 17 00:00:00 2001 From: Travis Martin Date: Mon, 10 Aug 2026 14:03:00 -0700 Subject: [PATCH 10/50] fix: Replace non-RFC-5737 IP address across NGF docs Replace 10.96.20.187 with 192.0.2.1 (RFC 5737 documentation range) in all six NGF files where it appeared as a Gateway IP example. Files updated: - content/ngf/traffic-security/basic-authentication.md - content/ngf/traffic-security/cors.md - content/ngf/traffic-security/jwt-authentication.md - content/ngf/traffic-security/oidc-authentication.md - content/ngf/waf-integration/get-started-http.md - content/ngf/waf-integration/get-started-plm.md --- content/ngf/traffic-security/basic-authentication.md | 2 +- content/ngf/traffic-security/cors.md | 2 +- content/ngf/traffic-security/jwt-authentication.md | 2 +- content/ngf/traffic-security/oidc-authentication.md | 2 +- content/ngf/waf-integration/get-started-http.md | 2 +- content/ngf/waf-integration/get-started-plm.md | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/content/ngf/traffic-security/basic-authentication.md b/content/ngf/traffic-security/basic-authentication.md index 47b20f4be..1a5c2d7a3 100644 --- a/content/ngf/traffic-security/basic-authentication.md +++ b/content/ngf/traffic-security/basic-authentication.md @@ -138,7 +138,7 @@ kubectl describe gateways.gateway.networking.k8s.io cafe-gateway ```text Addresses: Type: IPAddress - Value: 10.96.20.187 + Value: 192.0.2.1 ``` Save the public IP address and port(s) of the Gateway into shell variables: diff --git a/content/ngf/traffic-security/cors.md b/content/ngf/traffic-security/cors.md index 67b27a5dc..0d65aa3ec 100644 --- a/content/ngf/traffic-security/cors.md +++ b/content/ngf/traffic-security/cors.md @@ -95,7 +95,7 @@ kubectl describe gateways.gateway.networking.k8s.io gateway ```text Addresses: Type: IPAddress - Value: 10.96.20.187 + Value: 192.0.2.1 ``` Save the public IP address and port(s) of the Gateway into shell variables: diff --git a/content/ngf/traffic-security/jwt-authentication.md b/content/ngf/traffic-security/jwt-authentication.md index f7fe79667..79fe73666 100644 --- a/content/ngf/traffic-security/jwt-authentication.md +++ b/content/ngf/traffic-security/jwt-authentication.md @@ -151,7 +151,7 @@ kubectl describe gateways.gateway.networking.k8s.io cafe-gateway ```text Addresses: Type: IPAddress - Value: 10.96.20.187 + Value: 192.0.2.1 ``` Save the public IP address and port of the Gateway into shell variables: diff --git a/content/ngf/traffic-security/oidc-authentication.md b/content/ngf/traffic-security/oidc-authentication.md index 76214dde3..165bdaf5a 100644 --- a/content/ngf/traffic-security/oidc-authentication.md +++ b/content/ngf/traffic-security/oidc-authentication.md @@ -344,7 +344,7 @@ kubectl describe gateways.gateway.networking.k8s.io gateway ```text Addresses: Type: IPAddress - Value: 10.96.20.187 + Value: 192.0.2.1 ``` Save the IP and port into shell variables: diff --git a/content/ngf/waf-integration/get-started-http.md b/content/ngf/waf-integration/get-started-http.md index c82656fe6..30d8ac7ef 100644 --- a/content/ngf/waf-integration/get-started-http.md +++ b/content/ngf/waf-integration/get-started-http.md @@ -415,7 +415,7 @@ kubectl describe gateways.gateway.networking.k8s.io gateway ```text Addresses: Type: IPAddress - Value: 10.96.20.187 + Value: 192.0.2.1 ``` Save the public IP address and port(s) of the Gateway into shell variables: diff --git a/content/ngf/waf-integration/get-started-plm.md b/content/ngf/waf-integration/get-started-plm.md index 1b97c044a..c156e0ce5 100644 --- a/content/ngf/waf-integration/get-started-plm.md +++ b/content/ngf/waf-integration/get-started-plm.md @@ -516,7 +516,7 @@ kubectl describe gateways.gateway.networking.k8s.io gateway ```text Addresses: Type: IPAddress - Value: 10.96.20.187 + Value: 192.0.2.1 ``` Save the public IP address and port of the Gateway into shell variables: From 25c48b1d98c604cc2f5f63c0f197c487efe94ff7 Mon Sep 17 00:00:00 2001 From: Saylor Berman Date: Mon, 10 Aug 2026 15:39:59 -0600 Subject: [PATCH 11/50] Fix installation and version files --- content/ngf/waf-integration/get-started-plm.md | 13 ++----------- layouts/shortcodes/version-waf-compiler.html | 2 +- layouts/shortcodes/version-waf-config-mgr.html | 2 +- layouts/shortcodes/version-waf-enforcer.html | 2 +- layouts/shortcodes/version-waf-ip-intelligence.html | 2 +- .../shortcodes/version-waf-policy-controller.html | 2 +- layouts/shortcodes/version-waf.html | 2 +- 7 files changed, 8 insertions(+), 17 deletions(-) diff --git a/content/ngf/waf-integration/get-started-plm.md b/content/ngf/waf-integration/get-started-plm.md index c156e0ce5..b7adc22f0 100644 --- a/content/ngf/waf-integration/get-started-plm.md +++ b/content/ngf/waf-integration/get-started-plm.md @@ -35,19 +35,10 @@ NGINX Gateway Fabric fetches compiled bundles from in-cluster PLM storage. You s Create a `values.yaml` file that enables WAF and sets the PLM storage connection details under `nginxGateway.plmStorage`: ```yaml -# values.yaml -nginx: - image: - repository: private-registry.nginx.com/nginx-gateway-fabric/nginx-plus-f5waf - plus: true - config: - waf: - enable: true - imagePullSecret: nginx-plus-registry-secret nginxGateway: plmStorage: - url: "https://plm-storage-service.plm-system.svc.cluster.local" - credentialsSecretName: "plm-storage-credentials" # contains the seaweedfs_admin_secret field + url: "https://plm-f5-waf-seaweed-filer.plm-system.svc.cluster.local" + credentialsSecretName: "plm-system/plm-f5-waf-seaweedfs-auth" # contains the seaweedfs_admin_secret field tls: caSecretName: "plm-ca-secret" # Secret with ca.crt for verifying the storage service clientSSLSecretName: "plm-client-secret" # Secret with tls.crt/tls.key for mutual TLS diff --git a/layouts/shortcodes/version-waf-compiler.html b/layouts/shortcodes/version-waf-compiler.html index 0a70cf03c..5d4f567eb 100644 --- a/layouts/shortcodes/version-waf-compiler.html +++ b/layouts/shortcodes/version-waf-compiler.html @@ -1 +1 @@ -5.14.0 +5.14.0 \ No newline at end of file diff --git a/layouts/shortcodes/version-waf-config-mgr.html b/layouts/shortcodes/version-waf-config-mgr.html index 0a70cf03c..5d4f567eb 100644 --- a/layouts/shortcodes/version-waf-config-mgr.html +++ b/layouts/shortcodes/version-waf-config-mgr.html @@ -1 +1 @@ -5.14.0 +5.14.0 \ No newline at end of file diff --git a/layouts/shortcodes/version-waf-enforcer.html b/layouts/shortcodes/version-waf-enforcer.html index 0a70cf03c..5d4f567eb 100644 --- a/layouts/shortcodes/version-waf-enforcer.html +++ b/layouts/shortcodes/version-waf-enforcer.html @@ -1 +1 @@ -5.14.0 +5.14.0 \ No newline at end of file diff --git a/layouts/shortcodes/version-waf-ip-intelligence.html b/layouts/shortcodes/version-waf-ip-intelligence.html index 0a70cf03c..5d4f567eb 100644 --- a/layouts/shortcodes/version-waf-ip-intelligence.html +++ b/layouts/shortcodes/version-waf-ip-intelligence.html @@ -1 +1 @@ -5.14.0 +5.14.0 \ No newline at end of file diff --git a/layouts/shortcodes/version-waf-policy-controller.html b/layouts/shortcodes/version-waf-policy-controller.html index 0a70cf03c..5d4f567eb 100644 --- a/layouts/shortcodes/version-waf-policy-controller.html +++ b/layouts/shortcodes/version-waf-policy-controller.html @@ -1 +1 @@ -5.14.0 +5.14.0 \ No newline at end of file diff --git a/layouts/shortcodes/version-waf.html b/layouts/shortcodes/version-waf.html index 0a70cf03c..5d4f567eb 100644 --- a/layouts/shortcodes/version-waf.html +++ b/layouts/shortcodes/version-waf.html @@ -1 +1 @@ -5.14.0 +5.14.0 \ No newline at end of file From e7055dcd98e159269de05ede11ff7af896187a7a Mon Sep 17 00:00:00 2001 From: Travis Martin Date: Mon, 10 Aug 2026 15:51:15 -0700 Subject: [PATCH 12/50] style: Align placeholders in bundle-method include with deploy include Use concrete namespace (plm-system) to match the deploy infrastructure include. Normalize manifest filename and output placeholders to ALL_CAPS_UNDERSCORES per style guide. --- .../includes/waf/plm-define-policy-bundle-method.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/content/includes/waf/plm-define-policy-bundle-method.md b/content/includes/waf/plm-define-policy-bundle-method.md index 1f5e37dd4..f86bd3c63 100644 --- a/content/includes/waf/plm-define-policy-bundle-method.md +++ b/content/includes/waf/plm-define-policy-bundle-method.md @@ -19,18 +19,18 @@ apiVersion: appprotect.f5.com/v1 kind: APPolicy metadata: name: - namespace: + namespace: plm-system spec: policy: $ref: "https:///.tgz" ``` -Replace ``, ``, ``, and `` with your values. +Replace ``, ``, and `` with your values. Apply the resource: ```shell -kubectl apply -f .yaml +kubectl apply -f .yaml ``` {{< call-out class="note" title="Note" >}} @@ -43,7 +43,7 @@ The Policy Controller processes the bundle and updates the `APPolicy` status. Ch ```shell kubectl get appolicy \ - --namespace \ + --namespace plm-system \ --output jsonpath='State: {.status.bundle.state}{"\n"}Bundle: {.status.bundle.location}{"\n"}isCompiled: {.status.processing.isCompiled}{"\n"}' ``` @@ -51,7 +51,7 @@ When the bundle is ready, the output looks like this: ```text State: ready -Bundle: s3:///bundles/_imported_.tgz +Bundle: s3://plm-system/bundles/_imported_.tgz isCompiled: false ``` @@ -71,5 +71,5 @@ isCompiled: false The Policy Controller does not poll the artifact registry for changes. To pick up a new version of a bundle, update the `APPolicy` resource to reference the new bundle URL (or bump its revision annotation) and re-apply it: ```shell -kubectl apply -f .yaml +kubectl apply -f .yaml ``` From 083161c6065e96607cbcc89323b3361ecf170743 Mon Sep 17 00:00:00 2001 From: Travis Martin Date: Mon, 10 Aug 2026 16:04:49 -0700 Subject: [PATCH 13/50] docs: Add example values table to Before you begin Add a table mapping all concrete example values used throughout the tutorial so readers know what is configurable before running any commands. Covers: plm-system (PLM namespace), plm (Helm release), version-waf-policy-controller shortcode, security (policy namespace), default (Gateway/WAFPolicy namespace), and cafe.example.com (hostname). Removes the redundant note from the deploy infrastructure include -- the Before you begin table covers both includes in one place. --- content/ngf/waf-integration/get-started-plm.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/content/ngf/waf-integration/get-started-plm.md b/content/ngf/waf-integration/get-started-plm.md index b7adc22f0..6b8e1fdf9 100644 --- a/content/ngf/waf-integration/get-started-plm.md +++ b/content/ngf/waf-integration/get-started-plm.md @@ -24,6 +24,17 @@ PLM is one of four WAF policy source types. With PLM, you define your security p - Have a valid F5 WAF for NGINX subscription. F5 WAF for NGINX is a separate add-on to NGINX Plus and isn't included with the NGINX Plus license. - Have your private registry credentials Secret for `private-registry.nginx.com` available. You'll reference this Secret when you install NGINX Gateway Fabric. +This tutorial uses the following example values. You can use different values — if you do, replace them consistently throughout. + +| Example value | What it represents | +|---|---| +| `plm-system` | Namespace for the PLM backend components | +| `plm` | Helm release name for the PLM installation | +| `{{< version-waf-policy-controller >}}` | F5 WAF for NGINX Policy Controller chart and image version | +| `security` | Namespace for `APPolicy` and `APLogConf` resources | +| `default` | Namespace for the Gateway and `WAFPolicy` | +| `cafe.example.com` | Example hostname for HTTPRoutes | + ## Deploy PLM infrastructure {{< include "waf/plm-deploy-infrastructure.md" >}} From 72ea1f7cd044ad812e45e2f907ed0811baa3ffb2 Mon Sep 17 00:00:00 2001 From: Travis Martin Date: Mon, 10 Aug 2026 16:14:06 -0700 Subject: [PATCH 14/50] docs: Add workflow diagram to tutorial introduction Add a Mermaid flowchart showing the six-step tutorial workflow so readers can see the end-to-end picture before starting: 1. Deploy PLM infrastructure 2. Connect NGF to PLM storage 3. Define WAF policy (APPolicy/APLogConf) 4. Attach WAFPolicy to Gateway (with PLM compilation step called out) 5. Configure HTTPRoutes 6. Validate and test --- content/ngf/waf-integration/get-started-plm.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/content/ngf/waf-integration/get-started-plm.md b/content/ngf/waf-integration/get-started-plm.md index 6b8e1fdf9..88aa359a8 100644 --- a/content/ngf/waf-integration/get-started-plm.md +++ b/content/ngf/waf-integration/get-started-plm.md @@ -18,6 +18,15 @@ This guide walks through the complete flow of protecting traffic with F5 WAF for PLM is one of four WAF policy source types. With PLM, you define your security posture as `APPolicy` and `APLogConf` custom resources instead of compiling and hosting bundles yourself. For a comparison with the other source types, see [PLM (Policy Lifecycle Management)]({{< ref "/ngf/waf-integration/overview.md#plm-policy-lifecycle-management" >}}). +```mermaid +flowchart LR + A([1. Deploy PLM\ninfrastructure]) --> B([2. Connect NGF\nto PLM storage]) + B --> C([3. Define WAF policy\nAPPolicy / APLogConf]) + C -->|PLM compiles\nto bundle| D([4. Attach WAFPolicy\nto Gateway]) + D --> E([5. Configure\nHTTPRoutes]) + E --> F([6. Validate &\ntest]) +``` + ## Before you begin - Have `kubectl` access to a Kubernetes cluster. From ea847cb8ccb7bfa72a3e9ee9e4562f3c085356ff Mon Sep 17 00:00:00 2001 From: Travis Martin Date: Mon, 10 Aug 2026 16:20:14 -0700 Subject: [PATCH 15/50] fix: Switch Mermaid diagram from flowchart to graph syntax The Hugo theme's bundled Mermaid version does not support the flowchart directive or stadium node shapes. Switch to graph LR with quoted rectangle nodes and HTML line breaks, matching the syntax used in other working diagrams in the repo. --- content/ngf/waf-integration/get-started-plm.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/content/ngf/waf-integration/get-started-plm.md b/content/ngf/waf-integration/get-started-plm.md index 88aa359a8..4c7698551 100644 --- a/content/ngf/waf-integration/get-started-plm.md +++ b/content/ngf/waf-integration/get-started-plm.md @@ -19,12 +19,12 @@ This guide walks through the complete flow of protecting traffic with F5 WAF for PLM is one of four WAF policy source types. With PLM, you define your security posture as `APPolicy` and `APLogConf` custom resources instead of compiling and hosting bundles yourself. For a comparison with the other source types, see [PLM (Policy Lifecycle Management)]({{< ref "/ngf/waf-integration/overview.md#plm-policy-lifecycle-management" >}}). ```mermaid -flowchart LR - A([1. Deploy PLM\ninfrastructure]) --> B([2. Connect NGF\nto PLM storage]) - B --> C([3. Define WAF policy\nAPPolicy / APLogConf]) - C -->|PLM compiles\nto bundle| D([4. Attach WAFPolicy\nto Gateway]) - D --> E([5. Configure\nHTTPRoutes]) - E --> F([6. Validate &\ntest]) +graph LR + A["1. Deploy PLM
infrastructure"] --> B["2. Connect NGF
to PLM storage"] + B --> C["3. Define WAF policy
APPolicy / APLogConf"] + C -->|"PLM compiles
to bundle"| D["4. Attach WAFPolicy
to Gateway"] + D --> E["5. Configure
HTTPRoutes"] + E --> F["6. Validate &
test"] ``` ## Before you begin From ffe5a3e3d88cf96ed7f8c61f45f7298a48a0b908 Mon Sep 17 00:00:00 2001 From: Travis Martin Date: Mon, 10 Aug 2026 16:38:54 -0700 Subject: [PATCH 16/50] docs: Simplify tutorial introduction Replace the failed Mermaid diagram and redundant numbered list with a concise 'By the end, you will have:' bullet list. Rewrite the opening sentence to lead with the tutorial framing rather than a long colon-separated summary. The left-nav TOC already shows the section structure, so the numbered list added no value. --- content/ngf/waf-integration/get-started-plm.md | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/content/ngf/waf-integration/get-started-plm.md b/content/ngf/waf-integration/get-started-plm.md index 4c7698551..8821125d2 100644 --- a/content/ngf/waf-integration/get-started-plm.md +++ b/content/ngf/waf-integration/get-started-plm.md @@ -14,18 +14,15 @@ f5-summary: > f5-audience: operator --- -This guide walks through the complete flow of protecting traffic with F5 WAF for NGINX using Policy Lifecycle Management (PLM): connect NGINX Gateway Fabric to PLM storage, define a WAF policy as Kubernetes custom resources, apply it to a Gateway, and verify that attacks are blocked. +This tutorial walks through the complete flow of protecting traffic with F5 WAF for NGINX using Policy Lifecycle Management (PLM). By the end, you will have: -PLM is one of four WAF policy source types. With PLM, you define your security posture as `APPolicy` and `APLogConf` custom resources instead of compiling and hosting bundles yourself. For a comparison with the other source types, see [PLM (Policy Lifecycle Management)]({{< ref "/ngf/waf-integration/overview.md#plm-policy-lifecycle-management" >}}). +- Deployed the PLM infrastructure (Policy Controller and SeaweedFS storage) +- Connected NGINX Gateway Fabric to PLM storage +- Defined a WAF policy using `APPolicy` and `APLogConf` custom resources +- Attached a `WAFPolicy` to a Gateway and configured HTTPRoutes +- Validated policy compilation and verified that attacks are blocked -```mermaid -graph LR - A["1. Deploy PLM
infrastructure"] --> B["2. Connect NGF
to PLM storage"] - B --> C["3. Define WAF policy
APPolicy / APLogConf"] - C -->|"PLM compiles
to bundle"| D["4. Attach WAFPolicy
to Gateway"] - D --> E["5. Configure
HTTPRoutes"] - E --> F["6. Validate &
test"] -``` +PLM is one of four WAF policy source types. With PLM, you define your security posture as `APPolicy` and `APLogConf` custom resources instead of compiling and hosting bundles yourself. For a comparison with the other source types, see [PLM (Policy Lifecycle Management)]({{< ref "/ngf/waf-integration/overview.md#plm-policy-lifecycle-management" >}}). ## Before you begin From 3495abfcc2aa5fea215a1812227931c69f17940b Mon Sep 17 00:00:00 2001 From: Travis Martin Date: Mon, 10 Aug 2026 17:22:25 -0700 Subject: [PATCH 17/50] docs: Add persona context at role-shift points in PLM tutorial The tutorial spans three personas (cluster admin, SecOps, platform engineer/app dev) but gave no signal when ownership shifted between them. Add plain-prose callouts at four points: - Configure security logging: flag as a security team task - Define the WAF policy: explain the security namespace separation and name the handoff artifact (APPolicy name and namespace) - ReferenceGrant note: clarify that the grant lives in the security namespace and requires coordination with whoever owns it - Apply a route-level override: frame the pattern as a SecOps and app team collaboration rather than a generic optional step Also remove six standalone horizontal rules that preceded section headings, per repo convention. --- .../ngf/waf-integration/get-started-plm.md | 22 ++++++------------- 1 file changed, 7 insertions(+), 15 deletions(-) diff --git a/content/ngf/waf-integration/get-started-plm.md b/content/ngf/waf-integration/get-started-plm.md index 8821125d2..fda5ca33a 100644 --- a/content/ngf/waf-integration/get-started-plm.md +++ b/content/ngf/waf-integration/get-started-plm.md @@ -83,8 +83,6 @@ data: NGINX Gateway Fabric watches the PLM credentials and TLS Secrets and rebuilds its storage client when they change, so you can rotate credentials without restarting the pod. ---- - ## Deploy the sample application Deploy the `customers` and `orders` sample applications. The `customers` app returns a response containing fake sensitive data (credit card number and SSN), which you'll use later to demonstrate data guard masking: @@ -164,6 +162,8 @@ EOF ## Configure security logging (optional) +This section is typically owned by your security team. If that's not you, share it with them before continuing. + PLM security logging profiles are defined as `APLogConf` custom resources. Create a namespace to hold your security resources, then define a log profile that logs illegal requests: ```shell @@ -195,10 +195,10 @@ kubectl wait --for=jsonpath='{.status.bundle.state}'=ready aplogconf/log-illegal If you skip this section, omit the `securityLogs` field in the `WAFPolicy` resource in the next steps. ---- - ## Define the WAF policy +This section is typically owned by your security team. They define the policy in the `security` namespace, separate from the Gateway namespace, so that security resources are managed independently from routing configuration. If that's not you, share this section with them — you'll need the `APPolicy` name and namespace before continuing to the next section. + The `APPolicy` resource defines the security policy. The PLM controller watches it, compiles it, and writes `status.bundle` with `state: ready` when the bundle is available. Use the **Inline** tab for this guide's primary workflow. The other tabs provide alternate policy-source methods. @@ -263,7 +263,7 @@ spec: EOF ``` -{{< call-out "note" >}} Without a matching `ReferenceGrant`, the `WAFPolicy` is rejected with `ResolvedRefs=False` and reason `RefNotPermitted`. If you put the `APPolicy` and `APLogConf` in the same namespace as the `WAFPolicy`, you can skip the `ReferenceGrant`. See [Troubleshoot WAFPolicy status]({{< ref "/ngf/waf-integration/troubleshooting.md" >}}) for details. {{< /call-out >}} +{{< call-out "note" >}} The `ReferenceGrant` lives in the `security` namespace and must be created by whoever manages that namespace — typically your security team, not the platform engineer deploying the Gateway. Coordinate with them if you don't have access. Without a matching `ReferenceGrant`, the `WAFPolicy` is rejected with `ResolvedRefs=False` and reason `RefNotPermitted`. If you put the `APPolicy` and `APLogConf` in the same namespace as the `WAFPolicy`, you can skip the `ReferenceGrant`. See [Troubleshoot WAFPolicy status]({{< ref "/ngf/waf-integration/troubleshooting.md" >}}) for details. {{< /call-out >}} {{% /tab %}} @@ -407,8 +407,6 @@ EOF This `WAFPolicy` protects every route attached to the Gateway. Later changes to the `APPolicy` or `APLogConf` spec trigger recompilation and an automatic re-fetch — no change to the `WAFPolicy` is required. ---- - ## Configure HTTPRoutes Create two HTTPRoutes — `customers` and `orders` — attached to the Gateway. Because the `WAFPolicy` targets the Gateway, both routes inherit WAF protection automatically: @@ -455,8 +453,6 @@ spec: EOF ``` ---- - ## Validate policy compilation and application Confirm the `APPolicy` and `APLogConf` bundles compiled successfully: @@ -511,8 +507,6 @@ NAME READY STATUS RESTARTS AGE gateway-nginx-7f9b8d6c4d-xxxxx 3/3 Running 0 2m ``` ---- - ## Test deployment and policy enforcement Confirm the Gateway was assigned an IP address and reports `Programmed=True`: @@ -580,12 +574,12 @@ curl --resolve cafe.example.com:$GW_PORT:$GW_IP "http://cafe.example.com:$GW_POR {{< call-out "note" >}} The exact blocking response depends on your WAF policy configuration. Check the security log for a corresponding blocked event using `kubectl logs -c waf-enforcer`. {{< /call-out >}} ---- - ## Apply a route-level override (optional) In the previous step, you saw that the `customers` route returns sensitive data (credit card numbers and SSNs) in the response body. The gateway-level policy blocks inbound attacks, but doesn't inspect outbound responses. +This pattern is a good example of a SecOps and app team collaboration: the security team defines a stricter policy for a specific service, and the platform engineer or app developer attaches it as a route-level override. The override applies only to the `customers` route — other routes continue using the gateway-level policy. + To protect sensitive data in responses, define a **data guard** `APPolicy` and apply it as a route-level override on the `customers` route: ```yaml @@ -656,8 +650,6 @@ Credit Card: ***************1111 SSN: *******6789 ``` ---- - ## Next steps - [F5 WAF for NGINX overview]({{< ref "/ngf/waf-integration/overview.md" >}}) for architecture and policy lifecycle concepts. From 6d05bf6003ed56a99f419955cc42b2187adc9008 Mon Sep 17 00:00:00 2001 From: Saylor Berman Date: Tue, 11 Aug 2026 09:53:29 -0600 Subject: [PATCH 18/50] Add HTTP option for connecting to PLM storage --- .../ngf/waf-integration/get-started-plm.md | 25 ++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/content/ngf/waf-integration/get-started-plm.md b/content/ngf/waf-integration/get-started-plm.md index fda5ca33a..75639c53d 100644 --- a/content/ngf/waf-integration/get-started-plm.md +++ b/content/ngf/waf-integration/get-started-plm.md @@ -49,7 +49,11 @@ This tutorial uses the following example values. You can use different values NGINX Gateway Fabric fetches compiled bundles from in-cluster PLM storage. You set up storage access once, cluster-wide, at install time. It applies to every `WAFPolicy` that uses `type: PLM`. -Create a `values.yaml` file that enables WAF and sets the PLM storage connection details under `nginxGateway.plmStorage`: +Create a `values.yaml` file that enables WAF and sets the PLM storage connection details under `nginxGateway.plmStorage`. + +{{}} + +{{%tab name="HTTPS (secure)"%}} ```yaml nginxGateway: @@ -66,6 +70,25 @@ nginxGateway: {{< call-out "note" >}} `credentialsSecretName` and `caSecretName` must reference Secrets in the NGINX Gateway Fabric control plane namespace, unless you prefix them with `/`. {{< /call-out >}} +{{% /tab %}} + +{{%tab name="HTTP"%}} + +```yaml +nginxGateway: + plmStorage: + url: "http://plm-f5-waf-seaweed-filer.plm-system.svc.cluster.local:8333" + credentialsSecretName: "plm-system/plm-f5-waf-seaweedfs-auth" # contains the seaweedfs_admin_secret field +``` + +For an HTTP endpoint, do not include a `tls` section. + +{{< call-out "note" >}} `credentialsSecretName` must reference a Secret in the NGINX Gateway Fabric control plane namespace, unless you prefix it with `/`. {{< /call-out >}} + +{{% /tab %}} + +{{}} + Install NGINX Gateway Fabric by following [the installation guide]({{< ref "/ngf/install/helm.md" >}}) and using the **NGINX Plus with WAF** tab, and apply this `values.yaml` file in your install or upgrade command, specifying `--values values.yaml`. The PLM installation creates the credentials Secret automatically, containing the S3 secret access key in the `seaweedfs_admin_secret` field (access key ID `admin` by default): From 2eb517157ec4b9de2532bfe491e42c97f78189a3 Mon Sep 17 00:00:00 2001 From: Saylor Berman Date: Tue, 11 Aug 2026 09:54:42 -0600 Subject: [PATCH 19/50] Fix HTTP storage notes --- content/ngf/waf-integration/get-started-plm.md | 4 ---- 1 file changed, 4 deletions(-) diff --git a/content/ngf/waf-integration/get-started-plm.md b/content/ngf/waf-integration/get-started-plm.md index 75639c53d..6f0a5cde5 100644 --- a/content/ngf/waf-integration/get-started-plm.md +++ b/content/ngf/waf-integration/get-started-plm.md @@ -81,10 +81,6 @@ nginxGateway: credentialsSecretName: "plm-system/plm-f5-waf-seaweedfs-auth" # contains the seaweedfs_admin_secret field ``` -For an HTTP endpoint, do not include a `tls` section. - -{{< call-out "note" >}} `credentialsSecretName` must reference a Secret in the NGINX Gateway Fabric control plane namespace, unless you prefix it with `/`. {{< /call-out >}} - {{% /tab %}} {{}} From b9724238ac211a84108c202822f9ddaaf81a93e5 Mon Sep 17 00:00:00 2001 From: Travis Martin Date: Tue, 11 Aug 2026 09:11:25 -0700 Subject: [PATCH 20/50] fix: Convert APPolicy apply steps to inline EOF and add verify steps Address reviewer feedback from PR #2197: - Git-reference tab (public and private): replace standalone YAML block and 'kubectl apply -f ' with a single inline 'kubectl apply -f - <`, ``, and `` with your values: -```yaml +```shell +kubectl apply -f - </.tgz" -``` - -Replace ``, ``, and `` with your values. - -Apply the resource: - -```shell -kubectl apply -f .yaml +EOF ``` {{< call-out class="note" title="Note" >}} diff --git a/content/ngf/waf-integration/get-started-plm.md b/content/ngf/waf-integration/get-started-plm.md index 6f0a5cde5..093bb40c5 100644 --- a/content/ngf/waf-integration/get-started-plm.md +++ b/content/ngf/waf-integration/get-started-plm.md @@ -85,7 +85,7 @@ nginxGateway: {{}} -Install NGINX Gateway Fabric by following [the installation guide]({{< ref "/ngf/install/helm.md" >}}) and using the **NGINX Plus with WAF** tab, and apply this `values.yaml` file in your install or upgrade command, specifying `--values values.yaml`. +Install NGINX Gateway Fabric by following [the installation guide]({{< ref "/ngf/install/helm.md" >}}) and using the **NGINX Plus with WAF** tab, and apply this `values.yaml` file in your install or upgrade command, specifying `--values values.yaml`. For all available `plmStorage` options, see the [NGINX Gateway Fabric Helm chart reference]({{< ref "/ngf/reference/helm.md" >}}). The PLM installation creates the credentials Secret automatically, containing the S3 secret access key in the `seaweedfs_admin_secret` field (access key ID `admin` by default): @@ -292,9 +292,10 @@ Store your policy JSON in a Git repository and reference it from `APPolicy`. #### Public repository -Create an `APPolicy` resource that references the policy file by path: +Create an `APPolicy` resource that references the policy file by path. Replace ``, ``, ``, ``, ``, and `` with your values: -```yaml +```shell +kubectl apply -f - </.git ref: "" +EOF ``` -Replace ``, ``, ``, ``, ``, and `` with your values. - {{< call-out class="note" title="Note" >}} Pin `ref` to a tag or commit SHA rather than a branch name in production environments. {{< /call-out >}} -Apply the resource: +Check that the bundle compiled successfully: ```shell -kubectl apply -f .yaml +kubectl get appolicy \ + --namespace \ + --output jsonpath='State: {.status.bundle.state}{"\n"}Bundle: {.status.bundle.location}{"\n"}Compiler: {.status.bundle.compilerVersion}{"\n"}' ``` +The output should show `State: ready`. + #### Private repository For private repositories, create a Kubernetes secret with your personal access token (PAT): @@ -333,7 +337,8 @@ kubectl create secret generic git-token-secret \ Then reference the secret in the `APPolicy` resource: -```yaml +```shell +kubectl apply -f - <" authentication: token: git-token-secret -``` - -#### Confirm the policy is ready - -Check `bundle.state`: - -```shell -kubectl get appolicy \ - --namespace \ - --output jsonpath='State: {.status.bundle.state}{"\n"}Bundle: {.status.bundle.location}{"\n"}Compiler: {.status.bundle.compilerVersion}{"\n"}' +EOF ``` #### Update a Git-referenced policy From 47f770a339b6c6cf12c70a4fb6159780e3d99f5a Mon Sep 17 00:00:00 2001 From: Travis Martin Date: Tue, 11 Aug 2026 09:26:19 -0700 Subject: [PATCH 21/50] fix: Add PLM chart values reference and correct link placement Address @ciarams87 comment on plm-deploy-infrastructure.md: - Add 'helm show values' command after the helm install step so users can discover all available PLM chart configuration options; no public values.yaml URL exists for this chart, so the command is the authoritative substitute - Remove the NGF chart values.yaml link added in error to get-started-plm.md (wrong chart, wrong file) --- content/includes/waf/plm-deploy-infrastructure.md | 6 ++++++ content/ngf/waf-integration/get-started-plm.md | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/content/includes/waf/plm-deploy-infrastructure.md b/content/includes/waf/plm-deploy-infrastructure.md index 692009dc0..fd277924f 100644 --- a/content/includes/waf/plm-deploy-infrastructure.md +++ b/content/includes/waf/plm-deploy-infrastructure.md @@ -94,6 +94,12 @@ helm upgrade --install plm nginx-stable/f5-waf-policy-controller \ --values /tmp/plm-values.yaml ``` +To see all available configuration options for the PLM chart, run: + +```shell +helm show values nginx-stable/f5-waf-policy-controller --version {{< version-waf-policy-controller >}} +``` + ### Verify the deployment Wait for all PLM components to become ready. The Policy Controller's init container waits for both the compiler service and the SeaweedFS S3 endpoint to be available before it starts, so the controller pod will show `Init:0/1` until SeaweedFS is ready. diff --git a/content/ngf/waf-integration/get-started-plm.md b/content/ngf/waf-integration/get-started-plm.md index 093bb40c5..96a4184e3 100644 --- a/content/ngf/waf-integration/get-started-plm.md +++ b/content/ngf/waf-integration/get-started-plm.md @@ -85,7 +85,7 @@ nginxGateway: {{}} -Install NGINX Gateway Fabric by following [the installation guide]({{< ref "/ngf/install/helm.md" >}}) and using the **NGINX Plus with WAF** tab, and apply this `values.yaml` file in your install or upgrade command, specifying `--values values.yaml`. For all available `plmStorage` options, see the [NGINX Gateway Fabric Helm chart reference]({{< ref "/ngf/reference/helm.md" >}}). +Install NGINX Gateway Fabric by following [the installation guide]({{< ref "/ngf/install/helm.md" >}}) and using the **NGINX Plus with WAF** tab, and apply this `values.yaml` file in your install or upgrade command, specifying `--values values.yaml`. The PLM installation creates the credentials Secret automatically, containing the S3 secret access key in the `seaweedfs_admin_secret` field (access key ID `admin` by default): From 239217b62c5fc214f62626a363667df244a31655 Mon Sep 17 00:00:00 2001 From: Travis Martin Date: Tue, 11 Aug 2026 09:45:50 -0700 Subject: [PATCH 22/50] fix: Improve bundle-method include clarity and reuse safety - Add maintainer HTML comment noting the APPolicy CRD prerequisite and directing future reusers to add a parent-document note - Convert the 'Update a precompiled bundle' step to inline EOF pattern, consistent with the initial apply step - Add placeholder explanation ('Replace , , and with your values') before the update code block, following the pattern used elsewhere in the include --- .../waf/plm-define-policy-bundle-method.md | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/content/includes/waf/plm-define-policy-bundle-method.md b/content/includes/waf/plm-define-policy-bundle-method.md index 70cfc74b3..7627f620d 100644 --- a/content/includes/waf/plm-define-policy-bundle-method.md +++ b/content/includes/waf/plm-define-policy-bundle-method.md @@ -5,6 +5,8 @@ f5-files: - content/nic/waf-integration/get-started-plm.md --- + + The precompiled-bundle method lets you reference a `.tgz` policy bundle stored in an artifact registry (for example, Artifactory or Nexus). The Policy Controller imports the bundle and stores it in the SeaweedFS object store without recompiling it. Use this method when: @@ -62,8 +64,17 @@ isCompiled: false #### Update a precompiled bundle -The Policy Controller does not poll the artifact registry for changes. To pick up a new version of a bundle, update the `APPolicy` resource to reference the new bundle URL (or bump its revision annotation) and re-apply it: +The Policy Controller does not poll the artifact registry for changes. To pick up a new version of a bundle, update the `$ref` URL in your `APPolicy` resource (or bump its revision annotation) and re-apply it. Replace ``, ``, and `` with your values: ```shell -kubectl apply -f .yaml +kubectl apply -f - < + namespace: plm-system +spec: + policy: + $ref: "https:///.tgz" +EOF ``` From 62a1c01ab03221f06b51f1c7096db8f97b5ac34e Mon Sep 17 00:00:00 2001 From: Travis Martin Date: Wed, 12 Aug 2026 13:17:20 -0700 Subject: [PATCH 23/50] style edits applied to get-started-plm.md --- .../ngf/waf-integration/get-started-plm.md | 41 ++++++++++--------- 1 file changed, 21 insertions(+), 20 deletions(-) diff --git a/content/ngf/waf-integration/get-started-plm.md b/content/ngf/waf-integration/get-started-plm.md index 96a4184e3..24588832a 100644 --- a/content/ngf/waf-integration/get-started-plm.md +++ b/content/ngf/waf-integration/get-started-plm.md @@ -14,7 +14,7 @@ f5-summary: > f5-audience: operator --- -This tutorial walks through the complete flow of protecting traffic with F5 WAF for NGINX using Policy Lifecycle Management (PLM). By the end, you will have: +Use this tutorial to set up end-to-end traffic protection with F5 WAF for NGINX using Policy Lifecycle Management (PLM). By the end, you'll have: - Deployed the PLM infrastructure (Policy Controller and SeaweedFS storage) - Connected NGINX Gateway Fabric to PLM storage @@ -26,9 +26,11 @@ PLM is one of four WAF policy source types. With PLM, you define your security p ## Before you begin -- Have `kubectl` access to a Kubernetes cluster. -- Have a valid F5 WAF for NGINX subscription. F5 WAF for NGINX is a separate add-on to NGINX Plus and isn't included with the NGINX Plus license. -- Have your private registry credentials Secret for `private-registry.nginx.com` available. You'll reference this Secret when you install NGINX Gateway Fabric. +Before you start, make sure you have: + +- `kubectl` access to a Kubernetes cluster. +- A valid F5 WAF for NGINX subscription. F5 WAF for NGINX is a separate add-on to NGINX Plus and isn't included with the NGINX Plus license. +- Your private registry credentials Secret for `private-registry.nginx.com`. You'll reference this Secret when you install NGINX Gateway Fabric. This tutorial uses the following example values. You can use different values — if you do, replace them consistently throughout. @@ -45,9 +47,9 @@ This tutorial uses the following example values. You can use different values {{< include "waf/plm-deploy-infrastructure.md" >}} -## Configure NGF to connect to PLM storage +## Connect NGINX Gateway Fabric to PLM storage -NGINX Gateway Fabric fetches compiled bundles from in-cluster PLM storage. You set up storage access once, cluster-wide, at install time. It applies to every `WAFPolicy` that uses `type: PLM`. +NGINX Gateway Fabric fetches compiled bundles from in-cluster PLM storage. You set up storage access once, cluster-wide, at install time. This configuration applies to every `WAFPolicy` that uses `type: PLM`. Create a `values.yaml` file that enables WAF and sets the PLM storage connection details under `nginxGateway.plmStorage`. @@ -66,9 +68,9 @@ nginxGateway: insecureSkipVerify: false # use only for testing ``` -{{< call-out "caution" >}} Always use HTTPS with TLS verification (`caSecretName`) in production. Add `clientSSLSecretName` for mutual TLS in high-security environments, and never set `insecureSkipVerify: true`. {{< /call-out >}} +{{< call-out class="caution" title="Caution" >}} Always use HTTPS with TLS verification (`caSecretName`) in production. Add `clientSSLSecretName` for mutual TLS in high-security environments, and never set `insecureSkipVerify: true`. {{< /call-out >}} -{{< call-out "note" >}} `credentialsSecretName` and `caSecretName` must reference Secrets in the NGINX Gateway Fabric control plane namespace, unless you prefix them with `/`. {{< /call-out >}} +{{< call-out class="note" title="Note" >}} `credentialsSecretName` and `caSecretName` must reference Secrets in the NGINX Gateway Fabric control plane namespace, unless you prefix them with `/`. {{< /call-out >}} {{% /tab %}} @@ -100,7 +102,7 @@ data: seaweedfs_admin_secret: ``` -NGINX Gateway Fabric watches the PLM credentials and TLS Secrets and rebuilds its storage client when they change, so you can rotate credentials without restarting the pod. +NGINX Gateway Fabric reloads the PLM credentials and TLS Secrets when they change, so you can rotate credentials without restarting the pod. ## Deploy the sample application @@ -282,7 +284,7 @@ spec: EOF ``` -{{< call-out "note" >}} The `ReferenceGrant` lives in the `security` namespace and must be created by whoever manages that namespace — typically your security team, not the platform engineer deploying the Gateway. Coordinate with them if you don't have access. Without a matching `ReferenceGrant`, the `WAFPolicy` is rejected with `ResolvedRefs=False` and reason `RefNotPermitted`. If you put the `APPolicy` and `APLogConf` in the same namespace as the `WAFPolicy`, you can skip the `ReferenceGrant`. See [Troubleshoot WAFPolicy status]({{< ref "/ngf/waf-integration/troubleshooting.md" >}}) for details. {{< /call-out >}} +{{< call-out class="note" title="Note" >}} The `ReferenceGrant` lives in the `security` namespace and must be created by whoever manages that namespace — typically your security team, not the platform engineer deploying the Gateway. Coordinate with them if you don't have access. Without a matching `ReferenceGrant`, the `WAFPolicy` is rejected with `ResolvedRefs=False` and reason `RefNotPermitted`. If you put the `APPolicy` and `APLogConf` in the same namespace as the `WAFPolicy`, you can skip the `ReferenceGrant`. See [Troubleshoot WAFPolicy status]({{< ref "/ngf/waf-integration/troubleshooting.md" >}}) for details. {{< /call-out >}} {{% /tab %}} @@ -311,9 +313,7 @@ spec: EOF ``` -{{< call-out class="note" title="Note" >}} -Pin `ref` to a tag or commit SHA rather than a branch name in production environments. -{{< /call-out >}} +{{< call-out class="note" title="Note" >}} Pin `ref` to a tag or commit SHA rather than a branch name in production environments. {{< /call-out >}} Check that the bundle compiled successfully: @@ -358,7 +358,7 @@ EOF #### Update a Git-referenced policy -The Policy Controller does not poll the Git repository for changes. To pick up changes to the referenced policy file, re-apply the `APPolicy` resource (or update its revision annotation) after you push changes to the repository. +The Policy Controller doesn't poll the Git repository for changes. To pick up changes to the referenced policy file, re-apply the `APPolicy` resource (or update its revision annotation) after you push changes to the repository. {{% /tab %}} @@ -420,7 +420,7 @@ spec: EOF ``` -This `WAFPolicy` protects every route attached to the Gateway. Later changes to the `APPolicy` or `APLogConf` spec trigger recompilation and an automatic re-fetch — no change to the `WAFPolicy` is required. +This `WAFPolicy` protects every route attached to the Gateway. Later changes to the `APPolicy` or `APLogConf` spec trigger recompilation and an automatic re-fetch. You don't need to update the `WAFPolicy`. ## Configure HTTPRoutes @@ -524,7 +524,7 @@ gateway-nginx-7f9b8d6c4d-xxxxx 3/3 Running 0 2m ## Test deployment and policy enforcement -Confirm the Gateway was assigned an IP address and reports `Programmed=True`: +Confirm the Gateway has an IP address assigned and reports `Programmed=True`: ```shell kubectl describe gateways.gateway.networking.k8s.io gateway @@ -536,16 +536,17 @@ Addresses: Value: 192.0.2.1 ``` -Save the public IP address and port of the Gateway into shell variables: + +Save the public IP address and port of the Gateway to shell variables: ```text -GW_IP=XXX.YYY.ZZZ.III +GW_IP=192.0.2.1 GW_PORT= ``` **Verify normal traffic flows.** Send a request to the `customers` route — the response contains the fake sensitive data from the `customers` backend: -{{< call-out "note" >}} If you have a DNS record allocated for `cafe.example.com`, you can send the request directly to that hostname, without needing to resolve. {{< /call-out >}} +{{< call-out class="note" title="Note" >}} If you have a DNS record for `cafe.example.com`, you can send the request directly to that hostname without `--resolve`. {{< /call-out >}} ```shell curl --resolve cafe.example.com:$GW_PORT:$GW_IP http://cafe.example.com:$GW_PORT/customers @@ -587,7 +588,7 @@ curl --resolve cafe.example.com:$GW_PORT:$GW_IP "http://cafe.example.com:$GW_POR ... ``` -{{< call-out "note" >}} The exact blocking response depends on your WAF policy configuration. Check the security log for a corresponding blocked event using `kubectl logs -c waf-enforcer`. {{< /call-out >}} +{{< call-out class="note" title="Note" >}} The exact blocking response depends on your WAF policy configuration. Check the security log for a corresponding blocked event using `kubectl logs -c waf-enforcer`. {{< /call-out >}} ## Apply a route-level override (optional) From cd3c38e318ccca62094c279facd458b63bca7cfb Mon Sep 17 00:00:00 2001 From: Travis Martin <33876974+travisamartin@users.noreply.github.com> Date: Wed, 12 Aug 2026 13:43:33 -0700 Subject: [PATCH 24/50] Apply suggestions from code review Co-authored-by: Shaun --- content/includes/waf/plm-deploy-infrastructure.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/content/includes/waf/plm-deploy-infrastructure.md b/content/includes/waf/plm-deploy-infrastructure.md index fd277924f..49d6c5e28 100644 --- a/content/includes/waf/plm-deploy-infrastructure.md +++ b/content/includes/waf/plm-deploy-infrastructure.md @@ -17,7 +17,7 @@ Install the four custom resource definitions (CRDs) that the Policy Controller m kubectl apply -f https://raw.githubusercontent.com/nginx/waf-policy-controller/main/manifests/1-deploy-crds.yaml ``` -Confirm all four CRDs are present: +Confirm the CRDs are present: ```shell kubectl get crd | grep appprotect.f5.com @@ -124,13 +124,13 @@ kubectl rollout status deployment/plm-f5-waf-policy-controller \ --namespace plm-system --timeout=180s ``` -Confirm all eight pods are running: +Confirm all pods are running: ```shell kubectl get pods --namespace plm-system ``` -Expected output: +Example output: ```text NAME READY STATUS RESTARTS @@ -144,7 +144,7 @@ plm-f5-waf-seaweed-volume-1 1/1 Running 0 plm-f5-waf-seaweed-volume-2 1/1 Running 0 ``` -Confirm the four CRDs are present: +Confirm the CRDs are present: ```shell kubectl get crd | grep appprotect.f5.com From 8a7525272972d2e6792a68579ecb580fb9d54545 Mon Sep 17 00:00:00 2001 From: Travis Martin <33876974+travisamartin@users.noreply.github.com> Date: Wed, 12 Aug 2026 13:44:07 -0700 Subject: [PATCH 25/50] Apply suggestions from code review Co-authored-by: Jon Torre <78599298+JTorreG@users.noreply.github.com> --- content/includes/waf/plm-define-policy-bundle-method.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/includes/waf/plm-define-policy-bundle-method.md b/content/includes/waf/plm-define-policy-bundle-method.md index 7627f620d..4c394ce95 100644 --- a/content/includes/waf/plm-define-policy-bundle-method.md +++ b/content/includes/waf/plm-define-policy-bundle-method.md @@ -64,7 +64,7 @@ isCompiled: false #### Update a precompiled bundle -The Policy Controller does not poll the artifact registry for changes. To pick up a new version of a bundle, update the `$ref` URL in your `APPolicy` resource (or bump its revision annotation) and re-apply it. Replace ``, ``, and `` with your values: +The Policy Controller does not poll the artifact registry for changes. To pick up a new version of a bundle, update the `$ref` URL in your `APPolicy` resource (or bump its revision annotation) and reapply it. Replace ``, ``, and `` with your values: ```shell kubectl apply -f - < Date: Wed, 12 Aug 2026 13:45:21 -0700 Subject: [PATCH 26/50] removed TODO from plm-define-policy-bundle-method.md --- content/includes/waf/plm-define-policy-bundle-method.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/includes/waf/plm-define-policy-bundle-method.md b/content/includes/waf/plm-define-policy-bundle-method.md index 7627f620d..f77ad3c52 100644 --- a/content/includes/waf/plm-define-policy-bundle-method.md +++ b/content/includes/waf/plm-define-policy-bundle-method.md @@ -30,7 +30,7 @@ EOF ``` {{< call-out class="note" title="Note" >}} -The Policy Controller must be able to reach the artifact registry host over HTTPS. If the registry uses a private certificate authority, configure the Policy Controller to trust that CA. +The Policy Controller must be able to reach the artifact registry host over HTTPS. If the registry uses a private certificate authority, configure the Policy Controller to trust that CA. {{< /call-out >}} #### Confirm the policy is ready From 604ad99107812865941f51898e7f0afa08732b02 Mon Sep 17 00:00:00 2001 From: Travis Martin Date: Thu, 13 Aug 2026 13:34:56 -0700 Subject: [PATCH 27/50] fix: Move CRD step to end of PLM infrastructure include Helm installs CRDs on first install automatically, so the manual kubectl apply step is only required when upgrading. Relocate it from the top of the install flow (where it misled new users) to a new "Update the CRDs" section after "Verify the deployment". Add a note clarifying that the step can be skipped on a fresh install. Remove the redundant CRD verification block from the upgrade section, as the verify section already confirms CRD presence. Also pins the GitHub raw URL to the versioned tag using the version-waf-policy-controller shortcode, replacing the previous reference to main. --- .../includes/waf/plm-deploy-infrastructure.md | 31 +++++-------------- 1 file changed, 8 insertions(+), 23 deletions(-) diff --git a/content/includes/waf/plm-deploy-infrastructure.md b/content/includes/waf/plm-deploy-infrastructure.md index 49d6c5e28..6f9fb453d 100644 --- a/content/includes/waf/plm-deploy-infrastructure.md +++ b/content/includes/waf/plm-deploy-infrastructure.md @@ -9,29 +9,6 @@ The Policy Lifecycle Manager (PLM) backend runs as a Kubernetes operator. It wat F5 WAF for NGINX is installed using a separate Helm chart from your NGINX data plane. The steps in this section install only the F5 WAF for NGINX PLM components and do not affect your existing NGINX installation. -### Install the CRDs - -Install the four custom resource definitions (CRDs) that the Policy Controller manages: - -```shell -kubectl apply -f https://raw.githubusercontent.com/nginx/waf-policy-controller/main/manifests/1-deploy-crds.yaml -``` - -Confirm the CRDs are present: - -```shell -kubectl get crd | grep appprotect.f5.com -``` - -Expected output: - -```text -appolicies.appprotect.f5.com -aplogconfs.appprotect.f5.com -apsignatures.appprotect.f5.com -apusersigs.appprotect.f5.com -``` - ### Create the registry pull secret Create a namespace for the PLM components, then create the registry pull secret using the credentials from the previous section. Replace `` with your F5 WAF for NGINX JWT. @@ -151,3 +128,11 @@ kubectl get crd | grep appprotect.f5.com ``` All eight pods running and all four CRDs present confirms the PLM backend is ready. + +### Update the CRDs + +Skip this step on a fresh install — Helm installs the CRDs automatically. When upgrading PLM, apply the CRDs manually before running `helm upgrade`: + +```shell +kubectl apply -f https://raw.githubusercontent.com/nginx/waf-policy-controller/{{< version-waf-policy-controller >}}/manifests/1-deploy-crds.yaml +``` From f731e456ec869a901c173b6880f7248eb4715350 Mon Sep 17 00:00:00 2001 From: Travis Martin Date: Thu, 13 Aug 2026 14:09:35 -0700 Subject: [PATCH 28/50] fix: Improve PLM registry secret setup and JWT prerequisite - Rewrite registry pull secret steps to store the JWT in a Kubernetes Secret before use, avoiding inline JWT exposure - Add JWT download steps to get-started-plm.md prerequisites, reorder prereqs so JWT comes before the registry Secret - Copy edit download-jwt.md include: active voice in call-out, bold UI labels separated, update f5-files consumer list --- .../installation/nginx-plus/download-jwt.md | 10 +++--- .../includes/waf/plm-deploy-infrastructure.md | 34 +++++++++++++------ .../ngf/waf-integration/get-started-plm.md | 4 +++ 3 files changed, 33 insertions(+), 15 deletions(-) diff --git a/content/includes/ngf/installation/nginx-plus/download-jwt.md b/content/includes/ngf/installation/nginx-plus/download-jwt.md index a9535e6b5..1ee879f98 100644 --- a/content/includes/ngf/installation/nginx-plus/download-jwt.md +++ b/content/includes/ngf/installation/nginx-plus/download-jwt.md @@ -2,13 +2,15 @@ f5-product: NGINX Gateway Fabric f5-files: - content/ngf/install/helm.md -- content/ngf/install/manifests.md +- content/ngf/install/manifests/plus.md +- content/ngf/install/manifests/plus-experimental.md - content/ngf/install/nginx-plus.md +- content/ngf/waf-integration/get-started-plm.md --- 1. Log in to [MyF5](https://my.f5.com/manage/s/). -2. Go to **My Products & Plans > Subscriptions** to see your active subscriptions. -3. Find your NGINX products or services subscription, and select the **Subscription ID** for details. +2. Go to **My Products & Plans** > **Subscriptions** to see your active subscriptions. +3. Find your NGINX products or services subscription and select the **Subscription ID** for details. 4. Download the **JSON Web Token (JWT)** from the subscription page. -{{< call-out class="note" >}} The Connectivity Stack for Kubernetes JWT does not work with NGINX Plus reporting. A regular NGINX Plus instance JWT must be used. {{< /call-out >}} +{{< call-out class="note" title="Note" >}} The Connectivity Stack for Kubernetes JWT does not work with NGINX Plus reporting. Use a regular NGINX Plus instance JWT. {{< /call-out >}} diff --git a/content/includes/waf/plm-deploy-infrastructure.md b/content/includes/waf/plm-deploy-infrastructure.md index 6f9fb453d..9feb3c8a3 100644 --- a/content/includes/waf/plm-deploy-infrastructure.md +++ b/content/includes/waf/plm-deploy-infrastructure.md @@ -11,20 +11,32 @@ F5 WAF for NGINX is installed using a separate Helm chart from your NGINX data p ### Create the registry pull secret -Create a namespace for the PLM components, then create the registry pull secret using the credentials from the previous section. Replace `` with your F5 WAF for NGINX JWT. +Create a namespace for the PLM components, store your JWT in a Kubernetes Secret, then create the registry pull secret for the private F5 container registry. - +1. Create the namespace and store your JWT. The following commands assume your JWT file is named `license.jwt`: -```shell -kubectl create namespace plm-system + ```shell + kubectl create namespace plm-system -kubectl create secret docker-registry regcred \ - --namespace plm-system \ - --docker-server=private-registry.nginx.com \ - --docker-username= \ - --docker-password=none \ - --dry-run=client --output yaml | kubectl apply -f - -``` + kubectl create secret generic jwt-reg-secret \ + --namespace plm-system \ + --from-file=license.jwt + ``` + +2. Retrieve the JWT from the Secret and create the registry pull secret: + + ```shell + JWT=$(kubectl get secret jwt-reg-secret \ + --namespace plm-system \ + -o jsonpath='{.data.license\.jwt}' | base64 -d) + + kubectl create secret docker-registry regcred \ + --namespace plm-system \ + --docker-server=private-registry.nginx.com \ + --docker-username="$JWT" \ + --docker-password=none \ + --dry-run=client --output yaml | kubectl apply -f - + ``` ### Install the Policy Controller diff --git a/content/ngf/waf-integration/get-started-plm.md b/content/ngf/waf-integration/get-started-plm.md index 24588832a..93e96385a 100644 --- a/content/ngf/waf-integration/get-started-plm.md +++ b/content/ngf/waf-integration/get-started-plm.md @@ -30,6 +30,10 @@ Before you start, make sure you have: - `kubectl` access to a Kubernetes cluster. - A valid F5 WAF for NGINX subscription. F5 WAF for NGINX is a separate add-on to NGINX Plus and isn't included with the NGINX Plus license. +- Your F5 WAF for NGINX JWT from MyF5. To get it: + + {{< include "/ngf/installation/nginx-plus/download-jwt.md" >}} + - Your private registry credentials Secret for `private-registry.nginx.com`. You'll reference this Secret when you install NGINX Gateway Fabric. This tutorial uses the following example values. You can use different values — if you do, replace them consistently throughout. From 6c584356e276ac9ef3b57bff84981f7ea17f1461 Mon Sep 17 00:00:00 2001 From: Travis Martin Date: Thu, 13 Aug 2026 14:16:56 -0700 Subject: [PATCH 29/50] fix: Quote heredoc delimiter to prevent $ref expansion --- content/includes/waf/plm-define-policy-bundle-method.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/content/includes/waf/plm-define-policy-bundle-method.md b/content/includes/waf/plm-define-policy-bundle-method.md index c2cc30cbd..fd900177c 100644 --- a/content/includes/waf/plm-define-policy-bundle-method.md +++ b/content/includes/waf/plm-define-policy-bundle-method.md @@ -17,7 +17,7 @@ Use this method when: Create an `APPolicy` resource that references your bundle. Replace ``, ``, and `` with your values: ```shell -kubectl apply -f - <`, ``, and `` with your values: ```shell -kubectl apply -f - < Date: Thu, 13 Aug 2026 14:26:12 -0700 Subject: [PATCH 30/50] fix: Add example output to CRD verification and CRD upgrade steps --- content/includes/waf/plm-deploy-infrastructure.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/content/includes/waf/plm-deploy-infrastructure.md b/content/includes/waf/plm-deploy-infrastructure.md index 9feb3c8a3..f5f292415 100644 --- a/content/includes/waf/plm-deploy-infrastructure.md +++ b/content/includes/waf/plm-deploy-infrastructure.md @@ -139,6 +139,15 @@ Confirm the CRDs are present: kubectl get crd | grep appprotect.f5.com ``` +Expected output: + +```text +aplogconfs.appprotect.f5.com +appolicies.appprotect.f5.com +apsignatures.appprotect.f5.com +apusersigs.appprotect.f5.com +``` + All eight pods running and all four CRDs present confirms the PLM backend is ready. ### Update the CRDs From 25454064b14e06167248bd7739d55bf6c6fe3562 Mon Sep 17 00:00:00 2001 From: Travis Martin Date: Thu, 13 Aug 2026 14:50:43 -0700 Subject: [PATCH 31/50] fix: Clarify securityUpdatesRepo cert and key are optional --- content/includes/waf/plm-deploy-infrastructure.md | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/content/includes/waf/plm-deploy-infrastructure.md b/content/includes/waf/plm-deploy-infrastructure.md index f5f292415..f33cc0e5f 100644 --- a/content/includes/waf/plm-deploy-infrastructure.md +++ b/content/includes/waf/plm-deploy-infrastructure.md @@ -40,7 +40,11 @@ Create a namespace for the PLM components, store your JWT in a Kubernetes Secret ### Install the Policy Controller -Create a values file for the Helm installation. Replace `` and `` with the base64-encoded contents of your `nginx-repo.crt` and `nginx-repo.key` files. To encode them, run: +Create a values file for the Helm installation. + +The `securityUpdatesRepo.cert` and `securityUpdatesRepo.key` fields are optional. They are only required if your signature repository needs certificate-based authentication. The Policy Controller starts successfully with these fields left empty. + +If your signature repository requires them, replace `` and `` with the base64-encoded contents of your `nginx-repo.crt` and `nginx-repo.key` files. To encode them, run: ```shell base64 --wrap=0 < nginx-repo.crt @@ -53,8 +57,8 @@ Create `/tmp/plm-values.yaml`: imagePullSecrets: - name: regcred securityUpdatesRepo: - cert: "" - key: "" + cert: "" # optional: only needed for authenticated signature repository access + key: "" # optional: only needed for authenticated signature repository access policyController: image: tag: "{{< version-waf-policy-controller >}}" From 1c61ff88fe65a3fbad97f57660fb86ea556a7d35 Mon Sep 17 00:00:00 2001 From: Travis Martin Date: Thu, 13 Aug 2026 15:20:28 -0700 Subject: [PATCH 32/50] docs: Add PLM prerequisites include and copy edit tutorial MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add content/includes/waf/plm-prerequisites.md with PLM-specific prerequisites (StorageClass, Helm, JWT) and SeaweedFS storage defaults (embedded S3 store, default pod topology, credential Secret, HTTP-by-default note) - Restructure get-started-plm.md Before you begin: add PLM prerequisites subsection, add Example values H3 - Copy edit get-started-plm.md: fix causal 'since' → 'because', remove hedge ('should show'), split two-action step, rewrite passive results, tighten team-ownership callouts, fix directional reference, fix 'this guide' → 'this tutorial' - Copy edit plm-define-policy-bundle-method.md: use contraction ('doesn't poll') --- .../waf/plm-define-policy-bundle-method.md | 2 +- content/includes/waf/plm-prerequisites.md | 18 ++++++++++++ .../ngf/waf-integration/get-started-plm.md | 28 ++++++++++++------- 3 files changed, 37 insertions(+), 11 deletions(-) create mode 100644 content/includes/waf/plm-prerequisites.md diff --git a/content/includes/waf/plm-define-policy-bundle-method.md b/content/includes/waf/plm-define-policy-bundle-method.md index fd900177c..6c06668b5 100644 --- a/content/includes/waf/plm-define-policy-bundle-method.md +++ b/content/includes/waf/plm-define-policy-bundle-method.md @@ -64,7 +64,7 @@ isCompiled: false #### Update a precompiled bundle -The Policy Controller does not poll the artifact registry for changes. To pick up a new version of a bundle, update the `$ref` URL in your `APPolicy` resource (or bump its revision annotation) and reapply it. Replace ``, ``, and `` with your values: +The Policy Controller doesn't poll the artifact registry for changes. To pick up a new version of a bundle, update the `$ref` URL in your `APPolicy` resource (or bump its revision annotation) and reapply it. Replace ``, ``, and `` with your values: ```shell kubectl apply -f - <<'EOF' diff --git a/content/includes/waf/plm-prerequisites.md b/content/includes/waf/plm-prerequisites.md new file mode 100644 index 000000000..826e57c52 --- /dev/null +++ b/content/includes/waf/plm-prerequisites.md @@ -0,0 +1,18 @@ +--- +f5-product: F5 WAF for NGINX +f5-files: +- content/ngf/waf-integration/get-started-plm.md +--- + + + +- A Kubernetes cluster with a default StorageClass that supports dynamic provisioning. The PLM object store relies on PersistentVolumeClaims. Without a default StorageClass, the SeaweedFS pods stay `Pending`. +- Helm 3.x installed. The PLM backend installs as a Helm chart. +- An F5 WAF for NGINX JWT from MyF5, used to pull images from `private-registry.nginx.com`. +- Optionally, `nginx-repo.crt` and `nginx-repo.key` from MyF5, needed only for authenticated signature updates from `pkgs.nginx.com`. + +PLM ships with an embedded SeaweedFS S3-compatible object store. The bundled SeaweedFS operator deploys and manages it. You don't need to provide an external S3 bucket. The PLM controller writes compiled policy bundles to this store; the data plane reads from it. + +By default, the deployment creates one master pod, one filer pod, and three volume pods, each backed by its own PVC. The chart generates credentials for the store and saves them in the `-f5-waf-seaweedfs-auth` Secret. + +By default, communication between PLM and the object store uses unencrypted HTTP. To enable TLS, see the PLM chart values (`helm show values nginx-stable/f5-waf-policy-controller`). diff --git a/content/ngf/waf-integration/get-started-plm.md b/content/ngf/waf-integration/get-started-plm.md index 93e96385a..ccff1c8f5 100644 --- a/content/ngf/waf-integration/get-started-plm.md +++ b/content/ngf/waf-integration/get-started-plm.md @@ -36,6 +36,14 @@ Before you start, make sure you have: - Your private registry credentials Secret for `private-registry.nginx.com`. You'll reference this Secret when you install NGINX Gateway Fabric. +### PLM prerequisites + +The following requirements apply to the PLM backend you'll install in this tutorial: + +{{< include "waf/plm-prerequisites.md" >}} + +### Example values + This tutorial uses the following example values. You can use different values — if you do, replace them consistently throughout. | Example value | What it represents | @@ -91,7 +99,7 @@ nginxGateway: {{}} -Install NGINX Gateway Fabric by following [the installation guide]({{< ref "/ngf/install/helm.md" >}}) and using the **NGINX Plus with WAF** tab, and apply this `values.yaml` file in your install or upgrade command, specifying `--values values.yaml`. +Install NGINX Gateway Fabric by following [the installation guide]({{< ref "/ngf/install/helm.md" >}}) and using the **NGINX Plus with WAF** tab. Apply this `values.yaml` file in your install or upgrade command by specifying `--values values.yaml`. The PLM installation creates the credentials Secret automatically, containing the S3 secret access key in the `seaweedfs_admin_secret` field (access key ID `admin` by default): @@ -187,7 +195,7 @@ EOF ## Configure security logging (optional) -This section is typically owned by your security team. If that's not you, share it with them before continuing. +This section is typically owned by the security team. If you're not on the security team, share this section with them before continuing. PLM security logging profiles are defined as `APLogConf` custom resources. Create a namespace to hold your security resources, then define a log profile that logs illegal requests: @@ -222,11 +230,11 @@ If you skip this section, omit the `securityLogs` field in the `WAFPolicy` resou ## Define the WAF policy -This section is typically owned by your security team. They define the policy in the `security` namespace, separate from the Gateway namespace, so that security resources are managed independently from routing configuration. If that's not you, share this section with them — you'll need the `APPolicy` name and namespace before continuing to the next section. +This section is typically owned by the security team. They define the policy in the `security` namespace, separate from the Gateway namespace, so security resources are managed independently from routing configuration. If you're not on the security team, share this section with them — you'll need the `APPolicy` name and namespace before continuing. The `APPolicy` resource defines the security policy. The PLM controller watches it, compiles it, and writes `status.bundle` with `state: ready` when the bundle is available. -Use the **Inline** tab for this guide's primary workflow. The other tabs provide alternate policy-source methods. +Use the **Inline** tab for this tutorial's primary workflow. The other tabs cover alternate policy-source methods. {{}} @@ -266,7 +274,7 @@ Wait for the bundle to become ready: kubectl wait --for=jsonpath='{.status.bundle.state}'=ready appolicy/attack-signatures -n security --timeout=60s ``` -Because the `APPolicy` and `APLogConf` live in the `security` namespace but the `WAFPolicy` you create next targets a Gateway in the `default` namespace, create a `ReferenceGrant` in the `security` namespace to permit the cross-namespace reference: +The `APPolicy` and `APLogConf` are in the `security` namespace, but the `WAFPolicy` you create next targets a Gateway in the `default` namespace. To permit the cross-namespace reference, create a `ReferenceGrant` in the `security` namespace: ```yaml kubectl apply -f - < \ --output jsonpath='State: {.status.bundle.state}{"\n"}Bundle: {.status.bundle.location}{"\n"}Compiler: {.status.bundle.compilerVersion}{"\n"}' ``` -The output should show `State: ready`. +The output shows `State: ready` when compilation succeeds. #### Private repository @@ -580,7 +588,7 @@ The WAF detects the attack signature and rejects the request: ... ``` -**Verify the `orders` route is also protected.** Since the policy targets the Gateway, all attached routes inherit protection: +**Verify the `orders` route is also protected.** Because the policy targets the Gateway, all attached routes inherit protection: ```shell curl --resolve cafe.example.com:$GW_PORT:$GW_IP "http://cafe.example.com:$GW_PORT/orders?x=" @@ -596,9 +604,9 @@ curl --resolve cafe.example.com:$GW_PORT:$GW_IP "http://cafe.example.com:$GW_POR ## Apply a route-level override (optional) -In the previous step, you saw that the `customers` route returns sensitive data (credit card numbers and SSNs) in the response body. The gateway-level policy blocks inbound attacks, but doesn't inspect outbound responses. +The `customers` route returns sensitive data (credit card numbers and SSNs) in the response body. The gateway-level policy blocks inbound attacks but doesn't inspect outbound responses. -This pattern is a good example of a SecOps and app team collaboration: the security team defines a stricter policy for a specific service, and the platform engineer or app developer attaches it as a route-level override. The override applies only to the `customers` route — other routes continue using the gateway-level policy. +This is a common pattern for SecOps and app team collaboration: the security team defines a stricter policy for a specific service, and the platform engineer or app developer attaches it as a route-level override. The override applies only to the `customers` route — other routes continue using the gateway-level policy. To protect sensitive data in responses, define a **data guard** `APPolicy` and apply it as a route-level override on the `customers` route: @@ -660,7 +668,7 @@ kubectl wait --for=jsonpath='{.status.ancestors[0].conditions[?(@.type=="Program curl --resolve cafe.example.com:$GW_PORT:$GW_IP http://cafe.example.com:$GW_PORT/customers ``` -The credit card number and SSN are now masked in the response: +WAF now masks the credit card number and SSN in the response: ```text Customer List: From 2309a3817259f89d2e164bfe5824aedacd6cd825 Mon Sep 17 00:00:00 2001 From: Travis Martin Date: Fri, 14 Aug 2026 09:12:32 -0700 Subject: [PATCH 33/50] docs: Document user-provided certificates requirement for PLM TLS The PLM chart does not generate TLS certificates. If certificates.enabled is set to true in the seaweedfsOperatorConfig, all five Secrets (CA, master, volume, filer, client) must be pre-created before running helm upgrade --install. Missing Secrets cause SeaweedFS pods to fail on mount. Add an optional TLS subsection to the 'Install the Policy Controller' section that: - States TLS is off by default - Shows the certificates block with the five secretName keys - Warns explicitly that the chart does not generate certificates - Provides kubectl create secret commands for each required Secret - Explains the PKI prerequisite Resolves feedback from code review. --- .../includes/waf/plm-deploy-infrastructure.md | 50 +++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/content/includes/waf/plm-deploy-infrastructure.md b/content/includes/waf/plm-deploy-infrastructure.md index f33cc0e5f..86b5c0c01 100644 --- a/content/includes/waf/plm-deploy-infrastructure.md +++ b/content/includes/waf/plm-deploy-infrastructure.md @@ -75,6 +75,56 @@ seaweedfs-operator: pullSecrets: regcred ``` +#### Enable TLS for PLM storage (optional) + +By default, communication between PLM components and the SeaweedFS object store uses unencrypted HTTP. To enable TLS, add a `certificates` block to `/tmp/plm-values.yaml`: + +```yaml +seaweedfsOperatorConfig: + seaweedfs: + certificates: + enabled: true + caSecretName: plm-system-seaweedfs-ca-cert + masterSecretName: plm-system-seaweedfs-master-cert + volumeSecretName: plm-system-seaweedfs-volume-cert + filerSecretName: plm-system-seaweedfs-filer-cert + clientSecretName: plm-system-seaweedfs-client-cert +``` + +{{< call-out class="warning" title="Warning" >}} +The PLM chart does not generate certificates. You must create all five Secrets listed above before running `helm upgrade --install`. If any Secret is missing, the SeaweedFS pods will fail to mount their certificates and will not start. +{{< /call-out >}} + +Create the Secrets from your CA and certificate files before installing: + +```shell +kubectl create secret generic plm-system-seaweedfs-ca-cert \ + --namespace plm-system \ + --from-file=ca.crt= + +kubectl create secret tls plm-system-seaweedfs-master-cert \ + --namespace plm-system \ + --cert= \ + --key= + +kubectl create secret tls plm-system-seaweedfs-volume-cert \ + --namespace plm-system \ + --cert= \ + --key= + +kubectl create secret tls plm-system-seaweedfs-filer-cert \ + --namespace plm-system \ + --cert= \ + --key= + +kubectl create secret tls plm-system-seaweedfs-client-cert \ + --namespace plm-system \ + --cert= \ + --key= +``` + +Replace each `` placeholder with the path to the corresponding certificate and key file from your PKI. The CA must sign all component certificates. If you don't have an existing PKI, generate a CA and sign the five component certificates before proceeding. + Add the NGINX Helm repository and install the chart: ```shell From 6e46c1fba18d5f3ffa8379ac090b7d03c9334e03 Mon Sep 17 00:00:00 2001 From: Travis Martin Date: Fri, 14 Aug 2026 09:25:35 -0700 Subject: [PATCH 34/50] docs: Replace kubectl wait with polling loop for SeaweedFS pods The SeaweedFS operator deployment becomes Available before it reconciles the SeaweedFS CR and creates the SeaweedFS pods. This means kubectl wait --selector app.kubernetes.io/name=seaweedfs can exit immediately with no matches (and therefore an error) during the window between the operator being ready and the pods being created. Replace the two-step wait with a polling loop that retries kubectl wait every 5 seconds until either all SeaweedFS pods are Ready or a 300s timeout is exceeded. --- .../includes/waf/plm-deploy-infrastructure.md | 21 ++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/content/includes/waf/plm-deploy-infrastructure.md b/content/includes/waf/plm-deploy-infrastructure.md index 86b5c0c01..613183e73 100644 --- a/content/includes/waf/plm-deploy-infrastructure.md +++ b/content/includes/waf/plm-deploy-infrastructure.md @@ -152,12 +152,23 @@ Wait for the SeaweedFS storage backend: ```shell kubectl rollout status deployment/plm-seaweedfs-operator \ --namespace plm-system --timeout=120s +``` -kubectl wait pods \ - --selector app.kubernetes.io/name=seaweedfs \ - --for=condition=Ready \ - --namespace plm-system \ - --timeout=180s +The SeaweedFS operator creates the SeaweedFS pods after it reconciles the SeaweedFS custom resource, so there is a window where the operator deployment is ready but no SeaweedFS pods exist yet. Poll until the pods appear and are ready: + +```shell +end=$((SECONDS + 300)) +until kubectl wait pods \ + --selector app.kubernetes.io/name=seaweedfs \ + --for=condition=Ready \ + --namespace plm-system \ + --timeout=10s 2>/dev/null; do + if [ $SECONDS -ge $end ]; then + echo "Timed out waiting for SeaweedFS pods" + exit 1 + fi + sleep 5 +done ``` Wait for the Policy Controller: From afdded7339cf47e7e68e18ad0e9c74038323d173 Mon Sep 17 00:00:00 2001 From: Travis Martin Date: Fri, 14 Aug 2026 09:40:08 -0700 Subject: [PATCH 35/50] docs: Clarify heading structure in PLM deploy infrastructure include - Add '#### Install the chart' heading before the helm repo add / helm upgrade commands to visually separate them from the optional TLS subsection. Without this heading, the install commands appeared to be continuation content of the TLS section rather than a step for all readers. - Replace inline skip note on '### Update the CRDs' with a note call-out so the upgrade-only scope is the first thing readers see before the imperative content. --- content/includes/waf/plm-deploy-infrastructure.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/content/includes/waf/plm-deploy-infrastructure.md b/content/includes/waf/plm-deploy-infrastructure.md index 613183e73..766263a07 100644 --- a/content/includes/waf/plm-deploy-infrastructure.md +++ b/content/includes/waf/plm-deploy-infrastructure.md @@ -125,6 +125,8 @@ kubectl create secret tls plm-system-seaweedfs-client-cert \ Replace each `` placeholder with the path to the corresponding certificate and key file from your PKI. The CA must sign all component certificates. If you don't have an existing PKI, generate a CA and sign the five component certificates before proceeding. +#### Install the chart + Add the NGINX Helm repository and install the chart: ```shell @@ -217,7 +219,11 @@ All eight pods running and all four CRDs present confirms the PLM backend is rea ### Update the CRDs -Skip this step on a fresh install — Helm installs the CRDs automatically. When upgrading PLM, apply the CRDs manually before running `helm upgrade`: +{{< call-out class="note" title="Note" >}} +Skip this step on a fresh install — Helm installs the CRDs automatically. Only follow these steps when upgrading an existing PLM installation. +{{< /call-out >}} + +When upgrading PLM, apply the CRDs manually before running `helm upgrade`: ```shell kubectl apply -f https://raw.githubusercontent.com/nginx/waf-policy-controller/{{< version-waf-policy-controller >}}/manifests/1-deploy-crds.yaml From 2af0092925bda1d82541938730410c623d195041 Mon Sep 17 00:00:00 2001 From: Travis Martin Date: Wed, 19 Aug 2026 11:42:11 -0700 Subject: [PATCH 36/50] refactor: Extract reusable PLM sections into shared includes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Move the sample app deployment, security logging configuration, and APPolicy inline/Git-reference definitions from get-started-plm.md into product-agnostic shared includes so NIC can reuse them: - plm-sample-app.md: customers and orders Deployment/Service manifests - plm-configure-logging.md: security namespace and APLogConf definition - plm-define-policy-inline-git.md: APPolicy inline and Git-reference methods The ReferenceGrant block stays inline in the NGF tutorial — NIC does not use ReferenceGrant. The precompiled-bundle method remains in the existing plm-define-policy-bundle-method.md include. Addresses review comment by @ciarams87 (r3756532202). --- content/includes/waf/plm-configure-logging.md | 38 +++ .../waf/plm-define-policy-inline-git.md | 119 +++++++++ content/includes/waf/plm-sample-app.md | 82 +++++++ .../ngf/waf-integration/get-started-plm.md | 232 +----------------- 4 files changed, 243 insertions(+), 228 deletions(-) create mode 100644 content/includes/waf/plm-configure-logging.md create mode 100644 content/includes/waf/plm-define-policy-inline-git.md create mode 100644 content/includes/waf/plm-sample-app.md diff --git a/content/includes/waf/plm-configure-logging.md b/content/includes/waf/plm-configure-logging.md new file mode 100644 index 000000000..bfe98fec3 --- /dev/null +++ b/content/includes/waf/plm-configure-logging.md @@ -0,0 +1,38 @@ +--- +f5-product: F5 WAF for NGINX +f5-files: +- content/ngf/waf-integration/get-started-plm.md +--- + + + +This section is typically owned by the security team. If you're not on the security team, share this section with them before continuing. + +PLM security logging profiles are defined as `APLogConf` custom resources. Create a namespace to hold your security resources, then define a log profile that logs illegal requests: + +```shell +kubectl create namespace security +``` + +```yaml +kubectl apply -f - < + +This section is typically owned by the security team. They define the policy in the `security` namespace, separate from the Gateway namespace, so security resources are managed independently from routing configuration. If you're not on the security team, share this section with them — you'll need the `APPolicy` name and namespace before continuing. + +The `APPolicy` resource defines the security policy. The PLM controller watches it, compiles it, and writes `status.bundle` with `state: ready` when the bundle is available. + +### Inline policy + +Create an `APPolicy` resource with an inline policy that blocks all attack signatures: + +```yaml +kubectl apply -f - <`, ``, ``, ``, ``, and `` with your values: + +```shell +kubectl apply -f - < + namespace: +spec: + policy: + $ref: + externalReferenceDetails: + repositoryDetails: + repository: https://github.com//.git + ref: "" +EOF +``` + +{{< call-out class="note" title="Note" >}} Pin `ref` to a tag or commit SHA rather than a branch name in production environments. {{< /call-out >}} + +Check that the bundle compiled successfully: + +```shell +kubectl get appolicy \ + --namespace \ + --output jsonpath='State: {.status.bundle.state}{"\n"}Bundle: {.status.bundle.location}{"\n"}Compiler: {.status.bundle.compilerVersion}{"\n"}' +``` + +The output shows `State: ready` when compilation succeeds. + +#### Private repository + +For private repositories, create a Kubernetes Secret with your personal access token (PAT): + +```shell +kubectl create secret generic git-token-secret \ + --namespace \ + --from-literal=token= +``` + +Then reference the Secret in the `APPolicy` resource: + +```shell +kubectl apply -f - < + namespace: +spec: + policy: + $ref: + externalReferenceDetails: + repositoryDetails: + repository: https://github.com//.git + ref: "" + authentication: + token: git-token-secret +EOF +``` + +#### Update a Git-referenced policy + +The Policy Controller doesn't poll the Git repository for changes. To pick up changes to the referenced policy file, re-apply the `APPolicy` resource (or update its revision annotation) after you push changes to the repository. diff --git a/content/includes/waf/plm-sample-app.md b/content/includes/waf/plm-sample-app.md new file mode 100644 index 000000000..ddb9a5728 --- /dev/null +++ b/content/includes/waf/plm-sample-app.md @@ -0,0 +1,82 @@ +--- +f5-product: F5 WAF for NGINX +f5-files: +- content/ngf/waf-integration/get-started-plm.md +--- + + + +Deploy the `customers` and `orders` sample applications. The `customers` app returns a response containing fake sensitive data (credit card number and SSN), which you'll use later to demonstrate data guard masking: + +```yaml +kubectl apply -f - <}} ## Configure security logging (optional) -This section is typically owned by the security team. If you're not on the security team, share this section with them before continuing. - -PLM security logging profiles are defined as `APLogConf` custom resources. Create a namespace to hold your security resources, then define a log profile that logs illegal requests: - -```shell -kubectl create namespace security -``` - -```yaml -kubectl apply -f - <}} If you skip this section, omit the `securityLogs` field in the `WAFPolicy` resource in the next steps. ## Define the WAF policy -This section is typically owned by the security team. They define the policy in the `security` namespace, separate from the Gateway namespace, so security resources are managed independently from routing configuration. If you're not on the security team, share this section with them — you'll need the `APPolicy` name and namespace before continuing. - -The `APPolicy` resource defines the security policy. The PLM controller watches it, compiles it, and writes `status.bundle` with `state: ready` when the bundle is available. - -Use the **Inline** tab for this tutorial's primary workflow. The other tabs cover alternate policy-source methods. - -{{}} - -{{%tab name="Inline"%}} - -Create an `APPolicy` resource with an inline policy that blocks all attack signatures: - -```yaml -kubectl apply -f - <}} The `APPolicy` and `APLogConf` are in the `security` namespace, but the `WAFPolicy` you create next targets a Gateway in the `default` namespace. To permit the cross-namespace reference, create a `ReferenceGrant` in the `security` namespace: @@ -298,90 +154,10 @@ EOF {{< call-out class="note" title="Note" >}} The `ReferenceGrant` lives in the `security` namespace and must be created by whoever manages that namespace — typically your security team, not the platform engineer deploying the Gateway. Coordinate with them if you don't have access. Without a matching `ReferenceGrant`, the `WAFPolicy` is rejected with `ResolvedRefs=False` and reason `RefNotPermitted`. If you put the `APPolicy` and `APLogConf` in the same namespace as the `WAFPolicy`, you can skip the `ReferenceGrant`. See [Troubleshoot WAFPolicy status]({{< ref "/ngf/waf-integration/troubleshooting.md" >}}) for details. {{< /call-out >}} -{{% /tab %}} - -{{%tab name="Git reference"%}} - -Store your policy JSON in a Git repository and reference it from `APPolicy`. - -#### Public repository - -Create an `APPolicy` resource that references the policy file by path. Replace ``, ``, ``, ``, ``, and `` with your values: - -```shell -kubectl apply -f - < - namespace: -spec: - policy: - $ref: - externalReferenceDetails: - repositoryDetails: - repository: https://github.com//.git - ref: "" -EOF -``` - -{{< call-out class="note" title="Note" >}} Pin `ref` to a tag or commit SHA rather than a branch name in production environments. {{< /call-out >}} - -Check that the bundle compiled successfully: - -```shell -kubectl get appolicy \ - --namespace \ - --output jsonpath='State: {.status.bundle.state}{"\n"}Bundle: {.status.bundle.location}{"\n"}Compiler: {.status.bundle.compilerVersion}{"\n"}' -``` - -The output shows `State: ready` when compilation succeeds. - -#### Private repository - -For private repositories, create a Kubernetes secret with your personal access token (PAT): - -```shell -kubectl create secret generic git-token-secret \ - --namespace \ - --from-literal=token= -``` - -Then reference the secret in the `APPolicy` resource: - -```shell -kubectl apply -f - < - namespace: -spec: - policy: - $ref: - externalReferenceDetails: - repositoryDetails: - repository: https://github.com//.git - ref: "" - authentication: - token: git-token-secret -EOF -``` - -#### Update a Git-referenced policy - -The Policy Controller doesn't poll the Git repository for changes. To pick up changes to the referenced policy file, re-apply the `APPolicy` resource (or update its revision annotation) after you push changes to the repository. - -{{% /tab %}} - -{{%tab name="Precompiled bundle"%}} +For the precompiled-bundle method, see the **Precompiled bundle** tab: {{< include "waf/plm-define-policy-bundle-method.md" >}} -{{% /tab %}} - -{{}} - ## Deploy the Gateway and attach WAFPolicy Create a Gateway. WAF is already enabled globally, so NGINX Gateway Fabric automatically deploys the WAF sidecar containers alongside the NGINX Pod: From 781f7988a2ffc4a2d320a940130a17888ef1ca72 Mon Sep 17 00:00:00 2001 From: Travis Martin Date: Wed, 19 Aug 2026 11:42:18 -0700 Subject: [PATCH 37/50] style: Remove NIC f5-files entries from PLM includes NIC tutorials have not been created yet. Remove the placeholder content/nic/waf-integration/get-started-plm.md f5-files entry from all five PLM includes. These will be restored when the NIC tutorial is written as part of the NIC work. --- content/includes/waf/plm-define-policy-bundle-method.md | 1 - content/includes/waf/plm-deploy-infrastructure.md | 1 - 2 files changed, 2 deletions(-) diff --git a/content/includes/waf/plm-define-policy-bundle-method.md b/content/includes/waf/plm-define-policy-bundle-method.md index 6c06668b5..2537c211f 100644 --- a/content/includes/waf/plm-define-policy-bundle-method.md +++ b/content/includes/waf/plm-define-policy-bundle-method.md @@ -2,7 +2,6 @@ f5-product: F5 WAF for NGINX f5-files: - content/ngf/waf-integration/get-started-plm.md -- content/nic/waf-integration/get-started-plm.md --- diff --git a/content/includes/waf/plm-deploy-infrastructure.md b/content/includes/waf/plm-deploy-infrastructure.md index 766263a07..a5026b81b 100644 --- a/content/includes/waf/plm-deploy-infrastructure.md +++ b/content/includes/waf/plm-deploy-infrastructure.md @@ -2,7 +2,6 @@ f5-product: F5 WAF for NGINX f5-files: - content/ngf/waf-integration/get-started-plm.md -- content/nic/waf-integration/get-started-plm.md --- The Policy Lifecycle Manager (PLM) backend runs as a Kubernetes operator. It watches WAF custom resources and compiles WAF policies into bundles. The Policy Controller delegates compilation to a separate compiler service over gRPC. The resulting bundles are stored in an embedded SeaweedFS S3-compatible object store. From d591f4df9007aca84fee14f47214b8d3274217e9 Mon Sep 17 00:00:00 2001 From: Travis Martin Date: Wed, 19 Aug 2026 11:53:15 -0700 Subject: [PATCH 38/50] docs: Add troubleshooting section to PLM infrastructure include Add five deploy-time failure scenarios from SME review by @ohad-perets (r3769976191) to plm-deploy-infrastructure.md: - ImagePullBackOff: bad/expired JWT or line break in token - Policy Controller Init:0/1: SeaweedFS not ready, PVCs pending - SeaweedFS pods Pending: no default StorageClass - APPolicy invalid after enabling TLS: orphaned objects in storage - Helm ClusterRole conflict: duplicate PLM installation Also adds the Policy Controller log command as the primary diagnostic path, with a note that containers are distroless (kubectl exec unavailable). The APPolicy certificate error (x509: unknown authority) is placed in plm-define-policy-bundle-method.md as it only applies to the precompiled-bundle method, not general deployment. Addresses review comment by @ciarams87 and @ohad-perets (r3769976191). --- .../waf/plm-define-policy-bundle-method.md | 8 ++- .../includes/waf/plm-deploy-infrastructure.md | 57 +++++++++++++++++++ 2 files changed, 64 insertions(+), 1 deletion(-) diff --git a/content/includes/waf/plm-define-policy-bundle-method.md b/content/includes/waf/plm-define-policy-bundle-method.md index 2537c211f..8ae6acfcf 100644 --- a/content/includes/waf/plm-define-policy-bundle-method.md +++ b/content/includes/waf/plm-define-policy-bundle-method.md @@ -29,9 +29,15 @@ EOF ``` {{< call-out class="note" title="Note" >}} -The Policy Controller must be able to reach the artifact registry host over HTTPS. If the registry uses a private certificate authority, configure the Policy Controller to trust that CA. +The Policy Controller must be able to reach the artifact registry host over HTTPS. If the registry uses a private certificate authority (CA), mount the CA certificate into the Policy Controller pod and set the `SSL_CERT_FILE` environment variable to point to it. Note that `SSL_CERT_FILE` replaces the system trust store entirely rather than appending to it — if SeaweedFS TLS is also enabled, both CAs must be combined into a single file. {{< /call-out >}} +If the `APPolicy` status shows `x509: certificate signed by unknown authority`, the Policy Controller does not trust the artifact registry CA. Check the status for the full error: + +```shell +kubectl describe appolicy --namespace plm-system +``` + #### Confirm the policy is ready The Policy Controller processes the bundle and updates the `APPolicy` status. Check the `bundle.state` field: diff --git a/content/includes/waf/plm-deploy-infrastructure.md b/content/includes/waf/plm-deploy-infrastructure.md index a5026b81b..8665fa9cc 100644 --- a/content/includes/waf/plm-deploy-infrastructure.md +++ b/content/includes/waf/plm-deploy-infrastructure.md @@ -227,3 +227,60 @@ When upgrading PLM, apply the CRDs manually before running `helm upgrade`: ```shell kubectl apply -f https://raw.githubusercontent.com/nginx/waf-policy-controller/{{< version-waf-policy-controller >}}/manifests/1-deploy-crds.yaml ``` + +### Troubleshoot the PLM deployment + +The following are the most common failures during PLM installation, roughly in order of likelihood. + +**Pods stuck in `ImagePullBackOff`** + +The JWT is wrong, expired, or was pasted with a line break. Confirm with: + +```shell +kubectl get events --namespace plm-system --field-selector reason=Failed +``` + +The registry username must be the entire JWT string. The password must be the literal string `none`. + +**Policy Controller stuck in `Init:0/1`** + +This is expected during startup — the init container waits for both the compiler service and the S3 endpoint before starting. If it persists beyond a few minutes, check that the SeaweedFS pods are `Running`: + +```shell +kubectl get pods --namespace plm-system --selector app.kubernetes.io/name=seaweedfs +``` + +The most common cause is PVCs stuck in `Pending` because the cluster has no default StorageClass. + +**SeaweedFS pods `Pending`** + +No default StorageClass, or insufficient node capacity. Inspect the PVCs and available storage classes: + +```shell +kubectl get pvc --namespace plm-system +kubectl get storageclass +``` + +**`APPolicy` shows `invalid` with `unexpected EOF` after enabling TLS** + +Enabling TLS on an existing installation restarts the storage backend and can orphan objects written before TLS was enabled. Check the filer log for confirmation: + +```shell +kubectl logs --namespace plm-system plm-f5-waf-seaweed-filer-0 | grep "not found" +``` + +A `volume N not found` message confirms orphaned objects. Remove the affected `APPolicy` resource and reapply it so the Policy Controller regenerates the bundle. + +**Helm install fails on a ClusterRole** + +An error naming `seaweed-editor-role` or `seaweed-viewer-role` means another PLM installation already exists in the cluster. Only one PLM installation is supported per cluster. Remove the existing release before installing. + +**Checking Policy Controller logs** + +For any policy-related failure, the Policy Controller logs are the most useful diagnostic source: + +```shell +kubectl logs --namespace plm-system deploy/plm-f5-waf-policy-controller -c policy-controller +``` + +{{< call-out class="note" title="Note" >}} The `-c policy-controller` flag is required because the pod runs more than one container. The containers are distroless, so `kubectl exec` is not available for interactive debugging. {{< /call-out >}} From 1aa4cb227205d0a81385515c2d5d074c9111e74b Mon Sep 17 00:00:00 2001 From: Travis Martin Date: Wed, 19 Aug 2026 11:59:23 -0700 Subject: [PATCH 39/50] style: Copy edit PLM troubleshooting sections Apply F5 style guide to troubleshooting content added in the previous commit: - Convert bold symptom labels to #### subheadings (bold.md) - Rename heading from gerund to imperative (headings.md) - Rewrite fragment topic sentences (paragraph-structure.md) - Reorder conditional sentences: condition before instruction (conditional-sentences.md) - Tighten passive constructions to active imperative (active-voice.md) - Split sentences exceeding the 25-word conceptual limit (sentence-length.md) - Replace contractions withheld by ASD-STE100 (contractions.md) - Drop hedge phrase 'Note that' (modern-voice.md) - Tighten Note in plm-define-policy-bundle-method.md: split long sentence, remove 'must be able to' --- .../waf/plm-define-policy-bundle-method.md | 4 +-- .../includes/waf/plm-deploy-infrastructure.md | 34 +++++++++---------- 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/content/includes/waf/plm-define-policy-bundle-method.md b/content/includes/waf/plm-define-policy-bundle-method.md index 8ae6acfcf..960a8c87f 100644 --- a/content/includes/waf/plm-define-policy-bundle-method.md +++ b/content/includes/waf/plm-define-policy-bundle-method.md @@ -29,10 +29,10 @@ EOF ``` {{< call-out class="note" title="Note" >}} -The Policy Controller must be able to reach the artifact registry host over HTTPS. If the registry uses a private certificate authority (CA), mount the CA certificate into the Policy Controller pod and set the `SSL_CERT_FILE` environment variable to point to it. Note that `SSL_CERT_FILE` replaces the system trust store entirely rather than appending to it — if SeaweedFS TLS is also enabled, both CAs must be combined into a single file. +The Policy Controller must reach the artifact registry over HTTPS. If the registry uses a private certificate authority (CA), mount the CA certificate into the Policy Controller pod and set the `SSL_CERT_FILE` environment variable to its path. `SSL_CERT_FILE` replaces the system trust store entirely — it doesn't append to it. If SeaweedFS TLS is also enabled, combine both CAs into a single file and reference that file. {{< /call-out >}} -If the `APPolicy` status shows `x509: certificate signed by unknown authority`, the Policy Controller does not trust the artifact registry CA. Check the status for the full error: +If the `APPolicy` status shows `x509: certificate signed by unknown authority`, the Policy Controller doesn't trust the artifact registry CA. Check the status for the full error: ```shell kubectl describe appolicy --namespace plm-system diff --git a/content/includes/waf/plm-deploy-infrastructure.md b/content/includes/waf/plm-deploy-infrastructure.md index 8665fa9cc..b54f5b6d4 100644 --- a/content/includes/waf/plm-deploy-infrastructure.md +++ b/content/includes/waf/plm-deploy-infrastructure.md @@ -228,23 +228,23 @@ When upgrading PLM, apply the CRDs manually before running `helm upgrade`: kubectl apply -f https://raw.githubusercontent.com/nginx/waf-policy-controller/{{< version-waf-policy-controller >}}/manifests/1-deploy-crds.yaml ``` -### Troubleshoot the PLM deployment +### Troubleshoot the deployment -The following are the most common failures during PLM installation, roughly in order of likelihood. +These are the most common failures during PLM installation, roughly in order of likelihood. -**Pods stuck in `ImagePullBackOff`** +#### Pods stuck in `ImagePullBackOff` -The JWT is wrong, expired, or was pasted with a line break. Confirm with: +The JWT is wrong, expired, or contains a line break. Check the events log: ```shell kubectl get events --namespace plm-system --field-selector reason=Failed ``` -The registry username must be the entire JWT string. The password must be the literal string `none`. +Use the full JWT string as the registry username. Use the literal string `none` as the password. -**Policy Controller stuck in `Init:0/1`** +#### Policy Controller stuck in `Init:0/1` -This is expected during startup — the init container waits for both the compiler service and the S3 endpoint before starting. If it persists beyond a few minutes, check that the SeaweedFS pods are `Running`: +The `Init:0/1` state is expected during startup. The init container waits for the compiler service and the S3 endpoint before it starts. If the pod stays in `Init:0/1` for more than a few minutes, check that the SeaweedFS pods are `Running`: ```shell kubectl get pods --namespace plm-system --selector app.kubernetes.io/name=seaweedfs @@ -252,35 +252,35 @@ kubectl get pods --namespace plm-system --selector app.kubernetes.io/name=seawee The most common cause is PVCs stuck in `Pending` because the cluster has no default StorageClass. -**SeaweedFS pods `Pending`** +#### SeaweedFS pods `Pending` -No default StorageClass, or insufficient node capacity. Inspect the PVCs and available storage classes: +SeaweedFS pods stay `Pending` when the cluster has no default StorageClass or insufficient capacity. Check the PVCs and available storage classes: ```shell kubectl get pvc --namespace plm-system kubectl get storageclass ``` -**`APPolicy` shows `invalid` with `unexpected EOF` after enabling TLS** +#### `APPolicy` shows `invalid` with `unexpected EOF` after enabling TLS -Enabling TLS on an existing installation restarts the storage backend and can orphan objects written before TLS was enabled. Check the filer log for confirmation: +Enabling TLS on an existing installation restarts the storage backend. Objects written before TLS was enabled can become orphaned. Check the filer log: ```shell kubectl logs --namespace plm-system plm-f5-waf-seaweed-filer-0 | grep "not found" ``` -A `volume N not found` message confirms orphaned objects. Remove the affected `APPolicy` resource and reapply it so the Policy Controller regenerates the bundle. +If the output contains `volume N not found`, orphaned objects exist. Delete the affected `APPolicy` resource and reapply it. The Policy Controller regenerates the bundle. -**Helm install fails on a ClusterRole** +#### Helm install fails on a ClusterRole -An error naming `seaweed-editor-role` or `seaweed-viewer-role` means another PLM installation already exists in the cluster. Only one PLM installation is supported per cluster. Remove the existing release before installing. +If the error references `seaweed-editor-role` or `seaweed-viewer-role`, another PLM installation already exists in the cluster. Only one PLM installation is supported per cluster. Remove the existing release before installing. -**Checking Policy Controller logs** +#### Check the Policy Controller logs -For any policy-related failure, the Policy Controller logs are the most useful diagnostic source: +Use the Policy Controller logs to diagnose any policy-related failure: ```shell kubectl logs --namespace plm-system deploy/plm-f5-waf-policy-controller -c policy-controller ``` -{{< call-out class="note" title="Note" >}} The `-c policy-controller` flag is required because the pod runs more than one container. The containers are distroless, so `kubectl exec` is not available for interactive debugging. {{< /call-out >}} +{{< call-out class="note" title="Note" >}} The `-c policy-controller` flag is required because the pod has more than one container. The containers are distroless, so `kubectl exec` isn't available for interactive debugging. {{< /call-out >}} From d45ca96783026d99f8f09473fdba1932a71404e1 Mon Sep 17 00:00:00 2001 From: Travis Martin Date: Wed, 19 Aug 2026 13:12:27 -0700 Subject: [PATCH 40/50] refactor: Consolidate APPolicy method includes into tabbed include Merge plm-define-policy-inline-git.md and plm-define-policy-bundle-method.md into a single plm-define-policy-methods.md include that wraps all three policy definition methods (Inline, Git reference, Precompiled bundle) in a tabs shortcode. Hugo tab shortcodes cannot span include boundaries, so separate includes cannot be tabbed from a parent template. Consolidating into one include lets the tabbed UI render correctly while keeping the content reusable for the NIC tutorial. The ReferenceGrant block remains inline in get-started-plm.md after the include, as it is NGF-specific and does not belong in a shared include. --- .../waf/plm-define-policy-bundle-method.md | 85 ------- .../waf/plm-define-policy-inline-git.md | 119 ---------- .../includes/waf/plm-define-policy-methods.md | 209 ++++++++++++++++++ .../ngf/waf-integration/get-started-plm.md | 6 +- 4 files changed, 210 insertions(+), 209 deletions(-) delete mode 100644 content/includes/waf/plm-define-policy-bundle-method.md delete mode 100644 content/includes/waf/plm-define-policy-inline-git.md create mode 100644 content/includes/waf/plm-define-policy-methods.md diff --git a/content/includes/waf/plm-define-policy-bundle-method.md b/content/includes/waf/plm-define-policy-bundle-method.md deleted file mode 100644 index 960a8c87f..000000000 --- a/content/includes/waf/plm-define-policy-bundle-method.md +++ /dev/null @@ -1,85 +0,0 @@ ---- -f5-product: F5 WAF for NGINX -f5-files: -- content/ngf/waf-integration/get-started-plm.md ---- - - - -The precompiled-bundle method lets you reference a `.tgz` policy bundle stored in an artifact registry (for example, Artifactory or Nexus). The Policy Controller imports the bundle and stores it in the SeaweedFS object store without recompiling it. - -Use this method when: - -- Your security team compiles and publishes bundles through an external pipeline. -- You want to decouple policy compilation from cluster operations. - -Create an `APPolicy` resource that references your bundle. Replace ``, ``, and `` with your values: - -```shell -kubectl apply -f - <<'EOF' -apiVersion: appprotect.f5.com/v1 -kind: APPolicy -metadata: - name: - namespace: plm-system -spec: - policy: - $ref: "https:///.tgz" -EOF -``` - -{{< call-out class="note" title="Note" >}} -The Policy Controller must reach the artifact registry over HTTPS. If the registry uses a private certificate authority (CA), mount the CA certificate into the Policy Controller pod and set the `SSL_CERT_FILE` environment variable to its path. `SSL_CERT_FILE` replaces the system trust store entirely — it doesn't append to it. If SeaweedFS TLS is also enabled, combine both CAs into a single file and reference that file. -{{< /call-out >}} - -If the `APPolicy` status shows `x509: certificate signed by unknown authority`, the Policy Controller doesn't trust the artifact registry CA. Check the status for the full error: - -```shell -kubectl describe appolicy --namespace plm-system -``` - -#### Confirm the policy is ready - -The Policy Controller processes the bundle and updates the `APPolicy` status. Check the `bundle.state` field: - -```shell -kubectl get appolicy \ - --namespace plm-system \ - --output jsonpath='State: {.status.bundle.state}{"\n"}Bundle: {.status.bundle.location}{"\n"}isCompiled: {.status.processing.isCompiled}{"\n"}' -``` - -When the bundle is ready, the output looks like this: - -```text -State: ready -Bundle: s3://plm-system/bundles/_imported_.tgz -isCompiled: false -``` - -`isCompiled: false` confirms the bundle was imported as-is and not recompiled. - -`bundle.state` can be one of: - -| State | Meaning | -|-------|---------| -| `pending` | The Policy Controller has not yet processed the resource. | -| `processing` | The Policy Controller is importing or storing the bundle. | -| `ready` | The bundle is stored and ready to use. `bundle.location` is populated. | -| `invalid` | The bundle could not be imported. Check the status for error detail. | - -#### Update a precompiled bundle - -The Policy Controller doesn't poll the artifact registry for changes. To pick up a new version of a bundle, update the `$ref` URL in your `APPolicy` resource (or bump its revision annotation) and reapply it. Replace ``, ``, and `` with your values: - -```shell -kubectl apply -f - <<'EOF' -apiVersion: appprotect.f5.com/v1 -kind: APPolicy -metadata: - name: - namespace: plm-system -spec: - policy: - $ref: "https:///.tgz" -EOF -``` diff --git a/content/includes/waf/plm-define-policy-inline-git.md b/content/includes/waf/plm-define-policy-inline-git.md deleted file mode 100644 index d9fff002f..000000000 --- a/content/includes/waf/plm-define-policy-inline-git.md +++ /dev/null @@ -1,119 +0,0 @@ ---- -f5-product: F5 WAF for NGINX -f5-files: -- content/ngf/waf-integration/get-started-plm.md ---- - - - -This section is typically owned by the security team. They define the policy in the `security` namespace, separate from the Gateway namespace, so security resources are managed independently from routing configuration. If you're not on the security team, share this section with them — you'll need the `APPolicy` name and namespace before continuing. - -The `APPolicy` resource defines the security policy. The PLM controller watches it, compiles it, and writes `status.bundle` with `state: ready` when the bundle is available. - -### Inline policy - -Create an `APPolicy` resource with an inline policy that blocks all attack signatures: - -```yaml -kubectl apply -f - <`, ``, ``, ``, ``, and `` with your values: - -```shell -kubectl apply -f - < - namespace: -spec: - policy: - $ref: - externalReferenceDetails: - repositoryDetails: - repository: https://github.com//.git - ref: "" -EOF -``` - -{{< call-out class="note" title="Note" >}} Pin `ref` to a tag or commit SHA rather than a branch name in production environments. {{< /call-out >}} - -Check that the bundle compiled successfully: - -```shell -kubectl get appolicy \ - --namespace \ - --output jsonpath='State: {.status.bundle.state}{"\n"}Bundle: {.status.bundle.location}{"\n"}Compiler: {.status.bundle.compilerVersion}{"\n"}' -``` - -The output shows `State: ready` when compilation succeeds. - -#### Private repository - -For private repositories, create a Kubernetes Secret with your personal access token (PAT): - -```shell -kubectl create secret generic git-token-secret \ - --namespace \ - --from-literal=token= -``` - -Then reference the Secret in the `APPolicy` resource: - -```shell -kubectl apply -f - < - namespace: -spec: - policy: - $ref: - externalReferenceDetails: - repositoryDetails: - repository: https://github.com//.git - ref: "" - authentication: - token: git-token-secret -EOF -``` - -#### Update a Git-referenced policy - -The Policy Controller doesn't poll the Git repository for changes. To pick up changes to the referenced policy file, re-apply the `APPolicy` resource (or update its revision annotation) after you push changes to the repository. diff --git a/content/includes/waf/plm-define-policy-methods.md b/content/includes/waf/plm-define-policy-methods.md new file mode 100644 index 000000000..8b4ff5dd4 --- /dev/null +++ b/content/includes/waf/plm-define-policy-methods.md @@ -0,0 +1,209 @@ +--- +f5-product: F5 WAF for NGINX +f5-files: +- content/ngf/waf-integration/get-started-plm.md +--- + + + +This section is typically owned by the security team. They define the policy in the `security` namespace, separate from the Gateway namespace, so security resources are managed independently from routing configuration. If you're not on the security team, share this section with them — you'll need the `APPolicy` name and namespace before continuing. + +The `APPolicy` resource defines the security policy. The PLM controller watches it, compiles it, and writes `status.bundle` with `state: ready` when the bundle is available. + +{{}} + +{{%tab name="Inline"%}} + +Create an `APPolicy` resource with an inline policy that blocks all attack signatures: + +```yaml +kubectl apply -f - <`, ``, ``, ``, ``, and `` with your values: + +```shell +kubectl apply -f - < + namespace: +spec: + policy: + $ref: + externalReferenceDetails: + repositoryDetails: + repository: https://github.com//.git + ref: "" +EOF +``` + +{{< call-out class="note" title="Note" >}} Pin `ref` to a tag or commit SHA rather than a branch name in production environments. {{< /call-out >}} + +Check that the bundle compiled successfully: + +```shell +kubectl get appolicy \ + --namespace \ + --output jsonpath='State: {.status.bundle.state}{"\n"}Bundle: {.status.bundle.location}{"\n"}Compiler: {.status.bundle.compilerVersion}{"\n"}' +``` + +The output shows `State: ready` when compilation succeeds. + +#### Private repository + +For private repositories, create a Kubernetes Secret with your personal access token (PAT): + +```shell +kubectl create secret generic git-token-secret \ + --namespace \ + --from-literal=token= +``` + +Then reference the Secret in the `APPolicy` resource: + +```shell +kubectl apply -f - < + namespace: +spec: + policy: + $ref: + externalReferenceDetails: + repositoryDetails: + repository: https://github.com//.git + ref: "" + authentication: + token: git-token-secret +EOF +``` + +#### Update a Git-referenced policy + +The Policy Controller doesn't poll the Git repository for changes. To pick up changes to the referenced policy file, re-apply the `APPolicy` resource (or update its revision annotation) after you push changes to the repository. + +{{% /tab %}} + +{{%tab name="Precompiled bundle"%}} + +The precompiled-bundle method lets you reference a `.tgz` policy bundle stored in an artifact registry (for example, Artifactory or Nexus). The Policy Controller imports the bundle and stores it in the SeaweedFS object store without recompiling it. + +Use this method when: + +- Your security team compiles and publishes bundles through an external pipeline. +- You want to decouple policy compilation from cluster operations. + +Create an `APPolicy` resource that references your bundle. Replace ``, ``, and `` with your values: + +```shell +kubectl apply -f - <<'EOF' +apiVersion: appprotect.f5.com/v1 +kind: APPolicy +metadata: + name: + namespace: plm-system +spec: + policy: + $ref: "https:///.tgz" +EOF +``` + +{{< call-out class="note" title="Note" >}} +The Policy Controller must reach the artifact registry over HTTPS. If the registry uses a private certificate authority (CA), mount the CA certificate into the Policy Controller pod and set the `SSL_CERT_FILE` environment variable to its path. `SSL_CERT_FILE` replaces the system trust store entirely — it doesn't append to it. If SeaweedFS TLS is also enabled, combine both CAs into a single file and reference that file. +{{< /call-out >}} + +If the `APPolicy` status shows `x509: certificate signed by unknown authority`, the Policy Controller doesn't trust the artifact registry CA. Check the status for the full error: + +```shell +kubectl describe appolicy --namespace plm-system +``` + +#### Confirm the policy is ready + +The Policy Controller processes the bundle and updates the `APPolicy` status. Check the `bundle.state` field: + +```shell +kubectl get appolicy \ + --namespace plm-system \ + --output jsonpath='State: {.status.bundle.state}{"\n"}Bundle: {.status.bundle.location}{"\n"}isCompiled: {.status.processing.isCompiled}{"\n"}' +``` + +When the bundle is ready, the output looks like this: + +```text +State: ready +Bundle: s3://plm-system/bundles/_imported_.tgz +isCompiled: false +``` + +`isCompiled: false` confirms the bundle was imported as-is and not recompiled. + +`bundle.state` can be one of: + +| State | Meaning | +|-------|---------| +| `pending` | The Policy Controller hasn't yet processed the resource. | +| `processing` | The Policy Controller is importing or storing the bundle. | +| `ready` | The bundle is stored and ready to use. `bundle.location` is populated. | +| `invalid` | The bundle couldn't be imported. Check the status for error detail. | + +#### Update a precompiled bundle + +The Policy Controller doesn't poll the artifact registry for changes. To pick up a new version of a bundle, update the `$ref` URL in your `APPolicy` resource (or bump its revision annotation) and reapply it. Replace ``, ``, and `` with your values: + +```shell +kubectl apply -f - <<'EOF' +apiVersion: appprotect.f5.com/v1 +kind: APPolicy +metadata: + name: + namespace: plm-system +spec: + policy: + $ref: "https:///.tgz" +EOF +``` + +{{% /tab %}} + +{{}} diff --git a/content/ngf/waf-integration/get-started-plm.md b/content/ngf/waf-integration/get-started-plm.md index e5a9cf8df..73711fab8 100644 --- a/content/ngf/waf-integration/get-started-plm.md +++ b/content/ngf/waf-integration/get-started-plm.md @@ -128,7 +128,7 @@ If you skip this section, omit the `securityLogs` field in the `WAFPolicy` resou ## Define the WAF policy -{{< include "waf/plm-define-policy-inline-git.md" >}} +{{< include "waf/plm-define-policy-methods.md" >}} The `APPolicy` and `APLogConf` are in the `security` namespace, but the `WAFPolicy` you create next targets a Gateway in the `default` namespace. To permit the cross-namespace reference, create a `ReferenceGrant` in the `security` namespace: @@ -154,10 +154,6 @@ EOF {{< call-out class="note" title="Note" >}} The `ReferenceGrant` lives in the `security` namespace and must be created by whoever manages that namespace — typically your security team, not the platform engineer deploying the Gateway. Coordinate with them if you don't have access. Without a matching `ReferenceGrant`, the `WAFPolicy` is rejected with `ResolvedRefs=False` and reason `RefNotPermitted`. If you put the `APPolicy` and `APLogConf` in the same namespace as the `WAFPolicy`, you can skip the `ReferenceGrant`. See [Troubleshoot WAFPolicy status]({{< ref "/ngf/waf-integration/troubleshooting.md" >}}) for details. {{< /call-out >}} -For the precompiled-bundle method, see the **Precompiled bundle** tab: - -{{< include "waf/plm-define-policy-bundle-method.md" >}} - ## Deploy the Gateway and attach WAFPolicy Create a Gateway. WAF is already enabled globally, so NGINX Gateway Fabric automatically deploys the WAF sidecar containers alongside the NGINX Pod: From 124e57ba9e7ce2145e7d595661730cb353059255 Mon Sep 17 00:00:00 2001 From: Travis Martin Date: Wed, 19 Aug 2026 13:23:43 -0700 Subject: [PATCH 41/50] style: Wrap troubleshooting scenarios in details expandos Convert the five symptom-based troubleshooting items in the PLM infrastructure include from subheadings to {{< details >}} expandos. Readers can scan symptom labels and expand only the relevant one. The 'Check the Policy Controller logs' subsection stays open as a universal diagnostic step that applies to all scenarios. --- .../includes/waf/plm-deploy-infrastructure.md | 20 ++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/content/includes/waf/plm-deploy-infrastructure.md b/content/includes/waf/plm-deploy-infrastructure.md index b54f5b6d4..b5ead542b 100644 --- a/content/includes/waf/plm-deploy-infrastructure.md +++ b/content/includes/waf/plm-deploy-infrastructure.md @@ -232,7 +232,7 @@ kubectl apply -f https://raw.githubusercontent.com/nginx/waf-policy-controller/{ These are the most common failures during PLM installation, roughly in order of likelihood. -#### Pods stuck in `ImagePullBackOff` +{{< details summary="Pods stuck in `ImagePullBackOff`" >}} The JWT is wrong, expired, or contains a line break. Check the events log: @@ -242,7 +242,9 @@ kubectl get events --namespace plm-system --field-selector reason=Failed Use the full JWT string as the registry username. Use the literal string `none` as the password. -#### Policy Controller stuck in `Init:0/1` +{{< /details >}} + +{{< details summary="Policy Controller stuck in `Init:0/1`" >}} The `Init:0/1` state is expected during startup. The init container waits for the compiler service and the S3 endpoint before it starts. If the pod stays in `Init:0/1` for more than a few minutes, check that the SeaweedFS pods are `Running`: @@ -252,7 +254,9 @@ kubectl get pods --namespace plm-system --selector app.kubernetes.io/name=seawee The most common cause is PVCs stuck in `Pending` because the cluster has no default StorageClass. -#### SeaweedFS pods `Pending` +{{< /details >}} + +{{< details summary="SeaweedFS pods `Pending`" >}} SeaweedFS pods stay `Pending` when the cluster has no default StorageClass or insufficient capacity. Check the PVCs and available storage classes: @@ -261,7 +265,9 @@ kubectl get pvc --namespace plm-system kubectl get storageclass ``` -#### `APPolicy` shows `invalid` with `unexpected EOF` after enabling TLS +{{< /details >}} + +{{< details summary="`APPolicy` shows `invalid` with `unexpected EOF` after enabling TLS" >}} Enabling TLS on an existing installation restarts the storage backend. Objects written before TLS was enabled can become orphaned. Check the filer log: @@ -271,10 +277,14 @@ kubectl logs --namespace plm-system plm-f5-waf-seaweed-filer-0 | grep "not found If the output contains `volume N not found`, orphaned objects exist. Delete the affected `APPolicy` resource and reapply it. The Policy Controller regenerates the bundle. -#### Helm install fails on a ClusterRole +{{< /details >}} + +{{< details summary="Helm install fails on a ClusterRole" >}} If the error references `seaweed-editor-role` or `seaweed-viewer-role`, another PLM installation already exists in the cluster. Only one PLM installation is supported per cluster. Remove the existing release before installing. +{{< /details >}} + #### Check the Policy Controller logs Use the Policy Controller logs to diagnose any policy-related failure: From d2cc1d96573fd7247c5c1e860f193e006557e5fb Mon Sep 17 00:00:00 2001 From: Travis Martin Date: Wed, 19 Aug 2026 13:30:32 -0700 Subject: [PATCH 42/50] fix: Quote heredoc delimiters in Git-reference APPolicy blocks The two kubectl apply heredocs in the Git-reference tab contained $ref: fields. Using unquoted <`, ``, ``, ``, ``, and `` with your values: ```shell -kubectl apply -f - < Date: Wed, 19 Aug 2026 13:42:12 -0700 Subject: [PATCH 43/50] fix: Fix placeholder formatting across PLM docs Apply style guide placeholder rules (all-caps, angle brackets, PATH/TO/ for paths): - plm-prerequisites.md: -> - plm-deploy-infrastructure.md: -> for all five TLS certificate/key placeholders and the prose reference - get-started-plm.md: -> --- .../includes/waf/plm-deploy-infrastructure.md | 20 +++++++++---------- content/includes/waf/plm-prerequisites.md | 2 +- .../ngf/waf-integration/get-started-plm.md | 2 +- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/content/includes/waf/plm-deploy-infrastructure.md b/content/includes/waf/plm-deploy-infrastructure.md index b5ead542b..4cf041fb9 100644 --- a/content/includes/waf/plm-deploy-infrastructure.md +++ b/content/includes/waf/plm-deploy-infrastructure.md @@ -99,30 +99,30 @@ Create the Secrets from your CA and certificate files before installing: ```shell kubectl create secret generic plm-system-seaweedfs-ca-cert \ --namespace plm-system \ - --from-file=ca.crt= + --from-file=ca.crt= kubectl create secret tls plm-system-seaweedfs-master-cert \ --namespace plm-system \ - --cert= \ - --key= + --cert= \ + --key= kubectl create secret tls plm-system-seaweedfs-volume-cert \ --namespace plm-system \ - --cert= \ - --key= + --cert= \ + --key= kubectl create secret tls plm-system-seaweedfs-filer-cert \ --namespace plm-system \ - --cert= \ - --key= + --cert= \ + --key= kubectl create secret tls plm-system-seaweedfs-client-cert \ --namespace plm-system \ - --cert= \ - --key= + --cert= \ + --key= ``` -Replace each `` placeholder with the path to the corresponding certificate and key file from your PKI. The CA must sign all component certificates. If you don't have an existing PKI, generate a CA and sign the five component certificates before proceeding. +Replace each `` placeholder with the path to the corresponding certificate and key file from your PKI. The CA must sign all component certificates. If you don't have an existing PKI, generate a CA and sign the five component certificates before proceeding. #### Install the chart diff --git a/content/includes/waf/plm-prerequisites.md b/content/includes/waf/plm-prerequisites.md index 826e57c52..74ab47979 100644 --- a/content/includes/waf/plm-prerequisites.md +++ b/content/includes/waf/plm-prerequisites.md @@ -13,6 +13,6 @@ f5-files: PLM ships with an embedded SeaweedFS S3-compatible object store. The bundled SeaweedFS operator deploys and manages it. You don't need to provide an external S3 bucket. The PLM controller writes compiled policy bundles to this store; the data plane reads from it. -By default, the deployment creates one master pod, one filer pod, and three volume pods, each backed by its own PVC. The chart generates credentials for the store and saves them in the `-f5-waf-seaweedfs-auth` Secret. +By default, the deployment creates one master pod, one filer pod, and three volume pods, each backed by its own PVC. The chart generates credentials for the store and saves them in the `-f5-waf-seaweedfs-auth` Secret. By default, communication between PLM and the object store uses unencrypted HTTP. To enable TLS, see the PLM chart values (`helm show values nginx-stable/f5-waf-policy-controller`). diff --git a/content/ngf/waf-integration/get-started-plm.md b/content/ngf/waf-integration/get-started-plm.md index 73711fab8..1869417bc 100644 --- a/content/ngf/waf-integration/get-started-plm.md +++ b/content/ngf/waf-integration/get-started-plm.md @@ -372,7 +372,7 @@ curl --resolve cafe.example.com:$GW_PORT:$GW_IP "http://cafe.example.com:$GW_POR ... ``` -{{< call-out class="note" title="Note" >}} The exact blocking response depends on your WAF policy configuration. Check the security log for a corresponding blocked event using `kubectl logs -c waf-enforcer`. {{< /call-out >}} +{{< call-out class="note" title="Note" >}} The exact blocking response depends on your WAF policy configuration. Check the security log for a corresponding blocked event using `kubectl logs -c waf-enforcer`. {{< /call-out >}} ## Apply a route-level override (optional) From 35d25f41e2aec07b5c28f483d43b774c4eeaa7e5 Mon Sep 17 00:00:00 2001 From: Travis Martin Date: Wed, 19 Aug 2026 14:00:24 -0700 Subject: [PATCH 44/50] fix: Clarify skip-logging note and move it to section top - Moved the skip-logging note above the include so readers see it before deciding whether to continue with the section - Replaced vague 'in the next steps' with a direct link to the specific WAFPolicy section where securityLogs is configured --- content/ngf/waf-integration/get-started-plm.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/content/ngf/waf-integration/get-started-plm.md b/content/ngf/waf-integration/get-started-plm.md index 1869417bc..f3cbad681 100644 --- a/content/ngf/waf-integration/get-started-plm.md +++ b/content/ngf/waf-integration/get-started-plm.md @@ -122,9 +122,9 @@ NGINX Gateway Fabric reloads the PLM credentials and TLS Secrets when they chang ## Configure security logging (optional) -{{< include "waf/plm-configure-logging.md" >}} +{{< call-out class="note" title="Note" >}} If you skip this section, omit the `securityLogs` field when you create the `WAFPolicy` in [Deploy the Gateway and attach WAFPolicy](#deploy-the-gateway-and-attach-wafpolicy). {{< /call-out >}} -If you skip this section, omit the `securityLogs` field in the `WAFPolicy` resource in the next steps. +{{< include "waf/plm-configure-logging.md" >}} ## Define the WAF policy From 3ab3d150f0c5f44fd7ff40169794fc4ab2c858b2 Mon Sep 17 00:00:00 2001 From: Saylor Berman Date: Thu, 20 Aug 2026 08:43:02 -0600 Subject: [PATCH 45/50] fix: cover PLM refs, logging, and compile failures - configuration.md: document logRef.apLogConfRef as the PLM equivalent of logSource - troubleshooting.md: add RefNotPermitted condition and note PLM APPolicy/APLogConf refs in ResolvedRefs/InvalidRef - get-started-plm.md: add inline PLM-side compile-failure diagnosis (bundle.state table + Policy Controller logs), link CLI flags and storage rollout check, switch security logging to stderr for verifiability, and note per-Gateway WAF, GRPCRoute inheritance, and WAFPolicy conflict rules --- content/ngf/waf-integration/configuration.md | 4 ++- .../ngf/waf-integration/get-started-plm.md | 28 ++++++++++++++++--- .../ngf/waf-integration/troubleshooting.md | 9 +++--- 3 files changed, 32 insertions(+), 9 deletions(-) diff --git a/content/ngf/waf-integration/configuration.md b/content/ngf/waf-integration/configuration.md index 7454b1fee..bd522a12d 100644 --- a/content/ngf/waf-integration/configuration.md +++ b/content/ngf/waf-integration/configuration.md @@ -25,7 +25,7 @@ The `securityLogs` field on a `WAFPolicy` supports multiple log destinations. Ea ### Log source types -Each log entry must specify a `logSource` using one of: +For the `NIM`, `N1C`, and `HTTP` source types, each log entry must specify a `logSource` using one of: | Field | Description | |------------------|--------------------------------------| @@ -36,6 +36,8 @@ Each log entry must specify a `logSource` using one of: **Built-in log profiles:** `log_default`, `log_all`, `log_blocked`, `log_illegal`, `log_grpc_all`, `log_grpc_blocked`, `log_grpc_illegal` +For the `PLM` source type, use `logRef.apLogConfRef` instead of `logSource` to reference an `APLogConf` custom resource by name and namespace. See [PLM (Policy Lifecycle Management)]({{< ref "/ngf/waf-integration/overview.md#plm-policy-lifecycle-management" >}}) for the full field comparison. + ### Example: multiple log destinations ```yaml diff --git a/content/ngf/waf-integration/get-started-plm.md b/content/ngf/waf-integration/get-started-plm.md index f3cbad681..cc28b3e8a 100644 --- a/content/ngf/waf-integration/get-started-plm.md +++ b/content/ngf/waf-integration/get-started-plm.md @@ -116,6 +116,8 @@ data: NGINX Gateway Fabric reloads the PLM credentials and TLS Secrets when they change, so you can rotate credentials without restarting the pod. +If you install NGINX Gateway Fabric using Kubernetes manifests instead of Helm, see the `plm-storage-*` flags in the [command-line reference]({{< ref "/ngf/reference/cli-help.md" >}}) for the equivalent settings. + ## Deploy the sample application {{< include "waf/plm-sample-app.md" >}} @@ -194,9 +196,7 @@ spec: namespace: security securityLogs: - destination: - type: syslog - syslog: - server: syslog-svc.default.svc.cluster.local:514 + type: stderr logRef: apLogConfRef: name: log-illegal @@ -206,6 +206,8 @@ EOF This `WAFPolicy` protects every route attached to the Gateway. Later changes to the `APPolicy` or `APLogConf` spec trigger recompilation and an automatic re-fetch. You don't need to update the `WAFPolicy`. +{{< call-out class="note" title="Note" >}} This guide enables WAF globally on the GatewayClass-level `NginxProxy`. To enable WAF on a specific Gateway instead, attach a per-Gateway `NginxProxy` through `infrastructure.parametersRef`. See [Enable WAF per Gateway]({{< ref "/ngf/waf-integration/overview.md#enable-waf-per-gateway" >}}). {{< /call-out >}} + ## Configure HTTPRoutes Create two HTTPRoutes — `customers` and `orders` — attached to the Gateway. Because the `WAFPolicy` targets the Gateway, both routes inherit WAF protection automatically: @@ -252,6 +254,8 @@ spec: EOF ``` +{{< call-out class="note" title="Note" >}} GRPCRoutes inherit WAF protection the same way HTTPRoutes do. {{< /call-out >}} + ## Validate policy compilation and application Confirm the `APPolicy` and `APLogConf` bundles compiled successfully: @@ -261,7 +265,21 @@ kubectl get appolicy attack-signatures -n security -o jsonpath='{.status.bundle. kubectl get aplogconf log-illegal -n security -o jsonpath='{.status.bundle.state}{"\n"}' ``` -Both commands should print `ready`. +Both commands should print `ready`. The `status.bundle.location` field on each resource confirms where the compiled bundle was stored in PLM storage. + +If a bundle doesn't reach `ready`: + +| State | Meaning | +|--------------|-----------------------------------------------------------------------| +| `pending` | The Policy Controller hasn't processed the resource yet | +| `processing` | The Policy Controller is compiling the policy | +| `invalid` | Compilation failed; check the status message and Policy Controller logs | + +Check the Policy Controller logs for compilation errors: + +```shell +kubectl logs -n plm-system deploy/plm-f5-waf-policy-controller +``` Verify the `WAFPolicy` has been accepted and programmed: @@ -380,6 +398,8 @@ The `customers` route returns sensitive data (credit card numbers and SSNs) in t This is a common pattern for SecOps and app team collaboration: the security team defines a stricter policy for a specific service, and the platform engineer or app developer attaches it as a route-level override. The override applies only to the `customers` route — other routes continue using the gateway-level policy. +{{< call-out class="note" title="Note" >}} Only one `WAFPolicy` can target a given resource at a given level. Attaching a second gateway-level or route-level `WAFPolicy` to the same resource is rejected with `Accepted=False` and reason `Conflicted`. See [Policy attachment]({{< ref "/ngf/waf-integration/overview.md#policy-attachment" >}}). {{< /call-out >}} + To protect sensitive data in responses, define a **data guard** `APPolicy` and apply it as a route-level override on the `customers` route: ```yaml diff --git a/content/ngf/waf-integration/troubleshooting.md b/content/ngf/waf-integration/troubleshooting.md index 56faaec85..461153fc0 100644 --- a/content/ngf/waf-integration/troubleshooting.md +++ b/content/ngf/waf-integration/troubleshooting.md @@ -25,10 +25,11 @@ Use `kubectl describe wafpolicy ` to inspect status conditions. ### ResolvedRefs -| Status | Reason | Meaning | -|---------|----------------|---------------------------------------------------------------| -| `True` | `ResolvedRefs` | All referenced Secrets resolved successfully | -| `False` | `InvalidRef` | A referenced Secret was not found or is missing expected keys | +| Status | Reason | Meaning | +|---------|--------------------|--------------------------------------------------------------------------| +| `True` | `ResolvedRefs` | All referenced Secrets (or, for PLM, `APPolicy`/`APLogConf`) resolved successfully | +| `False` | `InvalidRef` | A referenced Secret was not found or is missing expected keys, or (PLM) a referenced `APPolicy`/`APLogConf` doesn't exist | +| `False` | `RefNotPermitted` | A referenced `APPolicy`/`APLogConf` is in a different namespace and no `ReferenceGrant` permits the reference | ### Programmed From f0cb58657bc6c22a2f9a97d042155dcc77af499d Mon Sep 17 00:00:00 2001 From: Travis Martin Date: Thu, 20 Aug 2026 11:13:07 -0700 Subject: [PATCH 46/50] fix: Move security namespace creation to a required step before optional logging The security namespace was created inside the optional 'Configure security logging' section. Readers who skipped that section got 'namespaces "security" not found' when applying the APPolicy in the next required step. Fix: extract namespace creation into a new shared include (plm-create-security-namespace.md) and pull it into the tutorial as a required '## Create the security namespace' step that runs before the optional logging section. The ordering is now: 1. Create the security namespace (required) 2. Configure security logging (optional) 3. Define the WAF policy (required, uses namespace: security) Readers who skip logging still have the namespace available for APPolicy, APLogConf, and ReferenceGrant resources. Addresses review comment by @ohad-perets (r3823360430). --- content/includes/waf/plm-configure-logging.md | 8 ++------ .../includes/waf/plm-create-security-namespace.md | 15 +++++++++++++++ content/includes/waf/plm-define-policy-methods.md | 2 +- content/ngf/waf-integration/get-started-plm.md | 6 +++++- 4 files changed, 23 insertions(+), 8 deletions(-) create mode 100644 content/includes/waf/plm-create-security-namespace.md diff --git a/content/includes/waf/plm-configure-logging.md b/content/includes/waf/plm-configure-logging.md index bfe98fec3..357a2094e 100644 --- a/content/includes/waf/plm-configure-logging.md +++ b/content/includes/waf/plm-configure-logging.md @@ -4,15 +4,11 @@ f5-files: - content/ngf/waf-integration/get-started-plm.md --- - + This section is typically owned by the security team. If you're not on the security team, share this section with them before continuing. -PLM security logging profiles are defined as `APLogConf` custom resources. Create a namespace to hold your security resources, then define a log profile that logs illegal requests: - -```shell -kubectl create namespace security -``` +PLM security logging profiles are defined as `APLogConf` custom resources. Define a log profile that logs illegal requests: ```yaml kubectl apply -f - < + +This section is typically owned by the security team. If you're not on the security team, share this section with them before continuing. + +Create the `security` namespace. The security team's `APPolicy` and `APLogConf` resources live here. In NGINX Gateway Fabric, the `ReferenceGrant` that permits cross-namespace WAFPolicy references also lives in this namespace. + +```shell +kubectl create namespace security +``` diff --git a/content/includes/waf/plm-define-policy-methods.md b/content/includes/waf/plm-define-policy-methods.md index 0f1ce66ab..f2fadfd5a 100644 --- a/content/includes/waf/plm-define-policy-methods.md +++ b/content/includes/waf/plm-define-policy-methods.md @@ -4,7 +4,7 @@ f5-files: - content/ngf/waf-integration/get-started-plm.md --- - + This section is typically owned by the security team. They define the policy in the `security` namespace, separate from the Gateway namespace, so security resources are managed independently from routing configuration. If you're not on the security team, share this section with them — you'll need the `APPolicy` name and namespace before continuing. diff --git a/content/ngf/waf-integration/get-started-plm.md b/content/ngf/waf-integration/get-started-plm.md index cc28b3e8a..696fe5da7 100644 --- a/content/ngf/waf-integration/get-started-plm.md +++ b/content/ngf/waf-integration/get-started-plm.md @@ -122,6 +122,10 @@ If you install NGINX Gateway Fabric using Kubernetes manifests instead of Helm, {{< include "waf/plm-sample-app.md" >}} +## Create the security namespace + +{{< include "waf/plm-create-security-namespace.md" >}} + ## Configure security logging (optional) {{< call-out class="note" title="Note" >}} If you skip this section, omit the `securityLogs` field when you create the `WAFPolicy` in [Deploy the Gateway and attach WAFPolicy](#deploy-the-gateway-and-attach-wafpolicy). {{< /call-out >}} @@ -321,7 +325,7 @@ Each NGINX Pod should show `3/3` in the `READY` column, indicating the main NGIN ```text NAME READY STATUS RESTARTS AGE -gateway-nginx-7f9b8d6c4d-xxxxx 3/3 Running 0 2m +gateway-nginx-7f9b8d6c4d-xxxxx 3/3 Running 0 2m ``` ## Test deployment and policy enforcement From 8c14eef3c15686f8c40fedd2a04de53b781978c2 Mon Sep 17 00:00:00 2001 From: Travis Martin Date: Thu, 20 Aug 2026 11:59:31 -0700 Subject: [PATCH 47/50] style: Copy edit sjberman's additions to get-started-plm and troubleshooting Edit pass on text added in commit 3ab3d150: get-started-plm.md: - CLI flags sentence: remove 'instead of Helm', tighten phrasing - bundle.state table: add missing 'ready' row; add sentence-ending periods per lists.md; reword 'was stored' -> 'is stored' (present tense) - Policy Controller logs: add -c policy-controller flag (pod has multiple containers; omitting the flag returns an error) - NginxProxy note: 'attach a per-Gateway NginxProxy through' -> 'create ... and reference it from' (active, imperative) - WAFPolicy conflict note: rewrite passive 'is rejected' with clear subject; tighten sentence length troubleshooting.md: - ResolvedRefs table: remove (or, for PLM, ...) and (PLM) parentheticals; rewrite meanings as plain statements covering all source types --- .../ngf/waf-integration/get-started-plm.md | 23 ++++++++++--------- .../ngf/waf-integration/troubleshooting.md | 6 ++--- 2 files changed, 15 insertions(+), 14 deletions(-) diff --git a/content/ngf/waf-integration/get-started-plm.md b/content/ngf/waf-integration/get-started-plm.md index 696fe5da7..08fefbf57 100644 --- a/content/ngf/waf-integration/get-started-plm.md +++ b/content/ngf/waf-integration/get-started-plm.md @@ -116,7 +116,7 @@ data: NGINX Gateway Fabric reloads the PLM credentials and TLS Secrets when they change, so you can rotate credentials without restarting the pod. -If you install NGINX Gateway Fabric using Kubernetes manifests instead of Helm, see the `plm-storage-*` flags in the [command-line reference]({{< ref "/ngf/reference/cli-help.md" >}}) for the equivalent settings. +If you install NGINX Gateway Fabric using Kubernetes manifests, use the equivalent `plm-storage-*` flags documented in the [command-line reference]({{< ref "/ngf/reference/cli-help.md" >}}). ## Deploy the sample application @@ -210,7 +210,7 @@ EOF This `WAFPolicy` protects every route attached to the Gateway. Later changes to the `APPolicy` or `APLogConf` spec trigger recompilation and an automatic re-fetch. You don't need to update the `WAFPolicy`. -{{< call-out class="note" title="Note" >}} This guide enables WAF globally on the GatewayClass-level `NginxProxy`. To enable WAF on a specific Gateway instead, attach a per-Gateway `NginxProxy` through `infrastructure.parametersRef`. See [Enable WAF per Gateway]({{< ref "/ngf/waf-integration/overview.md#enable-waf-per-gateway" >}}). {{< /call-out >}} +{{< call-out class="note" title="Note" >}} This guide enables WAF globally on the GatewayClass-level `NginxProxy`. To enable WAF on a specific Gateway only, create a per-Gateway `NginxProxy` and reference it from the Gateway's `infrastructure.parametersRef`. See [Enable WAF per Gateway]({{< ref "/ngf/waf-integration/overview.md#enable-waf-per-gateway" >}}). {{< /call-out >}} ## Configure HTTPRoutes @@ -269,20 +269,21 @@ kubectl get appolicy attack-signatures -n security -o jsonpath='{.status.bundle. kubectl get aplogconf log-illegal -n security -o jsonpath='{.status.bundle.state}{"\n"}' ``` -Both commands should print `ready`. The `status.bundle.location` field on each resource confirms where the compiled bundle was stored in PLM storage. +Both commands should print `ready`. The `status.bundle.location` field on each resource confirms where the compiled bundle is stored in PLM storage. -If a bundle doesn't reach `ready`: +If a bundle doesn't reach `ready`, check the `bundle.state` value: -| State | Meaning | -|--------------|-----------------------------------------------------------------------| -| `pending` | The Policy Controller hasn't processed the resource yet | -| `processing` | The Policy Controller is compiling the policy | -| `invalid` | Compilation failed; check the status message and Policy Controller logs | +| State | Meaning | +|--------------|-------------------------------------------------------------------------| +| `pending` | The Policy Controller hasn't processed the resource yet. | +| `processing` | The Policy Controller is compiling the policy. | +| `ready` | The bundle compiled successfully. `bundle.location` is populated. | +| `invalid` | Compilation failed. Check the status message and Policy Controller logs. | Check the Policy Controller logs for compilation errors: ```shell -kubectl logs -n plm-system deploy/plm-f5-waf-policy-controller +kubectl logs -n plm-system deploy/plm-f5-waf-policy-controller -c policy-controller ``` Verify the `WAFPolicy` has been accepted and programmed: @@ -402,7 +403,7 @@ The `customers` route returns sensitive data (credit card numbers and SSNs) in t This is a common pattern for SecOps and app team collaboration: the security team defines a stricter policy for a specific service, and the platform engineer or app developer attaches it as a route-level override. The override applies only to the `customers` route — other routes continue using the gateway-level policy. -{{< call-out class="note" title="Note" >}} Only one `WAFPolicy` can target a given resource at a given level. Attaching a second gateway-level or route-level `WAFPolicy` to the same resource is rejected with `Accepted=False` and reason `Conflicted`. See [Policy attachment]({{< ref "/ngf/waf-integration/overview.md#policy-attachment" >}}). {{< /call-out >}} +{{< call-out class="note" title="Note" >}} Only one `WAFPolicy` can target a given resource at a given level. If a second `WAFPolicy` targets the same Gateway or route, it is rejected with `Accepted=False` and reason `Conflicted`. See [Policy attachment]({{< ref "/ngf/waf-integration/overview.md#policy-attachment" >}}). {{< /call-out >}} To protect sensitive data in responses, define a **data guard** `APPolicy` and apply it as a route-level override on the `customers` route: diff --git a/content/ngf/waf-integration/troubleshooting.md b/content/ngf/waf-integration/troubleshooting.md index 461153fc0..6fe3f2e48 100644 --- a/content/ngf/waf-integration/troubleshooting.md +++ b/content/ngf/waf-integration/troubleshooting.md @@ -27,9 +27,9 @@ Use `kubectl describe wafpolicy ` to inspect status conditions. | Status | Reason | Meaning | |---------|--------------------|--------------------------------------------------------------------------| -| `True` | `ResolvedRefs` | All referenced Secrets (or, for PLM, `APPolicy`/`APLogConf`) resolved successfully | -| `False` | `InvalidRef` | A referenced Secret was not found or is missing expected keys, or (PLM) a referenced `APPolicy`/`APLogConf` doesn't exist | -| `False` | `RefNotPermitted` | A referenced `APPolicy`/`APLogConf` is in a different namespace and no `ReferenceGrant` permits the reference | +| `True` | `ResolvedRefs` | All referenced Secrets, `APPolicy`, and `APLogConf` resources resolved successfully | +| `False` | `InvalidRef` | A referenced Secret was not found or is missing expected keys; or a referenced `APPolicy`/`APLogConf` doesn't exist | +| `False` | `RefNotPermitted` | A referenced `APPolicy` or `APLogConf` is in a different namespace and no `ReferenceGrant` permits the reference | ### Programmed From d042f31bea289cdc91d6aa7e7945d71067aad91e Mon Sep 17 00:00:00 2001 From: Travis Martin Date: Thu, 20 Aug 2026 12:26:03 -0700 Subject: [PATCH 48/50] fix: Correct namespace and update trigger in precompiled-bundle tab plm-define-policy-methods.md precompiled-bundle tab: - namespace: plm-system -> security. APPolicy resources belong in the security namespace (per the tutorial example values table), not the PLM backend namespace. - Fix update trigger wording: annotating or re-applying an unchanged APPolicy doesn't trigger a fetch. To pick up a new bundle, update the $ref URL and reapply. To re-fetch the same URL, delete and recreate the APPolicy. (Per SME correction by @ohad-perets, r3823399725.) Also applies the same update-trigger correction to the Git-reference tab's 'Update a Git-referenced policy' section. --- .../includes/waf/plm-define-policy-methods.md | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/content/includes/waf/plm-define-policy-methods.md b/content/includes/waf/plm-define-policy-methods.md index f2fadfd5a..9e7a10da9 100644 --- a/content/includes/waf/plm-define-policy-methods.md +++ b/content/includes/waf/plm-define-policy-methods.md @@ -120,7 +120,11 @@ EOF #### Update a Git-referenced policy -The Policy Controller doesn't poll the Git repository for changes. To pick up changes to the referenced policy file, re-apply the `APPolicy` resource (or update its revision annotation) after you push changes to the repository. +The Policy Controller doesn't poll the Git repository for changes. It fetches the policy file when the `APPolicy` spec changes. + +To pick up a new version of the policy, push your changes to the repository, then update `ref` in the `APPolicy` resource to the new tag or commit SHA and reapply it. Re-applying an unchanged `APPolicy` doesn't trigger a fetch, and neither does changing an annotation. + +If you need to re-fetch the same `ref` — for example, after force-updating a tag — delete the `APPolicy` resource and recreate it. {{% /tab %}} @@ -141,7 +145,7 @@ apiVersion: appprotect.f5.com/v1 kind: APPolicy metadata: name: - namespace: plm-system + namespace: security spec: policy: $ref: "https:///.tgz" @@ -155,7 +159,7 @@ The Policy Controller must reach the artifact registry over HTTPS. If the regist If the `APPolicy` status shows `x509: certificate signed by unknown authority`, the Policy Controller doesn't trust the artifact registry CA. Check the status for the full error: ```shell -kubectl describe appolicy --namespace plm-system +kubectl describe appolicy --namespace security ``` #### Confirm the policy is ready @@ -164,7 +168,7 @@ The Policy Controller processes the bundle and updates the `APPolicy` status. Ch ```shell kubectl get appolicy \ - --namespace plm-system \ + --namespace security \ --output jsonpath='State: {.status.bundle.state}{"\n"}Bundle: {.status.bundle.location}{"\n"}isCompiled: {.status.processing.isCompiled}{"\n"}' ``` @@ -189,7 +193,7 @@ isCompiled: false #### Update a precompiled bundle -The Policy Controller doesn't poll the artifact registry for changes. To pick up a new version of a bundle, update the `$ref` URL in your `APPolicy` resource (or bump its revision annotation) and reapply it. Replace ``, ``, and `` with your values: +The Policy Controller doesn't poll the artifact registry for changes. To pick up a new version of a bundle, update the `$ref` URL in your `APPolicy` resource and reapply it. Changing an annotation doesn't trigger a new download. If you need to re-fetch the same URL, delete the `APPolicy` resource and recreate it. Replace ``, ``, and `` with your values: ```shell kubectl apply -f - <<'EOF' @@ -197,7 +201,7 @@ apiVersion: appprotect.f5.com/v1 kind: APPolicy metadata: name: - namespace: plm-system + namespace: security spec: policy: $ref: "https:///.tgz" From bcc127b5cc385af455f902e77632e91db5871719 Mon Sep 17 00:00:00 2001 From: Travis Martin Date: Thu, 20 Aug 2026 13:07:03 -0700 Subject: [PATCH 49/50] fix: Address Ohad's review comments on PLM TLS and call-out audit plm-deploy-infrastructure.md (Ohad's comment, r3822699625): - Fix CA Secret creation: add --from-file=tls.crt alongside ca.crt. PLM chart components mount the CA via tls.crt; the data plane S3 client reads ca.crt. Both keys are required. - Switch to chart-default Secret names (plm-f5-waf-seaweedfs-*) and remove the five explicit *SecretName overrides from Helm values. Names now match what helm show values documents. - Add prose explaining why both CA keys are needed. - Add forward reference from TLS caution to the troubleshooting entry for 'APPolicy invalid with unexpected EOF after enabling TLS'. - Fix broken anchor: replace direct expando link with link to the Troubleshoot the deployment section heading. Call-out audit across guide and all PLM includes: - Promote note -> important for skip-logging, ReferenceGrant ownership, and WAFPolicy conflict rule (reader must act) - Promote note -> caution for enabling TLS on existing install and Git-reference branch pin risk - Promote note -> tip for DNS record shortcut - Promote note -> important for private CA registry configuration - Add informative titles to all call-outs in place of 'Note'/'Warning' --- .../includes/waf/plm-define-policy-methods.md | 4 +-- .../includes/waf/plm-deploy-infrastructure.md | 32 ++++++++++--------- .../ngf/waf-integration/get-started-plm.md | 18 +++++------ 3 files changed, 28 insertions(+), 26 deletions(-) diff --git a/content/includes/waf/plm-define-policy-methods.md b/content/includes/waf/plm-define-policy-methods.md index 9e7a10da9..00bec3b89 100644 --- a/content/includes/waf/plm-define-policy-methods.md +++ b/content/includes/waf/plm-define-policy-methods.md @@ -75,7 +75,7 @@ spec: EOF ``` -{{< call-out class="note" title="Note" >}} Pin `ref` to a tag or commit SHA rather than a branch name in production environments. {{< /call-out >}} +{{< call-out class="caution" title="Pin ref to a tag in production" >}} Pin `ref` to a tag or commit SHA rather than a branch name in production environments. {{< /call-out >}} Check that the bundle compiled successfully: @@ -152,7 +152,7 @@ spec: EOF ``` -{{< call-out class="note" title="Note" >}} +{{< call-out class="important" title="Configure CA trust for private registries" >}} The Policy Controller must reach the artifact registry over HTTPS. If the registry uses a private certificate authority (CA), mount the CA certificate into the Policy Controller pod and set the `SSL_CERT_FILE` environment variable to its path. `SSL_CERT_FILE` replaces the system trust store entirely — it doesn't append to it. If SeaweedFS TLS is also enabled, combine both CAs into a single file and reference that file. {{< /call-out >}} diff --git a/content/includes/waf/plm-deploy-infrastructure.md b/content/includes/waf/plm-deploy-infrastructure.md index 4cf041fb9..7d211ba4b 100644 --- a/content/includes/waf/plm-deploy-infrastructure.md +++ b/content/includes/waf/plm-deploy-infrastructure.md @@ -83,45 +83,47 @@ seaweedfsOperatorConfig: seaweedfs: certificates: enabled: true - caSecretName: plm-system-seaweedfs-ca-cert - masterSecretName: plm-system-seaweedfs-master-cert - volumeSecretName: plm-system-seaweedfs-volume-cert - filerSecretName: plm-system-seaweedfs-filer-cert - clientSecretName: plm-system-seaweedfs-client-cert ``` -{{< call-out class="warning" title="Warning" >}} -The PLM chart does not generate certificates. You must create all five Secrets listed above before running `helm upgrade --install`. If any Secret is missing, the SeaweedFS pods will fail to mount their certificates and will not start. +{{< call-out class="warning" title="Create Secrets before installing" >}} +The PLM chart does not generate certificates. You must create the five Secrets listed in the commands below before running `helm upgrade --install`. If any Secret is missing, the SeaweedFS pods will fail to mount their certificates and will not start. {{< /call-out >}} -Create the Secrets from your CA and certificate files before installing: +{{< call-out class="caution" title="Enabling TLS on an existing install" >}} +If you're enabling TLS on an existing installation, the storage backend restarts and objects written before the switch can become orphaned. See the [APPolicy shows `invalid` with `unexpected EOF` after enabling TLS](#troubleshoot-the-deployment) entry in the troubleshooting section. A fresh installation with TLS enabled from the start doesn't have this issue. +{{< /call-out >}} + +Create the Secrets from your CA and certificate files before installing. The chart expects Secret names in the form `-f5-waf-seaweedfs-` — for the `plm` release name used in this tutorial, those are: ```shell -kubectl create secret generic plm-system-seaweedfs-ca-cert \ +kubectl create secret generic plm-f5-waf-seaweedfs-ca-cert \ --namespace plm-system \ + --from-file=tls.crt= \ --from-file=ca.crt= -kubectl create secret tls plm-system-seaweedfs-master-cert \ +kubectl create secret tls plm-f5-waf-seaweedfs-master-cert \ --namespace plm-system \ --cert= \ --key= -kubectl create secret tls plm-system-seaweedfs-volume-cert \ +kubectl create secret tls plm-f5-waf-seaweedfs-volume-cert \ --namespace plm-system \ --cert= \ --key= -kubectl create secret tls plm-system-seaweedfs-filer-cert \ +kubectl create secret tls plm-f5-waf-seaweedfs-filer-cert \ --namespace plm-system \ --cert= \ --key= -kubectl create secret tls plm-system-seaweedfs-client-cert \ +kubectl create secret tls plm-f5-waf-seaweedfs-client-cert \ --namespace plm-system \ --cert= \ --key= ``` +The CA Secret requires both `tls.crt` and `ca.crt` keys, both pointing to the same CA certificate file. The PLM chart mounts the CA using `tls.crt` into the Policy Controller, compiler, and SeaweedFS pods. The data plane's S3 client reads `ca.crt` from the same Secret when verifying the storage endpoint. The four component Secrets use `kubectl create secret tls`, which produces `tls.crt` and `tls.key` — no `ca.crt` key is needed for them. + Replace each `` placeholder with the path to the corresponding certificate and key file from your PKI. The CA must sign all component certificates. If you don't have an existing PKI, generate a CA and sign the five component certificates before proceeding. #### Install the chart @@ -218,7 +220,7 @@ All eight pods running and all four CRDs present confirms the PLM backend is rea ### Update the CRDs -{{< call-out class="note" title="Note" >}} +{{< call-out class="note" title="Fresh install: skip this step" >}} Skip this step on a fresh install — Helm installs the CRDs automatically. Only follow these steps when upgrading an existing PLM installation. {{< /call-out >}} @@ -293,4 +295,4 @@ Use the Policy Controller logs to diagnose any policy-related failure: kubectl logs --namespace plm-system deploy/plm-f5-waf-policy-controller -c policy-controller ``` -{{< call-out class="note" title="Note" >}} The `-c policy-controller` flag is required because the pod has more than one container. The containers are distroless, so `kubectl exec` isn't available for interactive debugging. {{< /call-out >}} +{{< call-out class="note" title="Specifying the container" >}} The `-c policy-controller` flag is required because the pod has more than one container. The containers are distroless, so `kubectl exec` isn't available for interactive debugging. {{< /call-out >}} diff --git a/content/ngf/waf-integration/get-started-plm.md b/content/ngf/waf-integration/get-started-plm.md index 08fefbf57..050c80dba 100644 --- a/content/ngf/waf-integration/get-started-plm.md +++ b/content/ngf/waf-integration/get-started-plm.md @@ -80,9 +80,9 @@ nginxGateway: insecureSkipVerify: false # use only for testing ``` -{{< call-out class="caution" title="Caution" >}} Always use HTTPS with TLS verification (`caSecretName`) in production. Add `clientSSLSecretName` for mutual TLS in high-security environments, and never set `insecureSkipVerify: true`. {{< /call-out >}} +{{< call-out class="caution" title="Use HTTPS in production" >}} Always use HTTPS with TLS verification (`caSecretName`) in production. Add `clientSSLSecretName` for mutual TLS in high-security environments, and never set `insecureSkipVerify: true`. {{< /call-out >}} -{{< call-out class="note" title="Note" >}} `credentialsSecretName` and `caSecretName` must reference Secrets in the NGINX Gateway Fabric control plane namespace, unless you prefix them with `/`. {{< /call-out >}} +{{< call-out class="note" title="Secret namespace" >}} `credentialsSecretName` and `caSecretName` must reference Secrets in the NGINX Gateway Fabric control plane namespace, unless you prefix them with `/`. {{< /call-out >}} {{% /tab %}} @@ -128,7 +128,7 @@ If you install NGINX Gateway Fabric using Kubernetes manifests, use the equivale ## Configure security logging (optional) -{{< call-out class="note" title="Note" >}} If you skip this section, omit the `securityLogs` field when you create the `WAFPolicy` in [Deploy the Gateway and attach WAFPolicy](#deploy-the-gateway-and-attach-wafpolicy). {{< /call-out >}} +{{< call-out class="important" title="If you skip this section" >}} Omit the `securityLogs` field when you create the `WAFPolicy` in [Deploy the Gateway and attach WAFPolicy](#deploy-the-gateway-and-attach-wafpolicy). {{< /call-out >}} {{< include "waf/plm-configure-logging.md" >}} @@ -158,7 +158,7 @@ spec: EOF ``` -{{< call-out class="note" title="Note" >}} The `ReferenceGrant` lives in the `security` namespace and must be created by whoever manages that namespace — typically your security team, not the platform engineer deploying the Gateway. Coordinate with them if you don't have access. Without a matching `ReferenceGrant`, the `WAFPolicy` is rejected with `ResolvedRefs=False` and reason `RefNotPermitted`. If you put the `APPolicy` and `APLogConf` in the same namespace as the `WAFPolicy`, you can skip the `ReferenceGrant`. See [Troubleshoot WAFPolicy status]({{< ref "/ngf/waf-integration/troubleshooting.md" >}}) for details. {{< /call-out >}} +{{< call-out class="important" title="Security team action required" >}} The `ReferenceGrant` lives in the `security` namespace and must be created by whoever manages that namespace — typically your security team, not the platform engineer deploying the Gateway. Coordinate with them if you don't have access. Without a matching `ReferenceGrant`, the `WAFPolicy` is rejected with `ResolvedRefs=False` and reason `RefNotPermitted`. If you put the `APPolicy` and `APLogConf` in the same namespace as the `WAFPolicy`, you can skip the `ReferenceGrant`. See [Troubleshoot WAFPolicy status]({{< ref "/ngf/waf-integration/troubleshooting.md" >}}) for details. {{< /call-out >}} ## Deploy the Gateway and attach WAFPolicy @@ -210,7 +210,7 @@ EOF This `WAFPolicy` protects every route attached to the Gateway. Later changes to the `APPolicy` or `APLogConf` spec trigger recompilation and an automatic re-fetch. You don't need to update the `WAFPolicy`. -{{< call-out class="note" title="Note" >}} This guide enables WAF globally on the GatewayClass-level `NginxProxy`. To enable WAF on a specific Gateway only, create a per-Gateway `NginxProxy` and reference it from the Gateway's `infrastructure.parametersRef`. See [Enable WAF per Gateway]({{< ref "/ngf/waf-integration/overview.md#enable-waf-per-gateway" >}}). {{< /call-out >}} +{{< call-out class="note" title="Per-Gateway WAF enablement" >}} This guide enables WAF globally on the GatewayClass-level `NginxProxy`. To enable WAF on a specific Gateway only, create a per-Gateway `NginxProxy` and reference it from the Gateway's `infrastructure.parametersRef`. See [Enable WAF per Gateway]({{< ref "/ngf/waf-integration/overview.md#enable-waf-per-gateway" >}}). {{< /call-out >}} ## Configure HTTPRoutes @@ -258,7 +258,7 @@ spec: EOF ``` -{{< call-out class="note" title="Note" >}} GRPCRoutes inherit WAF protection the same way HTTPRoutes do. {{< /call-out >}} +{{< call-out class="note" title="GRPCRoute support" >}} GRPCRoutes inherit WAF protection the same way HTTPRoutes do. {{< /call-out >}} ## Validate policy compilation and application @@ -353,7 +353,7 @@ GW_PORT= **Verify normal traffic flows.** Send a request to the `customers` route — the response contains the fake sensitive data from the `customers` backend: -{{< call-out class="note" title="Note" >}} If you have a DNS record for `cafe.example.com`, you can send the request directly to that hostname without `--resolve`. {{< /call-out >}} +{{< call-out class="tip" title="Using a DNS record" >}} If you have a DNS record for `cafe.example.com`, you can send the request directly to that hostname without `--resolve`. {{< /call-out >}} ```shell curl --resolve cafe.example.com:$GW_PORT:$GW_IP http://cafe.example.com:$GW_PORT/customers @@ -395,7 +395,7 @@ curl --resolve cafe.example.com:$GW_PORT:$GW_IP "http://cafe.example.com:$GW_POR ... ``` -{{< call-out class="note" title="Note" >}} The exact blocking response depends on your WAF policy configuration. Check the security log for a corresponding blocked event using `kubectl logs -c waf-enforcer`. {{< /call-out >}} +{{< call-out class="note" title="Checking blocked events" >}} The exact blocking response depends on your WAF policy configuration. Check the security log for a corresponding blocked event using `kubectl logs -c waf-enforcer`. {{< /call-out >}} ## Apply a route-level override (optional) @@ -403,7 +403,7 @@ The `customers` route returns sensitive data (credit card numbers and SSNs) in t This is a common pattern for SecOps and app team collaboration: the security team defines a stricter policy for a specific service, and the platform engineer or app developer attaches it as a route-level override. The override applies only to the `customers` route — other routes continue using the gateway-level policy. -{{< call-out class="note" title="Note" >}} Only one `WAFPolicy` can target a given resource at a given level. If a second `WAFPolicy` targets the same Gateway or route, it is rejected with `Accepted=False` and reason `Conflicted`. See [Policy attachment]({{< ref "/ngf/waf-integration/overview.md#policy-attachment" >}}). {{< /call-out >}} +{{< call-out class="important" title="One WAFPolicy per resource" >}} Only one `WAFPolicy` can target a given resource at a given level. If a second `WAFPolicy` targets the same Gateway or route, it is rejected with `Accepted=False` and reason `Conflicted`. See [Policy attachment]({{< ref "/ngf/waf-integration/overview.md#policy-attachment" >}}). {{< /call-out >}} To protect sensitive data in responses, define a **data guard** `APPolicy` and apply it as a route-level override on the `customers` route: From b70b3a4a65aa81fbf0c8c24a66ace0f93ff6c596 Mon Sep 17 00:00:00 2001 From: Travis Martin Date: Thu, 20 Aug 2026 14:16:28 -0700 Subject: [PATCH 50/50] =?UTF-8?q?style:=20Copy=20edit=20plm-define-policy-?= =?UTF-8?q?methods.md=20(lines=201=E2=80=93213)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Rewrite passive ownership sentence to active voice - Remove hedging ('typically') from security team ownership sentence - Split em-dash compound into two sentences (em-dash rule) - Replace ambiguous 'it' pronouns with specific nouns throughout - Split run-on task sentences that exceeded 20-word limit - Convert 'Re-applying' gerund to standard verb form 'Reapplying' - Replace em dashes with parentheses in git-ref update prose - Replace 'enabled' with 'turned on' (enable-disable rule) - Replace 'as-is' idiom with 'without recompilation' (global-audience) - Rewrite passive table entries to active voice (active-voice rule) - Replace 'decouple' with 'separate' (global-audience, simpler word) - Remove SSL_CERT_FILE em dash; clarify ambiguous 'it' referent --- .../includes/waf/plm-define-policy-methods.md | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/content/includes/waf/plm-define-policy-methods.md b/content/includes/waf/plm-define-policy-methods.md index 00bec3b89..a58fcc919 100644 --- a/content/includes/waf/plm-define-policy-methods.md +++ b/content/includes/waf/plm-define-policy-methods.md @@ -6,9 +6,9 @@ f5-files: -This section is typically owned by the security team. They define the policy in the `security` namespace, separate from the Gateway namespace, so security resources are managed independently from routing configuration. If you're not on the security team, share this section with them — you'll need the `APPolicy` name and namespace before continuing. +The security team usually owns this section. They define the policy in the `security` namespace, separate from the Gateway namespace, so they can manage security resources independently from routing configuration. If you're not on the security team, share this section with them. You'll need the `APPolicy` name and namespace before continuing. -The `APPolicy` resource defines the security policy. The PLM controller watches it, compiles it, and writes `status.bundle` with `state: ready` when the bundle is available. +The `APPolicy` resource defines the security policy. The PLM controller watches the resource, compiles the policy, and writes `status.bundle` with `state: ready` when the bundle is available. {{}} @@ -52,7 +52,7 @@ kubectl wait --for=jsonpath='{.status.bundle.state}'=ready appolicy/attack-signa {{%tab name="Git reference"%}} -Store your policy JSON in a Git repository and reference it from `APPolicy`. +Store your policy JSON in a Git repository and reference the file from an `APPolicy` resource. #### Public repository @@ -122,20 +122,20 @@ EOF The Policy Controller doesn't poll the Git repository for changes. It fetches the policy file when the `APPolicy` spec changes. -To pick up a new version of the policy, push your changes to the repository, then update `ref` in the `APPolicy` resource to the new tag or commit SHA and reapply it. Re-applying an unchanged `APPolicy` doesn't trigger a fetch, and neither does changing an annotation. +To pick up a new version of the policy, push your changes to the repository. Then update `ref` in the `APPolicy` resource to the new tag or commit SHA and reapply the resource. Reapplying an unchanged `APPolicy` doesn't trigger a fetch. Changing an annotation doesn't trigger a fetch either. -If you need to re-fetch the same `ref` — for example, after force-updating a tag — delete the `APPolicy` resource and recreate it. +If you need to re-fetch the same `ref` (for example, after force-updating a tag), delete the `APPolicy` resource and recreate it. {{% /tab %}} {{%tab name="Precompiled bundle"%}} -The precompiled-bundle method lets you reference a `.tgz` policy bundle stored in an artifact registry (for example, Artifactory or Nexus). The Policy Controller imports the bundle and stores it in the SeaweedFS object store without recompiling it. +The precompiled-bundle method lets you reference a `.tgz` policy bundle stored in an artifact registry (for example, Artifactory or Nexus). The Policy Controller imports the bundle and stores it in the SeaweedFS object store without recompiling the bundle. Use this method when: - Your security team compiles and publishes bundles through an external pipeline. -- You want to decouple policy compilation from cluster operations. +- You want to separate policy compilation from cluster operations. Create an `APPolicy` resource that references your bundle. Replace ``, ``, and `` with your values: @@ -153,7 +153,7 @@ EOF ``` {{< call-out class="important" title="Configure CA trust for private registries" >}} -The Policy Controller must reach the artifact registry over HTTPS. If the registry uses a private certificate authority (CA), mount the CA certificate into the Policy Controller pod and set the `SSL_CERT_FILE` environment variable to its path. `SSL_CERT_FILE` replaces the system trust store entirely — it doesn't append to it. If SeaweedFS TLS is also enabled, combine both CAs into a single file and reference that file. +The Policy Controller must reach the artifact registry over HTTPS. If the registry uses a private certificate authority (CA), mount the CA certificate into the Policy Controller pod and set the `SSL_CERT_FILE` environment variable to its path. `SSL_CERT_FILE` replaces the system trust store entirely. It doesn't append to the system trust store. If SeaweedFS TLS is also turned on, combine both CAs into a single file and reference that file. {{< /call-out >}} If the `APPolicy` status shows `x509: certificate signed by unknown authority`, the Policy Controller doesn't trust the artifact registry CA. Check the status for the full error: @@ -180,7 +180,7 @@ Bundle: s3://plm-system/bundles/_imported_.tgz isCompiled: false ``` -`isCompiled: false` confirms the bundle was imported as-is and not recompiled. +`isCompiled: false` confirms the bundle was imported without recompilation. `bundle.state` can be one of: @@ -188,12 +188,12 @@ isCompiled: false |-------|---------| | `pending` | The Policy Controller hasn't yet processed the resource. | | `processing` | The Policy Controller is importing or storing the bundle. | -| `ready` | The bundle is stored and ready to use. `bundle.location` is populated. | -| `invalid` | The bundle couldn't be imported. Check the status for error detail. | +| `ready` | The bundle is stored and ready to use. The Policy Controller has populated `bundle.location`. | +| `invalid` | The Policy Controller couldn't import the bundle. Check the status for error detail. | #### Update a precompiled bundle -The Policy Controller doesn't poll the artifact registry for changes. To pick up a new version of a bundle, update the `$ref` URL in your `APPolicy` resource and reapply it. Changing an annotation doesn't trigger a new download. If you need to re-fetch the same URL, delete the `APPolicy` resource and recreate it. Replace ``, ``, and `` with your values: +The Policy Controller doesn't poll the artifact registry for changes. To pick up a new version of a bundle, update the `$ref` URL in your `APPolicy` resource and reapply the resource. Changing an annotation doesn't trigger a new download. If you need to re-fetch the same URL, delete the `APPolicy` resource and recreate it. Replace ``, ``, and `` with your values: ```shell kubectl apply -f - <<'EOF'