Repository navigation
feat(ids): label Suricata alerts by interface, and write the Degens runbook (#89) - #242
Conversation
…nterface's runbook (#89) Suricata's syslog line does not say which interface saw the alert, and pfSense sends every interface's alerts under the same tag. With a second interface, every rule and panel would have merged them silently — the exact thing the issue says must not happen. The one per-interface setting that reaches the wire is the interface's Log Facility, so syslog.alloy now maps it to the same `interface` label filterlog carries: local1 → igc0.20 (the package default, Skids), local2 → igc0.10 (Degens, set when it is added). An unmapped facility gets no label rather than a wrong one. Verified from pfSense source rather than assumed: the remote System Events selector drops local0/3/4/7, so an interface on one of those would alert on the box and never reach Loki. The runbook says which facilities are safe. Both Loki rules now aggregate by interface and name it in the summary; thresholds unchanged. The five Suricata panels honour the $interface picker, whose query widens from filterlog to the whole network source. The runbook gains §6 for Degens, and §5 records the signature that is 79% of Skids' first fortnight (sid 2210044, stream-events, a spread of devices to :443) — measured, and deliberately not suppressed until the second interface can say whether it is the Wi-Fi or the devices. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
There was a problem hiding this comment.
🟡 Changes recommended
The dashboard’s explanatory text contradicts the new $interface filtering behavior (unlabeled Suricata streams would be hidden), and alert summaries should explicitly handle missing interface to avoid ambiguous notifications.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Pull request overview
This PR prepares the observability and runbook plumbing needed to enable Suricata on a second pfSense interface (Degens/VLAN 10) without merging alerts/panels across interfaces, by deriving a consistent interface label for Suricata alerts and propagating it through Loki rules, Grafana, and docs.
Changes:
- Add
interfacelabeling for Suricata alerts insyslog.alloy(facility → interface mapping). - Update Loki alerting rules and Grafana Suricata panels to aggregate/filter by
interface. - Expand the Suricata enablement runbook with a Degens (VLAN 10) procedure and clarify facility/priority implications.
File summaries
| File | Description |
|---|---|
| stacks/observability/alloy/syslog.alloy | Derives interface for Suricata alerts from syslog facility before parsing stages. |
| stacks/observability/loki/rules/security.rules.yaml | Aggregates Suricata alerts by interface and includes interface in summaries. |
| stacks/observability/grafana/dashboards/security-overview.json | Updates interface variable query and makes Suricata panels honor $interface; expands explanatory text. |
| docs/runbooks/enable-suricata.md | Updates facility guidance and adds a detailed Degens enablement/verification section. |
| docs/observability.md | Fixes doc reference from config.alloy to syslog.alloy. |
Review details
Suppressed comments (1)
stacks/observability/loki/rules/security.rules.yaml:311
- Same as above: if
interfaceis unset, the storm alert summary becomes hard to interpret. Default to an explicit placeholder so that an unlabeled Suricata stream is immediately obvious from the alert notification.
summary: "{{ $value }} Suricata alerts on {{ $labels.interface }} in 10 minutes"
- Files reviewed: 5/5 changed files
- Comments generated: 2
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
…nd stop claiming hidden streams are visible Review on #242. An unmapped facility leaves `interface` unset on purpose, but an alert summary that then reads "Suricata priority-1 on : ..." hides the very fault it should point at. Both summaries now name the case. And the dashboard text said such a stream would show up with an empty `interface`, when every Suricata panel filters on `$interface` with `.+` for All — it is hidden, not mislabelled, and the runbook's unfiltered query is what shows it. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
What changed
Part one of two for #89 — everything that has to be live before Degens (VLAN 10) is enabled.
syslog.alloy: Suricata alerts get aninterfacelabel derived from the syslog facility (local1→igc0.20,local2→igc0.10). Same name and vocabulary as filterlog's. Unmapped facility → no label, visibly absent.security.rules.yaml:SuricataHighPriorityAlertandSuricataAlertStormaggregate by interface and name it in the summary. Thresholds unchanged; rule count unchanged (13).security-overview.json: the five Suricata panels honour$interface; the variable's query widens to{source="network"}; the text panel explains the label's two provenances. Panel count unchanged (140).enable-suricata.md: §2's facility note is rewritten (it said facility was harmless; it is now load-bearing), §5 records the signature that is 79% of Skids' first fortnight, and a new §6 is the Degens procedure. Two staleconfig.alloyreferences fixed (here and inobservability.md).Why
The alert line carries no interface and pfSense hardcodes the syslog identity to
suricatafor every interface, so two interfaces on today's pipeline would be indistinguishable — every rule and panel would merge them, which is precisely what the issue's "one interface at a time" is meant to prevent. The facility is the only per-interface setting that reaches the wire.Measured before designing (Loki, 14 days): Skids at 190–357 alerts/day, all priority 3, none priority 1 or 2,
2200121gone, and 2,337 of 2,949 are sid2210044from a spread of devices to:443. Recorded in §5, not suppressed: at two per ten minutes it buries nothing, and whether Degens shows the same share is the question the second interface answers.Verified from pfSense source, not assumed: the remote System Events selector drops
local0,local3,local4andlocal7, so an interface on one of those would alert locally and never reach Loki. §6 says which facilities are safe.Two costs, stated: the dashboard's
.+all-value drops the pre-label Skids history, so the 24h stats read low for a day after the Alloy restart (30-day retention ages it out); and a ruleset update matching ordinary traffic on both segments at ~101 each no longer fires the storm rule where a global sum would have.Two things found on the way, neither fixed here: two of the top sources for
2210044(10.0.20.110,10.0.20.105) are not innetwork.md; and the Suricata engine-log checkbox, if ever ticked, would arrive asapp="suricata"with no classification (§6 says leave it off).Blast radius
secrets/*.sops.yamlVerification
make validatepasses — 13 Loki rules and 28 dashboard expressions evaluated by the ruler, 140 panels, docs counts agreemake up, wait ten minutes, thensum by (interface) (count_over_time({app="suricata"}[10m]))must return exactly one row,igc0.20. Degens is not enabled until that passes. Part two (present-tense docs, roadmap Done entry) follows after several days of Degens.🤖 Generated with Claude Code