From 9952433d56e5474e0869f0af5996e3fc9b6e7018 Mon Sep 17 00:00:00 2001 From: Thanatat Tamtan Date: Sat, 11 Jul 2026 08:16:52 +0700 Subject: [PATCH 1/2] =?UTF-8?q?docs(waf):=20Firewall=20events=20section=20?= =?UTF-8?q?=E2=80=94=20sampling=20caps,=203-day=20retention,=20privacy=20n?= =?UTF-8?q?ote,=20API/CLI?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Documents the sampled WAF match events (waf.events): what an event row shows, per-ingress-instance sampling caps (10/min per rule, blocks exempt; 60/min per zone), 3-day retention with an explicit privacy note on client IPs (no query strings, headers, cookies, or bodies), that the metrics charts remain the exact counts, filters + keyset pagination, and the curl/CLI usage. Screenshot is left as an escaped-shortcode placeholder (the shot shortcode fails the build on a missing image); capture it via scripts/screenshots/refresh.sh once the console section ships. --- content/networking/waf.md | 62 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) diff --git a/content/networking/waf.md b/content/networking/waf.md index 2e2b6d0..b4523f6 100644 --- a/content/networking/waf.md +++ b/content/networking/waf.md @@ -175,3 +175,65 @@ curl https://api.deploys.app/waf.limitMetrics \ -d '{ "project": "acme", "location": "gke.cluster-rcf2", "timeRange": "1d" }' ``` + +## Firewall events + +Metrics tell you *how often* a rule fired; events show you *what it caught*. +The metrics page keeps a table of recent match samples under the charts — each +row is one concrete request a rule matched: the time, the action taken (and +the response status for blocks), the rule, the client IP and its country, and +the request method, host, and path. That's usually enough to go from "this +rule matched 4,000 times" to "it's blocking `POST /wp-login.php` from one +network" without any log access. + + + +Filter the table by rule or by action (`block`, `log`, `allow`), and page +back through history with **Load more**. + +### Sampling and retention + +Events are **samples, not a request log**. Each ingress instance keeps at +most **10 events per minute per rule** — blocked requests are exempt from +that per-rule cap, since blocks are what you came to inspect — and at most +**60 events per minute per firewall zone**, which bounds everything, +blocks included. Under a flood you still get enough examples to recognize +the pattern (same IP? same path? same country?) without recording every +request. + +Because events are sampled, **never count them** — the metrics charts above +count every match and remain the source of truth; events are the examples. + +Events are kept for **3 days**, then deleted. + +{{< callout type="note" >}} +**Privacy.** An event carries the client IP, which is personal data. That is +why retention is deliberately short — 3 days, versus 30 for the anonymous +match counters — and why events never include query strings, request headers, +cookies, or bodies: only the method, host, and path are recorded. +{{< /callout >}} + +### API and CLI + +The table is served by `waf.events` — newest first, with optional `ruleId` +and `action` filters: + +```bash +curl https://api.deploys.app/waf.events \ + -H "Authorization: Bearer $DEPLOYS_TOKEN" \ + -d '{ "project": "acme", "location": "gke.cluster-rcf2", + "action": "block" }' +``` + +The result carries up to `limit` items (default 50, max 200) and a `next` +cursor; pass it back as `before` to fetch the next page, until `next` comes +back empty. + +The same data from the CLI: + +```bash +deploys waf events --project acme --location gke.cluster-rcf2 +``` From a2f9b42729e10a56d0a8248f434b786ab953b323 Mon Sep 17 00:00:00 2001 From: Thanatat Tamtan Date: Sat, 11 Jul 2026 08:38:13 +0700 Subject: [PATCH 2/2] =?UTF-8?q?docs(waf):=20review=20fixes=20=E2=80=94=20d?= =?UTF-8?q?rop=20status=20claim,=20add=20waf-events=20capture=20entry,=20c?= =?UTF-8?q?larify=20screenshot=20TODO?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - The console events table has no response-status column (Time, Action, Rule, IP, Country, Method, Host, Path) — remove the parenthetical claiming blocks show their status. - capture.mjs gains the waf-events entry per SPEC-waf-events.md §H, so resolving the screenshot TODO after the console section merges needs no further script change. - The TODO now says to strip the /* */ Hugo-escape markers, not just uncomment — uncommenting alone would render the shortcode literally. --- content/networking/waf.md | 12 +++++++----- scripts/screenshots/capture.mjs | 1 + 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/content/networking/waf.md b/content/networking/waf.md index b4523f6..09cf86c 100644 --- a/content/networking/waf.md +++ b/content/networking/waf.md @@ -180,14 +180,16 @@ curl https://api.deploys.app/waf.limitMetrics \ Metrics tell you *how often* a rule fired; events show you *what it caught*. The metrics page keeps a table of recent match samples under the charts — each -row is one concrete request a rule matched: the time, the action taken (and -the response status for blocks), the rule, the client IP and its country, and -the request method, host, and path. That's usually enough to go from "this +row is one concrete request a rule matched: the time, the action taken, the +rule, the client IP and its country, and the request method, host, and path. That's usually enough to go from "this rule matched 4,000 times" to "it's blocking `POST /wp-login.php` from one network" without any log access. - diff --git a/scripts/screenshots/capture.mjs b/scripts/screenshots/capture.mjs index 3e8886b..91576c1 100644 --- a/scripts/screenshots/capture.mjs +++ b/scripts/screenshots/capture.mjs @@ -48,6 +48,7 @@ const screens = [ ['billing-report', `/billing/report`], ['project-list', `/project`], ['waf-list', `/waf?${P}`], + ['waf-events', `/waf/metrics?${P}&${LOC}`, { h: 1500 }], ['cache-list', `/cache?${P}`], ['cache-manage', `/cache/manage?${P}&${LOC}`, { h: 1150 }], ['cache-metrics', `/cache/metrics?${P}&${LOC}`, { h: 1150 }],