Skip to content

Commit 46271ba

Browse files
committed
Round 8 closers: _axis_hits helper, asymmetric-wildcard test, defensive guards
- Extract _axis_hits(mr_values_set, cell_value) — collapses the three '*' in mr_X / val in mr_X callsites in _split_rule_after_revoke. Semantically asymmetric (only the revoke side honors '*'); distinct from _dim_matches which is symmetric. - Defensive: skip URL-branch cells when mr['nonResourceURLs'] is empty — today unreachable (matcher gates non-empty), but harder to break later. - _strip_to_set docstring. - 2 new unit tests: _axis_hits wildcard semantics; asymmetric-wildcard revoke where only one dimension carries '*' (regression guard for the prior per-cell filter bug).
1 parent 74c4164 commit 46271ba

2 files changed

Lines changed: 33 additions & 8 deletions

File tree

provider-kubeconfig.py

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -254,9 +254,14 @@ def _rule_matches_revoke(self, existing_norm, revoke_norm_rule):
254254
return True
255255

256256
def _strip_to_set(self, existing_verbs, revoke_verbs):
257+
"""Verbs remaining after revoke, as a set; empty set means cell is dropped."""
257258
stripped = self._strip_verbs(existing_verbs, revoke_verbs)
258259
return set(stripped) if stripped else set()
259260

261+
def _axis_hits(self, mr_values_set, cell_value):
262+
"""Does a revoke rule's axis set target this cell? '*' in the set matches anything."""
263+
return "*" in mr_values_set or cell_value in mr_values_set
264+
260265
def _normalize_rule_list(self, rules):
261266
normalized = [self._normalize_rule(r) for r in rules]
262267
# Sort deterministically by all tuple fields for stable diffs.
@@ -834,10 +839,11 @@ def _split_rule_after_revoke(self, rule, matched_revoke_rules, retained_keys):
834839
cell_verbs = {url: set(existing_verbs) for url in existing_urls}
835840
for mr in matched_revoke_rules:
836841
mr_urls = set(mr["nonResourceURLs"])
837-
mr_urls_wild = "*" in mr_urls
842+
if not mr_urls:
843+
continue
838844
mr_verbs = mr["verbs"]
839845
for url in cell_verbs:
840-
if not mr_urls_wild and url not in mr_urls:
846+
if not self._axis_hits(mr_urls, url):
841847
continue
842848
cell_verbs[url] = self._strip_to_set(cell_verbs[url], mr_verbs)
843849
out_rules = []
@@ -868,17 +874,15 @@ def _split_rule_after_revoke(self, rule, matched_revoke_rules, retained_keys):
868874
for mr in matched_revoke_rules:
869875
mr_apis = set(mr["apiGroups"])
870876
mr_ress = set(mr["resources"])
871-
mr_apis_wild = "*" in mr_apis
872-
mr_ress_wild = "*" in mr_ress
873-
mr_names = set(mr["resourceNames"]) if mr["resourceNames"] else None
874-
mr_verbs = mr["verbs"]
875877
if not mr_apis or not mr_ress:
876878
continue
879+
mr_names = set(mr["resourceNames"]) if mr["resourceNames"] else None
880+
mr_verbs = mr["verbs"]
877881
for cell in cell_verbs:
878882
ag, res, rn = cell
879-
if not mr_apis_wild and ag not in mr_apis:
883+
if not self._axis_hits(mr_apis, ag):
880884
continue
881-
if not mr_ress_wild and res not in mr_ress:
885+
if not self._axis_hits(mr_ress, res):
882886
continue
883887
if mr_names is not None and rn not in mr_names:
884888
continue

tests/test_provider_kubeconfig.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -428,6 +428,27 @@ def test_split_rule_url_path_output_ordering_deterministic(self):
428428
[(sorted(r["verbs"]), sorted(r["nonResourceURLs"])) for r in out_b],
429429
)
430430

431+
def test_axis_hits_wildcard_semantics(self):
432+
self.assertTrue(self.generator._axis_hits({"*"}, "anything"))
433+
self.assertTrue(self.generator._axis_hits({"pods"}, "pods"))
434+
self.assertFalse(self.generator._axis_hits({"pods"}, "services"))
435+
self.assertFalse(self.generator._axis_hits(set(), "pods"))
436+
437+
def test_split_rule_asymmetric_wildcard_revoke(self):
438+
"""Revoke wildcards one dimension only — existing rules on the specific dim trim."""
439+
rule = {
440+
"apiGroups": [""],
441+
"resources": ["pods", "configmaps"],
442+
"verbs": ["get", "delete"],
443+
}
444+
matched = [self.generator._normalize_rule(
445+
{"apiGroups": ["*"], "resources": ["pods"], "verbs": ["delete"]}
446+
)]
447+
result = self.generator._split_rule_after_revoke(rule, matched, set())
448+
by_res = {tuple(r["resources"]): sorted(r["verbs"]) for r in result}
449+
self.assertEqual(by_res[("pods",)], ["get"])
450+
self.assertEqual(by_res[("configmaps",)], ["delete", "get"])
451+
431452
def test_permission_fixture_files_parse_json_and_yaml(self):
432453
"""Fixture files in tests/permission_files should load and parse via script helpers."""
433454
fixture_dir = os.path.join(ROOT, "tests", "permission_files")

0 commit comments

Comments
 (0)