Move rules under the policy directory their path belongs to

This commit is contained in:
2026-09-27 18:07:43 +10:00
parent b944c61e7e
commit 848502c72e
13 changed files with 112 additions and 87 deletions
+3 -30
View File
@@ -11,21 +11,6 @@ REPO_ROOT = Path(__file__).resolve().parent.parent
# kv-v2 inserts one of these directly after the mount; it is not part of the scope.
KV_API_SEGMENTS = {"data", "metadata", "delete", "undelete", "destroy"}
ALLOWED = {
("policies/global-root.yaml", "*"), # root policy
("policies/gpg/admin.yaml", "sys/plugins/catalog/secret/vault-plugin-secrets-gpg"), # plugin catalog registration
("policies/kv/service/terraform/authentik.yaml", "kv/data/kubernetes/namespace/+/default/oauth-credentials"), # terraform writes k8s namespace secrets
("policies/kv/service/terraform/authentik.yaml", "kv/data/kubernetes/namespace/logging/default/vlogs-oauth-credentials"), # terraform writes k8s namespace secrets
("policies/kv/service/terraform/enc-encapi-environment.yaml", "kv/data/kubernetes/namespace/encapi/default/environment"), # terraform writes k8s namespace secrets
("policies/kv/service/terraform/rancher.yaml", "kv/data/kubernetes/namespace/cattle-system/default/oauth-credentials"), # terraform writes k8s namespace secrets
("policies/identity/group/admin.yaml", "identity/group-alias"), # group-alias endpoint for the same groups
("policies/identity/group/admin.yaml", "identity/group-alias/*"), # group-alias endpoint for the same groups
("policies/identity/group/admin.yaml", "identity/lookup/group"), # group lookup endpoint
("policies/sys/policy/admin.yaml", "sys/policies/acl"), # dir is policy, API path is policies
("policies/sys/policy/admin.yaml", "sys/policies/acl/*"), # dir is policy, API path is policies
("policies/sys/mounts/admin.yaml", "sys/mounts-tune/*"), # sibling API path of the mounts endpoint
}
class Rule(BaseModel):
model_config = ConfigDict(extra="forbid")
@@ -49,12 +34,9 @@ def policy_files():
def escaping_scope(policy_file, rule_path):
"""The policy directory a rule path reaches outside of, or None when in scope."""
if (policy_file, rule_path) in ALLOWED:
return None
parts = PurePosixPath(policy_file).parts
below_policies = PurePosixPath(*parts[parts.index("policies") + 1:])
# a policy at the policies/ root has no directory, so its own name is the scope
scope = below_policies.parent if below_policies.parent.parts else PurePosixPath(below_policies.stem)
# a policy at the policies/ root is located at the root, so its scope is the whole tree
scope = PurePosixPath(*parts[parts.index("policies") + 1:]).parent
path = PurePosixPath(rule_path)
if len(path.parts) > 1 and path.parts[1] in KV_API_SEGMENTS:
path = PurePosixPath(path.parts[0], *path.parts[2:])
@@ -94,6 +76,7 @@ class RuleScopeTests(unittest.TestCase):
("policies/kv/service/authentik/oidc-vault/read.yaml", "kv/data/service/authentik/oidc-vault"),
("policies/kv/service/vault/read.yaml", "kv/data/service/vault/+/+/auth_backend/*"),
("policies/kubernetes/au/admin.yaml", "kubernetes/au/+/config"),
("policies/global-root.yaml", "*"), # a root-level policy is scoped to the whole tree
]:
with self.subTest(policy=policy_file, rule=rule_path):
self.assertIsNone(escaping_scope(policy_file, rule_path))
@@ -107,16 +90,6 @@ class RuleScopeTests(unittest.TestCase):
with self.subTest(policy=policy_file, rule=rule_path):
self.assertEqual(escaping_scope(policy_file, rule_path), scope)
def test_allowlisted(self):
for policy_file, rule_path in [
("policies/global-root.yaml", "*"),
("policies/sys/policy/admin.yaml", "sys/policies/acl"),
("policies/sys/policy/admin.yaml", "sys/policies/acl/*"),
("policies/sys/mounts/admin.yaml", "sys/mounts-tune/*"),
]:
with self.subTest(policy=policy_file, rule=rule_path):
self.assertIsNone(escaping_scope(policy_file, rule_path))
def test_schema_rejects_malformed(self):
auth = {"approle": ["tf_vault"]}
rule = {"path": "kv/data/x", "capabilities": ["read"]}