Back to Checks
azure

Custom role is not a subscription owner role

iam_subscription_roles_owner_custom_not_created
Service: iamSeverity: high
OfficialBuilt-inVerified
Categories:identity-access
Resource Type:microsoft.authorization/roledefinitions

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 Cloud
Run
prowler azure --checks iam_subscription_roles_owner_custom_not_created

Recommendation

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

1In the Azure portal, go to Subscriptions > <your subscription> > Access control (IAM)
2Select the Roles tab, then open the Custom roles tab
3Click the custom role that is failing, then click Edit
4In Permissions, remove the action "*" (All permissions)
5Add only the specific actions required (avoid using "*")
6Click Save

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