Repository navigation
feat(soc): mirror the lab bridge to Zeek with tc, and a gauge that proves it (#437) - #762
Merged
Merged
Conversation
…oves it (#437) #437 said the mirror needs Open vSwitch. It does not: a clsact matchall mirred filter on each vmbr0 port's ingress copies every frame once, and vmbr0 stays a Linux bridge. ADR-0064 records the choice and what it rejects. - scripts/zeek-mirror.sh keeps the mirror on every port, each minute, matching the capture tap by name. A sensor restart leaves filters aimed at `device *`, mirroring nothing with no error; reproduced in a namespace. - scripts/collect-zeek-mirror-state.sh reads the same state read-only and publishes homelab_zeek_mirror_active. ZeekMirrorInactive and ZeekMirrorStateStale have promtool tests. - stacks/sensor: Zeek 9.0.0 and Alloy on fenrir (VMID 190), logs to alexander's Loki and never to VLAN 99. No secrets. - build-the-sensor-guest.md, including the reboot proof that closes #437. Refs #437 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Gerrrt
enabled auto-merge (squash)
September 30, 2026 18:18
main took ADR-0064 through ADR-0067 meanwhile, so the tc mirror's ADR is renumbered to ADR-0068, and only this branch's references move with it; main's ADR-0064 (Immich's library) is untouched. Conflicts in README.md, docs/changelog.md and docs/observability.md keep both sides, and the rule counts are re-derived for 107 metric-based rules. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Gerrrt
added a commit
that referenced
this pull request
Sep 30, 2026
… zeek.alloy as alloy fmt writes it (#437) (#767) * fix(soc): write the tc fixture's tabs as \t, so editorconfig passes (#437) The self-test fixture in both Zeek mirror scripts is tc's own output, whose action lines start with a literal tab. editorconfig-checker read those as tab indentation and failed lint on main. The fixture is now built with $'...' quoting and \t, byte-identical to the one it replaces. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * style(sensor): zeek.alloy as alloy fmt writes it, so Validate configs passes (#437) alloy fmt --test failed on main since #762 on one inline object: { log_type = "" } is written {log_type = ""}. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Your Name <you@example.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Refs #437. This does not close it: the issue closes on the reboot proof in
build-the-sensor-guest.md§7, afterfenriris built.The decision:
tc, not Open vSwitchThe issue says port mirroring needs OVS. It doesn't.
Saruman'svmbr0is a Linux bridge oneno1, andtcis already installed. Aclsact+matchall+mirred egress mirrorfilter on each port's ingress, plus one onvmbr0egress, copies every frame on the bridge exactly once. Sovmbr0isn't converted,10.0.30.110never moves, and no VLAN-capable bridge goes under the attack segment.ADR-0064 records this and rejects four alternatives: OVS, a SPAN from
neoor the CRS326, hub mode, and Zeek onodin. ADR-0039 gets a NOTE;roadmap.mdandsecurity.mdare updated.What ships
scripts/zeek-mirror.shplus a one-minute timer on the hypervisor. It keeps every port mirrored totap190i1, matched by name. When the sensor is absent it removes the filters.scripts/collect-zeek-mirror-state.shplus a five-minute timer. This is the read-only collector behindhomelab_zeek_mirror_active. It reads 1 only when all four hold:If VM 190 doesn't exist, it publishes nothing. That way installing it before the guest is built can't page.
Installer: both are rows in
install-agent-collectors.sh.-in the.promcolumn marks the non-collector.ZeekMirrorInactiveandZeekMirrorStateStaleinhost.rules.yaml, each with a firing and a quiet promtool case. The alert counts are updated wherecheck_docs.pyasserts them.stacks/sensor:zeek/zeek:9.0.0with host networking,NET_RAW/NET_ADMINonly and a read-only root, capturing onfenrir's second NIC, which sits alone on a port-lessvmbr1;alexander's Loki asjob="zeek", one stream perlog_type;make up(recorded insecurity.mdand.sops.yaml, like media).docs/runbooks/build-the-sensor-guest.mdand afenrirrow marked Not built yet inarchitecture.md.Found while building it
A sensor restart recreates
tap190i1with a new ifindex. Every existing filter then shows asEgress Mirror to device *and mirrors nothing, with no error anywhere. That's why the ensurer runs every minute and matches the tap by name rather than just checking that a filter exists. It's also why the gauge checks that packets actually moved.Verification
vmbr0). This covered:active=1with traffic flowing, andactive=0with a flat counter;mirrored=0/3with the dangling*;self-tests.sh: 223 fixtures across 17 suites, including 20 new collector fixtures and 5 ensurer fixtures.promtool check rulesandpromtool test ruleson the observability stack (v3.15.0, the pinned version).check_docs.py,check_sops_rules.py,check_image_pins.py,check_compose_health.py,yamllint --strict,shellcheck,markdownlint-cli2.docker compose configand the image probes. CI covers both.local.zeekwas checked against Zeek v9.0.0's script tree, and the runbook parses it on the pinned image (zeek -a) before first start.Left for the build
vmbr1and VM 190, deploy the stack, install both units.ZeekMirrorInactivefires.🤖 Generated with Claude Code