DEV Community

Cover image for Getting started with access lifecycle on Azure Resources with PowerShell
Kai Walter
Kai Walter

Posted on • Edited on

Getting started with access lifecycle on Azure Resources with PowerShell

intro

My objective was to automate the setup of approval and lifecycle processes for access on Azure resources. As Entitlement management API is currently in beta and a module for PowerShell is not yet available, I put the most basic operations into this PowerShell wrapper 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

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 can simply be included with dot sourcing into a script. Microsoft Graph SDK is used for authentication and authorization of the wrapper:

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

. ./AccessPackageClient.ps1
Enter fullscreen mode Exit fullscreen mode

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.

$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
}
Enter fullscreen mode Exit fullscreen mode

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:

$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 }
}
Enter fullscreen mode Exit fullscreen mode

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.

$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)
}
Enter fullscreen mode Exit fullscreen mode

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.

$accessPackage = $client.getAccessPackageById($accessPackage.id) # get expanded with sub elements
Enter fullscreen mode Exit fullscreen mode

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

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

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"
            }
        })
}
Enter fullscreen mode Exit fullscreen mode

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.

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

Creating accessPackageAssignmentPolicy allows several options for NoApproval, SingleStage or Serial.

Resource access without any approval:

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

$durationInDays = 60
Enter fullscreen mode Exit fullscreen mode

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
$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
Enter fullscreen mode Exit fullscreen mode

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
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
    })
Enter fullscreen mode Exit fullscreen mode

Top comments (0)