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
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
}
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 }
}
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)
}
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
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"
}
})
}
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)
}
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
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
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
})
Top comments (0)