Skip to content

[DOCS#EV-7238]: Document Kibana Core Rule Set exceptions for Gateway WAF - #3110

Open
vara2504 wants to merge 3 commits into
tigera:mainfrom
vara2504:vara-EV-7238-kibana-waf-docs
Open

vara2504 wants to merge 3 commits into
tigera:mainfrom
vara2504:vara-EV-7238-kibana-waf-docs

Conversation

@vara2504

Copy link
Copy Markdown
Contributor

Product Version(s): Calico Enterprise (next)

Issue: https://tigera.atlassian.net/browse/EV-7238

Link to docs preview:

SME review:

  • An SME has approved this change.

DOCS review:

  • A member of the docs team has approved this change.

Additional information:
Kibana Discover returns 403 when a WAFPolicy with the Core Rule Set covers the Manager Ingress Gateway. Kibana sends Elasticsearch query DSL in its search bodies (read as SQL injection, rule 942100) and a url argument on many requests (read as SSRF, rules 931100/934110). This is a Core Rule Set false positive on a known application, not a product defect — so it is fixed with configuration, not code.

This PR documents the fix as a worked GlobalWAFPlugin example (a cluster-scoped plugin may tune baseline rules; a namespaced WAFPlugin cannot) on Write custom rules with plugins, and links it from the false-positive entry in Troubleshooting. Rule IDs were captured from the live WAF audit log (OWASP CRS 4.14.0, paranoia level 1). Replaces the rejected code change in tigera/calico-private#14398.

This change was drafted with AI assistance (Claude).

Merge checklist:

  • Deploy preview inspected wherever changes were made
  • Build completed successfully
  • Tests have passed

Kibana Discover returns 403 when a WAFPolicy with the Core Rule Set
covers the Manager gateway: the Elasticsearch query bodies read as SQL
injection and the url argument as SSRF. Add a worked GlobalWAFPlugin
example that removes those rules on the Kibana paths, and link it from
the false-positive troubleshooting section.
Copilot AI lite review requested due to automatic review settings October 14, 2026 20:28
@vara2504
vara2504 requested a review from a team as a code owner October 14, 2026 20:28
@netlify

netlify Bot commented Oct 14, 2026 •

Copy link
Copy Markdown

✅ Deploy Preview succeeded!

Built without sensitive environment variables

Name Link
🔨 Latest commit f67e004
🔍 Latest deploy log https://app.netlify.com/projects/tigera/deploys/6ad26fe8a2836c92c64f4df1
😎 Deploy Preview https://deploy-preview-3110--tigera.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.
Lighthouse
Lighthouse
1 paths audited
Performance: 72 (🔴 down 1 from production)
Accessibility: 98 (no change from production)
Best Practices: 92 (no change from production)
SEO: 100 (no change from production)
PWA: -
View the detailed breakdown and full score reports
🤖 Make changes Run an agent on this branch

To edit notification comments on pull requests, go to your Netlify project configuration.

@netlify

netlify Bot commented Oct 14, 2026 •

Copy link
Copy Markdown

✅ Deploy Preview for calico-docs-preview-next ready!

Name Link
🔨 Latest commit f67e004
🔍 Latest deploy log https://app.netlify.com/projects/calico-docs-preview-next/deploys/6ad26fe8fa72152e2af26052
😎 Deploy Preview https://deploy-preview-3110--calico-docs-preview-next.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.
🤖 Make changes Run an agent on this branch

To edit notification comments on pull requests, go to your Netlify project configuration.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

The documented exception has unresolved scope and security-bypass risks.

2 open findings
What changed in this PR

Documents Kibana Core Rule Set exceptions for Gateway WAF and links the guidance from troubleshooting.

Changes:

  • Adds a GlobalWAFPlugin example for Kibana exceptions.
  • Documents affected CRS rules and configuration guidance.
  • Links the example from troubleshooting.
File Summary
calico-enterprise/​threat/​gateway-waf/​write-custom-rules.mdx Adds Kibana exception configuration; unresolved concerns remain around scope, match breadth, request-variable selection, and plugin guidance consistency.
calico-enterprise/​threat/​gateway-waf/​troubleshooting.mdx Links false-positive troubleshooting guidance to the Kibana example.

🧠 Review effort: Lite


💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment on lines +161 to +163
SecRule REQUEST_FILENAME "@beginsWith /tigera-kibana/internal/" \
"id:100000001,phase:1,pass,nolog,t:none,t:urlDecodeUni,t:normalizePath,\
ctl:ruleRemoveTargetById=942100;ARGS"

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Validated on a live gateway — ctl:ruleRemoveTargetById can't subtract the parsed search-body args, so the fix is ctl:ruleRemoveById=942100 scoped to the exact bsearch path; Discover passes while SQLi stays 403 on every other path. Fixed in the latest push.

Comment on lines +175 to +177
plugins:
- kind: GlobalWAFPlugin
name: kibana-exceptions

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A namespaced WAFPolicy can't reference a GlobalWAFPlugin (EV-6753) and only a global plugin can tune a CRS baseline rule, so the default GlobalWAFPolicy merge is the only viable wiring — cluster-wide scope now documented. Fixed in the latest push.

ruleRemoveTargetById cannot subtract the parsed search-body arguments,
so remove rule 942100 scoped to the bsearch path instead; keep the url
exception as a surgical target removal. Validated on a live gateway:
Discover passes while SQLi and SSRF stay blocked on other paths. Also
document the cluster-wide scope of the global plugin.
before: |
# Discover sends the Elasticsearch query in the request body, which rule
# 942100 reads as SQL injection. Remove that rule for the search path only.
SecRule REQUEST_FILENAME "@beginsWith /tigera-kibana/internal/bsearch" \

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think these rules match on path only. So any route on any WAF-covered gateway that serves /tigera-kibana/... gets the exemption too. Could we pin them to the Manager hostname? The WASM filter fills SERVER_NAME from :authority, so a chain works.

One gotcha. Coraza runs a chain starter's ctl even when the rest of the chain fails. So the ctl has to sit on the last rule, not the first. Something like:

SecRule SERVER_NAME "@streq manager.example.com" \
  "id:100000001,phase:1,pass,nolog,t:none,t:lowercase,chain"
  SecRule REQUEST_FILENAME "@beginsWith /tigera-kibana/internal/bsearch" \
    "t:none,t:urlDecodeUni,t:normalizePath,ctl:ruleRemoveById=942100"
SecRule SERVER_NAME "@streq manager.example.com" \
  "id:100000002,phase:1,pass,nolog,t:none,t:lowercase,chain"
  SecRule REQUEST_FILENAME "@beginsWith /tigera-kibana/" \
    "t:none,t:urlDecodeUni,t:normalizePath,\
     ctl:ruleRemoveTargetById=931100;ARGS:url,\
     ctl:ruleRemoveTargetById=934110;ARGS:url"

Then add a line that tells readers to put in their own Manager spec.ingressGateway.hostname. The second rule in each chain must not carry an id. The merge-time chain check rejects it if it does.


Kibana sends an Elasticsearch query in the body of its search requests, and a `url` argument on many requests. With the Core Rule Set enabled, the WAF reads the query as SQL injection and the `url` argument as server-side request forgery, so Kibana Discover fails with HTTP 403. The Core Rule Set is working as designed — Kibana needs app-specific exceptions, the same way the Core Rule Set ships exceptions for applications such as phpMyAdmin and WordPress.

These exceptions adjust Core Rule Set baseline rules, so they belong in a `GlobalWAFPlugin`. A namespaced `WAFPlugin` cannot alter a baseline rule — the admission webhook rejects it with `ProtectedIDRemoval` — and a namespaced `WAFPolicy` cannot reference a `GlobalWAFPlugin`. A cluster-scoped `GlobalWAFPlugin`, loaded through the `default` `GlobalWAFPolicy`, is the only configuration that both tunes a baseline rule and reaches the gateway.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think "the only configuration that tunes a baseline rule" is right. A namespaced plugin can't remove or retarget ids in 900000-999999. Could we still give readers a namespaced option, with the cost spelled out? Some won't want a plugin that reaches the whole cluster.

The closest one is a WAFPolicy with action: Detect that targets the HTTPRoute calico-manager-route. It has to live in the Manager gateway's namespace. That's calico-system, unless Manager spec.ingressGateway.gatewayNamespace says otherwise. targetRefs only resolve in the policy's own namespace.

The catch: it stops blocking for the whole Manager UI, not just Kibana. The operator renders that route as one rule with no path match. So sectionName can't split Kibana off, and the operator would revert a hand-edited route. If that trade is too big to document, could we at least say plainly that no namespaced way drops single CRS rules?


:::note

The plugin is cluster-scoped: through the `default` `GlobalWAFPolicy` it reaches every `WAFPolicy`. The `/tigera-kibana/` path match keeps it inert on gateways that don't serve Kibana, but a second gateway serving the same paths receives the same exceptions. This wide reach is unavoidable — only a `GlobalWAFPlugin` may adjust a baseline rule — so keep every match scoped to the Kibana paths.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like "This wide reach is unavoidable" oversells it a bit. The plugin does get merged into every WAFPolicy in the cluster. The controller reads GlobalWAFPolicy/default each time it handles a policy. But with the hostname chain from the line 164 comment, it only fires for Manager traffic. Could we reword the note to say both things plainly? It loads on every WAF-covered gateway and route. The hostname and path match keep it to Kibana on the Manager gateway.

name: kibana-exceptions
```

If the cluster already has a `default` `GlobalWAFPolicy`, add the plugin to its existing `plugins` list rather than replacing the object.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think applying the GlobalWAFPolicy block as shown could undo settings on a cluster that already has a default. Say that object was made with kubectl apply. A second apply drops fields the new file leaves out. So a defaultAction: Block falls back to Detect for every policy that inherits it. Could we show a JSON patch that appends to spec.plugins for that case? The full YAML can stay for clusters with no default yet.


:::note

The rule IDs `100000001` and `100000002` sit above every reserved range. The search endpoint differs between Kibana versions — read the `tigera_secure_ee_waf` log index for your cluster to confirm which paths and rules fire, and add a path match per search endpoint rather than widening the prefix.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit, not blocking: might help to say why these ids are safe. Namespace id windows stop at 99,999,999. The protected ranges stop at 1,099,999. So ids from 100,000,000 up can't clash with a tenant's rules or the baseline. Could we add that? Then readers can pick their own ids for other apps.


:::

When a baseline rule misfires on a known application — for example, Kibana Discover returning `403` because the Core Rule Set reads its Elasticsearch query bodies as SQL injection — tune it with a cluster-scoped `GlobalWAFPlugin`, which may adjust baseline rules. See [Unblock Kibana Discover](./write-custom-rules.mdx#example-unblock-kibana-discover).

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we say here too that a GlobalWAFPlugin reaches every WAF-covered route in the cluster? Someone who reads only this line might not click through to the scope note..

Removing rule 942100 does not unblock real Kibana Discover. A Discover
search sends an arbitrary Elasticsearch query in the body; the Core Rule
Set scores it across many rules and blocks on the 949110 anomaly
aggregator, and the matching rules change from query to query. Skip
request-body inspection on the search path instead, which keeps URL,
header, and method inspection in force and stays correct across queries.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants