Set a cluster-wide baseline for Gateway WAF
This feature is tech preview. Tech preview features may be subject to significant changes before they become GA.
The baseline is the cluster-wide default every namespace inherits — action mode, Core Rule Set, and paranoia level. Set it once so every Gateway starts protected and teams tune from a known-safe starting point.
Prerequisites
- Gateway WAF is enabled. See Enable Gateway WAF.
- Cluster-admin rights —
GlobalWAFPolicyis cluster-scoped.
Set the baseline
-
Create the singleton
GlobalWAFPolicy. It must be nameddefault— other names are rejected by the admission webhook. Start inDetectmode: it logs matches without blocking, so you can measure false positives before switching toBlock:apiVersion: applicationlayer.projectcalico.org/v3kind: GlobalWAFPolicymetadata:name: defaultspec:defaultAction: Detect # start here; promote to Block after validating false-positive ratecoreRuleSet:state: EnabledparanoiaLevel: 1Apply it with
kubectl apply -f globalwafpolicy.yaml. -
Pick a paranoia level. Higher catches more attacks at the cost of more false positives:
Level Posture Typical use 1 Balanced — low false-positive rate General-purpose default; a safe starting point 2 Moderate — catches common evasion patterns APIs that handle sensitive data 3 Strict — flags most suspicious patterns High-value or compliance-regulated endpoints 4 Paranoid — near-zero tolerance Extremely high-security targets; expect false positives tipStart at level 1 cluster-wide; let application operators raise it per namespace, and enforce a minimum later via a validation policy.
-
Once you've validated the false-positive rate, set
defaultAction: Block. Calico Cloud then returns HTTP 403 for matching requests.
Add organization-wide rules with a GlobalWAFPlugin (optional)
A GlobalWAFPlugin holds custom ModSecurity rules applied cluster-wide via the global policy's plugins[] list — for a security baseline every namespace inherits.
-
Create the
GlobalWAFPlugin. Rules use the four lifecycle slots described in Write custom rules:apiVersion: applicationlayer.projectcalico.org/v3kind: GlobalWAFPluginmetadata:name: org-baselinespec:description: "Organization-wide security baseline rules"config: |SecAction "id:800001,phase:1,pass,nolog,setvar:tx.enforce_https=1"before: |SecRule REQUEST_HEADERS:X-Forwarded-Proto "!@streq https" \"id:800002,phase:1,deny,status:403,msg:'HTTPS required',chain"SecRule TX:ENFORCE_HTTPS "@eq 1" ""rules: |SecRule REQUEST_HEADERS:X-Request-ID "@rx ^$" \"id:800003,phase:1,deny,status:400,msg:'Missing X-Request-ID header'"after: |# Cluster operator's after slot runs last in the merge order.SecRule TX:BLOCKING_ANOMALY_SCORE "@gt 0" \"id:800099,phase:5,pass,log,msg:'Request blocked by WAF'" -
Reference it from
GlobalWAFPolicy.spec.plugins[]— for exampleplugins: [{kind: GlobalWAFPlugin, name: org-baseline}].plugins[]is ordered by array position, up to 64 entries;WAFPluginentries can be added here too.
Verify the baseline
-
Confirm the policy is accepted:
kubectl get globalwafpolicy defaultNAME ACCEPTED AGEdefault True 30s
Detail lives in .status.conditions (kubectl get globalwafpolicy default -o yaml): Accepted=True reason Accepted = programmed; Accepted=False reason ValidationFailed = a validation policy blocked it (the message describes the violation). See Status conditions.
Every namespace with a WAFPolicy inherits defaultAction, coreRuleSet.state, and coreRuleSet.paranoiaLevel from GlobalWAFPolicy unless it overrides them:
GlobalWAFPolicy (baseline)
└─ WAFPolicy in namespace payments → overrides action: Block, paranoiaLevel: 2
└─ WAFPolicy in namespace team-a → inherits everything from GlobalWAFPolicy
Next steps
- Attach WAF to a Gateway or route — turn on inspection for a target.
- Write custom rules with plugins — add app-specific detections and exceptions.
- Validate configuration with Rego — enforce compliance guardrails on the merged config.