Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
9036177
docs: add Gateway WAF (per-route & namespaced) section
electricjesus May 29, 2026
cd13609
docs: fix Vale errors in Gateway WAF section
electricjesus May 29, 2026
59940e7
docs: spell out "Core Rule Set" in Gateway WAF prose
electricjesus May 29, 2026
cb00f3a
docs: render Gateway WAF architecture diagram with Mermaid
electricjesus May 29, 2026
dcbfd00
docs: use an SVG architecture diagram for Gateway WAF
electricjesus May 29, 2026
96bb113
docs: tune Gateway WAF architecture SVG layout
electricjesus May 29, 2026
4bd1a91
docs(gateway-waf): clarify SecRule ID range is an allowance, not a un…
electricjesus Jul 6, 2026
a63dcd0
docs(gateway-waf): consolidate 16 pages into 6
electricjesus Jul 9, 2026
de0351c
docs(gateway-waf): trim prose toward the preview budget
electricjesus Jul 9, 2026
ea50bf4
docs(gateway-waf): correct observability, sectionName, and status con…
electricjesus Jul 9, 2026
1ea2251
docs(gateway-waf): fix inbound link orphaned by the consolidation
electricjesus Jul 9, 2026
a53c8b9
docs(gateway-waf): reorganize into task-oriented pages
electricjesus Jul 9, 2026
a65bb56
docs(gateway-waf): fix Set-a-baseline prerequisite
electricjesus Jul 9, 2026
8a96343
Merge remote-tracking branch 'origin/main' into seth/EV-6466-aggressi…
electricjesus Jul 9, 2026
1f47add
docs(gateway-waf): apply doc review feedback
electricjesus Jul 13, 2026
1720e16
chore(ci): retrigger netlify deploy preview
electricjesus Jul 14, 2026
849e5f1
fix(gateway-waf): correct tutorial namespace and severity enum casing
electricjesus Jul 14, 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
17 changes: 17 additions & 0 deletions .github/styles/config/vocabularies/CalicoTerminology/accept.txt
Original file line number Diff line number Diff line change
Expand Up @@ -144,3 +144,20 @@ VNet[s]?
[dD]ecap(sulation)?
[rR]epo[s]?
[dD]iags

# Gateway WAF (per-route and namespaced WAF)
[rR]ego
[aA]llowlist(ed|ing|s)?
GlobalWAFPolic(y|ies)
WAFPolic(y|ies)
GlobalWAFPlugins?
WAFPlugins?
GlobalWAFValidationPolic(y|ies)
WAFValidationPolic(y|ies)
targetRefs?
namespaceSelector
paranoiaLevel
coreRuleSet
defaultAction
enforcementMode
SecLang
6 changes: 6 additions & 0 deletions calico-enterprise/threat/deploying-waf-ingress-gateway.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,12 @@ This feature is tech preview. Tech preview features may be subject to significan

:::

:::caution

This page describes the original, Gateway-wide WAF for Calico Ingress Gateway. It is superseded by [Gateway WAF](./gateway-waf/overview.mdx), which adds per-route and per-namespace policies, custom rules, and compliance validation. For Gateway-wide protection equivalent to this guide, attach a `WAFPolicy` to your `Gateway` — see [Attach WAF to a route](./gateway-waf/attach-waf-to-a-route.mdx). This page will be removed in a future release.

:::

## Prerequisites

* [Calico Ingress Gateway](../networking/ingress-gateway/about-calico-ingress-gateway.mdx) is set up for your cluster.
Expand Down
108 changes: 108 additions & 0 deletions calico-enterprise/threat/gateway-waf/attach-waf-to-a-route.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
---
description: Attach a WAFPolicy to a Gateway or HTTPRoute to turn on WAF inspection for that target, layering namespace overrides on the cluster baseline.
---

# Attach WAF to a Gateway or route

:::note

This feature is tech preview. Tech preview features may be subject to significant changes before they become GA.

:::

Create a `WAFPolicy` in your namespace pointing at Gateway API targets — WAF inspection doesn't start automatically when the feature is enabled. It inherits the cluster baseline with namespace overrides layered on top.

