mirror of
https://github.com/ghndrx/prowler.git
synced 2026-02-10 14:55:00 +00:00
feat(azure): Add new check "iam_custom_role_permits_administering_resource_locks" (#3317)
Co-authored-by: Pepe Fagoaga <pepe@prowler.com>
This commit is contained in:
@@ -0,0 +1,30 @@
|
|||||||
|
{
|
||||||
|
"Provider": "azure",
|
||||||
|
"CheckID": "iam_custom_role_has_permissions_to_administer_resource_locks",
|
||||||
|
"CheckTitle": "Ensure an IAM custom role has permissions to administer resource locks",
|
||||||
|
"CheckType": [],
|
||||||
|
"ServiceName": "iam",
|
||||||
|
"SubServiceName": "",
|
||||||
|
"ResourceIdTemplate": "",
|
||||||
|
"Severity": "high",
|
||||||
|
"ResourceType": "AzureRole",
|
||||||
|
"Description": "Ensure a Custom Role is Assigned Permissions for Administering Resource Locks",
|
||||||
|
"Risk": "In Azure, resource locks are a way to prevent accidental deletion or modification of critical resources. These locks can be set at the resource group level or the individual resource level. Resource locks administration is a critical task that should be preformed from a custom role with the appropriate permissions. This ensures that only authorized users can administer resource locks.",
|
||||||
|
"RelatedUrl": "https://learn.microsoft.com/en-us/azure/azure-resource-manager/management/lock-resources?tabs=json",
|
||||||
|
"Remediation": {
|
||||||
|
"Code": {
|
||||||
|
"CLI": "https://www.trendmicro.com/cloudoneconformity-staging/knowledge-base/azure/AccessControl/resource-lock-custom-role.html",
|
||||||
|
"NativeIaC": "",
|
||||||
|
"Other": "",
|
||||||
|
"Terraform": ""
|
||||||
|
},
|
||||||
|
"Recommendation": {
|
||||||
|
"Text": "Resouce locks are needed to prevent accidental deletion or modification of critical Azure resources. The administration of resource locks should be performed from a custom role with the appropriate permissions.",
|
||||||
|
"Url": "https://www.trendmicro.com/cloudoneconformity-staging/knowledge-base/azure/AccessControl/resource-lock-custom-role.html"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Categories": [],
|
||||||
|
"DependsOn": [],
|
||||||
|
"RelatedTo": [],
|
||||||
|
"Notes": ""
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
from re import search
|
||||||
|
|
||||||
|
from prowler.lib.check.models import Check, Check_Report_Azure
|
||||||
|
from prowler.providers.azure.services.iam.iam_client import iam_client
|
||||||
|
|
||||||
|
|
||||||
|
class iam_custom_role_has_permissions_to_administer_resource_locks(Check):
|
||||||
|
def execute(self) -> Check_Report_Azure:
|
||||||
|
findings = []
|
||||||
|
for subscription, roles in iam_client.custom_roles.items():
|
||||||
|
for role in roles:
|
||||||
|
report = Check_Report_Azure(self.metadata())
|
||||||
|
report.subscription = subscription
|
||||||
|
report.resource_id = role.id
|
||||||
|
report.resource_name = role.name
|
||||||
|
has_lock_permission = False
|
||||||
|
for permission_item in role.permissions:
|
||||||
|
if has_lock_permission:
|
||||||
|
break
|
||||||
|
for action in permission_item.actions:
|
||||||
|
if has_lock_permission:
|
||||||
|
break
|
||||||
|
if search("^Microsoft.Authorization/locks/.*", action):
|
||||||
|
report.status = "PASS"
|
||||||
|
report.status_extended = f"Role {role.name} from subscription {subscription} has permission to administer resource locks."
|
||||||
|
has_lock_permission = True
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
report.status = "FAIL"
|
||||||
|
report.status_extended = f"Role {role.name} from subscription {subscription} has no permission to administer resource locks."
|
||||||
|
break
|
||||||
|
findings.append(report)
|
||||||
|
return findings
|
||||||
@@ -11,33 +11,46 @@ from prowler.providers.azure.lib.service.service import AzureService
|
|||||||
class IAM(AzureService):
|
class IAM(AzureService):
|
||||||
def __init__(self, audit_info):
|
def __init__(self, audit_info):
|
||||||
super().__init__(AuthorizationManagementClient, audit_info)
|
super().__init__(AuthorizationManagementClient, audit_info)
|
||||||
self.roles = self.__get_roles__()
|
self.roles, self.custom_roles = self.__get_roles__()
|
||||||
|
|
||||||
def __get_roles__(self):
|
def __get_roles__(self):
|
||||||
logger.info("IAM - Getting roles...")
|
logger.info("IAM - Getting roles...")
|
||||||
roles = {}
|
builtin_roles = {}
|
||||||
|
custom_roles = {}
|
||||||
for subscription, client in self.clients.items():
|
for subscription, client in self.clients.items():
|
||||||
try:
|
try:
|
||||||
roles.update({subscription: []})
|
builtin_roles.update({subscription: []})
|
||||||
for role in client.role_definitions.list(
|
custom_roles.update({subscription: []})
|
||||||
|
all_roles = client.role_definitions.list(
|
||||||
scope=f"/subscriptions/{self.subscriptions[subscription]}",
|
scope=f"/subscriptions/{self.subscriptions[subscription]}",
|
||||||
filter="type eq 'CustomRole'",
|
)
|
||||||
):
|
for role in all_roles:
|
||||||
roles[subscription].append(
|
if role.role_type == "CustomRole":
|
||||||
Role(
|
custom_roles[subscription].append(
|
||||||
id=role.id,
|
Role(
|
||||||
name=role.role_name,
|
id=role.id,
|
||||||
type=role.role_type,
|
name=role.role_name,
|
||||||
assignable_scopes=role.assignable_scopes,
|
type=role.role_type,
|
||||||
permissions=role.permissions,
|
assignable_scopes=role.assignable_scopes,
|
||||||
|
permissions=role.permissions,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
builtin_roles[subscription].append(
|
||||||
|
Role(
|
||||||
|
id=role.id,
|
||||||
|
name=role.role_name,
|
||||||
|
type=role.role_type,
|
||||||
|
assignable_scopes=role.assignable_scopes,
|
||||||
|
permissions=role.permissions,
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
|
||||||
except Exception as error:
|
except Exception as error:
|
||||||
logger.error(f"Subscription name: {subscription}")
|
logger.error(f"Subscription name: {subscription}")
|
||||||
logger.error(
|
logger.error(
|
||||||
f"{error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
|
f"{error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
|
||||||
)
|
)
|
||||||
return roles
|
return builtin_roles, custom_roles
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
|
|||||||
@@ -10,17 +10,17 @@
|
|||||||
"ResourceType": "AzureRole",
|
"ResourceType": "AzureRole",
|
||||||
"Description": "Ensure that no custom subscription owner roles are created",
|
"Description": "Ensure that no custom subscription owner roles are created",
|
||||||
"Risk": "Subscription ownership should not include permission to create custom owner roles. The principle of least privilege should be followed and only necessary privileges should be assigned instead of allowing full administrative access.",
|
"Risk": "Subscription ownership should not include permission to create custom owner roles. The principle of least privilege should be followed and only necessary privileges should be assigned instead of allowing full administrative access.",
|
||||||
"RelatedUrl": "",
|
"RelatedUrl": "https://learn.microsoft.com/en-us/azure/role-based-access-control/custom-roles",
|
||||||
"Remediation": {
|
"Remediation": {
|
||||||
"Code": {
|
"Code": {
|
||||||
"CLI": "",
|
"CLI": "https://www.trendmicro.com/cloudoneconformity-staging/knowledge-base/azure/AccessControl/remove-custom-owner-roles.html",
|
||||||
"NativeIaC": "",
|
"NativeIaC": "",
|
||||||
"Other": "",
|
"Other": "",
|
||||||
"Terraform": ""
|
"Terraform": ""
|
||||||
},
|
},
|
||||||
"Recommendation": {
|
"Recommendation": {
|
||||||
"Text": "Subscriptions will need to be handled by Administrators with permissions.",
|
"Text": "Custom subscription owner roles should not be created. This is because the principle of least privilege should be followed and only necessary privileges should be assigned instead of allowing full administrative access",
|
||||||
"Url": ""
|
"Url": "https://www.trendmicro.com/cloudoneconformity-staging/knowledge-base/azure/AccessControl/remove-custom-owner-roles.html"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Categories": [],
|
"Categories": [],
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ from prowler.providers.azure.services.iam.iam_client import iam_client
|
|||||||
class iam_subscription_roles_owner_custom_not_created(Check):
|
class iam_subscription_roles_owner_custom_not_created(Check):
|
||||||
def execute(self) -> Check_Report_Azure:
|
def execute(self) -> Check_Report_Azure:
|
||||||
findings = []
|
findings = []
|
||||||
for subscription, roles in iam_client.roles.items():
|
for subscription, roles in iam_client.custom_roles.items():
|
||||||
for role in roles:
|
for role in roles:
|
||||||
report = Check_Report_Azure(self.metadata())
|
report = Check_Report_Azure(self.metadata())
|
||||||
report.subscription = subscription
|
report.subscription = subscription
|
||||||
|
|||||||
@@ -96,6 +96,10 @@ def mock_recover_checks_from_azure_provider(*_):
|
|||||||
"iam_subscription_roles_owner_custom_not_created",
|
"iam_subscription_roles_owner_custom_not_created",
|
||||||
"/root_dir/fake_path/iam/iam_subscription_roles_owner_custom_not_created",
|
"/root_dir/fake_path/iam/iam_subscription_roles_owner_custom_not_created",
|
||||||
),
|
),
|
||||||
|
(
|
||||||
|
"iam_custom_role_has_permissions_to_administer_resource_locks",
|
||||||
|
"/root_dir/fake_path/iam/iam_custom_role_has_permissions_to_administer_resource_locks",
|
||||||
|
),
|
||||||
(
|
(
|
||||||
"storage_default_network_access_rule_is_denied",
|
"storage_default_network_access_rule_is_denied",
|
||||||
"/root_dir/fake_path/storage/storage_default_network_access_rule_is_denied",
|
"/root_dir/fake_path/storage/storage_default_network_access_rule_is_denied",
|
||||||
|
|||||||
3
tests/providers/azure/azure_fixtures.py
Normal file
3
tests/providers/azure/azure_fixtures.py
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
from uuid import uuid4
|
||||||
|
|
||||||
|
AZURE_SUSCRIPTION = str(uuid4())
|
||||||
@@ -0,0 +1,112 @@
|
|||||||
|
from unittest import mock
|
||||||
|
from uuid import uuid4
|
||||||
|
|
||||||
|
from azure.mgmt.authorization.v2022_04_01.models import Permission
|
||||||
|
|
||||||
|
from prowler.providers.azure.services.iam.iam_service import Role
|
||||||
|
from tests.providers.azure.azure_fixtures import AZURE_SUSCRIPTION
|
||||||
|
|
||||||
|
|
||||||
|
class Test_iam_custom_role_has_permissions_to_administer_resource_locks:
|
||||||
|
def test_iam_no_roles(self):
|
||||||
|
defender_client = mock.MagicMock
|
||||||
|
defender_client.custom_roles = {}
|
||||||
|
|
||||||
|
with mock.patch(
|
||||||
|
"prowler.providers.azure.services.iam.iam_custom_role_has_permissions_to_administer_resource_locks.iam_custom_role_has_permissions_to_administer_resource_locks.iam_client",
|
||||||
|
new=defender_client,
|
||||||
|
):
|
||||||
|
from prowler.providers.azure.services.iam.iam_custom_role_has_permissions_to_administer_resource_locks.iam_custom_role_has_permissions_to_administer_resource_locks import (
|
||||||
|
iam_custom_role_has_permissions_to_administer_resource_locks,
|
||||||
|
)
|
||||||
|
|
||||||
|
check = iam_custom_role_has_permissions_to_administer_resource_locks()
|
||||||
|
result = check.execute()
|
||||||
|
assert len(result) == 0
|
||||||
|
|
||||||
|
def test_iam_custom_owner_role_created_with_lock_administration_permissions(
|
||||||
|
self,
|
||||||
|
):
|
||||||
|
defender_client = mock.MagicMock
|
||||||
|
role_name = "test-role"
|
||||||
|
defender_client.custom_roles = {
|
||||||
|
AZURE_SUSCRIPTION: [
|
||||||
|
Role(
|
||||||
|
id=str(uuid4()),
|
||||||
|
name=role_name,
|
||||||
|
type="CustomRole",
|
||||||
|
assignable_scopes=["/.*", "/test"],
|
||||||
|
permissions=[
|
||||||
|
Permission(
|
||||||
|
actions=[
|
||||||
|
"Microsoft.Authorization/locks/*",
|
||||||
|
"microsoft.aadiam/azureADMetrics/read",
|
||||||
|
]
|
||||||
|
)
|
||||||
|
],
|
||||||
|
)
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
with mock.patch(
|
||||||
|
"prowler.providers.azure.services.iam.iam_custom_role_has_permissions_to_administer_resource_locks.iam_custom_role_has_permissions_to_administer_resource_locks.iam_client",
|
||||||
|
new=defender_client,
|
||||||
|
):
|
||||||
|
from prowler.providers.azure.services.iam.iam_custom_role_has_permissions_to_administer_resource_locks.iam_custom_role_has_permissions_to_administer_resource_locks import (
|
||||||
|
iam_custom_role_has_permissions_to_administer_resource_locks,
|
||||||
|
)
|
||||||
|
|
||||||
|
check = iam_custom_role_has_permissions_to_administer_resource_locks()
|
||||||
|
result = check.execute()
|
||||||
|
assert len(result) == 1
|
||||||
|
assert result[0].status == "PASS"
|
||||||
|
assert (
|
||||||
|
result[0].status_extended
|
||||||
|
== f"Role {role_name} from subscription {AZURE_SUSCRIPTION} has permission to administer resource locks."
|
||||||
|
)
|
||||||
|
assert result[0].subscription == AZURE_SUSCRIPTION
|
||||||
|
assert (
|
||||||
|
result[0].resource_id
|
||||||
|
== defender_client.custom_roles[AZURE_SUSCRIPTION][0].id
|
||||||
|
)
|
||||||
|
assert result[0].resource_name == role_name
|
||||||
|
|
||||||
|
def test_iam_custom_owner_role_created_with_no_lock_administration_permissions(
|
||||||
|
self,
|
||||||
|
):
|
||||||
|
defender_client = mock.MagicMock
|
||||||
|
role_name = "test-role"
|
||||||
|
defender_client.custom_roles = {
|
||||||
|
AZURE_SUSCRIPTION: [
|
||||||
|
Role(
|
||||||
|
id=str(uuid4()),
|
||||||
|
name=role_name,
|
||||||
|
type="CustomRole",
|
||||||
|
assignable_scopes=["/*"],
|
||||||
|
permissions=[Permission(actions=["*"])],
|
||||||
|
)
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
with mock.patch(
|
||||||
|
"prowler.providers.azure.services.iam.iam_custom_role_has_permissions_to_administer_resource_locks.iam_custom_role_has_permissions_to_administer_resource_locks.iam_client",
|
||||||
|
new=defender_client,
|
||||||
|
):
|
||||||
|
from prowler.providers.azure.services.iam.iam_custom_role_has_permissions_to_administer_resource_locks.iam_custom_role_has_permissions_to_administer_resource_locks import (
|
||||||
|
iam_custom_role_has_permissions_to_administer_resource_locks,
|
||||||
|
)
|
||||||
|
|
||||||
|
check = iam_custom_role_has_permissions_to_administer_resource_locks()
|
||||||
|
result = check.execute()
|
||||||
|
assert len(result) == 1
|
||||||
|
assert result[0].status == "FAIL"
|
||||||
|
assert (
|
||||||
|
result[0].status_extended
|
||||||
|
== f"Role {role_name} from subscription {AZURE_SUSCRIPTION} has no permission to administer resource locks."
|
||||||
|
)
|
||||||
|
assert result[0].subscription == AZURE_SUSCRIPTION
|
||||||
|
assert (
|
||||||
|
result[0].resource_id
|
||||||
|
== defender_client.custom_roles[AZURE_SUSCRIPTION][0].id
|
||||||
|
)
|
||||||
|
assert result[0].resource_name == role_name
|
||||||
@@ -4,14 +4,13 @@ from uuid import uuid4
|
|||||||
from azure.mgmt.authorization.v2022_04_01.models import Permission
|
from azure.mgmt.authorization.v2022_04_01.models import Permission
|
||||||
|
|
||||||
from prowler.providers.azure.services.iam.iam_service import Role
|
from prowler.providers.azure.services.iam.iam_service import Role
|
||||||
|
from tests.providers.azure.azure_fixtures import AZURE_SUSCRIPTION
|
||||||
AZURE_SUSCRIPTION = str(uuid4())
|
|
||||||
|
|
||||||
|
|
||||||
class Test_defender_ensure_defender_for_storage_is_on:
|
class Test_iam_subscription_roles_owner_custom_not_created:
|
||||||
def test_iam_no_roles(self):
|
def test_iam_no_roles(self):
|
||||||
defender_client = mock.MagicMock
|
defender_client = mock.MagicMock
|
||||||
defender_client.roles = {}
|
defender_client.custom_roles = {}
|
||||||
|
|
||||||
with mock.patch(
|
with mock.patch(
|
||||||
"prowler.providers.azure.services.iam.iam_subscription_roles_owner_custom_not_created.iam_subscription_roles_owner_custom_not_created.iam_client",
|
"prowler.providers.azure.services.iam.iam_subscription_roles_owner_custom_not_created.iam_subscription_roles_owner_custom_not_created.iam_client",
|
||||||
@@ -28,12 +27,12 @@ class Test_defender_ensure_defender_for_storage_is_on:
|
|||||||
def test_iam_custom_owner_role_created_with_all(self):
|
def test_iam_custom_owner_role_created_with_all(self):
|
||||||
defender_client = mock.MagicMock
|
defender_client = mock.MagicMock
|
||||||
role_name = "test-role"
|
role_name = "test-role"
|
||||||
defender_client.roles = {
|
defender_client.custom_roles = {
|
||||||
AZURE_SUSCRIPTION: [
|
AZURE_SUSCRIPTION: [
|
||||||
Role(
|
Role(
|
||||||
id=str(uuid4()),
|
id=str(uuid4()),
|
||||||
name=role_name,
|
name=role_name,
|
||||||
type="type-role",
|
type="CustomRole",
|
||||||
assignable_scopes=["/*"],
|
assignable_scopes=["/*"],
|
||||||
permissions=[Permission(actions="*")],
|
permissions=[Permission(actions="*")],
|
||||||
)
|
)
|
||||||
@@ -56,11 +55,17 @@ class Test_defender_ensure_defender_for_storage_is_on:
|
|||||||
result[0].status_extended
|
result[0].status_extended
|
||||||
== f"Role {role_name} from subscription {AZURE_SUSCRIPTION} is a custom owner role."
|
== f"Role {role_name} from subscription {AZURE_SUSCRIPTION} is a custom owner role."
|
||||||
)
|
)
|
||||||
|
assert result[0].subscription == AZURE_SUSCRIPTION
|
||||||
|
assert (
|
||||||
|
result[0].resource_id
|
||||||
|
== defender_client.custom_roles[AZURE_SUSCRIPTION][0].id
|
||||||
|
)
|
||||||
|
assert result[0].resource_name == role_name
|
||||||
|
|
||||||
def test_iam_custom_owner_role_created_with_no_permissions(self):
|
def test_iam_custom_owner_role_created_with_no_permissions(self):
|
||||||
defender_client = mock.MagicMock
|
defender_client = mock.MagicMock
|
||||||
role_name = "test-role"
|
role_name = "test-role"
|
||||||
defender_client.roles = {
|
defender_client.custom_roles = {
|
||||||
AZURE_SUSCRIPTION: [
|
AZURE_SUSCRIPTION: [
|
||||||
Role(
|
Role(
|
||||||
id=str(uuid4()),
|
id=str(uuid4()),
|
||||||
@@ -88,3 +93,9 @@ class Test_defender_ensure_defender_for_storage_is_on:
|
|||||||
result[0].status_extended
|
result[0].status_extended
|
||||||
== f"Role {role_name} from subscription {AZURE_SUSCRIPTION} is not a custom owner role."
|
== f"Role {role_name} from subscription {AZURE_SUSCRIPTION} is not a custom owner role."
|
||||||
)
|
)
|
||||||
|
assert result[0].subscription == AZURE_SUSCRIPTION
|
||||||
|
assert (
|
||||||
|
result[0].resource_id
|
||||||
|
== defender_client.custom_roles[AZURE_SUSCRIPTION][0].id
|
||||||
|
)
|
||||||
|
assert result[0].resource_name == role_name
|
||||||
|
|||||||
Reference in New Issue
Block a user