Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
339b49f
feat: add external WAF bundle sources (NGINX Instance Manager, NGINX …
AlexFenlon Jun 16, 2026
258a44a
Merge branch 'main' into feat/n1c-waf-policy
AlexFenlon Jun 16, 2026
d39aed4
add pytests
AlexFenlon Jun 17, 2026
7feb158
Merge branch 'main' into feat/n1c-waf-policy
AlexFenlon Jun 17, 2026
856a50d
improve pod log retrieval in tests by specifying container name
AlexFenlon Jun 22, 2026
27a32dc
Merge branch 'main' into feat/n1c-waf-policy
AlexFenlon Jun 22, 2026
f871593
Merge branch 'main' into feat/n1c-waf-policy
AlexFenlon Jun 23, 2026
b981be4
update pollInterval defaults and increase retry limits in WAF tests
AlexFenlon Jun 23, 2026
3340974
Merge branch 'main' into feat/n1c-waf-policy
AlexFenlon Jun 23, 2026
4377ff2
update codegen
AlexFenlon Jun 23, 2026
d547e51
update codegen
AlexFenlon Jun 23, 2026
de88a7e
Merge branch 'main' into feat/n1c-waf-policy
AlexFenlon Jun 24, 2026
76974ad
add better warning messages, improve pytest/remove flaky test, stops …
AlexFenlon Jun 24, 2026
c5a983a
Merge branch 'main' into feat/n1c-waf-policy
AlexFenlon Jun 24, 2026
7a5353c
update pytest
AlexFenlon Jun 25, 2026
d8b3461
Merge remote-tracking branch 'origin/feat/n1c-waf-policy' into feat/n…
AlexFenlon Jun 25, 2026
ab3b3f0
Merge branch 'main' into feat/n1c-waf-policy
AlexFenlon Jun 25, 2026
e083a95
update pytest
AlexFenlon Jun 25, 2026
5af61a7
update pytest
AlexFenlon Jun 25, 2026
b9133e8
revert unrelated pytest
AlexFenlon Jun 26, 2026
59d498c
Merge branch 'main' into feat/n1c-waf-policy
AlexFenlon Jun 26, 2026
c2d4e96
update pytest
AlexFenlon Jun 26, 2026
1f9fea8
Merge remote-tracking branch 'origin/feat/n1c-waf-policy' into feat/n…
AlexFenlon Jun 26, 2026
634c96a
refactor policy status handling to improve bundle source validation
AlexFenlon Jun 26, 2026
cd172ab
update pytest
AlexFenlon Jun 26, 2026
f9e5fc3
clean up pytests
AlexFenlon Jun 26, 2026
d240bb7
Address copilot comments and split telemetry to have WAFBundle and lo…
AlexFenlon Jun 26, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,18 @@ examples/ingress-resources/external-auth-mergeable/external-auth-server-tls-secr
examples/custom-resources/external-auth/external-auth-server-tls-secret.yaml
examples/custom-resources/external-auth-oauth2/external-auth-server-tls-secret.yaml
tests/data/external-auth/backend/external-auth-server-tls-secret.yaml
common-secrets/bundle-server-ca-secret.yaml
common-secrets/bundle-server-tls-secret.yaml
common-secrets/bundle-server-ca-secret-crl.yaml
common-secrets/bundle-client-tls-secret.yaml
examples/shared-examples/waf-bundle-server/bundle-server-ca-secret.yaml
examples/shared-examples/waf-bundle-server/bundle-server-ca-secret-crl.yaml
tests/data/ap-waf-bundle-source/secret/bundle-server-ca-secret.yaml
tests/data/ap-waf-bundle-source/secret/bundle-server-ca-secret-crl.yaml
examples/shared-examples/waf-bundle-server/bundle-client-tls-secret.yaml
tests/data/ap-waf-bundle-source/secret/bundle-client-tls-secret.yaml
examples/shared-examples/waf-bundle-server/bundle-server-tls-secret.yaml
tests/data/ap-waf-bundle-source/secret/bundle-server-tls-secret.yaml

# TLS Certificate secrets
common-secrets/cafe-passwd-basic-auth-secret.yaml
Expand Down
1 change: 1 addition & 0 deletions cmd/nginx-ingress/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -343,6 +343,7 @@ func main() {
DynamicWeightChangesReload: *enableDynamicWeightChangesReload,
InstallationFlags: parsedFlags,
ShuttingDown: false,
AppProtectBundlePath: appProtectBundlePath,
}

lbc := k8s.NewLoadBalancerController(lbcInput)
Expand Down
255 changes: 251 additions & 4 deletions config/crd/bases/k8s.nginx.org_policies.yaml

Large diffs are not rendered by default.

255 changes: 251 additions & 4 deletions deploy/crds.yaml

Large diffs are not rendered by default.

45 changes: 42 additions & 3 deletions docs/crd/k8s.nginx.org_policies.md
Original file line number Diff line number Diff line change
Expand Up @@ -138,16 +138,55 @@ The `.spec` object supports the following fields:
| `rateLimit.scale` | `boolean` | Enables a constant rate-limit by dividing the configured rate by the number of nginx-ingress pods currently serving traffic. This adjustment ensures that the rate-limit remains consistent, even as the number of nginx-pods fluctuates due to autoscaling. This will not work properly if requests from a client are not evenly distributed across all ingress pods (Such as with sticky sessions, long lived TCP Connections with many requests, and so forth). In such cases using zone-sync instead would give better results. Enabling zone-sync will suppress this setting. |
| `rateLimit.zoneSize` | `string` | Size of the shared memory zone. Only positive values are allowed. Allowed suffixes are k or m, if none are present k is assumed. |
| `waf` | `object` | The WAF policy configures WAF and log configuration policies for NGINX AppProtect |
| `waf.apBundle` | `string` | The App Protect WAF policy bundle. Mutually exclusive with apPolicy. |
| `waf.apPolicy` | `string` | The App Protect WAF policy of the WAF. Accepts an optional namespace. Mutually exclusive with apBundle. |
| `waf.apBundle` | `string` | The App Protect WAF policy bundle. Mutually exclusive with apPolicy and apBundleSource. |
| `waf.apBundleSource` | `object` | ApBundleSource fetches the WAF policy bundle from N1C, NIM, or an HTTPS endpoint. Mutually exclusive with ApPolicy and ApBundle. |
| `waf.apBundleSource.enablePolling` | `boolean` | EnablePolling enables background polling to automatically detect and fetch updated bundles at the configured PollInterval. When false, the bundle is fetched once on policy creation or update; subsequent updates require modifying the Policy resource to trigger a new fetch. |
| `waf.apBundleSource.insecureSkipVerify` | `boolean` | InsecureSkipVerify disables TLS certificate verification when fetching bundles. Not recommended for production use. |
| `waf.apBundleSource.policyName` | `string` | PolicyName is the policy name on the management plane. Required for NIM and N1C; forbidden for HTTPS. |
| `waf.apBundleSource.policyNamespace` | `string` | PolicyNamespace is the namespace/tenant on the management plane. Required for N1C only. |
| `waf.apBundleSource.pollInterval` | `string` | PollInterval is how often to re-fetch the bundle when enablePolling is true. Minimum 1m. Default 5m. Ignored when enablePolling is false. |
| `waf.apBundleSource.retryAttempts` | `integer` | RetryAttempts is the number of retry attempts on transient failure. Range 1–10. |
| `waf.apBundleSource.secret` | `string` | Secret is the name of a Kubernetes Secret in the same namespace as the Policy. For HTTPS: kubernetes.io/tls (tls.crt + tls.key for client mTLS; optional ca.crt for server CA). For N1C: nginx.com/waf-bundle Secret with a 'token' field containing the API token. For NIM: nginx.com/waf-bundle Secret with a 'token' field (bearer auth) or 'username'+'password' fields (basic auth). |
| `waf.apBundleSource.timeout` | `string` | Timeout is the per-request HTTP timeout. Default 60s. |
| `waf.apBundleSource.trustedCertSecret` | `string` | TrustedCertSecret is the name of a Kubernetes Secret with a custom CA certificate for verifying the remote endpoint TLS certificate. The secret must be in the same namespace as the Policy, must be of type nginx.org/ca, and must include ca.crt. |
| `waf.apBundleSource.type` | `string` | Type is the bundle source backend. Defaults to HTTPS. Allowed values: `"HTTPS"`, `"NIM"`, `"N1C"`. |
| `waf.apBundleSource.url` | `string` | URL is the full bundle URL for HTTPS type, or the API base URL for NIM/N1C. Must use https://. |
| `waf.apBundleSource.verifyChecksum` | `boolean` | VerifyChecksum enables SHA-256 verification of the downloaded bundle. HTTPS type only. |
| `waf.apPolicy` | `string` | The App Protect WAF policy of the WAF. Accepts an optional namespace. Mutually exclusive with apBundle and apBundleSource. |
| `waf.enable` | `boolean` | Enables NGINX App Protect WAF. |
| `waf.securityLog` | `object` | SecurityLog defines the security log of a WAF policy. |
| `waf.securityLog` | `object` | SecurityLog defines the security log of a WAF policy. Mutual exclusivity of apLogConf, apLogBundle, and apLogBundleSource is enforced by the Go validation layer. |
| `waf.securityLog.apLogBundle` | `string` | The App Protect WAF log bundle resource. Only works with apBundle. |
| `waf.securityLog.apLogBundleSource` | `object` | ApLogBundleSource fetches the log profile bundle from N1C, NIM, or an HTTPS endpoint. Mutually exclusive with ApLogConf and ApLogBundle. Requires apBundleSource on the parent WAF. |
| `waf.securityLog.apLogBundleSource.enablePolling` | `boolean` | EnablePolling enables background polling to automatically detect and fetch updated bundles at the configured PollInterval. When false, the bundle is fetched once on policy creation or update; subsequent updates require modifying the Policy resource to trigger a new fetch. |
| `waf.securityLog.apLogBundleSource.insecureSkipVerify` | `boolean` | InsecureSkipVerify disables TLS certificate verification when fetching bundles. Not recommended for production use. |
| `waf.securityLog.apLogBundleSource.policyName` | `string` | PolicyName is the policy name on the management plane. Required for NIM and N1C; forbidden for HTTPS. |
| `waf.securityLog.apLogBundleSource.policyNamespace` | `string` | PolicyNamespace is the namespace/tenant on the management plane. Required for N1C only. |
| `waf.securityLog.apLogBundleSource.pollInterval` | `string` | PollInterval is how often to re-fetch the bundle when enablePolling is true. Minimum 1m. Default 5m. Ignored when enablePolling is false. |
| `waf.securityLog.apLogBundleSource.retryAttempts` | `integer` | RetryAttempts is the number of retry attempts on transient failure. Range 1–10. |
| `waf.securityLog.apLogBundleSource.secret` | `string` | Secret is the name of a Kubernetes Secret in the same namespace as the Policy. For HTTPS: kubernetes.io/tls (tls.crt + tls.key for client mTLS; optional ca.crt for server CA). For N1C: nginx.com/waf-bundle Secret with a 'token' field containing the API token. For NIM: nginx.com/waf-bundle Secret with a 'token' field (bearer auth) or 'username'+'password' fields (basic auth). |
| `waf.securityLog.apLogBundleSource.timeout` | `string` | Timeout is the per-request HTTP timeout. Default 60s. |
| `waf.securityLog.apLogBundleSource.trustedCertSecret` | `string` | TrustedCertSecret is the name of a Kubernetes Secret with a custom CA certificate for verifying the remote endpoint TLS certificate. The secret must be in the same namespace as the Policy, must be of type nginx.org/ca, and must include ca.crt. |
| `waf.securityLog.apLogBundleSource.type` | `string` | Type is the bundle source backend. Defaults to HTTPS. Allowed values: `"HTTPS"`, `"NIM"`, `"N1C"`. |
| `waf.securityLog.apLogBundleSource.url` | `string` | URL is the full bundle URL for HTTPS type, or the API base URL for NIM/N1C. Must use https://. |
| `waf.securityLog.apLogBundleSource.verifyChecksum` | `boolean` | VerifyChecksum enables SHA-256 verification of the downloaded bundle. HTTPS type only. |
| `waf.securityLog.apLogConf` | `string` | The App Protect WAF log conf resource. Accepts an optional namespace. Only works with apPolicy. |
| `waf.securityLog.enable` | `boolean` | Enables security log. |
| `waf.securityLog.logDest` | `string` | The log destination for the security log. Only accepted variables are syslog:server=<ip-address>; localhost; fqdn>:<port>, stderr, <absolute path to file>. |
| `waf.securityLogs` | `array` | List of configuration values. |
| `waf.securityLogs[].apLogBundle` | `string` | The App Protect WAF log bundle resource. Only works with apBundle. |
| `waf.securityLogs[].apLogBundleSource` | `object` | ApLogBundleSource fetches the log profile bundle from N1C, NIM, or an HTTPS endpoint. Mutually exclusive with ApLogConf and ApLogBundle. Requires apBundleSource on the parent WAF. |
| `waf.securityLogs[].apLogBundleSource.enablePolling` | `boolean` | EnablePolling enables background polling to automatically detect and fetch updated bundles at the configured PollInterval. When false, the bundle is fetched once on policy creation or update; subsequent updates require modifying the Policy resource to trigger a new fetch. |
| `waf.securityLogs[].apLogBundleSource.insecureSkipVerify` | `boolean` | InsecureSkipVerify disables TLS certificate verification when fetching bundles. Not recommended for production use. |
| `waf.securityLogs[].apLogBundleSource.policyName` | `string` | PolicyName is the policy name on the management plane. Required for NIM and N1C; forbidden for HTTPS. |
| `waf.securityLogs[].apLogBundleSource.policyNamespace` | `string` | PolicyNamespace is the namespace/tenant on the management plane. Required for N1C only. |
| `waf.securityLogs[].apLogBundleSource.pollInterval` | `string` | PollInterval is how often to re-fetch the bundle when enablePolling is true. Minimum 1m. Default 5m. Ignored when enablePolling is false. |
| `waf.securityLogs[].apLogBundleSource.retryAttempts` | `integer` | RetryAttempts is the number of retry attempts on transient failure. Range 1–10. |
| `waf.securityLogs[].apLogBundleSource.secret` | `string` | Secret is the name of a Kubernetes Secret in the same namespace as the Policy. For HTTPS: kubernetes.io/tls (tls.crt + tls.key for client mTLS; optional ca.crt for server CA). For N1C: nginx.com/waf-bundle Secret with a 'token' field containing the API token. For NIM: nginx.com/waf-bundle Secret with a 'token' field (bearer auth) or 'username'+'password' fields (basic auth). |
| `waf.securityLogs[].apLogBundleSource.timeout` | `string` | Timeout is the per-request HTTP timeout. Default 60s. |
| `waf.securityLogs[].apLogBundleSource.trustedCertSecret` | `string` | TrustedCertSecret is the name of a Kubernetes Secret with a custom CA certificate for verifying the remote endpoint TLS certificate. The secret must be in the same namespace as the Policy, must be of type nginx.org/ca, and must include ca.crt. |
| `waf.securityLogs[].apLogBundleSource.type` | `string` | Type is the bundle source backend. Defaults to HTTPS. Allowed values: `"HTTPS"`, `"NIM"`, `"N1C"`. |
| `waf.securityLogs[].apLogBundleSource.url` | `string` | URL is the full bundle URL for HTTPS type, or the API base URL for NIM/N1C. Must use https://. |
| `waf.securityLogs[].apLogBundleSource.verifyChecksum` | `boolean` | VerifyChecksum enables SHA-256 verification of the downloaded bundle. HTTPS type only. |
| `waf.securityLogs[].apLogConf` | `string` | The App Protect WAF log conf resource. Accepts an optional namespace. Only works with apPolicy. |
| `waf.securityLogs[].enable` | `boolean` | Enables security log. |
| `waf.securityLogs[].logDest` | `string` | The log destination for the security log. Only accepted variables are syslog:server=<ip-address>; localhost; fqdn>:<port>, stderr, <absolute path to file>. |
50 changes: 45 additions & 5 deletions examples/custom-resources/security-monitoring-v5/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@

This example describes how to deploy NGINX Plus Ingress Controller with [F5 WAF for NGINX v5](https://docs.nginx.com/waf/) and [NGINX Agent](https://docs.nginx.com/nginx-agent/overview/) to integrate with NGINX Security Monitoring. It deploys a simple web application and configures WAF protection using compiled policy and log bundles, forwarding security logs to the Security Monitoring dashboard via syslog.

WAF policy bundles can be sourced in two ways:

- **Filesystem bundles** (`apBundle`) — manually compile and copy `.tgz` bundles to the pod volume. See Steps 2–4 below.
- **Remote bundle sources** (`apBundleSource`) — NIC automatically fetches bundles from NGINX One Console (N1C), NGINX Instance Manager (NIM), or any HTTPS endpoint. See [WAF with Management Plane Sources](../waf-management-plane/) or [WAF with HTTPS Bundle Sources](../waf-https-bundles/) for details, or use `waf-n1c.yaml` / `waf-nim.yaml` in Step 4 below.

This example works with both:

- **NGINX Instance Manager** (Agent 2.*) - See the [Security Monitoring tutorial](https://docs.nginx.com/nginx-ingress-controller/tutorials/security-monitoring/) for agent configuration.
Expand Down Expand Up @@ -45,7 +50,9 @@ Create the application deployment and service:
kubectl apply -f webapp.yaml
```

## Step 2 - Create and Deploy the WAF Policy and Log Bundles
## Step 2 - Create and Deploy the WAF Policy and Log Bundles (filesystem bundles only)

> **Skip this step** if using remote bundle sources (`waf-n1c.yaml`) — NIC fetches bundles automatically.

1. Compile your WAF policy and log configuration into bundles (`.tgz` files) using the `waf-compiler` image. See [Compile WAF Policy from JSON to Bundle](https://docs.nginx.com/nginx-ingress-controller/install/waf-helm/#compile-waf-policy-from-json-to-bundle) for compilation steps.

Expand All @@ -70,21 +77,54 @@ If you are using Agent (3.*) (NGINX One Console), skip this step. NGINX Agent 3.

## Step 4 - Deploy the WAF Policy

Create the WAF policy referencing the compiled bundles. Choose the file that matches your agent version:
Create the WAF policy referencing the compiled bundles. Choose the file that matches your setup:

**Agent 2.* (NGINX Instance Manager)** — logs sent to the syslog service:
**Agent 2.* (NGINX Instance Manager)** — filesystem bundles, logs sent to the syslog service:

```console
kubectl apply -f waf.yaml
```

**Agent 3.* (NGINX One Console)** — logs sent directly to the local NGINX Agent listener:
**Agent 2.* (NGINX Instance Manager) with remote bundle pulling** — bundles fetched from NIM via `apBundleSource`, no manual file copy needed:

```console
kubectl create secret generic nim-credentials \
--type=nginx.com/waf-bundle \
--from-literal=token=<Your NIM Token>
kubectl apply -f waf-nim.yaml
```

Edit `waf-nim.yaml` and replace `<nim_host>`, `<policy_name>`, and `<log_profile_name>` with your NGINX Instance Manager values.

> When using `waf-nim.yaml`, skip Step 2 (manual bundle compilation and copy) — NIC fetches bundles directly from NGINX Instance Manager.

**Agent 3.* (NGINX One Console)** — filesystem bundles, logs sent to the local NGINX Agent listener:

```console
kubectl apply -f waf-agent-v3.yaml
```

Note the log bundle referenced in the `apLogBundle` field must be compiled from a log profile that matches the format required by NGINX Security Monitoring.
**Agent 3.* (NGINX One Console) with remote bundle pulling** — bundles fetched from N1C via `apBundleSource`, no manual file copy needed:

```console
kubectl create secret generic n1c-credentials \
--type=nginx.com/waf-bundle \
--from-literal=token=<Your API Token>
kubectl apply -f waf-n1c.yaml
```

Edit `waf-n1c.yaml` and replace `<tenant>` and `<policy_name>` with your NGINX One Console values. The API token is generated from the [F5 Distributed Cloud Console](https://console.ves.volterra.io) under **Account Settings > Credentials > Add Credentials > API Token**. See [Managing User Credentials](https://docs.cloud.f5.com/docs/how-to/user-mgmt/credentials) for details.

> When using `waf-n1c.yaml`, skip Step 2 (manual bundle compilation and copy) — NIC fetches bundles directly from NGINX One Console.

Verify the policy status:

```console
kubectl describe policy waf-policy
```

The policy should show `State: Valid`. If the bundle source is unreachable, the status will be
`Warning` with reason `BundleFetchFailed`. NIC retries on the next `pollInterval`.

## Step 5 - Configure Load Balancing

Expand Down
26 changes: 26 additions & 0 deletions examples/custom-resources/security-monitoring-v5/waf-n1c.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
apiVersion: k8s.nginx.org/v1
kind: Policy
metadata:
name: waf-policy
spec:
waf:
enable: true
apBundleSource:
type: N1C
url: "https://<tenant>.console.ves.volterra.io"
policyName: "<policy_name>"
policyNamespace: "default"
secret: "n1c-credentials"
enablePolling: true
pollInterval: "5m"
securityLogs:
- enable: true
apLogBundleSource:
type: N1C
url: "https://<tenant>.console.ves.volterra.io"
policyName: "secops_dashboard"
policyNamespace: "default"
secret: "n1c-credentials"
enablePolling: true
pollInterval: "5m"
logDest: "syslog:server=127.0.0.1:1514"
24 changes: 24 additions & 0 deletions examples/custom-resources/security-monitoring-v5/waf-nim.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
apiVersion: k8s.nginx.org/v1
kind: Policy
metadata:
name: waf-policy
spec:
waf:
enable: true
apBundleSource:
type: NIM
url: "https://<nim_host>"
policyName: "<policy_name>"
secret: "nim-credentials"
enablePolling: true
pollInterval: "5m"
securityLogs:
- enable: true
apLogBundleSource:
type: NIM
url: "https://<nim_host>"
policyName: "<log_profile_name>"
secret: "nim-credentials"
enablePolling: true
pollInterval: "5m"
logDest: "syslog:server=syslog-svc.default:514"
Loading
Loading