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-configure-logging.md b/content/includes/waf/plm-configure-logging.md new file mode 100644 index 000000000..357a2094e --- /dev/null +++ b/content/includes/waf/plm-configure-logging.md @@ -0,0 +1,34 @@ +--- +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. 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 new file mode 100644 index 000000000..a58fcc919 --- /dev/null +++ b/content/includes/waf/plm-define-policy-methods.md @@ -0,0 +1,213 @@ +--- +f5-product: F5 WAF for NGINX +f5-files: +- content/ngf/waf-integration/get-started-plm.md +--- + + + +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 the resource, compiles the policy, 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 - <<'EOF' +apiVersion: appprotect.f5.com/v1 +kind: APPolicy +metadata: + name: + namespace: +spec: + policy: + $ref: + externalReferenceDetails: + repositoryDetails: + repository: https://github.com//.git + ref: "" +EOF +``` + +{{< 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: + +```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 - <<'EOF' +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 +EOF +``` + +#### Update a Git-referenced policy + +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 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. + +{{% /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 the bundle. + +Use this method when: + +- Your security team compiles and publishes bundles through an external pipeline. +- You want to separate 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: security +spec: + policy: + $ref: "https:///.tgz" +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 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: + +```shell +kubectl describe appolicy --namespace security +``` + +#### 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 security \ + --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 without recompilation. + +`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. 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 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' +apiVersion: appprotect.f5.com/v1 +kind: APPolicy +metadata: + name: + namespace: security +spec: + policy: + $ref: "https:///.tgz" +EOF +``` + +{{% /tab %}} + +{{}} diff --git a/content/includes/waf/plm-deploy-infrastructure.md b/content/includes/waf/plm-deploy-infrastructure.md new file mode 100644 index 000000000..7d211ba4b --- /dev/null +++ b/content/includes/waf/plm-deploy-infrastructure.md @@ -0,0 +1,298 @@ +--- +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 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. + +### Create the registry pull secret + +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 + + 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 + +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 +base64 --wrap=0 < nginx-repo.key +``` + +Create `/tmp/plm-values.yaml`: + +```yaml +imagePullSecrets: + - name: regcred +securityUpdatesRepo: + 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 >}}" +compiler: + image: + tag: "{{< version-waf-policy-controller >}}" +seaweedfsOperatorConfig: + seaweedfs: + image: + tag: "{{< version-waf-policy-controller >}}" +seaweedfs-operator: + image: + tag: "{{< version-waf-policy-controller >}}" + 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 +``` + +{{< 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 >}} + +{{< 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-f5-waf-seaweedfs-ca-cert \ + --namespace plm-system \ + --from-file=tls.crt= \ + --from-file=ca.crt= + +kubectl create secret tls plm-f5-waf-seaweedfs-master-cert \ + --namespace plm-system \ + --cert= \ + --key= + +kubectl create secret tls plm-f5-waf-seaweedfs-volume-cert \ + --namespace plm-system \ + --cert= \ + --key= + +kubectl create secret tls plm-f5-waf-seaweedfs-filer-cert \ + --namespace plm-system \ + --cert= \ + --key= + +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 + +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 plm nginx-stable/f5-waf-policy-controller \ + --version {{< version-waf-policy-controller >}} \ + --namespace plm-system \ + --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. + +Wait for the SeaweedFS storage backend: + +```shell +kubectl rollout status deployment/plm-seaweedfs-operator \ + --namespace plm-system --timeout=120s +``` + +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: + +```shell +kubectl rollout status deployment/plm-f5-waf-policy-controller \ + --namespace plm-system --timeout=180s +``` + +Confirm all pods are running: + +```shell +kubectl get pods --namespace plm-system +``` + +Example output: + +```text +NAME READY STATUS RESTARTS +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 CRDs are present: + +```shell +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 + +{{< 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 >}} + +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 deployment + +These are the most common failures during PLM installation, roughly in order of likelihood. + +{{< details summary="Pods stuck in `ImagePullBackOff`" >}} + +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 +``` + +Use the full JWT string as the registry username. Use the literal string `none` as the password. + +{{< /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`: + +```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. + +{{< /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: + +```shell +kubectl get pvc --namespace plm-system +kubectl get storageclass +``` + +{{< /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: + +```shell +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. + +{{< /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: + +```shell +kubectl logs --namespace plm-system deploy/plm-f5-waf-policy-controller -c policy-controller +``` + +{{< 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/includes/waf/plm-prerequisites.md b/content/includes/waf/plm-prerequisites.md new file mode 100644 index 000000000..74ab47979 --- /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/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 - </`. | +| _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/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/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.md b/content/ngf/waf-integration/get-started-http.md similarity index 98% rename from content/ngf/waf-integration/get-started.md rename to content/ngf/waf-integration/get-started-http.md index 78ffd4493..30d8ac7ef 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. @@ -413,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: @@ -528,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 new file mode 100644 index 000000000..050c80dba --- /dev/null +++ b/content/ngf/waf-integration/get-started-plm.md @@ -0,0 +1,483 @@ +--- +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 +--- + +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 +- 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 + +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 + +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. + +### 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 | +|---|---| +| `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" >}} + +## 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. 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`. + +{{}} + +{{%tab name="HTTPS (secure)"%}} + +```yaml +nginxGateway: + plmStorage: + 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 + insecureSkipVerify: false # use only for testing +``` + +{{< 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="Secret namespace" >}} `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 +``` + +{{% /tab %}} + +{{}} + +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): + +```yaml +apiVersion: v1 +kind: Secret +metadata: + name: plm-storage-credentials + namespace: nginx-gateway +type: Opaque +data: + seaweedfs_admin_secret: +``` + +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, use the equivalent `plm-storage-*` flags documented in the [command-line reference]({{< ref "/ngf/reference/cli-help.md" >}}). + +## Deploy the sample application + +{{< include "waf/plm-sample-app.md" >}} + +## Create the security namespace + +{{< include "waf/plm-create-security-namespace.md" >}} + +## Configure security logging (optional) + +{{< 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" >}} + +## Define the WAF policy + +{{< 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: + +```yaml +kubectl apply -f - <}} 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 + +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 - <}} 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 + +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 - <}} GRPCRoutes inherit WAF protection the same way HTTPRoutes do. {{< /call-out >}} + +## Validate policy compilation and application + +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`. 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`, 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. | +| `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 -c policy-controller +``` + +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 + +Confirm the Gateway has an IP address assigned and reports `Programmed=True`: + +```shell +kubectl describe gateways.gateway.networking.k8s.io gateway +``` + +```text +Addresses: + Type: IPAddress + Value: 192.0.2.1 +``` + + +Save the public IP address and port of the Gateway to shell variables: + +```text +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 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 +``` + +```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.** 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=" +``` + +```text + +Request Rejected +... +``` + +{{< 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) + +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 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="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: + +```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 f686d0ff4..b825330f6 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 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" >}}). ### 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,24 +100,57 @@ 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. 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 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 with fetching a compiled bundle and deploying it to the data plane. ### 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 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 | + +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 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. + +{{< 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 >}} + --- ## Policy attachment @@ -126,7 +159,7 @@ For details on configuring each source type, see [Configure policy sources]({{< - 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 @@ -141,7 +174,8 @@ 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" >}}) +- [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 4208ed5ff..260cd3050 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 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 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 - <` 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`. --- +## 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. -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 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 >}} @@ -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" >}}) diff --git a/content/ngf/waf-integration/troubleshooting.md b/content/ngf/waf-integration/troubleshooting.md index 56faaec85..6fe3f2e48 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, `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 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