## Prerequisites

- The cluster operator has created the `GlobalWAFPolicy` baseline. See [Set a cluster-wide baseline for Gateway WAF](./set-a-baseline.mdx).
- You have write access to `WAFPolicy` in your namespace.
- A `Gateway` or `HTTPRoute` in your namespace is already receiving traffic.

## Choose a target

`WAFPolicy.spec.targetRefs[]` accepts two kinds of targets:

| `kind` in `targetRefs` | WAF covers | Use when |
| :--- | :--- | :--- |
| `Gateway` | Every `HTTPRoute` bound to that Gateway | You want uniform WAF across all routes on a Gateway |
| `HTTPRoute` | Just that one route | You need per-route tuning — different paranoia level, different plugins |

Each `targetRefs` entry generates a separate `EnvoyExtensionPolicy` (max 16 entries).

:::note

`sectionName` scopes a `targetRefs` entry to a specific listener on a `Gateway` (or a named section), so WAF applies to just that listener rather than the whole Gateway.

:::

## Attach a WAFPolicy

1. Create a `WAFPolicy` in your namespace pointing at the target. This example attaches to one `HTTPRoute`, overriding action and paranoia; the unset `coreRuleSet.state` still inherits from the baseline:

```yaml
apiVersion: applicationlayer.projectcalico.org/v3
kind: WAFPolicy
metadata:
name: payments-api-waf
namespace: payments
spec:
targetRefs:
- group: gateway.networking.k8s.io
kind: HTTPRoute
name: payments-api
action: Block # override: Block even if global baseline is Detect
coreRuleSet:
paranoiaLevel: 2 # stricter than the cluster default
# coreRuleSet.state is unset → inherited from GlobalWAFPolicy
```

