Custom role is not a subscription owner role
Azure custom roles are analyzed for wildcard permissions. Roles that allow * in actions within their assignable scopes are treated as owner-equivalent, granting unrestricted control over subscription resources.
Risk
Wildcard access grants full administrative control at subscription scope. If abused or compromised, an actor can exfiltrate data, alter configurations, deploy malware, delete resources, and disable logging, impacting confidentiality, integrity, and availability across the subscription.
Run this check with Prowler
Run in Prowler Cloudprowler azure --checks iam_subscription_roles_owner_custom_not_createdRecommendation
Avoid owner-equivalent custom roles. Apply least privilege: prefer built-in roles, define explicit allowed actions (avoid *), and limit assignment scope to the minimum needed. Enforce separation of duties, require just-in-time elevation, and perform periodic access reviews to prevent privilege creep.
Remediation
CLI
az role definition update --role-definition '{"Name":"<example_role_name>","Description":"Restricted custom role","Actions":["Microsoft.Resources/subscriptions/resourceGroups/read"],"NotActions":[],"DataActions":[],"NotDataActions":[],"AssignableScopes":["/subscriptions/<example_subscription_id>"]}'Terraform
# Define a custom role without using the global "*" action
resource "azurerm_role_definition" "<example_resource_name>" {
name = "<example_resource_name>"
scope = "/subscriptions/<example_subscription_id>"
permissions {
actions = ["Microsoft.Resources/subscriptions/resourceGroups/read"] # CRITICAL: do not use "*"; specify only required actions
}
assignable_scopes = ["/subscriptions/<example_subscription_id>"]
}NativeIaC
// Subscription-scoped deployment to ensure the custom role does not use global "*" permissions
targetScope = 'subscription'
resource roleDef 'Microsoft.Authorization/roleDefinitions@2022-04-01' = {
name: guid(subscription().id, '<role_name>') // CRITICAL: use GUID for role definition name
properties: {
roleName: '<role_name>'
description: 'Restricted custom role'
assignableScopes: [
subscription().id
]
permissions: [
{
actions: [
'Microsoft.Resources/subscriptions/resourceGroups/read' // CRITICAL: remove "*" and allow only specific actions to avoid owner-equivalent wildcard
]
notActions: []
dataActions: []
notDataActions: []
}
]
}
}Other
Check Code
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_subscription_roles_owner_custom_not_created(Check):
def execute(self) -> Check_Report_Azure:
findings = []
for subscription, roles in iam_client.custom_roles.items():
subscription_name = iam_client.subscriptions.get(subscription, subscription)
for custom_role in roles.values():
report = Check_Report_Azure(
metadata=self.metadata(), resource=custom_role
)
report.subscription = subscription
report.status = "PASS"
report.status_extended = f"Role {custom_role.name} from subscription {subscription_name} ({subscription}) is not a custom owner role."
for scope in custom_role.assignable_scopes:
if search("^/.*", scope):
for permission_item in custom_role.permissions:
for action in permission_item.actions:
if action == "*":
report.status = "FAIL"
report.status_extended = f"Role {custom_role.name} from subscription {subscription_name} ({subscription}) is a custom owner role."
break
findings.append(report)
return findings