fix: apply local security_rules to the kernel firewall on initial load#452
Merged
Conversation
In standalone (no-API-key) mode two issues stopped access rules loaded
from a local security_rules.yaml from ever reaching the XDP/nftables
firewall:
1. Config.created_at / updated_at / last_modified were required fields, so
a hand-written local rules file failed to deserialize ("missing field
created_at") and was silently ignored. These are API-envelope metadata,
not something a local author supplies — mark them #[serde(default)] so a
minimal local file (access_rules + waf_rules) parses.
2. The initial local-config load called apply_rules_from_global_with_state
— which reads global_config() — without first calling set_global_config.
The block/allow IPs therefore diffed against an empty config and were
never programmed into the banned_ips map; the rules only took effect
after a later file-change reload (whose path does promote). Extract the
initial-load logic into apply_initial_local_config and promote the
freshly-loaded config to the global slot before applying, matching the
reload path.
Tests: config_parses_without_metadata_timestamps,
initial_local_config_is_promoted_to_global.
Add a regression test that a local security_rules.yaml is programmed into the kernel XDP banned_ips map on the agent's INITIAL load (no file re-touch), plus a workflow to run it on amd64 and arm64. - scripts/local-rules-firewall-test.sh: runs the agent against an isolated `dummy` interface with a minimal rules file (no API metadata timestamps) and asserts the blocked IP appears in banned_ips via bpftool. No traffic or VM emulation needed; asserts the kernel map directly. - .github/workflows/firewall-integration.yaml: build + run the test. Guards both halves of the local-config fix (file parses without the API metadata; rules reach the kernel firewall on initial load).
- drop arm64 job: arm runners have no binary-arm64 apt packages for the build deps (arm64 build coverage lives in build.yaml's container build); the tested behaviour is arch-independent config logic. - install linux-tools-$(uname -r) so bpftool's kernel-specific binary is present (the generic package is only a wrapper); verify with 'bpftool version'.
The apt bpftool is a wrapper that execs a kernel-version-specific binary, which isn't published for the GitHub runner's kernel. Download the static upstream bpftool (sha256-verified) instead, and let the test honor a BPFTOOL override. Validated: test passes with the static binary.
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.
Problem
In standalone (no-API-key) mode, access rules loaded from a local
security_rules.yamlnever reached the XDP/nftables firewall. Two causes:Parse failure on a hand-written file.
Config.created_at/updated_at/last_modifiedwere required (noserdedefault), so aminimal local rules file failed to deserialize (
missing field created_at) and was silently dropped. These are API-envelope metadata,not fields a local author provides.
Rules never programmed on initial load. The initial local-config
load called
apply_rules_from_global_with_state— which readsglobal_config()— without first callingset_global_config. Theblock/allow IPs diffed against an empty config and were never written to
the
banned_ipsmap. Rules only took effect after a subsequentfile-change reload, whose path already promotes to the global slot.
Net effect: a static
block.ipsentry silently did nothing on a freshstart.
Fix
synapse-config: markcreated_at/updated_at/last_modified#[serde(default)]so a local file with justaccess_rules+waf_rulesparses.
synapse-worker: extract the initial local-load logic intoapply_initial_local_configand callset_global_configbefore applying,so the loaded rules are programmed into the firewall on initial load —
matching the reload path.
Tests
config_parses_without_metadata_timestampsinitial_local_config_is_promoted_to_globalcargo fmt/cargo clippy --deny warningsclean on both crates.