5 minute read

Subject says it all

The Setting

Since we all want to not YoloIT-it things to much… securing privileged accounts is a good place to start and in Azure we can use Privileged Identity Management (PIM) to help with this. PIM provides a time and approval-based role activation to mitigate the risks of excessive, unnecessary, or misused access permissions to your Resources. Read more about it PIM
In short, NO!, you do not need permanent owner 24/7 in Azure to do your job (but it does suck to not have 24/7 permissions… I agree)

Below is an example of how you can assign a role using Bicep. For reference look at: Microsoft.Authorization roleEligibilityScheduleRequests

// --- note this template has not been tested but should be close to a working example --- //
param principal string = 'aad-principal-id-to-assign'
param start string = utcNow()
param end string = dateTimeAdd(utcNow(), 'PT8H')
param typeOfrequest string = 'AdminUpdate'
param expiration string = 'AfterDuration'
param duration string = 'P365D'
param assignmentName string = newGuid()
param currentSubscription string = ${subscription().id}
param role string = '8e3af657-a8ff-443c-a75c-2fe8c4bcb635' // owner

resource assignment 'Microsoft.Authorization/roleEligibilityScheduleRequests@2022-04-01-preview' = {
  name: assignmentName

  properties: {
    principalId: principal
    requestType: typeOfrequest
    roleDefinitionId: '${currentSubscription}/providers/Microsoft.Authorization/roleDefinitions/${role}'
    scheduleInfo: {
      startDateTime: start
      expiration: {
        duration: duration
        endDateTime: end
        type: expiration
      }
    }
  }
}

The Problem

Groovy… You are now all cool and stuff using a Bicep template to create PIM assignments! But for Yolo’s sake… all these #¤%& emails each time someone thinks about using PIM. How do I configure a PIM roles with code? In many cases this needs happen for each role for each assignment scope(landingZone). Settings like this along with notification settings:

Problem is, I can’t find out how to do this. There are some posts out there on PIM but mainly for Azure AD, not so much for Azure. The documentation from Microsoft on this is also a bit confusing (at least for me)

The Goal

Be able to programmatically configure PIM role settings.

One way to do solve it

So far the best solution I have found is to use APIs to configure these settings. Came accross this Github issue with the missing piece of the puzzle Github issue that had the solution
Some other references and articles for further reading:

Finding your access token

As we are about to use api’s we need to have an access token.


    # sign in to Azure
    Clear-AzContext -Force
    Add-AzAccount -Tenant "yourTenant.onmicrosoft.com" # if you are working with mulitple tenants, this one is handy

    # if you are using a spesific role to a spesifc subscription you will need to
    $context = Set-AzContext -SubscriptionName  "name-of-your-subscription"

    # define the scope of the configuration in this case, subscription
    $rmcScope = "subscriptions/$($context.Subscription.Id)"

    # Use powershell to get an accestoken based on the currently signed in account/service principal
    # build a headers hash table
    $accessToken = Get-AzAccessToken -ResourceUrl "https://management.azure.com"
    $headers = @{
    	Authorization = "$($accessToken.type) $($accessToken.Token)"
    }

Finding the correct role to update

Before we can make any changes we need to find the details for our Role Management Policy before we can make any changes.
Me not being the most experienced with APIs had a bit of a hard time figuring out how to build the correct URI. There is some serious black magic going on in the background there as the roleDefinition ID in you find in Azure is not the same in your PIM Role Management Policy. It has a separate ID and there is no obvious link between the two. However, using the URI below does connect the dots.


    # Find the Details for the Role Definition you want to configure
    $roleDefinitionName = "Owner"
    $roleId = Get-AzRoleDefinition -Name $roleDefinitionName

    # Get the details for your role manageemnt policy
    $rmcRequest = @{
    	headers = $headers
    	uri     = "https://management.azure.com/$rmcScope/providers/Microsoft.Authorization/roleManagementPolicies?api-version=2020-10-01&" + '$' + "filter=roleDefinitionId+eq+'$rmcScope/providers/Microsoft.Authorization/roleDefinitions/$($roleId.id)'"
    	method  = "GET"
    }
    $rmcRequestResults = (invoke-RestMethod @rmcRequest).value

    # export the results to file for comparing. This step is only needed for tuning the settings
    $rmcRequestResults | ConvertTo-Json -Depth 100 | Out-File -FilePath ".\settings.json"

Finding details for your Role

Next, lets update some settings. We need to loop each rule and make changes. You need to find the corresponding settings to the ones you want to change.
What I did was:

  1. Export settings without making changes (as in example above)
  2. Make the desired changes in the portal
  3. Export again
  4. Compare the files to find what to change Comparing files in Visual Studio
    • example of using vscode file-compare

