Move rules under the policy directory their path belongs to
This commit is contained in:
+3
-15
@@ -1,20 +1,8 @@
|
||||
# Allow the vault deployer to import the gpg plugin and manage its OpenPGP keys.
|
||||
#
|
||||
# terraform-vault registers the plugin itself (vault_plugin -> sys/plugins/catalog,
|
||||
# a sudo-protected path) and manages keys via the gpgvaultsecret provider, so the
|
||||
# deployer needs catalog access on top of the mount access it already has
|
||||
# (sys/mounts/*). Without this, apply 403s on the plugin registration and on
|
||||
# gpg/keys writes.
|
||||
# Allow the vault deployer to manage the gpg mount's OpenPGP keys via the
|
||||
# gpgvaultsecret provider. Registering the plugin itself is a sys/plugins/catalog
|
||||
# grant, carried by policies/sys/plugins/catalog/admin.yaml.
|
||||
---
|
||||
rules:
|
||||
# Import / register (and deregister) the gpg plugin in the catalog.
|
||||
- path: "sys/plugins/catalog/secret/vault-plugin-secrets-gpg"
|
||||
capabilities:
|
||||
- create
|
||||
- read
|
||||
- update
|
||||
- delete
|
||||
- sudo
|
||||
# Manage keys (create/rotate/config/delete) in the gpg mount.
|
||||
- path: "gpg/keys/*"
|
||||
capabilities:
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
# Allow the deployer to manage the identity group aliases that map external OIDC
|
||||
# group membership (the ak_groups claim) onto the Vault groups managed under
|
||||
# policies/identity/group/. Both endpoints are needed: create posts to
|
||||
# identity/group-alias, subsequent reads and updates address
|
||||
# identity/group-alias/id/<id>.
|
||||
---
|
||||
rules:
|
||||
- path: "identity/group-alias"
|
||||
capabilities:
|
||||
- create
|
||||
- update
|
||||
- path: "identity/group-alias/*"
|
||||
capabilities:
|
||||
- create
|
||||
- update
|
||||
- read
|
||||
- delete
|
||||
- list
|
||||
|
||||
auth:
|
||||
approle:
|
||||
- tf_vault
|
||||
k8s/au/syd1:
|
||||
- woodpecker_terraform_vault
|
||||
@@ -1,7 +1,7 @@
|
||||
# Allow the deployer to manage external identity groups and their aliases, which
|
||||
# is how OIDC group membership (the ak_groups claim) maps onto Vault policies.
|
||||
# Both the collection endpoints and the per-id endpoints are needed: create posts
|
||||
# to identity/group, subsequent reads and updates address identity/group/id/<id>.
|
||||
# Allow the deployer to manage the external identity groups that OIDC group
|
||||
# membership (the ak_groups claim) maps onto Vault policies through. Both the
|
||||
# collection endpoint and the per-id endpoints are needed: create posts to
|
||||
# identity/group, subsequent reads and updates address identity/group/id/<id>.
|
||||
---
|
||||
rules:
|
||||
- path: "identity/group"
|
||||
@@ -15,21 +15,6 @@ rules:
|
||||
- read
|
||||
- delete
|
||||
- list
|
||||
- path: "identity/group-alias"
|
||||
capabilities:
|
||||
- create
|
||||
- update
|
||||
- path: "identity/group-alias/*"
|
||||
capabilities:
|
||||
- create
|
||||
- update
|
||||
- read
|
||||
- delete
|
||||
- list
|
||||
- path: "identity/lookup/group"
|
||||
capabilities:
|
||||
- create
|
||||
- update
|
||||
|
||||
auth:
|
||||
approle:
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
# Allow the deployer to look up an identity group by name, which is how it
|
||||
# resolves the group it is about to update under policies/identity/group/.
|
||||
---
|
||||
rules:
|
||||
- path: "identity/lookup/group"
|
||||
capabilities:
|
||||
- create
|
||||
- update
|
||||
|
||||
auth:
|
||||
approle:
|
||||
- tf_vault
|
||||
k8s/au/syd1:
|
||||
- woodpecker_terraform_vault
|
||||
@@ -0,0 +1,15 @@
|
||||
# Allow the Terraform Authentik runner to read the OAuth2/OIDC client secret that
|
||||
# backs an authentik provider, in whichever namespace the target service lives
|
||||
# (the module's data.vault_kv_secret_v2). The literal + is a Vault single-segment
|
||||
# wildcard: one oauth-credentials secret per onboarded namespace.
|
||||
---
|
||||
rules:
|
||||
- path: "kv/data/kubernetes/namespace/+/default/oauth-credentials"
|
||||
capabilities:
|
||||
- read
|
||||
|
||||
auth:
|
||||
approle:
|
||||
- terraform_authentik
|
||||
k8s/au/syd1:
|
||||
- woodpecker_terraform_authentik
|
||||
@@ -0,0 +1,14 @@
|
||||
# Allow the Terraform Rancher runner to read the OAuth2/OIDC client secret backing
|
||||
# the Rancher keycloakoidc AuthConfig (the same secret Authentik sets on the
|
||||
# provider).
|
||||
---
|
||||
rules:
|
||||
- path: "kv/data/kubernetes/namespace/cattle-system/default/oauth-credentials"
|
||||
capabilities:
|
||||
- read
|
||||
|
||||
auth:
|
||||
approle:
|
||||
- terraform_rancher
|
||||
k8s/au/syd1:
|
||||
- woodpecker_terraform_rancher
|
||||
@@ -0,0 +1,14 @@
|
||||
# Allow the Terraform Authentik runner to read the vlogs OIDC client secret. It is
|
||||
# a second OIDC client in an already-onboarded namespace, so it cannot use the
|
||||
# one-per-namespace oauth-credentials path.
|
||||
---
|
||||
rules:
|
||||
- path: "kv/data/kubernetes/namespace/logging/default/vlogs-oauth-credentials"
|
||||
capabilities:
|
||||
- read
|
||||
|
||||
auth:
|
||||
approle:
|
||||
- terraform_authentik
|
||||
k8s/au/syd1:
|
||||
- woodpecker_terraform_authentik
|
||||
@@ -1,20 +1,11 @@
|
||||
# Allow the Terraform Authentik runner to read:
|
||||
# - its own Authentik API token (provider auth), and
|
||||
# - OAuth2/OIDC client secrets that back authentik providers (per target
|
||||
# service's kv namespace path, via the module's data.vault_kv_secret_v2).
|
||||
# Allow the Terraform Authentik runner to read its own Authentik API token
|
||||
# (provider auth). The OAuth2/OIDC client secrets backing authentik providers are
|
||||
# granted per secret under policies/kv/kubernetes/namespace/.
|
||||
---
|
||||
rules:
|
||||
- path: "kv/data/service/terraform/authentik"
|
||||
capabilities:
|
||||
- read
|
||||
- path: "kv/data/kubernetes/namespace/+/default/oauth-credentials"
|
||||
capabilities:
|
||||
- read
|
||||
# Second OIDC client in an already-onboarded namespace, so it cannot use the
|
||||
# one-per-namespace oauth-credentials path above.
|
||||
- path: "kv/data/kubernetes/namespace/logging/default/vlogs-oauth-credentials"
|
||||
capabilities:
|
||||
- read
|
||||
|
||||
auth:
|
||||
approle:
|
||||
|
||||
@@ -1,15 +1,11 @@
|
||||
# Allow the Terraform Rancher runner to read:
|
||||
# - its own Rancher admin API token (rancher2 provider auth), and
|
||||
# - the OAuth2/OIDC client secret backing the Rancher keycloakoidc AuthConfig
|
||||
# (same secret Authentik sets on the provider).
|
||||
# Allow the Terraform Rancher runner to read its own Rancher admin API token
|
||||
# (rancher2 provider auth). The OAuth2/OIDC client secret backing the Rancher
|
||||
# keycloakoidc AuthConfig is granted under policies/kv/kubernetes/namespace/.
|
||||
---
|
||||
rules:
|
||||
- path: "kv/data/service/terraform/rancher"
|
||||
capabilities:
|
||||
- read
|
||||
- path: "kv/data/kubernetes/namespace/cattle-system/default/oauth-credentials"
|
||||
capabilities:
|
||||
- read
|
||||
|
||||
auth:
|
||||
approle:
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
# Allow the deployer to read and tune the configuration of a mounted secret
|
||||
# engine. sys/mounts-tune is the tuning endpoint beside the mount management
|
||||
# granted by policies/sys/mounts/admin.yaml.
|
||||
---
|
||||
rules:
|
||||
- path: "sys/mounts-tune/*"
|
||||
capabilities:
|
||||
- update
|
||||
- read
|
||||
|
||||
auth:
|
||||
approle:
|
||||
- tf_vault
|
||||
k8s/au/syd1:
|
||||
- woodpecker_terraform_vault
|
||||
@@ -8,10 +8,6 @@ rules:
|
||||
- delete
|
||||
- read
|
||||
- list
|
||||
- path: "sys/mounts-tune/*"
|
||||
capabilities:
|
||||
- update
|
||||
- read
|
||||
- path: "sys/mounts"
|
||||
capabilities:
|
||||
- read
|
||||
|
||||
+3
-30
@@ -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"]}
|
||||
|
||||
Reference in New Issue
Block a user