Apply it with `kubectl apply -f wafpolicy.yaml`. For inheritance details, see the [Overview](./overview.mdx#inheritance-namespaces-override-they-dont-restart).

1. Verify the policy was accepted and programmed:

```bash
kubectl get wafpolicy payments-api-waf -n payments
```

```
NAME ACCEPTED PROGRAMMED AGE
payments-api-waf True True 45s
```

`.status.conditions` reports `Accepted`, `Programmed`, and validation findings. Confirm Envoy Gateway received the filter with `kubectl get envoyextensionpolicies -n payments` (one per `targetRefs` entry).

1. Test that the WAF blocks an attack:

```bash
curl -si "https://<gateway-host>/api?id=1+OR+1%3D1"
```

```
HTTP/1.1 403 Forbidden
content-length: 11
content-type: text/plain

Access denied
```

:::tip

In `Detect` mode the request is allowed (200), but the WAF hit is still recorded in the `tigera_secure_ee_waf` log index (carrying `gateway_name`/`gateway_namespace`). On this release, gateway-WAF hits are not raised as Security Events — see [Observing WAF activity](./troubleshooting.mdx#observing-waf-activity).

:::

:::caution

`ACCEPTED=False` reason `ValidationFailed` means a `GlobalWAFValidationPolicy` is blocking your config — the status message names the policy and rule. Contact your cluster operator or see [Validate configuration with Rego](./validate-config.mdx).

:::

## Conflict resolution

Only one `WAFPolicy` may be active per target. If two in the same namespace target the same Gateway/HTTPRoute, $[prodname] follows [GEP-713](https://gateway-api.sigs.k8s.io/geps/gep-713/): oldest `creationTimestamp` wins; the newer is marked `Accepted=False` reason `Conflicted`, and only the winner's directives are merged. Fix by deleting the newer policy or repointing its `targetRefs`.

Check failure on line 102 in calico-enterprise/threat/gateway-waf/attach-waf-to-a-route.mdx

View workflow job for this annotation

GitHub Actions / runner / vale

[vale] reported by reviewdog 🐶 [Vale.Spelling] Did you really mean 'repointing'? Raw Output: {"message": "[Vale.Spelling] Did you really mean 'repointing'?", "location": {"path": "calico-enterprise/threat/gateway-waf/attach-waf-to-a-route.mdx", "range": {"start": {"line": 102, "column": 360}}}, "severity": "ERROR"}

## Next steps

- [Write custom rules with plugins](./write-custom-rules.mdx) — add app-specific detections beyond the baseline.
- [Validate configuration with Rego](./validate-config.mdx) — check the merged config against compliance guardrails.
- [Troubleshooting](./troubleshooting.mdx) — diagnose a policy that won't accept or program.
91 changes: 91 additions & 0 deletions calico-enterprise/threat/gateway-waf/enable.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
---
description: Turn Gateway WAF on or off by setting the operator GatewayAPI CR's extensions.waf.state field.

Check warning on line 2 in calico-enterprise/threat/gateway-waf/enable.mdx

View workflow job for this annotation

GitHub Actions / runner / vale

[vale] reported by reviewdog 🐶 [Vale.Terms] Use 'WAFs?' instead of 'waf'. Raw Output: {"message": "[Vale.Terms] Use 'WAFs?' instead of 'waf'.", "location": {"path": "calico-enterprise/threat/gateway-waf/enable.mdx", "range": {"start": {"line": 2, "column": 92}}}, "severity": "WARNING"}
---

# Enable Gateway WAF

:::note

This feature is tech preview. Tech preview features may be subject to significant changes before they become GA.

:::

Gateway WAF is off by default. Turning it on is a one-line change to the operator's `GatewayAPI` CR — no new workload is deployed, so there's no extra resource footprint to plan for.

## Prerequisites

- $[prodname] is installed with a valid license that includes the Gateway WAF feature.
- $[prodname] Ingress Gateway is already set up. See [About Calico Ingress Gateway](../../networking/ingress-gateway/about-calico-ingress-gateway.mdx).
- Cluster-admin rights — the `GatewayAPI` CR is a cluster-scoped operator resource.

:::note

Customer-installed (BYO) Envoy Gateway is not supported. Gateway WAF requires the Ingress Gateway that $[prodname] manages.

:::

## Enable Gateway WAF

1. Edit the operator `GatewayAPI` CR named `default` and set `spec.extensions.waf.state` to `Enabled` (the default is `Disabled`):

```bash
kubectl apply -f - <<'EOF'
apiVersion: operator.tigera.io/v1
kind: GatewayAPI
metadata:
name: default
spec:
extensions:
waf:
state: Enabled
EOF
```

The operator installs the six WAF CRDs and the validating admission webhook, and wires the WAF reconcilers into the existing `calico-kube-controllers` pod in `calico-system`. No new workload is deployed.

1. Confirm the six CRDs are installed:

```bash
kubectl get crd | grep applicationlayer.projectcalico.org
```

```text
globalwafplugins.applicationlayer.projectcalico.org
globalwafpolicies.applicationlayer.projectcalico.org
globalwafvalidationpolicies.applicationlayer.projectcalico.org
wafplugins.applicationlayer.projectcalico.org
wafpolicies.applicationlayer.projectcalico.org
wafvalidationpolicies.applicationlayer.projectcalico.org
```

All six must be present before you create any WAF resources.

1. Confirm the admission webhook is active:

```bash
kubectl get validatingwebhookconfigurations | grep waf
```

You should see one entry. If it's absent, check the `calico-kube-controllers` pod logs in the `calico-system` namespace.

## Disable Gateway WAF

Set `spec.extensions.waf.state` back to `Disabled` on the same `GatewayAPI` CR to turn the feature off:

```yaml
apiVersion: operator.tigera.io/v1
kind: GatewayAPI
metadata:
name: default
spec:
extensions:
waf:
state: Disabled
```

The operator removes the admission webhook and stops the WAF reconcilers; existing `WAFPolicy`, `GlobalWAFPolicy`, and other WAF resources stay on the cluster but are no longer reconciled or enforced until you re-enable the feature.

## Next steps

- [Tutorial](./tutorial.mdx) — deploy a test app and block your first attack.
- [Set a cluster-wide baseline for Gateway WAF](./set-a-baseline.mdx) — start your production rollout.
89 changes: 89 additions & 0 deletions calico-enterprise/threat/gateway-waf/multi-tenant-setup.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
---
description: Run a full multi-tenant Gateway WAF setup — a cluster baseline, org-wide plugin, and validation policy that stops teams weakening protection while each team tunes independently.
---

# Multi-tenant setup

:::note

This feature is tech preview. Tech preview features may be subject to significant changes before they become GA.

:::

Multi-tenant setup ties the baseline, plugins, and validation guardrails together so a shared cluster stays safe by default while each team still controls its own Gateway.

:::note[Scenario]

A cluster shared by `team-a` and `payments`, with a cluster operator setting the baseline via a `GlobalWAFPolicy`, an org-wide `GlobalWAFPlugin`, and a `GlobalWAFValidationPolicy` that stops teams weakening protection. Each team attaches WAF to its own Gateway and tunes independently.

:::

## Prerequisites

- Gateway WAF is enabled and licensed. See [Enable Gateway WAF](./enable.mdx).
- Cluster-admin rights for the cluster-operator steps; each team needs write access to WAF resources in its own namespace.

## Set it up

1. As cluster operator, create the baseline resources: the `default` `GlobalWAFPolicy`, an `org-security-baseline` `GlobalWAFPlugin`, and a `production-requirements` `GlobalWAFValidationPolicy` (`Enforce`, scoped to `env: production`). Follow the patterns in [Set a cluster-wide baseline](./set-a-baseline.mdx), [Write custom rules](./write-custom-rules.mdx), and [Validate configuration with Rego](./validate-config.mdx).

1. Grant application operators read access to the `Global*` resources so they can see what they're inheriting:

```yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: waf-global-reader
rules:
- apiGroups: ["applicationlayer.projectcalico.org"]
resources:
- globalwafpolicies
- globalwafplugins
- globalwafvalidationpolicies
verbs: ["get", "list", "watch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: waf-global-reader
namespace: team-a # repeat for payments
subjects:
- kind: Group
name: team-a-operators
apiGroup: rbac.authorization.k8s.io
roleRef:
kind: ClusterRole
name: waf-global-reader
apiGroup: rbac.authorization.k8s.io
```

:::note

This grants read on cluster-scoped resources via a namespace-scoped `RoleBinding` — application operators can see but not edit `Global*` resources.

:::

1. Each team labels its namespace so the validation policy applies (`kubectl label namespace team-a env=production`, same for `payments`), then creates a `WAFPolicy` targeting its own Gateway. `team-a` raises paranoia to 2, inheriting `coreRuleSet.state: Enabled`; `payments` runs paranoia 3 plus a namespace `WAFPlugin` blocking GraphQL introspection, using an ID from its own [SecRule ID range](./reference.mdx#secrule-id-ranges).

## What happens if a team tries to weaken the baseline

If `payments` applies `action: Detect`, `production-requirements` (`Enforce`) fires its `must-use-block-mode` critical rule — the controller marks the policy `Programmed=False, Reason=NotAttempted` and surfaces the violation:

```bash
kubectl get wafpolicy payments-waf -n payments \
-o jsonpath='{.status.validation.failures}' | jq
```

No `EnvoyExtensionPolicy` is generated until corrected — restore `action: Block` or request an exception from the cluster operator.

## Result: isolation in practice

- **`team-a`** — Core Rule Set at paranoia 2, own custom rules, own ID range.
- **`payments`** — Core Rule Set at paranoia 3, GraphQL introspection block, a different ID range.
- Neither team can disable the org-wide rule from `GlobalWAFPlugin` or drop below Block mode.
- The cluster operator sees all violations centrally on `GlobalWAFValidationPolicy/production-requirements` status — one place to see which namespaces are non-compliant.

## Next steps

- [Reference](./reference.mdx) — CRDs, Rego validation, SecRule ID ranges, and status conditions.
- [Troubleshooting](./troubleshooting.mdx) — diagnose a policy blocked by a guardrail.
Loading