From 97d1f17b7d7863ee9553b4a4a3f7ee147cfd93c6 Mon Sep 17 00:00:00 2001 From: Venktesh Shivam Patel Date: Wed, 12 Aug 2026 17:19:14 +0100 Subject: [PATCH 1/4] add PLM upgrade/install docs --- content/nic/install/plm-installation.md | 582 ++++++++++++++++++++++++ content/nic/install/plm-upgrade.md | 295 ++++++++++++ 2 files changed, 877 insertions(+) create mode 100644 content/nic/install/plm-installation.md create mode 100644 content/nic/install/plm-upgrade.md diff --git a/content/nic/install/plm-installation.md b/content/nic/install/plm-installation.md new file mode 100644 index 000000000..d0b481055 --- /dev/null +++ b/content/nic/install/plm-installation.md @@ -0,0 +1,582 @@ +--- +title: Install NGINX Ingress Controller with F5 WAF for NGINX using PLM +weight: 200 +toc: true +f5-content-type: tutorial +f5-product: F5 NGINX Ingress Controller +f5-description: > + Install F5 NGINX Ingress Controller with F5 WAF for NGINX and Policy + Lifecycle Management (PLM). Deploy WAF-protected traffic using either a + VirtualServer or an Ingress resource. +f5-audience: operator +--- + +This tutorial installs F5 NGINX Ingress Controller (NIC) with F5 WAF for NGINX +using Policy Lifecycle Management (PLM). PLM defines WAF policies as +Kubernetes custom resources, compiles them automatically, and stores the +compiled bundles in an in-cluster S3-compatible object store. NIC fetches the +bundles from PLM storage and enforces the policies at request time. + +By the end of this tutorial you will have: + +- A running PLM backend and a NIC deployment configured for PLM storage. +- An `APPolicy` and an `APLogConf` resource compiled by PLM. +- A `k8s.nginx.org/v1` Policy that references the compiled resources. +- The Policy attached to either a VirtualServer or an Ingress, with traffic + flowing normally and attack payloads blocked. + +## Before you begin + +- You have `kubectl` access to a Kubernetes cluster. +- You have Helm. +- You have credentials for `private-registry.nginx.com`. + +This tutorial uses the following example values. If you use different values, +substitute them consistently throughout. + +| Example value | What it represents | +|---|---| +| `plm-system` | Namespace for the PLM backend | +| `plm` | Helm release name for PLM | +| `nginx-ingress` | Namespace for NIC | +| `nic` | Helm release name for NIC | +| `security` | Namespace for `APPolicy` and `APLogConf` resources | +| `default` | Namespace for the NIC Policy, sample application, and routing resource | +| `webapp.example.com` | Example hostname for VirtualServer routing | +| `cafe.example.com` | Example hostname for Ingress routing | + +## Deploy PLM infrastructure + +Install the PLM backend before you install NIC. It provisions the App +Protect v1 CRDs, the Policy Controller, the compiler service, and the +SeaweedFS storage backend in the `plm-system` namespace. + +{{< include "waf/plm-deploy-infrastructure.md" >}} + +Confirm the CRDs are installed: + +```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 +``` + +## Look up the PLM storage endpoint and credentials + +NIC connects to PLM's SeaweedFS filer over S3 to fetch compiled bundles. +Before you install NIC, collect four values that the PLM install produced: + +1. **PLM storage URL**: the SeaweedFS filer endpoint (HTTPS or HTTP). +2. **Credentials Secret**: the S3 credentials Secret. The access key ID is + `admin` by default; the secret access key is stored in the + `seaweedfs_admin_secret` field. +3. **CA Secret** (HTTPS only): verifies the SeaweedFS filer certificate. +4. **Client TLS Secret** (mutual TLS only): presented by NIC when it + connects to the filer. + +List the Services PLM created and identify the filer: + +```shell +kubectl get service --namespace plm-system +``` + +Expected output includes an entry similar to: + +```text +NAME TYPE CLUSTER-IP PORT(S) +plm-f5-waf-seaweed-filer ClusterIP 10.0.0.10 8333/TCP,9333/TCP,... +``` + +Assemble the URL from the service name, namespace, and port. Use `9333` +for HTTPS and `8333` for HTTP: + +- HTTPS (mTLS): `https://plm-f5-waf-seaweed-filer.plm-system.svc.cluster.local:9333` +- HTTP: `http://plm-f5-waf-seaweed-filer.plm-system.svc.cluster.local:8333` + +List the Secrets PLM created: + +```shell +kubectl get secret --namespace plm-system +``` + +The default PLM install creates three Secrets that NIC references: + +- `plm-f5-waf-seaweedfs-auth`: SeaweedFS credentials. +- `plm-f5-waf-seaweedfs-ca-cert`: CA certificate for the HTTPS filer. +- `plm-f5-waf-seaweedfs-client-cert`: client TLS certificate for mTLS. + +Record the Secret references in `/` form. You'll pass all +four values to NIC as `--set controller.appprotect.plmStorage.*` flags + +## Install NIC with PLM storage + +Because PLM owns the `appprotect.f5.com/v1` CRDs, install NIC with +`--skip-crds`. You apply NIC's own CRDs from the bundled manifest first. + +Apply the NIC CRDs. The `deploy/crds.yaml` bundle contains every CRD NIC +needs (`VirtualServer`, `VirtualServerRoute`, `Policy`, `TransportServer`, +`GlobalConfiguration`, `DNSEndpoint`) and deliberately excludes the App +Protect CRDs, which PLM owns. + +```shell +kubectl apply -f https://raw.githubusercontent.com/nginx/kubernetes-ingress/v{{< nic-version >}}/deploy/crds.yaml +``` + +Create a namespace and image pull Secret for NIC: + +```shell +kubectl create namespace nginx-ingress +kubectl create secret docker-registry regcred \ + --namespace nginx-ingress \ + --docker-server=private-registry.nginx.com \ + --docker-username= \ + --docker-password=none +``` + +Add the NGINX Helm repository: + +```shell +helm repo add nginx-stable https://helm.nginx.com/stable +helm repo update nginx-stable +``` + +Install NIC with PLM storage enabled. This example uses HTTPS PLM storage with mutual TLS. +For HTTP storage, set `controller.appprotect.plmStorage.url` and `controller.appprotect.plmStorage.credentialsSecret` only. + +```shell +helm install nic nginx-stable/nginx-ingress \ + --namespace nginx-ingress \ + --skip-crds \ + --set controller.image.repository="private-registry.nginx.com/nginx-ic-nap-v5/nginx-plus-ingress" \ + --set controller.image.tag="{{< nic-version >}}" \ + --set controller.nginxplus=true \ + --set controller.appprotect.enable=true \ + --set controller.appprotect.v5=true \ + --set controller.appprotect.plmStorage.url="https://plm-f5-waf-seaweed-filer.plm-system.svc.cluster.local:9333" \ + --set controller.appprotect.plmStorage.credentialsSecret="plm-system/plm-f5-waf-seaweedfs-auth" \ + --set controller.appprotect.plmStorage.caSecret="plm-system/plm-f5-waf-seaweedfs-ca-cert" \ + --set controller.appprotect.plmStorage.clientSSLSecret="plm-system/plm-f5-waf-seaweedfs-client-cert" \ + --set controller.appprotect.plmStorage.insecureSkipVerify=false \ + --set controller.serviceAccount.imagePullSecretName=regcred +``` + +Wait for the NIC Pod to become ready. Each Pod runs three containers: +`nginx-ingress`, `waf-enforcer`, and `waf-config-mgr`. + +```shell +kubectl wait --for=condition=Ready pods \ + --namespace nginx-ingress \ + --selector app.kubernetes.io/name=nginx-ingress \ + --timeout=180s + +kubectl get pods --namespace nginx-ingress +``` + +Expected output: + +```text +NAME READY STATUS RESTARTS AGE +nic-nginx-ingress-controller-xxxxx 3/3 Running 0 2m +``` + +Save the public IP address and HTTP port of the NIC LoadBalancer service into +shell variables: + +```shell +IC_IP= +IC_HTTP_PORT= +``` + +## Define the WAF policy + +Create the `security` namespace to hold the `APPolicy` and `APLogConf` +resources: + +```shell +kubectl create namespace security +``` + +Save the following as `waf-resources.yaml`. It defines an `APPolicy` that +blocks attack signatures and masks credit card numbers and social security +numbers in responses, plus an `APLogConf` for security logging. + +```yaml +apiVersion: appprotect.f5.com/v1 +kind: APPolicy +metadata: + name: dataguard-blocking + namespace: security +spec: + policy: + name: dataguard-blocking + template: + name: POLICY_TEMPLATE_NGINX_BASE + applicationLanguage: utf-8 + enforcementMode: blocking + blocking-settings: + violations: + - name: VIOL_DATA_GUARD + alarm: true + block: true + data-guard: + enabled: true + maskData: true + creditCardNumbers: true + usSocialSecurityNumbers: true +--- +apiVersion: appprotect.f5.com/v1 +kind: APLogConf +metadata: + name: log-default + namespace: security +spec: + content: + format: default + max_message_size: 64k + max_request_size: any + filter: + request_type: all +``` + +Apply the file: + +```shell +kubectl apply -f waf-resources.yaml +``` + +Wait for both resources to reach `ready`: + +```shell +kubectl wait --for=jsonpath='{.status.bundle.state}'=ready \ + appolicy/dataguard-blocking --namespace security --timeout=180s + +kubectl wait --for=jsonpath='{.status.bundle.state}'=ready \ + aplogconf/log-default --namespace security --timeout=180s +``` + +Confirm the compiled bundle location: + +```shell +kubectl get appolicy dataguard-blocking --namespace security \ + --output jsonpath='State: {.status.bundle.state}{"\n"}Location: {.status.bundle.location}{"\n"}' +``` + +## Create the NIC Policy + +Save the following as `waf-policy.yaml`. The `apPolicy` and `apLogConf` +fields accept `[/]`. When namespace is omitted, NIC +defaults to the Policy's own namespace. + +```yaml +apiVersion: k8s.nginx.org/v1 +kind: Policy +metadata: + name: waf-policy + namespace: default +spec: + waf: + enable: true + apPolicy: "security/dataguard-blocking" + securityLogs: + - enable: true + apLogConf: "security/log-default" + logDest: "syslog:server=syslog-svc.default:514" +``` + +Apply the file: + +```shell +kubectl apply -f waf-policy.yaml +``` + +Wait for the Policy to become `Valid`: + +```shell +kubectl wait --for=jsonpath='{.status.state}'=Valid \ + policy/waf-policy --namespace default --timeout=180s +``` + +If the Policy stays in `Warning` with a `BundleFetchFailed` reason, see +[Troubleshooting](#troubleshooting). + +Continue with Section 1 to attach the Policy to a VirtualServer, or Section 2 +to attach it to an Ingress. The Policy resource is the same for both. + +## Section 1: Attach to a VirtualServer + +Save the following as `webapp.yaml`. It contains the sample application +Deployment, its Service, and a VirtualServer that references the +`waf-policy` Policy. + +```yaml +apiVersion: apps/v1 +kind: Deployment +metadata: + name: webapp + namespace: default +spec: + replicas: 1 + selector: + matchLabels: + app: webapp + template: + metadata: + labels: + app: webapp + spec: + containers: + - name: webapp + image: nginxdemos/nginx-hello:plain-text + ports: + - containerPort: 8080 +--- +apiVersion: v1 +kind: Service +metadata: + name: webapp-svc + namespace: default +spec: + selector: + app: webapp + ports: + - name: http + port: 80 + targetPort: 8080 +--- +apiVersion: k8s.nginx.org/v1 +kind: VirtualServer +metadata: + name: webapp + namespace: default +spec: + host: webapp.example.com + policies: + - name: waf-policy + upstreams: + - name: webapp + service: webapp-svc + port: 80 + routes: + - path: / + action: + pass: webapp +``` + +Apply the file: + +```shell +kubectl apply -f webapp.yaml +``` + +Send a normal request to confirm the application responds: + +```shell +curl --resolve webapp.example.com:$IC_HTTP_PORT:$IC_IP \ + http://webapp.example.com:$IC_HTTP_PORT/ +``` + +Expected output: + +```text +Server address: 10.0.0.1:8080 +Server name: webapp-xxxxx +``` + +Send a request that triggers the data guard violation: + +```shell +curl --resolve webapp.example.com:$IC_HTTP_PORT:$IC_IP \ + "http://webapp.example.com:$IC_HTTP_PORT/" +``` + +Expected output: + +```text +Request Rejected +The requested URL was rejected. Please consult with your administrator. +... + +``` + +## Section 2: Attach to an Ingress + +Save the following as `cafe.yaml`. It contains the sample cafe application +(`coffee` and `tea` Deployments and Services) and an Ingress. The +`nginx.com/policies` annotation attaches the `waf-policy` Policy to every +route on the Ingress. + +```yaml +apiVersion: apps/v1 +kind: Deployment +metadata: + name: coffee + namespace: default +spec: + replicas: 2 + selector: + matchLabels: + app: coffee + template: + metadata: + labels: + app: coffee + spec: + containers: + - name: coffee + image: nginxdemos/hello:plain-text + ports: + - containerPort: 80 +--- +apiVersion: v1 +kind: Service +metadata: + name: coffee-svc + namespace: default +spec: + selector: + app: coffee + ports: + - port: 80 + targetPort: 80 + name: http +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: tea + namespace: default +spec: + replicas: 2 + selector: + matchLabels: + app: tea + template: + metadata: + labels: + app: tea + spec: + containers: + - name: tea + image: nginxdemos/hello:plain-text + ports: + - containerPort: 80 +--- +apiVersion: v1 +kind: Service +metadata: + name: tea-svc + namespace: default +spec: + selector: + app: tea + ports: + - port: 80 + targetPort: 80 + name: http +--- +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: cafe-ingress + namespace: default + annotations: + nginx.com/policies: "waf-policy" +spec: + ingressClassName: nginx + rules: + - host: cafe.example.com + http: + paths: + - path: /tea + pathType: Prefix + backend: + service: + name: tea-svc + port: + number: 80 + - path: /coffee + pathType: Prefix + backend: + service: + name: coffee-svc + port: + number: 80 +``` + +Apply the file: + +```shell +kubectl apply -f cafe.yaml +``` + +Send a normal request: + +```shell +curl --resolve cafe.example.com:$IC_HTTP_PORT:$IC_IP \ + http://cafe.example.com:$IC_HTTP_PORT/coffee +``` + +Send a request that triggers the data guard violation: + +```shell +curl --resolve cafe.example.com:$IC_HTTP_PORT:$IC_IP \ + "http://cafe.example.com:$IC_HTTP_PORT/coffee/" +``` + +The response is `Request Rejected` for the attack payload. + +Under PLM, Ingress-only App Protect annotations +(`appprotect.f5.com/app-protect-policy`, +`appprotect.f5.com/app-protect-security-log`) are not supported. Use the +`k8s.nginx.org/v1` Policy resource and the `nginx.com/policies` annotation, +as shown above. + +## Verify bundles on disk + +Confirm the compiled bundles were fetched into the NIC Pod: + +```shell +NIC_POD=$(kubectl get pods --namespace nginx-ingress \ + --selector app.kubernetes.io/name=nginx-ingress \ + --output jsonpath='{.items[0].metadata.name}') + +kubectl exec --namespace nginx-ingress $NIC_POD --container nginx-ingress -- \ + ls -ltr /etc/app_protect/bundles/ +``` + +Expected output: + +```text +total 1860 +-rw------- 1 nginx nginx 1654 Aug 12 10:06 fetched_default_waf-policy_log_0.tgz +-rw------- 1 nginx nginx 1898698 Aug 12 13:32 fetched_default_waf-policy_policy.tgz +``` + +Check the Policy status: + +```shell +kubectl describe policy waf-policy +``` + +The status reports `State: Valid` and `Reason: AddedOrUpdated` when the +bundles are fetched successfully. + +## Troubleshooting + +- **Policy status is `Warning` with reason `BundleFetchFailed`.** Check the + `APPolicy` or `APLogConf` referenced by the Policy. Run + `kubectl describe appolicy --namespace security` and confirm + `status.bundle.state` is `ready`. If PLM has not compiled the resource, + the Policy fetch cannot proceed. +- **NIC reports the referenced namespace is not watched.** If + `controller.watchNamespace` is set, include the namespace holding the + `APPolicy` and `APLogConf` resources. If `controller.watchSecretNamespace` + is set, include the PLM namespace so NIC can observe storage Secret + rotation. diff --git a/content/nic/install/plm-upgrade.md b/content/nic/install/plm-upgrade.md new file mode 100644 index 000000000..a375c39f6 --- /dev/null +++ b/content/nic/install/plm-upgrade.md @@ -0,0 +1,295 @@ +--- +title: Upgrade NGINX Ingress Controller to F5 WAF for NGINX with PLM +weight: 210 +toc: true +f5-content-type: how-to +f5-product: F5 NGINX Ingress Controller +f5-description: > + Upgrade an existing F5 NGINX Ingress Controller (NIC) deployment from + in-pod App Protect policy compilation (v1beta1 CRDs) to F5 WAF for NGINX + with Policy Lifecycle Management (PLM). +f5-audience: operator +--- + +This guide upgrades an existing NIC + F5 WAF for NGINX deployment from in-pod +App Protect policy compilation to Policy Lifecycle Management (PLM). PLM +compiles `APPolicy` and `APLogConf` resources in a dedicated controller and +stores the resulting bundles in an in-cluster S3-compatible object store. NIC +fetches the compiled bundles instead of compiling them in the data plane. + +Under PLM, the fields you already write on the NIC `Policy` resource +(`waf.apPolicy` and `waf.securityLogs[].apLogConf`) continue to work +unchanged. The `k8s.nginx.org/v1` Policy manifest for a VirtualServer or +Ingress does not need to be rewritten during the upgrade. + +By the end of this guide you will have: + +- The PLM backend installed alongside your existing NIC deployment. +- NIC upgraded to a PLM-capable release with PLM storage configured. +- Existing `APPolicy` and `APLogConf` resources adopted by PLM and compiled + into bundles. +- WAF-protected traffic served by NIC, with bundles fetched from PLM storage + instead of compiled in the data plane. + +## Before you begin + +- Your cluster runs an existing NIC + F5 WAF for NGINX deployment. +- Existing `APPolicy` and `APLogConf` resources are served by the + `appprotect.f5.com/v1beta1` CRDs shipped with your current NIC installation. +- You have `kubectl` and Helm access to the cluster. +- You have credentials for `private-registry.nginx.com`. + +Record your current values before you begin: + +| Value | Where to find it | +|---|---| +| NIC release name | `helm list --namespace ` | +| NIC chart version | `helm list --namespace ` | +| Existing NIC values | `helm get values --namespace ` | +| Existing `APPolicy` resources | `kubectl get appolicy --all-namespaces` | +| Existing `APLogConf` resources | `kubectl get aplogconf --all-namespaces` | + +This guide uses `nic` as the NIC release name, `nginx-ingress` as the NIC +namespace, `plm-system` as the PLM namespace, and `plm` as the PLM release +name. Substitute your values consistently. + +## Deploy PLM infrastructure + +Install the PLM backend into the `plm-system` namespace. The install adds +the `appprotect.f5.com/v1` versions to the existing `appprotect.f5.com` +CRDs. The v1 versions are a superset of v1beta1, so adding them does not +affect NIC while it still watches v1beta1. + +{{< include "waf/plm-deploy-infrastructure.md" >}} + +After the install completes, PLM adopts every existing `APPolicy` and +`APLogConf` resource in the cluster by adding the +`appprotect.f5.com/finalizer` finalizer and compiling each resource against +its current signature package. Your running NIC continues to compile the +same resources in-pod. Both mechanisms operate independently until you +upgrade NIC. + +Confirm PLM has compiled the existing resources: + +```shell +kubectl get appolicy --all-namespaces \ + --output custom-columns='NAMESPACE:.metadata.namespace,NAME:.metadata.name,STATE:.status.bundle.state' + +kubectl get aplogconf --all-namespaces \ + --output custom-columns='NAMESPACE:.metadata.namespace,NAME:.metadata.name,STATE:.status.bundle.state' +``` + +Every resource should report `STATE: ready`. If any resource is not `ready`, +inspect the PLM policy-controller logs and resolve compilation errors before +you continue. + +## Look up the PLM storage endpoint and credentials + +NIC connects to PLM's SeaweedFS filer over S3 to fetch compiled bundles. +Before you run `helm upgrade`, collect four values that the PLM install +produced: + +1. **PLM storage URL**: the SeaweedFS filer endpoint (HTTPS or HTTP). +2. **Credentials Secret**: the S3 credentials Secret. The access key ID is + `admin` by default; the secret access key is stored in the + `seaweedfs_admin_secret` field. +3. **CA Secret** (HTTPS only): verifies the SeaweedFS filer certificate. +4. **Client TLS Secret** (mutual TLS only): presented by NIC when it + connects to the filer. + +List the Services PLM created and identify the filer: + +```shell +kubectl get service --namespace plm-system +``` + +Expected output includes an entry similar to: + +```text +NAME TYPE CLUSTER-IP PORT(S) +plm-f5-waf-seaweed-filer ClusterIP 10.0.0.10 8333/TCP,9333/TCP,... +``` + +Assemble the URL from the service name, namespace, and port. Use `9333` +for HTTPS and `8333` for HTTP: + +- HTTPS (mTLS): `https://plm-f5-waf-seaweed-filer.plm-system.svc.cluster.local:9333` +- HTTP: `http://plm-f5-waf-seaweed-filer.plm-system.svc.cluster.local:8333` + +List the Secrets PLM created: + +```shell +kubectl get secret --namespace plm-system +``` + +The default PLM install creates three Secrets that NIC references: + +- `plm-f5-waf-seaweedfs-auth`: SeaweedFS credentials. +- `plm-f5-waf-seaweedfs-ca-cert`: CA certificate for the HTTPS filer. +- `plm-f5-waf-seaweedfs-client-cert`: client TLS certificate for mTLS. + +Record the Secret references in `/` form. You'll pass all +four values to NIC as `--set controller.appprotect.plmStorage.*` flags when +you run `helm upgrade`. + +## Apply NIC CRDs + +Before you run `helm upgrade`, apply the NIC CRDs from the bundled manifest +for your target release. The `deploy/crds.yaml` bundle contains every CRD +NIC needs (`VirtualServer`, `VirtualServerRoute`, `Policy`, +`TransportServer`, `GlobalConfiguration`, `DNSEndpoint`) and deliberately +excludes the App Protect CRDs, which PLM owns. + +```shell +kubectl apply -f https://raw.githubusercontent.com/nginx/kubernetes-ingress/v{{< nic-version >}}/deploy/crds.yaml +``` + +## Section 1: Upgrade a VirtualServer-based deployment + +This section applies when your existing NIC deployment routes traffic +through `k8s.nginx.org/v1` VirtualServer resources that reference a Policy +resource with `waf.apPolicy` and `waf.securityLogs[].apLogConf` fields. + +### 1. Upgrade NIC to enable PLM + +Upgrade the NIC release. `--reuse-values` preserves your existing NIC +configuration; the `--set` flags overlay PLM storage on top. `--skip-crds` +prevents Helm from touching CRDs, since you applied the NIC CRDs in the +previous step and PLM owns the App Protect CRDs. + +This example uses HTTPS PLM storage with mutual TLS. For HTTP storage, set +`controller.appprotect.plmStorage.url` to `http://:8333` and omit the +`caSecret` and `clientSSLSecret` flags. + +```shell +helm upgrade nic nginx-stable/nginx-ingress \ + --namespace nginx-ingress \ + --skip-crds \ + --reuse-values \ + --set controller.image.repository="private-registry.nginx.com/nginx-ic-nap-v5/nginx-plus-ingress" \ + --set controller.image.tag="{{< nic-version >}}" \ + --set controller.appprotect.plmStorage.url="https://plm-f5-waf-seaweed-filer.plm-system.svc.cluster.local:9333" \ + --set controller.appprotect.plmStorage.credentialsSecret="plm-system/plm-f5-waf-seaweedfs-auth" \ + --set controller.appprotect.plmStorage.caSecret="plm-system/plm-f5-waf-seaweedfs-ca-cert" \ + --set controller.appprotect.plmStorage.clientSSLSecret="plm-system/plm-f5-waf-seaweedfs-client-cert" \ + --set controller.appprotect.plmStorage.insecureSkipVerify=false +``` + +Wait for the rollout to complete: + +```shell +kubectl rollout status deployment/nic-nginx-ingress-controller \ + --namespace nginx-ingress \ + --timeout=180s +``` + +Confirm NIC is watching the v1 CRDs by inspecting the controller log: + +```shell +kubectl logs deployment/nic-nginx-ingress-controller \ + --namespace nginx-ingress \ + --container nginx-ingress | grep 'appprotect.f5.com/v' +``` + +Expected output includes: + +```text +Using appprotect.f5.com/v1 CRDs +``` + +### 2. Verify Policy status + +Check each WAF Policy referenced by a VirtualServer: + +```shell +kubectl get policy --all-namespaces \ + --output custom-columns='NAMESPACE:.metadata.namespace,NAME:.metadata.name,STATE:.status.state,REASON:.status.reason' +``` + +Expected output after the first successful fetch: + +```text +NAMESPACE NAME STATE REASON +default waf-policy Valid AddedOrUpdated +``` + +If a Policy remains in `Warning` with `BundlePending`, the referenced +`APPolicy` or `APLogConf` is not yet `ready` in PLM. Confirm PLM has +compiled the resource and retry. + +### 3. Verify traffic + +Send a normal request to the VirtualServer and confirm the response is +served: + +```shell +curl --resolve webapp.example.com:$IC_HTTP_PORT:$IC_IP \ + http://webapp.example.com:$IC_HTTP_PORT/ +``` + +Send a request that triggers the configured WAF violation and confirm the +response is `Request Rejected`. + +### 4. Confirm bundles come from PLM storage + +Check the bundle files in the NIC Pod: + +```shell +NIC_POD=$(kubectl get pods --namespace nginx-ingress \ + --selector app.kubernetes.io/name=nginx-ingress \ + --output jsonpath='{.items[0].metadata.name}') + +kubectl exec --namespace nginx-ingress $NIC_POD --container nginx-ingress -- \ + ls -l /etc/app_protect/bundles/ +``` + +Files named `fetched___policy.tgz` and +`fetched___log_.tgz` confirm the bundles were +fetched from PLM storage. + +## Section 2: Upgrade an Ingress-based deployment + +This section applies when your existing NIC deployment routes traffic +through Kubernetes Ingress resources. It follows the same overall procedure +as Section 1, plus one Ingress-specific step. + +### 1. Audit Ingress annotations + +Under PLM, NIC does not support the App Protect Ingress annotations: + +- `appprotect.f5.com/app-protect-policy` +- `appprotect.f5.com/app-protect-security-log` +- `appprotect.f5.com/app-protect-security-log-enable` + +An Ingress that uses these annotations is accepted but produces a warning +after the PLM upgrade, and WAF is not applied to that route. Migrate every +such Ingress to a `k8s.nginx.org/v1` Policy resource before you enable PLM +storage. + +### 2. Upgrade NIC to enable PLM + +Run the same `helm upgrade` command shown in +[Section 1, step 1](#1-upgrade-nic-to-enable-plm). + +### 3. Verify Ingress traffic + +For each Ingress, send a normal request and confirm the response is served. +Then send a request that matches a WAF violation and confirm the response +is `Request Rejected`. + +### 4. Confirm bundles come from PLM storage + +Use the same procedure as [Section 1, step 4](#4-confirm-bundles-come-from-plm-storage). + +## Troubleshooting + +- **Policy stays in `BundlePending` after the upgrade.** The referenced + `APPolicy` or `APLogConf` is not `ready` in PLM. Inspect + `kubectl describe appolicy ` and the PLM policy-controller logs. +- **NIC reports the referenced namespace is not watched.** If the NIC + deployment sets `controller.watchNamespace`, include the namespace of + every `APPolicy` and `APLogConf` resource. Also include the PLM namespace + in `controller.watchSecretNamespace` so NIC can observe storage Secret + rotation. +- **Helm upgrade fails with a CRD conflict.** Confirm PLM is installed and + its v1 CRDs are present. Then run the upgrade with `--skip-crds`. From 0a0bd5ae04870428101779358db6234201737e15 Mon Sep 17 00:00:00 2001 From: Venktesh Patel Date: Wed, 12 Aug 2026 18:00:16 +0100 Subject: [PATCH 2/4] fix: Apply suggestion --- content/nic/install/plm-installation.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/nic/install/plm-installation.md b/content/nic/install/plm-installation.md index d0b481055..5971657c8 100644 --- a/content/nic/install/plm-installation.md +++ b/content/nic/install/plm-installation.md @@ -11,7 +11,7 @@ f5-description: > f5-audience: operator --- -This tutorial installs F5 NGINX Ingress Controller (NIC) with F5 WAF for NGINX +This guide installs F5 NGINX Ingress Controller (NIC) with F5 WAF for NGINX using Policy Lifecycle Management (PLM). PLM defines WAF policies as Kubernetes custom resources, compiles them automatically, and stores the compiled bundles in an in-cluster S3-compatible object store. NIC fetches the From 570eb71d669d1b73159137f3ecb94e17e8bdec02 Mon Sep 17 00:00:00 2001 From: Travis Martin <33876974+travisamartin@users.noreply.github.com> Date: Thu, 13 Aug 2026 06:32:09 -0700 Subject: [PATCH 3/4] =?UTF-8?q?Edits=20the=20by=20Tech=20Writer=20Agent=20?= =?UTF-8?q?following=20the=20F5=20Technical=20Writing=20Sty=E2=80=A6=20(#2?= =?UTF-8?q?204)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Edits the by Tech Writer Agent following the F5 Technical Writing Style Guide --- content/nic/install/plm-installation.md | 134 +++++++------------- content/nic/install/plm-upgrade.md | 158 +++++++----------------- 2 files changed, 94 insertions(+), 198 deletions(-) diff --git a/content/nic/install/plm-installation.md b/content/nic/install/plm-installation.md index 5971657c8..46b5185f8 100644 --- a/content/nic/install/plm-installation.md +++ b/content/nic/install/plm-installation.md @@ -11,45 +11,39 @@ f5-description: > f5-audience: operator --- -This guide installs F5 NGINX Ingress Controller (NIC) with F5 WAF for NGINX -using Policy Lifecycle Management (PLM). PLM defines WAF policies as -Kubernetes custom resources, compiles them automatically, and stores the -compiled bundles in an in-cluster S3-compatible object store. NIC fetches the -bundles from PLM storage and enforces the policies at request time. +Use this guide to install F5 NGINX Ingress Controller with F5 WAF for NGINX using Policy Lifecycle Management (PLM). PLM defines WAF policies as Kubernetes custom resources, compiles them automatically, and stores the compiled bundles in an in-cluster S3-compatible object store. NGINX Ingress Controller then fetches the bundles from PLM storage and enforces the policies at request time. -By the end of this tutorial you will have: +By the end of this tutorial, you'll have: -- A running PLM backend and a NIC deployment configured for PLM storage. +- A running PLM backend and NGINX Ingress Controller deployment configured for PLM storage. - An `APPolicy` and an `APLogConf` resource compiled by PLM. - A `k8s.nginx.org/v1` Policy that references the compiled resources. -- The Policy attached to either a VirtualServer or an Ingress, with traffic - flowing normally and attack payloads blocked. +- The Policy attached to a VirtualServer or an Ingress, with traffic flowing normally and attack payloads blocked. ## Before you begin -- You have `kubectl` access to a Kubernetes cluster. -- You have Helm. -- You have credentials for `private-registry.nginx.com`. +Before you start, make sure you have: -This tutorial uses the following example values. If you use different values, -substitute them consistently throughout. +- `kubectl` access to a Kubernetes cluster. +- Helm installed. +- Credentials for `private-registry.nginx.com`. + +This tutorial uses the following example values. If you use different values, replace them consistently throughout. | Example value | What it represents | |---|---| | `plm-system` | Namespace for the PLM backend | | `plm` | Helm release name for PLM | -| `nginx-ingress` | Namespace for NIC | -| `nic` | Helm release name for NIC | +| `nginx-ingress` | Namespace for NGINX Ingress Controller | +| `nic` | Helm release name for NGINX Ingress Controller | | `security` | Namespace for `APPolicy` and `APLogConf` resources | -| `default` | Namespace for the NIC Policy, sample application, and routing resource | +| `default` | Namespace for the Policy, sample application, and routing resource | | `webapp.example.com` | Example hostname for VirtualServer routing | | `cafe.example.com` | Example hostname for Ingress routing | ## Deploy PLM infrastructure -Install the PLM backend before you install NIC. It provisions the App -Protect v1 CRDs, the Policy Controller, the compiler service, and the -SeaweedFS storage backend in the `plm-system` namespace. +Install the PLM backend before installing NGINX Ingress Controller. The PLM backend provisions the App Protect v1 CRDs, the Policy Controller, the compiler service, and the SeaweedFS storage backend in the `plm-system` namespace. {{< include "waf/plm-deploy-infrastructure.md" >}} @@ -70,16 +64,12 @@ apusersigs.appprotect.f5.com ## Look up the PLM storage endpoint and credentials -NIC connects to PLM's SeaweedFS filer over S3 to fetch compiled bundles. -Before you install NIC, collect four values that the PLM install produced: +NGINX Ingress Controller connects to PLM's SeaweedFS filer over S3 to fetch compiled bundles. Before you install the controller, collect these four values from the PLM installation: 1. **PLM storage URL**: the SeaweedFS filer endpoint (HTTPS or HTTP). -2. **Credentials Secret**: the S3 credentials Secret. The access key ID is - `admin` by default; the secret access key is stored in the - `seaweedfs_admin_secret` field. +2. **Credentials Secret**: the S3 credentials Secret. The access key ID is `admin` by default. The secret access key is in the `seaweedfs_admin_secret` field. 3. **CA Secret** (HTTPS only): verifies the SeaweedFS filer certificate. -4. **Client TLS Secret** (mutual TLS only): presented by NIC when it - connects to the filer. +4. **Client TLS Secret** (mutual TLS only): presented by NGINX Ingress Controller when it connects to the filer. List the Services PLM created and identify the filer: @@ -94,8 +84,7 @@ NAME TYPE CLUSTER-IP PORT(S) plm-f5-waf-seaweed-filer ClusterIP 10.0.0.10 8333/TCP,9333/TCP,... ``` -Assemble the URL from the service name, namespace, and port. Use `9333` -for HTTPS and `8333` for HTTP: +Assemble the URL from the service name, namespace, and port. Use `9333` for HTTPS and `8333` for HTTP: - HTTPS (mTLS): `https://plm-f5-waf-seaweed-filer.plm-system.svc.cluster.local:9333` - HTTP: `http://plm-f5-waf-seaweed-filer.plm-system.svc.cluster.local:8333` @@ -106,30 +95,23 @@ List the Secrets PLM created: kubectl get secret --namespace plm-system ``` -The default PLM install creates three Secrets that NIC references: +The default PLM install creates three Secrets that NGINX Ingress Controller references: - `plm-f5-waf-seaweedfs-auth`: SeaweedFS credentials. - `plm-f5-waf-seaweedfs-ca-cert`: CA certificate for the HTTPS filer. - `plm-f5-waf-seaweedfs-client-cert`: client TLS certificate for mTLS. -Record the Secret references in `/` form. You'll pass all -four values to NIC as `--set controller.appprotect.plmStorage.*` flags - -## Install NIC with PLM storage +Record the Secret references in `/` form. You'll pass all four values to NGINX Ingress Controller using `--set controller.appprotect.plmStorage.*` flags. -Because PLM owns the `appprotect.f5.com/v1` CRDs, install NIC with -`--skip-crds`. You apply NIC's own CRDs from the bundled manifest first. +## Install NGINX Ingress Controller with PLM storage -Apply the NIC CRDs. The `deploy/crds.yaml` bundle contains every CRD NIC -needs (`VirtualServer`, `VirtualServerRoute`, `Policy`, `TransportServer`, -`GlobalConfiguration`, `DNSEndpoint`) and deliberately excludes the App -Protect CRDs, which PLM owns. +Because PLM owns the `appprotect.f5.com/v1` CRDs, you must apply the controller's own CRDs before running `helm install`. The `deploy/crds.yaml` bundle contains every CRD the controller needs (`VirtualServer`, `VirtualServerRoute`, `Policy`, `TransportServer`, `GlobalConfiguration`, `DNSEndpoint`). The bundle deliberately excludes the App Protect CRDs, which PLM owns. ```shell kubectl apply -f https://raw.githubusercontent.com/nginx/kubernetes-ingress/v{{< nic-version >}}/deploy/crds.yaml ``` -Create a namespace and image pull Secret for NIC: +Create a namespace and image pull Secret for NGINX Ingress Controller: ```shell kubectl create namespace nginx-ingress @@ -147,8 +129,7 @@ helm repo add nginx-stable https://helm.nginx.com/stable helm repo update nginx-stable ``` -Install NIC with PLM storage enabled. This example uses HTTPS PLM storage with mutual TLS. -For HTTP storage, set `controller.appprotect.plmStorage.url` and `controller.appprotect.plmStorage.credentialsSecret` only. +Install NGINX Ingress Controller with PLM storage turned on. This example uses HTTPS with mutual TLS. For HTTP storage, set only `controller.appprotect.plmStorage.url` and `controller.appprotect.plmStorage.credentialsSecret`. ```shell helm install nic nginx-stable/nginx-ingress \ @@ -167,8 +148,7 @@ helm install nic nginx-stable/nginx-ingress \ --set controller.serviceAccount.imagePullSecretName=regcred ``` -Wait for the NIC Pod to become ready. Each Pod runs three containers: -`nginx-ingress`, `waf-enforcer`, and `waf-config-mgr`. +Wait for the controller pod to become ready. Each pod runs three containers: `nginx-ingress`, `waf-enforcer`, and `waf-config-mgr`. ```shell kubectl wait --for=condition=Ready pods \ @@ -186,8 +166,7 @@ NAME READY STATUS RESTARTS AGE nic-nginx-ingress-controller-xxxxx 3/3 Running 0 2m ``` -Save the public IP address and HTTP port of the NIC LoadBalancer service into -shell variables: +Save the public IP address and HTTP port of the NGINX Ingress Controller LoadBalancer service to shell variables: ```shell IC_IP= @@ -196,16 +175,13 @@ IC_HTTP_PORT= ## Define the WAF policy -Create the `security` namespace to hold the `APPolicy` and `APLogConf` -resources: +Create the `security` namespace to hold the `APPolicy` and `APLogConf` resources: ```shell kubectl create namespace security ``` -Save the following as `waf-resources.yaml`. It defines an `APPolicy` that -blocks attack signatures and masks credit card numbers and social security -numbers in responses, plus an `APLogConf` for security logging. +Save the following as `waf-resources.yaml`. The file defines an `APPolicy` that blocks attack signatures and masks credit card numbers and social security numbers in responses. It also defines an `APLogConf` for security logging. ```yaml apiVersion: appprotect.f5.com/v1 @@ -268,11 +244,9 @@ kubectl get appolicy dataguard-blocking --namespace security \ --output jsonpath='State: {.status.bundle.state}{"\n"}Location: {.status.bundle.location}{"\n"}' ``` -## Create the NIC Policy +## Create the Policy -Save the following as `waf-policy.yaml`. The `apPolicy` and `apLogConf` -fields accept `[/]`. When namespace is omitted, NIC -defaults to the Policy's own namespace. +Save the following as `waf-policy.yaml`. The `apPolicy` and `apLogConf` fields accept `[/]`. When you omit the namespace, NGINX Ingress Controller defaults to the Policy's own namespace. ```yaml apiVersion: k8s.nginx.org/v1 @@ -303,17 +277,17 @@ kubectl wait --for=jsonpath='{.status.state}'=Valid \ policy/waf-policy --namespace default --timeout=180s ``` -If the Policy stays in `Warning` with a `BundleFetchFailed` reason, see -[Troubleshooting](#troubleshooting). +If the Policy stays in `Warning` with a `BundleFetchFailed` reason, see [Troubleshooting](#troubleshooting). + +Choose how to attach the Policy: +- [Attach to a VirtualServer](#attach-to-a-virtualserver) +- [Attach to an Ingress](#attach-to-an-ingress) -Continue with Section 1 to attach the Policy to a VirtualServer, or Section 2 -to attach it to an Ingress. The Policy resource is the same for both. +The Policy resource is the same for both. -## Section 1: Attach to a VirtualServer +## Attach to a VirtualServer -Save the following as `webapp.yaml`. It contains the sample application -Deployment, its Service, and a VirtualServer that references the -`waf-policy` Policy. +Save the following as `webapp.yaml`. The file defines the sample application Deployment, its Service, and a VirtualServer that references the `waf-policy` Policy. ```yaml apiVersion: apps/v1 @@ -405,12 +379,9 @@ The requested URL was rejected. Please consult with your administrator. ``` -## Section 2: Attach to an Ingress +## Attach to an Ingress -Save the following as `cafe.yaml`. It contains the sample cafe application -(`coffee` and `tea` Deployments and Services) and an Ingress. The -`nginx.com/policies` annotation attaches the `waf-policy` Policy to every -route on the Ingress. +Save the following as `cafe.yaml`. The file defines the sample cafe application (`coffee` and `tea` Deployments and Services) and an Ingress. The `nginx.com/policies` annotation attaches the `waf-policy` Policy to every route on the Ingress. ```yaml apiVersion: apps/v1 @@ -530,17 +501,13 @@ curl --resolve cafe.example.com:$IC_HTTP_PORT:$IC_IP \ "http://cafe.example.com:$IC_HTTP_PORT/coffee/" ``` -The response is `Request Rejected` for the attack payload. +The response body is `Request Rejected`. -Under PLM, Ingress-only App Protect annotations -(`appprotect.f5.com/app-protect-policy`, -`appprotect.f5.com/app-protect-security-log`) are not supported. Use the -`k8s.nginx.org/v1` Policy resource and the `nginx.com/policies` annotation, -as shown above. +Under PLM, the Ingress-only App Protect annotations (`appprotect.f5.com/app-protect-policy` and `appprotect.f5.com/app-protect-security-log`) aren't supported. Use the `k8s.nginx.org/v1` Policy resource and the `nginx.com/policies` annotation instead, as shown above. ## Verify bundles on disk -Confirm the compiled bundles were fetched into the NIC Pod: +Confirm the compiled bundles are present in the ingress controller pod: ```shell NIC_POD=$(kubectl get pods --namespace nginx-ingress \ @@ -565,18 +532,9 @@ Check the Policy status: kubectl describe policy waf-policy ``` -The status reports `State: Valid` and `Reason: AddedOrUpdated` when the -bundles are fetched successfully. +A `State: Valid` and `Reason: AddedOrUpdated` status confirms the bundles were fetched successfully. ## Troubleshooting -- **Policy status is `Warning` with reason `BundleFetchFailed`.** Check the - `APPolicy` or `APLogConf` referenced by the Policy. Run - `kubectl describe appolicy --namespace security` and confirm - `status.bundle.state` is `ready`. If PLM has not compiled the resource, - the Policy fetch cannot proceed. -- **NIC reports the referenced namespace is not watched.** If - `controller.watchNamespace` is set, include the namespace holding the - `APPolicy` and `APLogConf` resources. If `controller.watchSecretNamespace` - is set, include the PLM namespace so NIC can observe storage Secret - rotation. +- **Policy status is `Warning` with reason `BundleFetchFailed`.** Run `kubectl describe appolicy --namespace security` and confirm `status.bundle.state` is `ready`. If PLM hasn't compiled the resource yet, the Policy fetch can't proceed. +- **NGINX Ingress Controller reports the referenced namespace isn't watched.** If `controller.watchNamespace` is set, include the namespace that holds the `APPolicy` and `APLogConf` resources. If `controller.watchSecretNamespace` is set, include the PLM namespace so the controller can observe storage Secret rotation. diff --git a/content/nic/install/plm-upgrade.md b/content/nic/install/plm-upgrade.md index a375c39f6..f186ba1db 100644 --- a/content/nic/install/plm-upgrade.md +++ b/content/nic/install/plm-upgrade.md @@ -5,69 +5,49 @@ toc: true f5-content-type: how-to f5-product: F5 NGINX Ingress Controller f5-description: > - Upgrade an existing F5 NGINX Ingress Controller (NIC) deployment from - in-pod App Protect policy compilation (v1beta1 CRDs) to F5 WAF for NGINX - with Policy Lifecycle Management (PLM). + Upgrade an existing F5 NGINX Ingress Controller deployment from in-pod App Protect policy compilation (v1beta1 CRDs) to F5 WAF for NGINX with Policy Lifecycle Management (PLM). f5-audience: operator --- -This guide upgrades an existing NIC + F5 WAF for NGINX deployment from in-pod -App Protect policy compilation to Policy Lifecycle Management (PLM). PLM -compiles `APPolicy` and `APLogConf` resources in a dedicated controller and -stores the resulting bundles in an in-cluster S3-compatible object store. NIC -fetches the compiled bundles instead of compiling them in the data plane. +Use this guide to upgrade an existing NGINX Ingress Controller + F5 WAF for NGINX deployment from in-pod App Protect policy compilation to Policy Lifecycle Management (PLM). PLM compiles `APPolicy` and `APLogConf` resources in a dedicated controller and stores the resulting bundles in an in-cluster S3-compatible object store. NGINX Ingress Controller then fetches the compiled bundles instead of compiling them in the data plane. -Under PLM, the fields you already write on the NIC `Policy` resource -(`waf.apPolicy` and `waf.securityLogs[].apLogConf`) continue to work -unchanged. The `k8s.nginx.org/v1` Policy manifest for a VirtualServer or -Ingress does not need to be rewritten during the upgrade. +Under PLM, the fields you already write on the NGINX Ingress Controller `Policy` resource (`waf.apPolicy` and `waf.securityLogs[].apLogConf`) continue to work unchanged. You don't need to rewrite the `k8s.nginx.org/v1` Policy manifest for a VirtualServer or Ingress during the upgrade. -By the end of this guide you will have: +By the end of this guide, you'll have: -- The PLM backend installed alongside your existing NIC deployment. -- NIC upgraded to a PLM-capable release with PLM storage configured. -- Existing `APPolicy` and `APLogConf` resources adopted by PLM and compiled - into bundles. -- WAF-protected traffic served by NIC, with bundles fetched from PLM storage - instead of compiled in the data plane. +- The PLM backend installed alongside your existing NGINX Ingress Controller deployment. +- NGINX Ingress Controller upgraded to a PLM-capable release with PLM storage configured. +- Existing `APPolicy` and `APLogConf` resources adopted by PLM and compiled into bundles. +- WAF-protected traffic served by NGINX Ingress Controller, with bundles fetched from PLM storage instead of compiled in the data plane. ## Before you begin -- Your cluster runs an existing NIC + F5 WAF for NGINX deployment. -- Existing `APPolicy` and `APLogConf` resources are served by the - `appprotect.f5.com/v1beta1` CRDs shipped with your current NIC installation. -- You have `kubectl` and Helm access to the cluster. -- You have credentials for `private-registry.nginx.com`. +Before you start, make sure you have: + +- An existing NGINX Ingress Controller + F5 WAF for NGINX deployment running in your cluster. +- Existing `APPolicy` and `APLogConf` resources served by the `appprotect.f5.com/v1beta1` CRDs shipped with your current NGINX Ingress Controller installation. +- `kubectl` and Helm access to the cluster. +- Credentials for `private-registry.nginx.com`. Record your current values before you begin: | Value | Where to find it | |---|---| -| NIC release name | `helm list --namespace ` | -| NIC chart version | `helm list --namespace ` | -| Existing NIC values | `helm get values --namespace ` | +| NGINX Ingress Controller release name | `helm list --namespace ` | +| NGINX Ingress Controller chart version | `helm list --namespace ` | +| Existing NGINX Ingress Controller values | `helm get values --namespace ` | | Existing `APPolicy` resources | `kubectl get appolicy --all-namespaces` | | Existing `APLogConf` resources | `kubectl get aplogconf --all-namespaces` | -This guide uses `nic` as the NIC release name, `nginx-ingress` as the NIC -namespace, `plm-system` as the PLM namespace, and `plm` as the PLM release -name. Substitute your values consistently. +This guide uses `nic` as the NGINX Ingress Controller release name, `nginx-ingress` as the NGINX Ingress Controller namespace, `plm-system` as the PLM namespace, and `plm` as the PLM release name. Replace these with your own values consistently throughout. ## Deploy PLM infrastructure -Install the PLM backend into the `plm-system` namespace. The install adds -the `appprotect.f5.com/v1` versions to the existing `appprotect.f5.com` -CRDs. The v1 versions are a superset of v1beta1, so adding them does not -affect NIC while it still watches v1beta1. +Install the PLM backend into the `plm-system` namespace. The install adds the `appprotect.f5.com/v1` versions to the existing `appprotect.f5.com` CRDs. Because the v1 versions are a superset of v1beta1, adding them doesn't affect NGINX Ingress Controller while it still watches v1beta1. {{< include "waf/plm-deploy-infrastructure.md" >}} -After the install completes, PLM adopts every existing `APPolicy` and -`APLogConf` resource in the cluster by adding the -`appprotect.f5.com/finalizer` finalizer and compiling each resource against -its current signature package. Your running NIC continues to compile the -same resources in-pod. Both mechanisms operate independently until you -upgrade NIC. +After the install completes, PLM adopts every existing `APPolicy` and `APLogConf` resource in the cluster. It does this by adding the `appprotect.f5.com/finalizer` finalizer and compiling each resource against its current signature package. Your running NGINX Ingress Controller continues to compile the same resources in-pod. The two mechanisms operate independently until you upgrade NGINX Ingress Controller. Confirm PLM has compiled the existing resources: @@ -79,23 +59,16 @@ kubectl get aplogconf --all-namespaces \ --output custom-columns='NAMESPACE:.metadata.namespace,NAME:.metadata.name,STATE:.status.bundle.state' ``` -Every resource should report `STATE: ready`. If any resource is not `ready`, -inspect the PLM policy-controller logs and resolve compilation errors before -you continue. +Every resource should report `STATE: ready`. If any resource isn't `ready`, inspect the PLM policy-controller logs, resolve the compilation errors, and then continue. ## Look up the PLM storage endpoint and credentials -NIC connects to PLM's SeaweedFS filer over S3 to fetch compiled bundles. -Before you run `helm upgrade`, collect four values that the PLM install -produced: +NGINX Ingress Controller connects to PLM's SeaweedFS filer over S3 to fetch compiled bundles. Before you run `helm upgrade`, collect these four values from the PLM installation: 1. **PLM storage URL**: the SeaweedFS filer endpoint (HTTPS or HTTP). -2. **Credentials Secret**: the S3 credentials Secret. The access key ID is - `admin` by default; the secret access key is stored in the - `seaweedfs_admin_secret` field. +2. **Credentials Secret**: the S3 credentials Secret. The access key ID is `admin` by default. The secret access key is in the `seaweedfs_admin_secret` field. 3. **CA Secret** (HTTPS only): verifies the SeaweedFS filer certificate. -4. **Client TLS Secret** (mutual TLS only): presented by NIC when it - connects to the filer. +4. **Client TLS Secret** (mutual TLS only): presented by NGINX Ingress Controller when it connects to the filer. List the Services PLM created and identify the filer: @@ -110,8 +83,7 @@ NAME TYPE CLUSTER-IP PORT(S) plm-f5-waf-seaweed-filer ClusterIP 10.0.0.10 8333/TCP,9333/TCP,... ``` -Assemble the URL from the service name, namespace, and port. Use `9333` -for HTTPS and `8333` for HTTP: +Assemble the URL from the service name, namespace, and port. Use `9333` for HTTPS and `8333` for HTTP: - HTTPS (mTLS): `https://plm-f5-waf-seaweed-filer.plm-system.svc.cluster.local:9333` - HTTP: `http://plm-f5-waf-seaweed-filer.plm-system.svc.cluster.local:8333` @@ -122,23 +94,17 @@ List the Secrets PLM created: kubectl get secret --namespace plm-system ``` -The default PLM install creates three Secrets that NIC references: +The default PLM install creates three Secrets that NGINX Ingress Controller references: - `plm-f5-waf-seaweedfs-auth`: SeaweedFS credentials. - `plm-f5-waf-seaweedfs-ca-cert`: CA certificate for the HTTPS filer. - `plm-f5-waf-seaweedfs-client-cert`: client TLS certificate for mTLS. -Record the Secret references in `/` form. You'll pass all -four values to NIC as `--set controller.appprotect.plmStorage.*` flags when -you run `helm upgrade`. +Record the Secret references in `/` form. When you run `helm upgrade`, pass all four values to NGINX Ingress Controller using `--set controller.appprotect.plmStorage.*` flags. -## Apply NIC CRDs +## Apply the NGINX Ingress Controller CRDs -Before you run `helm upgrade`, apply the NIC CRDs from the bundled manifest -for your target release. The `deploy/crds.yaml` bundle contains every CRD -NIC needs (`VirtualServer`, `VirtualServerRoute`, `Policy`, -`TransportServer`, `GlobalConfiguration`, `DNSEndpoint`) and deliberately -excludes the App Protect CRDs, which PLM owns. +Before you run `helm upgrade`, apply the NGINX Ingress Controller CRDs from the bundled manifest for your target release. The `deploy/crds.yaml` bundle contains every CRD the controller needs (`VirtualServer`, `VirtualServerRoute`, `Policy`, `TransportServer`, `GlobalConfiguration`, `DNSEndpoint`). The bundle deliberately excludes the App Protect CRDs, which PLM owns. ```shell kubectl apply -f https://raw.githubusercontent.com/nginx/kubernetes-ingress/v{{< nic-version >}}/deploy/crds.yaml @@ -146,20 +112,13 @@ kubectl apply -f https://raw.githubusercontent.com/nginx/kubernetes-ingress/v{{< ## Section 1: Upgrade a VirtualServer-based deployment -This section applies when your existing NIC deployment routes traffic -through `k8s.nginx.org/v1` VirtualServer resources that reference a Policy -resource with `waf.apPolicy` and `waf.securityLogs[].apLogConf` fields. +Follow this section if your existing NGINX Ingress Controller deployment routes traffic through `k8s.nginx.org/v1` VirtualServer resources that reference a Policy resource with `waf.apPolicy` and `waf.securityLogs[].apLogConf` fields. -### 1. Upgrade NIC to enable PLM +### 1. Upgrade NGINX Ingress Controller with PLM storage -Upgrade the NIC release. `--reuse-values` preserves your existing NIC -configuration; the `--set` flags overlay PLM storage on top. `--skip-crds` -prevents Helm from touching CRDs, since you applied the NIC CRDs in the -previous step and PLM owns the App Protect CRDs. +Upgrade the NGINX Ingress Controller release. `--reuse-values` preserves your existing configuration. The `--set` flags overlay PLM storage on top. `--skip-crds` prevents Helm from touching CRDs, because you applied the NGINX Ingress Controller CRDs in the previous step and PLM owns the App Protect CRDs. -This example uses HTTPS PLM storage with mutual TLS. For HTTP storage, set -`controller.appprotect.plmStorage.url` to `http://:8333` and omit the -`caSecret` and `clientSSLSecret` flags. +This example uses HTTPS PLM storage with mutual TLS. For HTTP storage, set `controller.appprotect.plmStorage.url` to `http://:8333` and omit the `caSecret` and `clientSSLSecret` flags. ```shell helm upgrade nic nginx-stable/nginx-ingress \ @@ -183,7 +142,7 @@ kubectl rollout status deployment/nic-nginx-ingress-controller \ --timeout=180s ``` -Confirm NIC is watching the v1 CRDs by inspecting the controller log: +Confirm NGINX Ingress Controller is watching the v1 CRDs by inspecting the controller log: ```shell kubectl logs deployment/nic-nginx-ingress-controller \ @@ -213,26 +172,22 @@ NAMESPACE NAME STATE REASON default waf-policy Valid AddedOrUpdated ``` -If a Policy remains in `Warning` with `BundlePending`, the referenced -`APPolicy` or `APLogConf` is not yet `ready` in PLM. Confirm PLM has -compiled the resource and retry. +If a Policy remains in `Warning` with `BundlePending`, the referenced `APPolicy` or `APLogConf` isn't yet `ready` in PLM. Confirm that PLM has compiled the resource, then retry. ### 3. Verify traffic -Send a normal request to the VirtualServer and confirm the response is -served: +Send a normal request to the VirtualServer and confirm the application responds: ```shell curl --resolve webapp.example.com:$IC_HTTP_PORT:$IC_IP \ http://webapp.example.com:$IC_HTTP_PORT/ ``` -Send a request that triggers the configured WAF violation and confirm the -response is `Request Rejected`. +Then send a request that triggers the configured WAF violation and confirm the response is `Request Rejected`. ### 4. Confirm bundles come from PLM storage -Check the bundle files in the NIC Pod: +Check the bundle files in the ingress controller pod: ```shell NIC_POD=$(kubectl get pods --namespace nginx-ingress \ @@ -243,39 +198,29 @@ kubectl exec --namespace nginx-ingress $NIC_POD --container nginx-ingress -- \ ls -l /etc/app_protect/bundles/ ``` -Files named `fetched___policy.tgz` and -`fetched___log_.tgz` confirm the bundles were -fetched from PLM storage. +Files named `fetched___policy.tgz` and `fetched___log_.tgz` confirm that the bundles were fetched from PLM storage. ## Section 2: Upgrade an Ingress-based deployment -This section applies when your existing NIC deployment routes traffic -through Kubernetes Ingress resources. It follows the same overall procedure -as Section 1, plus one Ingress-specific step. +Follow this section if your existing NGINX Ingress Controller deployment routes traffic through Kubernetes Ingress resources. The procedure is the same as Section 1, with one additional Ingress-specific step. ### 1. Audit Ingress annotations -Under PLM, NIC does not support the App Protect Ingress annotations: +Under PLM, NGINX Ingress Controller doesn't support the App Protect Ingress annotations: - `appprotect.f5.com/app-protect-policy` - `appprotect.f5.com/app-protect-security-log` - `appprotect.f5.com/app-protect-security-log-enable` -An Ingress that uses these annotations is accepted but produces a warning -after the PLM upgrade, and WAF is not applied to that route. Migrate every -such Ingress to a `k8s.nginx.org/v1` Policy resource before you enable PLM -storage. +An Ingress that uses these annotations is accepted but produces a warning after the upgrade. WAF isn't applied to that route. Before you turn on PLM storage, migrate every such Ingress to a `k8s.nginx.org/v1` Policy resource. -### 2. Upgrade NIC to enable PLM +### 2. Upgrade NGINX Ingress Controller with PLM storage -Run the same `helm upgrade` command shown in -[Section 1, step 1](#1-upgrade-nic-to-enable-plm). +Run the same `helm upgrade` command shown in [Section 1, step 1](#1-upgrade-nginx-ingress-controller-with-plm-storage). ### 3. Verify Ingress traffic -For each Ingress, send a normal request and confirm the response is served. -Then send a request that matches a WAF violation and confirm the response -is `Request Rejected`. +For each Ingress, send a normal request and confirm the application responds. Then send a request that matches a WAF violation and confirm the response is `Request Rejected`. ### 4. Confirm bundles come from PLM storage @@ -283,13 +228,6 @@ Use the same procedure as [Section 1, step 4](#4-confirm-bundles-come-from-plm-s ## Troubleshooting -- **Policy stays in `BundlePending` after the upgrade.** The referenced - `APPolicy` or `APLogConf` is not `ready` in PLM. Inspect - `kubectl describe appolicy ` and the PLM policy-controller logs. -- **NIC reports the referenced namespace is not watched.** If the NIC - deployment sets `controller.watchNamespace`, include the namespace of - every `APPolicy` and `APLogConf` resource. Also include the PLM namespace - in `controller.watchSecretNamespace` so NIC can observe storage Secret - rotation. -- **Helm upgrade fails with a CRD conflict.** Confirm PLM is installed and - its v1 CRDs are present. Then run the upgrade with `--skip-crds`. +- **Policy stays in `BundlePending` after the upgrade.** The referenced `APPolicy` or `APLogConf` isn't `ready` in PLM. Run `kubectl describe appolicy ` and inspect the PLM policy-controller logs. +- **NGINX Ingress Controller reports the referenced namespace isn't watched.** If the deployment sets `controller.watchNamespace`, include the namespace of every `APPolicy` and `APLogConf` resource. Also include the PLM namespace in `controller.watchSecretNamespace` so NGINX Ingress Controller can observe storage Secret rotation. +- **Helm upgrade fails with a CRD conflict.** Confirm PLM is installed and its v1 CRDs are present, then run the upgrade with `--skip-crds`. From 0ce24faac098493e43e7031c30fed08d165af8d3 Mon Sep 17 00:00:00 2001 From: Venktesh Shivam Patel Date: Fri, 14 Aug 2026 16:34:41 +0100 Subject: [PATCH 4/4] fix: license token cmd and format --- content/nic/install/plm-installation.md | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/content/nic/install/plm-installation.md b/content/nic/install/plm-installation.md index 46b5185f8..59bd61d0f 100644 --- a/content/nic/install/plm-installation.md +++ b/content/nic/install/plm-installation.md @@ -101,12 +101,14 @@ The default PLM install creates three Secrets that NGINX Ingress Controller refe - `plm-f5-waf-seaweedfs-ca-cert`: CA certificate for the HTTPS filer. - `plm-f5-waf-seaweedfs-client-cert`: client TLS certificate for mTLS. -Record the Secret references in `/` form. You'll pass all four values to NGINX Ingress Controller using `--set controller.appprotect.plmStorage.*` flags. +Record the Secret references in `` form. You'll pass all four values to NGINX Ingress Controller using `--set controller.appprotect.plmStorage.*` flags. ## Install NGINX Ingress Controller with PLM storage Because PLM owns the `appprotect.f5.com/v1` CRDs, you must apply the controller's own CRDs before running `helm install`. The `deploy/crds.yaml` bundle contains every CRD the controller needs (`VirtualServer`, `VirtualServerRoute`, `Policy`, `TransportServer`, `GlobalConfiguration`, `DNSEndpoint`). The bundle deliberately excludes the App Protect CRDs, which PLM owns. +Download your NGINX Ingress Controller subscription’s JSON Web Token and rename it to `nginx-repo.jwt`. + ```shell kubectl apply -f https://raw.githubusercontent.com/nginx/kubernetes-ingress/v{{< nic-version >}}/deploy/crds.yaml ``` @@ -118,8 +120,13 @@ kubectl create namespace nginx-ingress kubectl create secret docker-registry regcred \ --namespace nginx-ingress \ --docker-server=private-registry.nginx.com \ - --docker-username= \ + --docker-username=$(cat nginx-repo.jwt) \ --docker-password=none + +kubectl create secret generic license-token \ + --namespace nginx-ingress \ + --from-file=license.jwt=nginx-repo.jwt \ + --type=nginx.com/license ``` Add the NGINX Helm repository: @@ -246,7 +253,7 @@ kubectl get appolicy dataguard-blocking --namespace security \ ## Create the Policy -Save the following as `waf-policy.yaml`. The `apPolicy` and `apLogConf` fields accept `[/]`. When you omit the namespace, NGINX Ingress Controller defaults to the Policy's own namespace. +Save the following as `waf-policy.yaml`. The `apPolicy` and `apLogConf` fields accept `[/]`. When you omit the namespace, NGINX Ingress Controller defaults to the Policy's own namespace. ```yaml apiVersion: k8s.nginx.org/v1