The examples are not a reccomended set of settings, just examples of how to set them. Evaluate, test them out and decide what kind of settings and notifications you want to have.
In the example I’m using powershell, saving the settings in a .json file and importing it would be another good way to do it.


    # we need to extract some details. I like having them as separate variables
    $rmcPolicyName = $rmcRequestResults.Name
    $rmcPolicyId = $rmcRequestResults.id
    $rmcPolicyProperties = $rmcRequestResults.properties

    # update Notification settings in the variable (assuming there are equal number of effective rules as rules)
    for ($p = 0; $p -lt ($rmcPolicyProperties.rules).Length; $p++) {
    	switch -Exact ($rmcPolicyProperties.rules[$p].id) {
    		"Expiration_Admin_Eligibility" {
    			$rmcPolicyProperties.rules[$p].isExpirationRequired = $false
    			$rmcPolicyProperties.effectiveRules[$p].isExpirationRequired = $false
    		} # permanent eligebility

    		"Enablement_EndUser_Assignment" {
    			$rmcPolicyProperties.rules[$p].enabledRules = @("MultiFactorAuthentication", "Justification")
    			$rmcPolicyProperties.effectiveRules[$p].enabledRules = @("MultiFactorAuthentication", "Justification")
    		} # requires MFA and Justification text

    		"Expiration_EndUser_Assignment" {
    			$rmcPolicyProperties.rules[$p].maximumDuration = "PT4H"
    			$rmcPolicyProperties.effectiveRules[$p].maximumDuration = "PT4H"
    		} # 4 hours of max activation time allowed

    		"Notification_Requestor_EndUser_Assignment" {
    			$rmcPolicyProperties.rules[$p].isDefaultRecipientsEnabled = $true
    			$rmcPolicyProperties.effectiveRules[$p].isDefaultRecipientsEnabled = $true
    		} # Notification to activated user (requestor)

    		"Notification_Admin_Admin_Eligibility" {
    			$rmcPolicyProperties.rules[$p].isDefaultRecipientsEnabled = $false
    			$rmcPolicyProperties.effectiveRules[$p].isDefaultRecipientsEnabled = $false
    		}

    		"Notification_Admin_EndUser_Assignment" {
    			$rmcPolicyProperties.rules[$p].isDefaultRecipientsEnabled = $false
    			$rmcPolicyProperties.effectiveRules[$p].isDefaultRecipientsEnabled = $false
    		}

    		"Notification_Admin_Admin_Assignment" {
    			$rmcPolicyProperties.rules[$p].isDefaultRecipientsEnabled = $false
    			$rmcPolicyProperties.effectiveRules[$p].isDefaultRecipientsEnabled = $false
    		}

    		"Notification_Requestor_Admin_Eligibility" {
    			$rmcPolicyProperties.rules[$p].isDefaultRecipientsEnabled = $false
    			$rmcPolicyProperties.effectiveRules[$p].isDefaultRecipientsEnabled = $false
    		}

    		"Notification_Requestor_Admin_Assignment" {
    			$rmcPolicyProperties.rules[$p].isDefaultRecipientsEnabled = $false
    			$rmcPolicyProperties.effectiveRules[$p].isDefaultRecipientsEnabled = $false
    		}

    		"Notification_Approver_EndUser_Assignment" {
    			$rmcPolicyProperties.rules[$p].isDefaultRecipientsEnabled = $false
    			$rmcPolicyProperties.effectiveRules[$p].isDefaultRecipientsEnabled = $false
    		}

    		"Notification_Approver_Admin_Assignment" {
    			$rmcPolicyProperties.rules[$p].isDefaultRecipientsEnabled = $false
    			$rmcPolicyProperties.effectiveRules[$p].isDefaultRecipientsEnabled = $false
    		}

    		"Notification_Approver_Admin_Eligibility" {
    			$rmcPolicyProperties.rules[$p].isDefaultRecipientsEnabled = $false
    			$rmcPolicyProperties.effectiveRules[$p].isDefaultRecipientsEnabled = $false
    		}

    		Default { }
    	}
    }

Update you role management policy

Now that your configuration variable has been updated we need to push these updates back to into the Role Management Policy


    $body = @{
    	properties = @{
    		rules          = $rmcPolicyProperties.rules
    		effectiveRules = $rmcPolicyProperties.effectiveRules
    	}
    } | ConvertTo-Json -Depth 20

    $rmcUpdateRequest = @{
    	headers     = $headers
    	uri         = "https://management.azure.com/$rmcScope/providers/Microsoft.Authorization/roleManagementPolicies/$($rmcPolicyName)?api-version=2020-10-01"
    	method      = "PATCH"
    	body        = $body
    	ContentType = 'application/json'
    }
    # make the update
    $rmcUpdateRequestResults = invoke-RestMethod @rmcUpdateRequest

The Future

I hope this also is can be done using a template. Earlier you needed to onboard a subscription in order to configure PIM settings, this no loger appears to be the case. You will find your subscriptions as unmanaged and that is borderline YoloITing even if it does work. Fixing that will be a problem for another day

The End

Questions, comments and so on, send it: i.need.permanent.owner@yoloit.no
Live long and Yolo!