Getting started with access lifecycle on Azure Resources with PowerShell
Tags: azure, powershell, iam
Published: 2020-07-14 03:46:16

---
title: Getting started with access lifecycle on Azure Resources with PowerShell
published: true
description: Azure IAM Entitlement management API currently is in beta and still has no (Azure PowerShell) module available. With this article I wrapped fundamental operations to setup access lifecycle with PowerShell.
tags: #azure #powershell #iam
cover_image: https://dev-to-uploads.s3.amazonaws.com/i/asz4y7dk4in4y4n0ehh9.JPG
---

## intro

My objective was to automate the setup of [approval and lifecycle processes for access on Azure resources](https://docs.microsoft.com/en-us/azure/active-directory/governance/identity-governance-overview#access-lifecycle). As [Entitlement management API](https://docs.microsoft.com/en-us/graph/api/resources/entitlementmanagement-root?view=graph-rest-beta) is currently in _beta_ and a module for PowerShell is not yet available, I put the most basic operations into [this PowerShell wrapper](https://gist.github.com/KaiWalter/b4c42719d4341f498ad6ffc7a83fbdcb) and will explain the usage in this article.

> samples included in this article only show a basic level required to create the various objects; more options can be found in sub section of [Entitlement management API](https://docs.microsoft.com/en-us/graph/api/resources/entitlementmanagement-root?view=graph-rest-beta)

## prerequisites

- PowerShell (Core) - scripts in this article "worked on my machine" with PowerShell Core 7.1.0 but should at least be compatible down to PowerShell 5
- Microsoft Graph PowerShell SDK
- Azure PowerShell

## usage

### create the API client

[AccessPackageClient.ps1](https://gist.github.com/KaiWalter/b4c42719d4341f498ad6ffc7a83fbdcb) can simply be included with dot sourcing into a script. __Microsoft Graph SDK__ is used for authentication and authorization of the wrapper:

```PowerShell
Import-Module Microsoft.Graph
$ctx = Get-MgContext
if (!$ctx) {
    Connect-MgGraph -TenantId $config.tenantId -Scopes "EntitlementManagement.ReadWrite.All"
}

. ./AccessPackageClient.ps1
```

### create or obtain Access Package Catalog

Access packages are managed in catalogs. Hence such a catalog needs be created or a reference to an existing catalog needs to be obtained.

```PowerShell
$displayName = "Project ABC"
$description = "Access packages for platform ABC"

$accessPackageCatalog = $client.getAccessPackageCatalogByDisplayName($accessPackageCatalogDisplayName)

if ($accessPackageCatalog) {
    Write-Host "catalog found" $accessPackageCatalog
}
else {
    $client.postAccessPackageCatalog(@{
            displayName         = $displayName
            description         = $description
            isExternallyVisible = $true
        })

    $accessPackageCatalog = $client.getAccessPackageCatalogByDisplayName($accessPackageCatalogDisplayName)
    Write-Host "catalog created" $accessPackageCatalog
}
```

### create or obtain Access Package Catalog Resource

Additionally the scope of Azure resources to be covered by an access package is referenced in a catalog resource. An AAD group with the actual role assignment (Owner, Contributor, Reader) on a scope of Azure resources (Subscription, Resource Group, Resources) is added as catalog resource request:

```PowerShell
$aadGroupName = "Project A developers"
$aadGroup = Get-AzADGroup -DisplayName $aadGroupName -ErrorAction Stop

$accessResource = $client.getAccessPackageCatalogResources($accessPackageCatalog.id) | ? { $_.originId -eq $aadGroup.Id }

if (!$accessResource) {
    $accessResource = $client.postAccessPackageResourceRequests(@{
            catalogId             = $accessPackageCatalog.id
            requestType           = "AdminAdd"
            accessPackageResource = @{
                resourceType = "O365 Group"
                originId     = $aadGroup.Id
                originSystem = "AadGroup"
            }
        })
    $accessResource = $client.getAccessPackageCatalogResources($accessPackageCatalog.id) | ? { $_.originId -eq $aadGroup.Id }
}
```

### create or obtain Access Package

An access package is created in several stages (Access Package, Role Scope, Assignment Policy) - for each stage separate API calls are required. 

```PowerShell
$displayName = "Project A development level access"
$description = "restricted resource to platform ABC"

$accessPackage = $client.getAccessPackageByDisplayName($accessPackageCatalog.id, $displayName)

if (!$accessPackage) {
    Write-Host "create access package" $displayName
    $client.postAccessPackage(@{
            catalogId   = $accessPackageCatalog.id
            displayName = $displayName
            description = $description
        })
    $accessPackage = $client.getAccessPackageByDisplayName($accessPackageCatalog.id, $displayName)
}
```

`getAccessPackageByDisplayName` only returns the access package base element. `getAccessPackageById` can be used to expand the result also to `accessPackageResourceRole`, `accessPackageResourceScope` and `accessPackageAssignmentPolicies`. This is useful to check which sub elements are already existing, when incrementally building up the access package.

```PowerShell
$accessPackage = $client.getAccessPackageById($accessPackage.id) # get expanded with sub elements
```

#### add access package resource role and scope

> In Azure AD entitlement management, an access package resource role scope is a reference to both a scope within a resource, and a role in that resource for that scope. An access package will have access package resource role scopes for the resources in its catalog which are relevant to that access package. When a subject receives an access package assignment, the subject will be provisioned with the role in that scope of each access package resource role scope. [source](https://docs.microsoft.com/en-us/graph/api/resources/accesspackageresourcerolescope?view=graph-rest-beta)

Resource role and scope pair is created with one API call:

```PowerShell
if (!$accessPackage.accessPackageResourceRoleScopes) {
    Write-Host "create role scope for" $accessPackage.displayName
    $client.postAccessPackageResourceRoleScope($accessPackage.id, @{
            accessPackageResourceRole  = @{
                displayName           = "Member"
                originSystem          = "AadGroup"
                originId              = "Member_" + $aadGroup.Id
                accessPackageResource = @{
                    id           = $accessResource.id
                    resourceType = "O365 Group"
                    originId     = $aadGroup.Id
                    originSystem = "AadGroup"
                }
            }
            accessPackageResourceScope = @{
                originId     = $aadGroup.Id
                originSystem = "AadGroup"
            }
        })
}
```

#### update or create assignment policies

In my scenario policies for assignment can change more frequently than the more or less statis setup of AAD groups, roles and scopes. Hence I drop and re-create those.

```PowerShell
if ($accessPackage.accessPackageAssignmentPolicies) {
    Write-Host "delete assignment policy for" $accessPackage.displayName
    $client.deleteAccessPackageAssignmentPolicies($accessPackage.accessPackageAssignmentPolicies[0].id)
}
```

Creating [accessPackageAssignmentPolicy](https://docs.microsoft.com/en-us/graph/api/accesspackageassignmentpolicy-post?view=graph-rest-beta&tabs=http) allows several options for `NoApproval`, `SingleStage` or `Serial`.

Resource access without any approval:

```PowerShell
$requestApprovalSettings = @{
    isApprovalRequired               = $false
    isApprovalRequiredForExtension   = $false
    isRequestorJustificationRequired = $true
    approvalMode                     = "NoApproval"
}

$durationInDays = 60
```

Short lived resource access e.g. to production resources with approval:

| scriptlet input variable | purpose |
| ---- | ---- |
| `$primaryApprovers` | string array with email addresses of primary approvers |
| `$escalationApprovers` | string array with email addresses of approvers in case of an escalation |

```PowerShell
$primaryApproversExtended = @()
foreach ($approver in $primaryApprovers) {
    $primaryApproversExtended += @{
        "@odata.type" = "#microsoft.graph.singleUser"
        id            = (Get-AzADUser -Mail $approver -ErrorAction Stop).id
        isBackup      = $false
    }
}

$escalationApproversExtended = @()
foreach ($approver in $escalationApprovers) {
    $escalationApproversExtended += @{
        "@odata.type" = "#microsoft.graph.singleUser"
        id            = (Get-AzADUser -Mail $approver -ErrorAction Stop).id
        isBackup      = $false
    }
}

$requestApprovalSettings = @{
    isApprovalRequired               = $true
    isApprovalRequiredForExtension   = $true
    isRequestorJustificationRequired = $true
    approvalMode                     = "SingleStage"
    approvalStages                   = @(
        @{
            "@odata.type"                   = "microsoft.graph.approvalStage"
            approvalStageTimeOutInDays      = 7
            isApproverJustificationRequired = $true
            isEscalationEnabled             = $true
            escalationTimeInMinutes         = 7200
            primaryApprovers                = $primaryApproversExtended
            escalationApprovers             = $escalationApproversExtended
        }
    )
}

$durationInDays = 1
```

Finally using either of both inputs above and create the assignment policy:

| scriptlet input variable | purpose |
| ---- | ---- |
| `$aadGroupTeam` | reference to AAD group - team of people to allowed to request access to resources |

```PowerShell
Write-Host "create assignment policy for" $accessPackage.displayName

$client.postAccessPackageAssignmentPolicies(@{
        accessPackageId         = $accessPackage.id
        displayName             = $accessPackage.displayName
        description             = $accessPackage.description
        canExtend               = $false
        durationInDays          = $durationInDays  
        accessReviewSettings    = $null
        requestorSettings       = @{
            scopeType         = "SpecificDirectorySubjects"
            acceptRequests    = $true
            allowedRequestors = @(
                @{
                    "@odata.type" = "#microsoft.graph.groupMembers"
                    id            = $aadGroupTeam.id
                    isBackup      = $false
                    description   = "Authorized requestors"
                }
            )
        }
        requestApprovalSettings = $requestApprovalSettings
    })
```