docs: Add PLM policy source tutorial and copy edits for NGF WAF integration - #2197
docs: Add PLM policy source tutorial and copy edits for NGF WAF integration#2197travisamartin wants to merge 29 commits into
Conversation
Satisfies TECHDOCS-5343 (Story 3) and TECHDOCS-5347 (Story 7) for the NGF + F5 WAF for NGINX with PLM tutorial. Story 3 — Deploy PLM infrastructure (shared include): - Add content/includes/waf/plm-deploy-infrastructure.md - Covers PLM architecture (Policy Controller → gRPC → compiler → SeaweedFS/S3), CRD installation, registry pull secret creation, Helm install with plm-values.yaml, and deployment verification - No NGF-specific content; consumable by the NIC tutorial unmodified - Flags open item: registry token credential (TECHDOCS-5342) Story 7 — Define the WAF policy (NGF content + shared include): - Add content/includes/waf/plm-define-policy-bundle-method.md (precompiled-bundle method; product-neutral shared include) - Add inline and Git-reference methods directly to get-started-plm.md as NGF-specific content - Documents correct recompile trigger (spec/annotation change required; controller does not poll Git or Artifactory) - Documents bundle.state values: pending | processing | ready | invalid - Documents isCompiled: false for precompiled bundles Also updates get-started-plm.md with TODO placeholders for all remaining sections (Stories 2, 4, 5, 6, 8, 9, 10, 11).
✅ Deploy Preview will be available once build job completes!
|
Remove the hard-coded NGINX Gateway Fabric product name from the deploy-infrastructure include. The separate-Helm-charts note now refers to the NGINX data plane generically so both the NGF and NIC tutorials can consume the include unmodified. Also adds the NIC target file to f5-files front matter in both includes.
Headings in include files cause TOC and hierarchy issues depending on the level of the section they are embedded in. Replace all ### and #### headings with bold lead-in sentences.
Match heading levels to the consuming section in each case: - plm-deploy-infrastructure.md: ### (consumed under ##) - plm-define-policy-bundle-method.md: #### (consumed under ###) Ensures TOC entries and visual hierarchy are consistent with the parallel method subsections in get-started-plm.md.
| Install the four custom resource definitions (CRDs) that the Policy Controller manages: | ||
|
|
||
| ```shell | ||
| kubectl apply -f https://raw.githubusercontent.com/nginx/waf-policy-controller/main/manifests/1-deploy-crds.yaml |
There was a problem hiding this comment.
iirc PLM controller creates the CRDs
There was a problem hiding this comment.
This would only be beneficial for an upgrade since helm doesn't upgrade CRDs.
There was a problem hiding this comment.
maybe in that case point to upcoming release tag instead of main
cc @ohad-perets
There was a problem hiding this comment.
If we are going to use this command anywhere, it should probably use the latest released version instead of main.
So something like this:
kubectl apply -f https://raw.githubusercontent.com/nginx/waf-policy-controller/{{< version-waf >}}/manifests/1-deploy-crds.yaml
There was a problem hiding this comment.
Agreed on pinning to a release tag rather than main — main will drift out from under the published docs.
- The chart does ship the CRDs. They're in
crds/(the fourappprotect.f5.comCRDs) and in theseaweedfs-operatorsubchart'scrds/(seaweeds.seaweed.seaweedfs.com). - Helm applies
crds/on first install only and never on upgrade, so @sjberman is correct that the manual apply is only useful for upgrades. On a clean install this step is redundant.
*We can frame the step as an upgrade concern, or mark it optional on first install, so a new user isn't led to think the install fails without it.
|
We probably need some consistency brought into this doc. In NGF, we avoid placeholder values as much as possible (except for sensitive info) so that a user can just copy/paste commands. The PLM docs don't follow this right now. |
Address PR feedback (sjberman): avoid placeholder values where possible
so commands are copy-pasteable.
- Namespace: plm-system (matches plm-storage-service.plm-system URL
already used in the NGF guide)
- Release name: plm
- Version: {{< version-waf-policy-controller >}} shortcode (already
exists at 5.14.0; auto-updates with future releases)
Sensitive values retained as placeholders: <JWT>,
<BASE64_NGINX_REPO_CRT>, <BASE64_NGINX_REPO_KEY>.
Apply F5 style guide rules across four files: - get-started-plm.md - overview.md - policy-sources.md - cli-help.md (lines 47-51, PLM storage flags) Changes applied: - em-dash: replace all em dashes with commas, periods, colons, or parentheses; use -- only where unavoidable - latin-abbreviations: via → through/using; since (causal) → because - ensure-vs-make-sure: ensure → make sure - contractions: does not/cannot → doesn't/can't per Modern Voice - possessives: PLM's in-cluster storage → in-cluster PLM storage - word-list: ensures that → means; enables → results in - placeholders: <namespace>/ → <NAMESPACE>/ per formatting rules - sensitive-information: non-RFC-5737 IP replaced with 192.0.2.1 - f5-product-names: PLM storage flag descriptions spell out Policy Lifecycle Manager on first mention - Removed redundant heading: NGINX Instance Manager (NGINX Instance Manager) → NGINX Instance Manager - Updated f5-description in policy-sources.md to include PLM
- possessives: fix PLM's, NGINX Agent's (product name possessives recasted using 'of' or restructured) - em-dash: fix reverted em dash in get-started-plm.md - sentence-length: split long Bundles sentence in overview.md - grammar: 'begins at fetching' → 'begins with fetching' - active-voice: recast passive 'to be set up' → 'to be configured' in overview.md call-out - possessives: Gateway's parametersRef → parametersRef field on a Gateway (inanimate object restructured)
Replace 10.96.20.187 with 192.0.2.1 (RFC 5737 documentation range) in all six NGF files where it appeared as a Gateway IP example. Files updated: - content/ngf/traffic-security/basic-authentication.md - content/ngf/traffic-security/cors.md - content/ngf/traffic-security/jwt-authentication.md - content/ngf/traffic-security/oidc-authentication.md - content/ngf/waf-integration/get-started-http.md - content/ngf/waf-integration/get-started-plm.md
| seaweedfs: | ||
| image: | ||
| tag: "{{< version-waf-policy-controller >}}" | ||
| seaweedfs-operator: |
There was a problem hiding this comment.
We need to at the very least provide a link to the full values.yaml so the user knows all the possible configuration options available.
There was a problem hiding this comment.
@ohad-perets Can you share the link? or is there a reference page we can link to?
As a workaround, I added:
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 >}}
```
There was a problem hiding this comment.
This is what we have: helm show values nginx-stable/f5-waf-policy-controller --version 5.14.0
It needs no authentication (helm.nginx.com/stable is public), returns the full values file, and the file is well commented — including the certificates block and filerHttpsMtls. It also has the advantage of always matching whatever version the reader is installing, which a static link wouldn't.
| --dry-run=client --output yaml | kubectl apply -f - | ||
| ``` | ||
|
|
||
| ### Install the Policy Controller |
There was a problem hiding this comment.
We absolutely require a prerequisite section for PLM detailing the storage requirements and configurations and detailing the default behaviour
There was a problem hiding this comment.
@ohad-perets -- Can you draft a prerequisite section for PLM covering storage requirements, configuration, and default behavior?
There was a problem hiding this comment.
Prerequisites**
Before installing PLM you need:
- A Kubernetes cluster with a default StorageClass that supports dynamic provisioning. PLM's object store is backed by PersistentVolumeClaims; without a default StorageClass the SeaweedFS pods stay
Pending. - An F5 WAF for NGINX JWT from MyF5, used to pull images from
private-registry.nginx.com. - Optionally,
nginx-repo.crtandnginx-repo.keyfrom MyF5, used only for authenticated signature updates frompkgs.nginx.com.
Storage and default behaviour
PLM ships with an embedded SeaweedFS S3-compatible object store, deployed by the bundled SeaweedFS operator. You do not need to provide an external S3 bucket. Compiled policy bundles are written here, and the data plane reads them from it.
By default the deployment creates one master, one filer, and three volume pods, each volume pod backed by its own PVC. The store is provisioned with credentials generated by the chart and held in the <release>-f5-waf-seaweedfs-auth Secret.
Communication between PLM and the object store is unencrypted HTTP by default. To enable TLS, see [mTLS section].
| plm-f5-waf-seaweed-volume-2 1/1 Running 0 | ||
| ``` | ||
|
|
||
| Confirm the four CRDs are present: |
There was a problem hiding this comment.
We should also have a troubleshooting section.
There was a problem hiding this comment.
@ohad-perets , since you're the SME here, can you draft the troubleshooting steps for this section? Doesn't need to cover everything, just the 80/20: the issues most likely to come up during deploy.
There was a problem hiding this comment.
These are the five failures I actually hit while validating on 5.14.0, roughly in order of likelihood.
Troubleshooting
Pods stuck in ImagePullBackOff
The JWT is wrong, expired, or was pasted with a line break. Confirm with:
kubectl get events --namespace plm-system --field-selector reason=FailedThe username is the entire JWT and the password is the literal string none.
Policy Controller stays in Init:0/1
Expected during startup. The init container waits for both the compiler service and the S3 endpoint. If it persists beyond a few minutes, check that the SeaweedFS pods are Running:
kubectl get pods --namespace plm-system --selector app.kubernetes.io/name=seaweedfsThe usual cause is PVCs stuck Pending because the cluster has no default StorageClass.
SeaweedFS pods Pending
No default StorageClass, or insufficient capacity:
kubectl get pvc --namespace plm-system
kubectl get storageclassAPPolicy stuck in pending with a certificate error
Applies to the precompiled-bundle method. Check the resource status:
kubectl describe appolicy <name> --namespace plm-systemx509: certificate signed by unknown authority means the Policy Controller does not trust the artifact registry's CA. Note that SSL_CERT_FILE replaces the trust store rather than adding to it, so if SeaweedFS TLS is also enabled, both CAs must be in a single file.
APPolicy shows invalid with unexpected EOF after enabling TLS
Enabling TLS on an existing installation restarts the storage backend and can orphan objects written beforehand. Confirm in the filer log:
kubectl logs --namespace plm-system <release>-f5-waf-seaweed-filer-0 | grep "not found"A volume N not found message confirms it. Remove the orphaned object and restart the Policy Controller so it is regenerated.
Helm install fails on a ClusterRole
An error naming seaweed-editor-role or seaweed-viewer-role means another PLM installation already exists in the cluster. Only one is supported; remove the existing release first.
Most useful single command for anything policy-related:
kubectl logs --namespace plm-system deploy/<release>-f5-waf-policy-controller -c policy-controllerNote the -c policy-controller — the pod has more than one container, and the containers are distroless, so kubectl exec isn't available for debugging.
| securityUpdatesRepo: | ||
| cert: "<BASE64_NGINX_REPO_CRT>" | ||
| key: "<BASE64_NGINX_REPO_KEY>" | ||
| policyController: |
There was a problem hiding this comment.
We don't detail anywhere in this deployment guide how to configure mTLS, but we specify it in the NGF guide, with no guidance in either on how to create/ manage/ provide the CA or certificates
There was a problem hiding this comment.
@ciarams87 @ohad-perets — this include is WAF-team-owned content, so I want to make sure we're putting the mTLS guidance in the right place before writing it.
Two questions:
-
Is this include the right place for mTLS setup? The include covers PLM infrastructure deployment and is shared between the NGF and NIC tutorials. If mTLS configuration between PLM storage and the data plane is a one-time cluster-level setup step that happens at install time, it belongs here. If it's something configured separately per product (NGF vs NIC), it may belong in the parent tutorial instead.
-
If it does belong here, can you draft the steps? Specifically:
- How is the CA certificate obtained — generated by the PLM chart, or user-provided?
- What Secret format does
caSecretNameexpect (field name, for exampleca.crt)? - Same for
clientSSLSecretName(tls.crt/tls.key)? - Is there a recommended way to generate a self-signed CA for non-production use?
If the information belongs somewhere other than this include, let me know where and I'll move the placeholder.
There was a problem hiding this comment.
Is the CA chart-generated or user-provided?
User-provided. The chart does not generate certificates — there is no genCA/genSignedCert anywhere in it. If you set certificates.enabled=true without pre-creating the Secrets, the pods will fail to mount. This is the single most important thing to state, and it's currently not stated anywhere.
TBH, not sure where this info belongs - adding also @ViktorFefilovF5
|
|
||
| NGINX Gateway Fabric watches the PLM credentials and TLS Secrets and rebuilds its storage client when they change, so you can rotate credentials without restarting the pod. | ||
|
|
||
| ## Deploy the sample application |
There was a problem hiding this comment.
An awful lot of the following sections could be reused for NIC - the sample deployment, the ApLogConf definition, the ApPolicy configuration. How these policies are referenced by the products is different (and NIC does not use a ReferenceGrant), but the PLM owned CRD config can be pulled out to shared includes files
| ```shell | ||
| kubectl apply -f <POLICY_MANIFEST_FILE>.yaml | ||
| ``` |
There was a problem hiding this comment.
Instead of having a separate kubectl apply -f, step, we usually wrap the full yaml example file in the command.
So something like this:
kubectl apply -f - <<EOF
apiVersion: appprotect.f5.com/v1
kind: APPolicy
metadata:
name: <POLICY_NAME>
namespace: plm-system
spec:
policy:
$ref: "https://<ARTIFACT_REGISTRY_HOST>/<PATH/TO/POLICY_BUNDLE>.tgz"
EOFThis saves the user from creating their own file and allows them to just apply the resource directly. They will still need to update certain fields.
There was a problem hiding this comment.
Also, after they apply the file, it's good to add a step to confirm the resource was applied correctly, and to include the output from that command. There's an example here where we verify a service.
There was a problem hiding this comment.
Updated. Can you take another look?
| kubectl create secret docker-registry regcred \ | ||
| --namespace plm-system \ | ||
| --docker-server=private-registry.nginx.com \ | ||
| --docker-username=<JWT> \ |
There was a problem hiding this comment.
We want to avoid users needing to add their JWT to a command.
Have the user first create a new Secret in their cluster, which contains a base64 encoded version of their JWT. Assuming their JWT is stored in a file, they can run this command first to create the secret from that file. This example assumes their JWT is in a file called license.jwt
kubectl create secret generic jwt-reg-secret --from-file=license.jwtYou then should be able to replace --docker-username=<JWT> with this:
--docker-username=<(kubectl get secret jwt-reg-secret -o jsonpath='{.data.license\.jwt}' | base64 -d) \This will extract the data from the secret, and pass the JWT directly to the --docker-username flag.
There was a problem hiding this comment.
Two questions:
- Is this the JWT from MyF5?
- Should having the JWT be listed in the tutorial prerequisites?
There was a problem hiding this comment.
- Is this the JWT from MyF5?
Yes, they come from MyF5. Here's an example of that step from our Plus install guide
- Should having the JWT be listed in the tutorial prerequisites?
Yes, especially since that's required for creating this regcred secret.
| securityUpdatesRepo: | ||
| cert: "<BASE64_NGINX_REPO_CRT>" | ||
| key: "<BASE64_NGINX_REPO_KEY>" |
There was a problem hiding this comment.
Are users really required to add their full cert and key in this config? Or does this reference to their cert and key file?
There was a problem hiding this comment.
@ohad-perets Can you confirm whether the full cert and key are required, or if these are references to the files? If the latter, I'll update the placeholder text to make it clearer.
There was a problem hiding this comment.
We probably can't change this now, but this securityUpdatesRepo field should really use a Kubernetes Secret instead of the certs and keys directly. @ohad-perets is something that we could change in the helm chart in the future?
There was a problem hiding this comment.
Not sure; we will need to check it. @ViktorFefilovF5 can you check it?
There was a problem hiding this comment.
These certificates are OPTIONAL and only needed for authenticated access to signature repositories. The Policy Controller will start successfully with empty values.
Address reviewer feedback from PR #2197: - Git-reference tab (public and private): replace standalone YAML block and 'kubectl apply -f <file>' with a single inline 'kubectl apply -f - <<EOF' command, so users can copy-paste without creating a file - Git-reference tab: add a verify step (kubectl get appolicy ... jsonpath) immediately after apply, co-located where the user needs it; remove the separate 'Confirm the policy is ready' sub-section that duplicated this check - Precompiled-bundle include: same EOF conversion for the APPolicy apply - Storage section: add link to NGF Helm chart reference for full plmStorage option set
Address @ciarams87 comment on plm-deploy-infrastructure.md: - Add 'helm show values' command after the helm install step so users can discover all available PLM chart configuration options; no public values.yaml URL exists for this chart, so the command is the authoritative substitute - Remove the NGF chart values.yaml link added in error to get-started-plm.md (wrong chart, wrong file)
- Add maintainer HTML comment noting the APPolicy CRD prerequisite
and directing future reusers to add a parent-document note
- Convert the 'Update a precompiled bundle' step to inline EOF pattern,
consistent with the initial apply step
- Add placeholder explanation ('Replace <X>, <Y>, and <Z> with your
values') before the update code block, following the pattern used
elsewhere in the include
| - content/nic/waf-integration/get-started-plm.md | ||
| --- | ||
|
|
||
| <!-- Maintainer note: This include assumes the APPolicy CRD is already installed. In the NGF and NIC tutorials, CRD installation happens in the "Deploy PLM infrastructure" section. If you reuse this include elsewhere, add a prerequisite note in the parent document confirming CRDs are present before this section. --> |
There was a problem hiding this comment.
wondering if notes could be a metadata field like f5-note
| ```shell | ||
| kubectl get crd | grep appprotect.f5.com | ||
| ``` |
There was a problem hiding this comment.
Lets an example output here
Co-authored-by: Shaun <s.odonovan@f5.com>
Co-authored-by: Jon Torre <78599298+JTorreG@users.noreply.github.com>
…ocumentation into techdocs-5345-deploy-plm
| Create an `APPolicy` resource that references your bundle. Replace `<POLICY_NAME>`, `<ARTIFACT_REGISTRY_HOST>`, and `<PATH/TO/POLICY_BUNDLE>` with your values: | ||
|
|
||
| ```shell | ||
| kubectl apply -f - <<EOF |
There was a problem hiding this comment.
Are you sure that this one will work?
i think we need to change both <<EOF to <<'EOF'. The closing EOF stays unquoted.
| The Policy Controller does not poll the artifact registry for changes. To pick up a new version of a bundle, update the `$ref` URL in your `APPolicy` resource (or bump its revision annotation) and reapply it. Replace `<POLICY_NAME>`, `<ARTIFACT_REGISTRY_HOST>`, and `<PATH/TO/UPDATED_POLICY_BUNDLE>` with your values: | ||
|
|
||
| ```shell | ||
| kubectl apply -f - <<EOF |
There was a problem hiding this comment.
also here: change both <<EOF to <<'EOF'. The closing EOF stays unquoted.
| kubectl rollout status deployment/plm-seaweedfs-operator \ | ||
| --namespace plm-system --timeout=120s | ||
|
|
||
| kubectl wait pods \ |
There was a problem hiding this comment.
The SeaweedFS pods are created by that operator afterwards,
When it reconciles the Seaweed CR — so there is a real window where the operator is
Available, but no seaweedfs pods exist yet, and the documented command aborts the procedure.
not sure if we really can do something with that, but just raising. @ViktorFefilovF5 what do you think?
What this PR does
Adds documentation for using Policy Lifecycle Management (PLM) as a WAF policy source in NGINX Gateway Fabric, and applies style guide copy edits across the NGF WAF integration docs.
New files
content/includes/waf/plm-deploy-infrastructure.mdA shared include covering PLM backend deployment. Consumable by both the NGINX Gateway Fabric and NGINX Ingress Controller tutorials without modification. Covers:
appolicies,aplogconfs,apusersigs,apsignatures)plm-values.yaml(uses theversion-waf-policy-controllershortcode for the chart version)content/includes/waf/plm-define-policy-bundle-method.mdA shared include covering the precompiled-bundle policy definition method. Product-neutral and consumable by both tutorials. Covers:
.tgzbundle from an artifact registrybundle.statevalues (pending,processing,ready,invalid)isCompiled: falsebehaviorAPPolicyresource (the Policy Controller does not poll)content/ngf/waf-integration/get-started-plm.mdNew end-to-end tutorial for protecting traffic with F5 WAF for NGINX using PLM in NGINX Gateway Fabric. Covers:
WAFPolicyUpdated files
content/ngf/waf-integration/overview.mdcontent/ngf/waf-integration/policy-sources.mdcontent/ngf/reference/cli-help.md(PLM storage flags)Copy edits applying the F5 style guide:
via→through/using)since(causal) withbecauseensurewithmake surePLM's,NGINX Agent's)<namespace>/→<NAMESPACE>/)10.96.20.187→192.0.2.1) across six filesf5-descriptioninpolicy-sources.mdto include PLMpolicy-sources.mdOpen questions for SME review