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 too 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)

To create a PIM assignment using IAC, see the template/ powershell commands below. For reference look at: Microsoft.Authorization roleEligibilityScheduleRequests

# --- save as a name.bicep template --- #
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 = 'NoExpiration'
param duration string = 'P365D'
param assignmentName string = newGuid()
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: '${subscription().id}/providers/Microsoft.Authorization/roleDefinitions/${role}'
    scheduleInfo: {
      startDateTime: start // yes yes this moves the start date
      expiration: {
        duration: duration
        endDateTime: end
        type: expiration
      }
    }
  }
}
# --- deploy template from powershell --- #
# NOTE: Elevate to owner (could be some other role works but...) at the assignment level (and assuming AAD Premium P2 licenses)

clear-azContext -force # clear other contexts and sign-in
Connect-AzAccount -te # sign-in

$context = Set-AzContext -Subscription "your-subscription-name" # update context to correct subscription

$whatIForNot = $true # true/false run template deploy with whatif

$templatePath = "C:\your\template\path\pim.bicep" #
$params = @{
	deploymentName          = "$(Get-Date -Format 'yyMMddHHmmss')-$(($templatePath.split('\')[-1]).split('.')[0]))" # create new timestamp deploy
	templateFile            = $templatePath
	location                = "norwayeast"
	templateParameterObject = @{ principal = "xxxxx-xxxxx-xxxxx-xxxxx-xxxxx" } # replace with a group/user AAD object ID
}
New-AzDeployment @params -Verbose -WhatIf:$whatIForNot

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 role settings with code? In many cases this needs to happen for each role for each assignment scope(landingZone). Settings like these along with notification settings:

There are some posts out there on PIM but mainly for Azure AD, not so much for Azure Resources. The documentation from Microsoft on this is also a bit confusing (at least for me)

The Goal

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. I came across 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.
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. To find what settings to change, my non-fancy process was:

  1. Export settings without making any changes in the portal.
  2. Make the desired changes in the portal
  3. Export the settings again
  4. Compare the files to find what to change. To find the setting name the value.
    (right click file –> select for compare, right click other file –> compare with selected)

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.

    # 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 the 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 can be done using a template. Earlier you needed to onboard a subscription in order to configure PIM settings, this no longer 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!

Updated: