mirror of
https://github.com/prowler-cloud/prowler.git
synced 2026-05-06 08:47:18 +00:00
fix(k8s): match RBAC rules by apiGroup, not just core (#10969)
Co-authored-by: Andoni A. <14891798+andoniaf@users.noreply.github.com>
This commit is contained in:
@@ -37,6 +37,7 @@ All notable changes to the **Prowler SDK** are documented in this file.
|
|||||||
### 🐞 Fixed
|
### 🐞 Fixed
|
||||||
|
|
||||||
- Duplicate Kubernetes RBAC findings when the same User or Group subject appeared in multiple ClusterRoleBindings [(#10242)](https://github.com/prowler-cloud/prowler/pull/10242)
|
- Duplicate Kubernetes RBAC findings when the same User or Group subject appeared in multiple ClusterRoleBindings [(#10242)](https://github.com/prowler-cloud/prowler/pull/10242)
|
||||||
|
- Match K8s RBAC rules by `apiGroup` [(#10969)](https://github.com/prowler-cloud/prowler/pull/10969)
|
||||||
- Return a compact actor name from CloudTrail `userIdentity` events [(#10986)](https://github.com/prowler-cloud/prowler/pull/10986)
|
- Return a compact actor name from CloudTrail `userIdentity` events [(#10986)](https://github.com/prowler-cloud/prowler/pull/10986)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|||||||
@@ -1,36 +1,37 @@
|
|||||||
def is_rule_allowing_permissions(rules, resources, verbs):
|
def is_rule_allowing_permissions(rules, resources, verbs, api_groups=("",)):
|
||||||
"""
|
"""
|
||||||
Check Kubernetes role permissions.
|
Check whether any RBAC rule grants the specified verbs on the specified
|
||||||
|
resources within the specified API groups.
|
||||||
|
|
||||||
This function takes in Kubernetes role rules, resources, and verbs,
|
A rule matches when its `apiGroups` includes any of `api_groups` (or "*"),
|
||||||
and checks if any of the rules grant permissions on the specified
|
its `resources` includes any of `resources` (or "*"), and its `verbs`
|
||||||
resources with the specified verbs.
|
includes any of `verbs` (or "*").
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
rules (List[Rule]): The list of Kubernetes role rules.
|
rules (List[Rule]): RBAC rules from a Role or ClusterRole.
|
||||||
resources (List[str]): The list of resources to check permissions for.
|
resources (List[str]): Resources (or sub-resources) to check.
|
||||||
verbs (List[str]): The list of verbs to check permissions for.
|
verbs (List[str]): Verbs to check.
|
||||||
|
api_groups (Iterable[str]): API groups the resources live in. Defaults
|
||||||
|
to ("",), the core API group, which matches the most common case.
|
||||||
|
Pass an explicit value for resources outside the core group, e.g.
|
||||||
|
("admissionregistration.k8s.io",) for webhook configurations.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
bool: True if any of the rules grant permissions, False otherwise.
|
bool: True if any rule grants the permission, False otherwise.
|
||||||
"""
|
"""
|
||||||
if rules:
|
if not rules:
|
||||||
# Iterate through each rule in the list of rules
|
return False
|
||||||
for rule in rules:
|
for rule in rules:
|
||||||
# Ensure apiGroups are relevant ("" or "v1" for secrets)
|
rule_api_groups = rule.apiGroups or [""]
|
||||||
if rule.apiGroups and all(api not in ["", "v1"] for api in rule.apiGroups):
|
if not (
|
||||||
continue # Skip rules with unrelated apiGroups
|
any(g in rule_api_groups for g in api_groups) or "*" in rule_api_groups
|
||||||
# Check if the rule has resources, verbs, and matches any of the specified resources and verbs
|
):
|
||||||
if (
|
continue
|
||||||
rule.resources
|
if (
|
||||||
and (
|
rule.resources
|
||||||
any(resource in rule.resources for resource in resources)
|
and (any(r in rule.resources for r in resources) or "*" in rule.resources)
|
||||||
or "*" in rule.resources
|
and rule.verbs
|
||||||
)
|
and (any(v in rule.verbs for v in verbs) or "*" in rule.verbs)
|
||||||
and rule.verbs
|
):
|
||||||
and (any(verb in rule.verbs for verb in verbs) or "*" in rule.verbs)
|
return True
|
||||||
):
|
|
||||||
# If the rule matches, return True
|
|
||||||
return True
|
|
||||||
# If no rule matches, return False
|
|
||||||
return False
|
return False
|
||||||
|
|||||||
+4
-1
@@ -6,6 +6,7 @@ from prowler.providers.kubernetes.services.rbac.rbac_client import rbac_client
|
|||||||
|
|
||||||
verbs = ["update", "patch"]
|
verbs = ["update", "patch"]
|
||||||
resources = ["certificatesigningrequests/approval"]
|
resources = ["certificatesigningrequests/approval"]
|
||||||
|
api_groups = ["certificates.k8s.io"]
|
||||||
|
|
||||||
|
|
||||||
class rbac_minimize_csr_approval_access(Check):
|
class rbac_minimize_csr_approval_access(Check):
|
||||||
@@ -33,7 +34,9 @@ class rbac_minimize_csr_approval_access(Check):
|
|||||||
report.status_extended = f"User or group '{subject.name}' does not have access to update the CSR approval sub-resource."
|
report.status_extended = f"User or group '{subject.name}' does not have access to update the CSR approval sub-resource."
|
||||||
for role_name in role_names:
|
for role_name in role_names:
|
||||||
cr = cluster_roles_by_name.get(role_name)
|
cr = cluster_roles_by_name.get(role_name)
|
||||||
if cr and is_rule_allowing_permissions(cr.rules, resources, verbs):
|
if cr and is_rule_allowing_permissions(
|
||||||
|
cr.rules, resources, verbs, api_groups
|
||||||
|
):
|
||||||
report.status = "FAIL"
|
report.status = "FAIL"
|
||||||
report.status_extended = f"User or group '{subject.name}' has access to update the CSR approval sub-resource."
|
report.status_extended = f"User or group '{subject.name}' has access to update the CSR approval sub-resource."
|
||||||
break
|
break
|
||||||
|
|||||||
+4
-1
@@ -9,6 +9,7 @@ resources = [
|
|||||||
"mutatingwebhookconfigurations",
|
"mutatingwebhookconfigurations",
|
||||||
]
|
]
|
||||||
verbs = ["create", "update", "delete"]
|
verbs = ["create", "update", "delete"]
|
||||||
|
api_groups = ["admissionregistration.k8s.io"]
|
||||||
|
|
||||||
|
|
||||||
class rbac_minimize_webhook_config_access(Check):
|
class rbac_minimize_webhook_config_access(Check):
|
||||||
@@ -36,7 +37,9 @@ class rbac_minimize_webhook_config_access(Check):
|
|||||||
report.status_extended = f"User or group '{subject.name}' does not have access to create, update, or delete webhook configurations."
|
report.status_extended = f"User or group '{subject.name}' does not have access to create, update, or delete webhook configurations."
|
||||||
for role_name in role_names:
|
for role_name in role_names:
|
||||||
cr = cluster_roles_by_name.get(role_name)
|
cr = cluster_roles_by_name.get(role_name)
|
||||||
if cr and is_rule_allowing_permissions(cr.rules, resources, verbs):
|
if cr and is_rule_allowing_permissions(
|
||||||
|
cr.rules, resources, verbs, api_groups
|
||||||
|
):
|
||||||
report.status = "FAIL"
|
report.status = "FAIL"
|
||||||
report.status_extended = f"User or group '{subject.name}' has access to create, update, or delete webhook configurations."
|
report.status_extended = f"User or group '{subject.name}' has access to create, update, or delete webhook configurations."
|
||||||
break
|
break
|
||||||
|
|||||||
@@ -6,90 +6,92 @@ from prowler.providers.kubernetes.services.rbac.rbac_service import Rule
|
|||||||
|
|
||||||
class TestCheckRolePermissions:
|
class TestCheckRolePermissions:
|
||||||
def test_is_rule_allowing_permissions(self):
|
def test_is_rule_allowing_permissions(self):
|
||||||
# Define some sample rules, resources, and verbs for testing
|
|
||||||
rules = [
|
rules = [
|
||||||
# Rule 1: Allows 'get' and 'list' on 'pods' and 'services'
|
|
||||||
Rule(resources=["pods", "services"], verbs=["get", "list"]),
|
Rule(resources=["pods", "services"], verbs=["get", "list"]),
|
||||||
# Rule 2: Allows 'create' and 'delete' on 'deployments'
|
|
||||||
Rule(resources=["deployments"], verbs=["create", "delete"]),
|
Rule(resources=["deployments"], verbs=["create", "delete"]),
|
||||||
]
|
]
|
||||||
resources = ["pods", "deployments"]
|
assert is_rule_allowing_permissions(
|
||||||
verbs = ["get", "create"]
|
rules, ["pods", "deployments"], ["get", "create"]
|
||||||
|
)
|
||||||
assert is_rule_allowing_permissions(rules, resources, verbs)
|
|
||||||
|
|
||||||
def test_no_permissions(self):
|
def test_no_permissions(self):
|
||||||
# Test when there are no rules
|
assert not is_rule_allowing_permissions([], ["pods"], ["get"])
|
||||||
rules = []
|
|
||||||
resources = ["pods", "deployments"]
|
|
||||||
verbs = ["get", "create"]
|
|
||||||
|
|
||||||
assert not is_rule_allowing_permissions(rules, resources, verbs)
|
|
||||||
|
|
||||||
def test_no_matching_rules(self):
|
def test_no_matching_rules(self):
|
||||||
# Test when there are rules, but none match the specified resources and verbs
|
|
||||||
rules = [
|
rules = [
|
||||||
Rule(resources=["services"], verbs=["get", "list"]),
|
Rule(resources=["services"], verbs=["get", "list"]),
|
||||||
Rule(resources=["pods"], verbs=["create", "delete"]),
|
Rule(resources=["pods"], verbs=["create", "delete"]),
|
||||||
]
|
]
|
||||||
resources = ["deployments", "configmaps"]
|
assert not is_rule_allowing_permissions(
|
||||||
verbs = ["get", "create"]
|
rules, ["deployments", "configmaps"], ["get", "create"]
|
||||||
|
)
|
||||||
assert not is_rule_allowing_permissions(rules, resources, verbs)
|
|
||||||
|
|
||||||
def test_empty_rules(self):
|
def test_empty_rules(self):
|
||||||
# Test when the rules list is empty
|
assert not is_rule_allowing_permissions([], ["pods"], ["get"])
|
||||||
rules = []
|
|
||||||
resources = ["pods", "deployments"]
|
|
||||||
verbs = ["get", "create"]
|
|
||||||
|
|
||||||
assert not is_rule_allowing_permissions(rules, resources, verbs)
|
|
||||||
|
|
||||||
def test_empty_resources_and_verbs(self):
|
def test_empty_resources_and_verbs(self):
|
||||||
# Test when resources and verbs are empty lists
|
rules = [Rule(resources=["pods"], verbs=["get"])]
|
||||||
rules = [
|
assert not is_rule_allowing_permissions(rules, [], [])
|
||||||
Rule(resources=["pods"], verbs=["get"]),
|
|
||||||
Rule(resources=["services"], verbs=["list"]),
|
|
||||||
]
|
|
||||||
resources = []
|
|
||||||
verbs = []
|
|
||||||
|
|
||||||
assert not is_rule_allowing_permissions(rules, resources, verbs)
|
|
||||||
|
|
||||||
def test_matching_rule_with_empty_resources_or_verbs(self):
|
def test_matching_rule_with_empty_resources_or_verbs(self):
|
||||||
# Test when a rule matches, but either resources or verbs are empty
|
rules = [Rule(resources=["pods"], verbs=["get"])]
|
||||||
|
assert not is_rule_allowing_permissions(rules, [], ["get"])
|
||||||
|
assert not is_rule_allowing_permissions(rules, ["pods"], [])
|
||||||
|
|
||||||
|
def test_rule_with_non_matching_api_group(self):
|
||||||
|
rules = [Rule(resources=["pods"], verbs=["get"], apiGroups=["apps"])]
|
||||||
|
assert not is_rule_allowing_permissions(rules, ["pods"], ["get"])
|
||||||
|
|
||||||
|
def test_rule_with_matching_api_group(self):
|
||||||
|
rules = [Rule(resources=["pods"], verbs=["get"], apiGroups=[""])]
|
||||||
|
assert is_rule_allowing_permissions(rules, ["pods"], ["get"])
|
||||||
|
|
||||||
|
def test_default_api_group_is_core(self):
|
||||||
|
rules = [Rule(resources=["pods"], verbs=["get"], apiGroups=None)]
|
||||||
|
assert is_rule_allowing_permissions(rules, ["pods"], ["get"])
|
||||||
|
|
||||||
|
def test_rule_with_empty_api_groups_does_not_match_non_core_request(self):
|
||||||
|
rules = [Rule(resources=["pods"], verbs=["get"], apiGroups=None)]
|
||||||
|
assert not is_rule_allowing_permissions(
|
||||||
|
rules, ["pods"], ["get"], ["admissionregistration.k8s.io"]
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_non_core_rule_does_not_match_without_api_groups_argument(self):
|
||||||
rules = [
|
rules = [
|
||||||
Rule(resources=["pods"], verbs=["get"]),
|
Rule(
|
||||||
Rule(resources=["services"], verbs=["list"]),
|
resources=["validatingwebhookconfigurations"],
|
||||||
|
verbs=["create"],
|
||||||
|
apiGroups=["admissionregistration.k8s.io"],
|
||||||
|
)
|
||||||
]
|
]
|
||||||
resources = []
|
assert not is_rule_allowing_permissions(
|
||||||
verbs = ["get"]
|
rules, ["validatingwebhookconfigurations"], ["create"]
|
||||||
|
)
|
||||||
|
|
||||||
assert not is_rule_allowing_permissions(rules, resources, verbs)
|
def test_explicit_non_core_api_group(self):
|
||||||
|
|
||||||
resources = ["pods"]
|
|
||||||
verbs = []
|
|
||||||
|
|
||||||
assert not is_rule_allowing_permissions(rules, resources, verbs)
|
|
||||||
|
|
||||||
def test_rule_with_ignored_api_groups(self):
|
|
||||||
# Test when a rule has apiGroups that are not relevant
|
|
||||||
rules = [
|
rules = [
|
||||||
Rule(resources=["pods"], verbs=["get"], apiGroups=["test"]),
|
Rule(
|
||||||
Rule(resources=["services"], verbs=["list"], apiGroups=["test2"]),
|
resources=["validatingwebhookconfigurations"],
|
||||||
|
verbs=["create"],
|
||||||
|
apiGroups=["admissionregistration.k8s.io"],
|
||||||
|
)
|
||||||
]
|
]
|
||||||
resources = ["pods"]
|
assert is_rule_allowing_permissions(
|
||||||
verbs = ["get"]
|
rules,
|
||||||
|
["validatingwebhookconfigurations"],
|
||||||
|
["create"],
|
||||||
|
["admissionregistration.k8s.io"],
|
||||||
|
)
|
||||||
|
|
||||||
assert not is_rule_allowing_permissions(rules, resources, verbs)
|
def test_rule_with_wildcard_api_group(self):
|
||||||
|
rules = [Rule(resources=["pods"], verbs=["get"], apiGroups=["*"])]
|
||||||
|
assert is_rule_allowing_permissions(rules, ["pods"], ["get"])
|
||||||
|
assert is_rule_allowing_permissions(rules, ["pods"], ["get"], ["apps"])
|
||||||
|
|
||||||
def test_rule_with_relevant_api_groups(self):
|
def test_rule_with_wildcard_resources(self):
|
||||||
# Test when a rule has apiGroups that are relevant
|
rules = [Rule(resources=["*"], verbs=["get"], apiGroups=[""])]
|
||||||
rules = [
|
assert is_rule_allowing_permissions(rules, ["pods"], ["get"])
|
||||||
Rule(resources=["pods"], verbs=["get"], apiGroups=["", "v1"]),
|
|
||||||
Rule(resources=["services"], verbs=["list"], apiGroups=["test2"]),
|
|
||||||
]
|
|
||||||
resources = ["pods"]
|
|
||||||
verbs = ["get"]
|
|
||||||
|
|
||||||
assert is_rule_allowing_permissions(rules, resources, verbs)
|
def test_rule_with_wildcard_verbs(self):
|
||||||
|
rules = [Rule(resources=["pods"], verbs=["*"], apiGroups=[""])]
|
||||||
|
assert is_rule_allowing_permissions(rules, ["pods"], ["get"])
|
||||||
|
|||||||
Reference in New Issue
Block a user