Welcome back! If you haven’t seen my deep dive on conditional role assignments with Bicep make sure to read that first. Because I left a major flaw in that example code. I assigned a permenantly active ‘Owner’ role assignment. Of course, this is not a realistic scenario. To manage your Azure resources safely, we need to have Privileged Identity Management (PIM)!

Let’s iterate further on my previous blog and see how you can combine PIM with role assignment conditions to keep your landing zones secure.

What you need

  • Azure AD P2 or Entra ID Premium
  • PIM enabled
  • Azure PowerShell (Az module)
  • Permission to create PIM role assignment schedule requests
  • Familiarity with conditional role assignments (see my previous post!)

The scenario

Let’s say you want to make a user or group eligible for Owner at the subscription level, but you want to make sure that when they activate they can’t assign Owner, User Access Administrator, or RBAC Admin to anyone else. We’ll use a conditional role assignment to enforce this policy, just as in my previous post.

Step 1: Write the condition

The condition is almost identical to what we used for regular role assignments. We’re blocking create (write) and delete actions for the three privileged roles. All other roles are allowed. Here’s the condition:

1
2
3
4
5
6
$condition = @"
((!(ActionMatches{'Microsoft.Authorization/roleAssignments/write'})) OR 
(@Request[Microsoft.Authorization/roleAssignments:RoleDefinitionId] ForAnyOfAllValues:GuidNotEquals {8e3af657-a8ff-443c-a75c-2fe8c4bcb635, 18d7d88d-d35e-4fb5-a5c3-7773c20a72d9, f58310d9-a9f6-439a-9e8d-f62e7b41a168})) AND 
((!(ActionMatches{'Microsoft.Authorization/roleAssignments/delete'})) OR 
(@Resource[Microsoft.Authorization/roleAssignments:RoleDefinitionId] ForAnyOfAllValues:GuidNotEquals {8e3af657-a8ff-443c-a75c-2fe8c4bcb635, 18d7d88d-d35e-4fb5-a5c3-7773c20a72d9, f58310d9-a9f6-439a-9e8d-f62e7b41a168}))
"@

Step 2: Create the PIM assignment (with condition!)

We’ll use PowerShell to create a PIM eligible assignment for Owner, but with our condition attached. That way, when someone activates Owner, they’re still blocked from assigning those privileged roles.

Here’s how we can do this:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
# Prerequisites: You should already have your $headers (see my PIM as code post)
$subscription = Get-AzSubscription -SubscriptionId 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx' # > replace this with your own subscription ID
# Switch to the target subscription
Set-AzContext -Subscription $subscription
$principalId = '00000000-0000-0000-0000-000000000002' ## your Entra group/user ID
$condition = @"
((!(ActionMatches{'Microsoft.Authorization/roleAssignments/write'})) OR 
(@Request[Microsoft.Authorization/roleAssignments:RoleDefinitionId] ForAnyOfAllValues:GuidNotEquals {8e3af657-a8ff-443c-a75c-2fe8c4bcb635, 18d7d88d-d35e-4fb5-a5c3-7773c20a72d9, f58310d9-a9f6-439a-9e8d-f62e7b41a168})) AND 
((!(ActionMatches{'Microsoft.Authorization/roleAssignments/delete'})) OR 
(@Resource[Microsoft.Authorization/roleAssignments:RoleDefinitionId] ForAnyOfAllValues:GuidNotEquals {8e3af657-a8ff-443c-a75c-2fe8c4bcb635, 18d7d88d-d35e-4fb5-a5c3-7773c20a72d9, f58310d9-a9f6-439a-9e8d-f62e7b41a168}))
"@

$roleDefinitionId = '8e3af657-a8ff-443c-a75c-2fe8c4bcb635' # Owner
$guid = [guid]::NewGuid()
$createEligibleRoleUri = "https://management.azure.com/providers/Microsoft.Subscription/subscriptions/{0}/providers/Microsoft.Authorization/roleEligibilityScheduleRequests/{1}?api-version=2020-10-01" -f $subscription.Id, $guid

$body = @{
    Properties = @{
        RoleDefinitionID = "/subscriptions/$Subscription.Id/providers/Microsoft.Authorization/roleDefinitions/$contributorRoleId"
        PrincipalId      = $pimRequestorGroup.Id
        RequestType      = 'AdminAssign'
        ScheduleInfo     = @{
            Expiration = @{
                Type = 'NoExpiration'
            }
        }
    }
}
$guid = [guid]::NewGuid()
# Construct Uri with subscription Id and new GUID
$createEligibleRoleUri = "https://management.azure.com/providers/Microsoft.Subscription/subscriptions/{0}/providers/Microsoft.Authorization/roleEligibilityScheduleRequests/{1}?api-version=2020-10-01" -f $Subscription.Id, $guid

$body = @{
    properties = @{
        roleDefinitionId = "/subscriptions/$($subscription.Id)/providers/Microsoft.Authorization/roleDefinitions/$roleDefinitionId"
        principalId      = $principalId
        requestType      = 'AdminAssign'
        condition        = $condition
        conditionVersion = '2.0'
        scheduleInfo     = @{
            expiration= @{
                type = "AfterDuration"
                endDateTime = $null
                duration = "P365D"
            }
        }
    }
}

# Call the API with PUT to assign the role to the targeted principal with the condition
Invoke-RestMethod -Uri $createEligibleRoleUri -Method Put -Headers $headers -Body ($body | ConvertTo-Json -Depth 10)

This makes the user or group eligible for Owner, but when they activate, the condition kicks in and blocks them from assigning or deleting those privileged roles. Everything else works as usual.

Step 3: Test and verify

After running the commands I checked the role assignments. The role assignment from my previous blog looked like this:

verifying role assignments See the Active Permanent state?

After deleting that role assignment, and creating the one with PIM it changed to:

verifying role assignments

Benefits

This pattern gives you:

  1. Least Privilege: Even when teams activate Owner, they can’t escalate further.
  2. Just-In-Time access: Give users permissions only for the duration required.
  3. Autonomy: Teams can self-activate when needed, no more waiting for tickets.
  4. Auditability: Every activation and failed assignment attempt is logged.

Wrapping up

By combining PIM eligible roles with conditional role assignments, you get the best of both worlds: teams can move fast, as platform you stay in control.

As always, leave a comment on LinkedIn if you have any questions. Happy coding! ☕