Skip to main content
Calico Cloud documentation

Set a cluster-wide baseline for Gateway WAF

note

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 — GlobalWAFPolicy is cluster-scoped.

Set the baseline​

  1. Create the singleton GlobalWAFPolicy. It must be named default — other names are rejected by the admission webhook. Start in Detect mode: it logs matches without blocking, so you can measure false positives before switching to Block:

    apiVersion: applicationlayer.projectcalico.org/v3
    kind: GlobalWAFPolicy
    metadata:
    name: default
    spec:
    defaultAction: Detect # start here; promote to Block after validating false-positive rate
    coreRuleSet:
    state: Enabled
    paranoiaLevel: 1

    Apply it with kubectl apply -f globalwafpolicy.yaml.

  2. Pick a paranoia level. Higher catches more attacks at the cost of more false positives:

    LevelPostureTypical use
    1Balanced — low false-positive rateGeneral-purpose default; a safe starting point
    2Moderate — catches common evasion patternsAPIs that handle sensitive data
    3Strict — flags most suspicious patternsHigh-value or compliance-regulated endpoints
    4Paranoid — near-zero toleranceExtremely high-security targets; expect false positives
    tip

    Start at level 1 cluster-wide; let application operators raise it per namespace, and enforce a minimum later via a validation policy.

  3. 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.

  1. Create the GlobalWAFPlugin. Rules use the four lifecycle slots described in Write custom rules:

    apiVersion: applicationlayer.projectcalico.org/v3
    kind: GlobalWAFPlugin
    metadata:
    name: org-baseline
    spec:
    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'"
  2. Reference it from GlobalWAFPolicy.spec.plugins[] — for example plugins: [{kind: GlobalWAFPlugin, name: org-baseline}]. plugins[] is ordered by array position, up to 64 entries; WAFPlugin entries can be added here too.

Verify the baseline​

  1. Confirm the policy is accepted:

    kubectl get globalwafpolicy default
    NAME ACCEPTED AGE
    default 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​