sonic: enforce per-key ownership of ACL tables#2370
Open
ideaship wants to merge 1 commit into
Open
Conversation
ACL_TABLE and ACL_RULE are owned by more than one control-plane section helper (SSH, SNMP, gNMI). The decided ownership model (generate_sonic_config docstring, locked by #2297 and the static classification guard from #2339) settles whole-table ownership but not how several helpers share one owned table. The open ACL PRs each rebind the whole table -- config["ACL_TABLE"] = {...} -- so whichever helper runs second drops the first's entries, leaving SSH or SNMP/gNMI unrestricted. Each PR passes its own tests because neither calls the other helper, so the collision is invisible when the PRs are reviewed in isolation. The fix is a rule that co-owned tables must be merged per named entry, never rebound. Following the approach of the #2339 classification guard, the rule is locked in code rather than left to review, so a divergent PR fails CI before it can merge. Add MULTI_OWNER_OWNED_TABLE_KEYS listing ACL_TABLE/ACL_RULE, and a static guard (TestMultiOwnerTableGuard) that parses the generator source and fails on every form that rebinds a listed table wholesale on config: config["X"] = <expr>, config.update({"X": ...}), config |= {"X": ...}, and config = {**config, "X": ...}. These mirror the wholesale-write forms the #2339 mutation backstop recognizes, so no analyzable write can rebind a listed table without being caught. Forms that mutate the table dict in place are left alone: a nested-subscript write config["X"]["k"] = ..., config["X"].update(...), config["X"] |= ..., and config.setdefault("X", {}). Synthetic-source detector tests prove the guard catches each rebind form and passes the in-place forms, so it cannot pass vacuously. The tables are listed ahead of the ACL helpers that introduce them, so the first such helper to land is forced onto the per-key pattern rather than establishing the unsafe one. Two invariants tie the list to the rest of the model: a referenced multi-owner table must also be in OWNED_TABLE_KEYS (so the central up-front drop clears it and no stale entry survives the merge), and multi-owner tables must not be inherited or image-consumed (those regimes are not dropped). Both hold now and after the ACL helpers add ACL_TABLE/ACL_RULE to ON_DEMAND_OWNED. Assisted-by: Claude:claude-fable-5 Signed-off-by: Roger Luethi <luethi@osism.tech>
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
ACL_TABLEandACL_RULEare owned by more than one control-plane section helper (SSH, SNMP, gNMI). The ownership model settled in thegenerate_sonic_configdocstring covers whole-table ownership but not how several helpers share one owned table. The open ACL PRs each rebind the whole table —config["ACL_TABLE"] = {...}— so whichever helper runs second drops the first's entries, leaving SSH or SNMP/gNMI unrestricted. Each PR passes its own tests because neither calls the other helper, so the collision is invisible when the PRs are reviewed in isolation.Change
Co-owned tables must be merged per named entry, never rebound. The rule is locked in code rather than left to review, so a divergent PR fails CI before it can merge:
MULTI_OWNER_OWNED_TABLE_KEYSlistsACL_TABLE/ACL_RULE.TestMultiOwnerTableGuard) parses the generator source and fails on every form that rebinds a listed table wholesale onconfig:config["X"] = <expr>,config.update({"X": ...}),config |= {"X": ...}, andconfig = {**config, "X": ...}. These mirror the wholesale-write forms the existing mutation backstop recognizes, so no analyzable write can rebind a listed table without being caught.config["X"]["k"] = ...,config["X"].update(...),config["X"] |= ..., andconfig.setdefault("X", {}). Synthetic-source detector tests prove the guard catches each rebind form and passes the in-place forms, so it cannot pass vacuously.The tables are listed ahead of the helpers that introduce them, so the first ACL helper to land is forced onto the per-key pattern rather than establishing the unsafe one. Two invariants tie the list to the rest of the model: a referenced multi-owner table must also be in
OWNED_TABLE_KEYS(so the central up-front drop clears it and no stale entry survives the merge), and multi-owner tables must not be inherited or image-consumed (those regimes are not dropped). Both hold now and after the ACL helpers addACL_TABLE/ACL_RULEtoON_DEMAND_OWNED_TABLE_KEYS.Note for the ACL PRs
This guard is dormant until a helper references the tables. When the control-plane ACL work lands, each helper must merge its own keys and the PR must add
ACL_TABLE/ACL_RULEtoON_DEMAND_OWNED_TABLE_KEYSin the same change (thetest_referenced_multi_owner_tables_are_ownedinvariant enforces this).Related