Public/ServiceEM/New-EntraOpsServiceAZContainer.ps1
|
<# .SYNOPSIS Creates an Azure Resource Group (or targets the subscription) and assigns EAM-aligned RBAC/PIM authorizations. .DESCRIPTION Creates (or looks up) an Azure Resource Group named "RG-<ServiceName>" and assigns Azure RBAC and/or PIM eligible role assignments to the EAM security groups produced by New-EntraOpsServiceBootstrap. With -AzureScope Subscription no resource group is created and the same assignments are made on the subscription of the current Azure context. The following assignments are created: - WorkloadPlane-Admins: permanent Reader and PIM eligible constrained Role Based Access Control Administrator (may only assign the ConstrainedDelegation.WorkloadPlane.AllowedRoleDefinitionIds to WorkloadPlane-Users); resource-level roles are assigned to them by ManagementPlane-Admins - ManagementPlane-Admins: PIM eligible Contributor (skipped if inherited) and PIM eligible constrained Role Based Access Control Administrator (may assign any role except the ConstrainedDelegation.ManagementPlane.ExcludedRoleDefinitionIds to WorkloadPlane-Admins) - ControlPlane-Admins: PIM eligible User Access Administrator (skipped if inherited or when SkipControlPlaneDelegation is set) - Resource groups created by this function are tagged EntraOpsServiceEM = <ServiceName> An existing eligible Role Based Access Control Administrator assignment of the same principal on the scope is only replaced when it has the condition created by ServiceEM and its target group no longer exists (leftover of a deleted landing zone). Any other assignment is kept, the remaining assignments are still created, and the function then stops with an error because the constrained delegation for this landing zone isn't assigned. Constrained delegation settings are read from EntraOpsConfig.ServiceEM.ConstrainedDelegation when available. .PARAMETER ServiceName Name of the service. The resource group will be created as "RG-<ServiceName>". .PARAMETER ServiceGroups The Entra group objects produced by New-EntraOpsServiceBootstrap / New-EntraOpsServiceEntraGroup. Groups are matched by DisplayName patterns (e.g. *-ControlPlane-Admins). .PARAMETER SkipControlPlaneDelegation When set, skips the PIM eligible User Access Administrator assignment for the ControlPlane-Admins group. Set automatically by Bootstrap when a delegated ControlPlaneDelegationGroupId is provided. .PARAMETER Location Azure region for the resource group (e.g. "westeurope", "northeurope"). Not used for -AzureScope Subscription. .PARAMETER AzureScope "ResourceGroup" (default) or "Subscription". With "Subscription" the role assignments and the PIM role settings (eligible assignments without expiration) apply to the whole subscription. .PARAMETER logPrefix Text prepended to verbose messages. Defaults to the function name. .EXAMPLE New-EntraOpsServiceAZContainer -ServiceName "MyService" -ServiceGroups $groups ` -Location "westeurope" Creates RG-MyService in West Europe and assigns the default PIM eligible roles: Contributor and constrained RBAC Administrator to ManagementPlane-Admins, constrained RBAC Administrator to WorkloadPlane-Admins, User Access Administrator to ControlPlane-Admins, plus permanent Reader to WorkloadPlane-Admins. .EXAMPLE New-EntraOpsServiceAZContainer -ServiceName "MyService" -ServiceGroups $groups ` -Location "westeurope" -SkipControlPlaneDelegation Same as above but omits the User Access Administrator PIM eligible assignment, used when ControlPlane is delegated to an external group. #> function New-EntraOpsServiceAZContainer { [OutputType([psobject])] [cmdletbinding()] param( [Parameter(Mandatory)] [string]$ServiceName, [Parameter(Mandatory)] [psobject[]]$ServiceGroups, [switch]$SkipControlPlaneDelegation, [string]$Location, [ValidateSet("ResourceGroup", "Subscription")] [string]$AzureScope = "ResourceGroup", [string]$logPrefix = "[$($MyInvocation.MyCommand)]" ) begin { $subscriptionScope = "/subscriptions/$((Get-AzContext).Subscription.Id)" $resourceGroup = $null if ($AzureScope -eq "Subscription") { Write-Verbose "$logPrefix Using subscription scope $subscriptionScope (no resource group)" } else { # Normalize resource group name: strip Sub-/Rg- scope prefix to avoid RG-Sub-/RG-Rg- duplication $rgBaseName = $serviceName if ($rgBaseName -match '^(Sub|Rg)-') { $rgBaseName = $rgBaseName.Substring(3) } $rgName = "RG-$rgBaseName" try { Write-Verbose "$logPrefix Looking up Azure Resource Group" $resourceGroup = Get-AzResourceGroup -Name $rgName -ErrorAction Stop # Warn when using a resource group that ServiceEM didn't create $resourceGroupTag = if ($null -ne $resourceGroup -and $null -ne $resourceGroup.Tags) { "$($resourceGroup.Tags['EntraOpsServiceEM'])" } if ($null -ne $resourceGroup -and $resourceGroupTag -ne $ServiceName) { Write-Warning "$logPrefix Resource group '$rgName' exists and is not tagged 'EntraOpsServiceEM = $ServiceName'. It is used as landing zone: the service groups get eligible Contributor and RBAC Administrator access to all of its existing resources." } } catch { if ($_.Exception.Message -like "*not exist*" -or $_.Exception.Message -like "*ResourceGroupNotFound*") { Write-Verbose "$logPrefix Azure Resource Group not found, creating" # Remove-EntraOpsServiceCatalog -RemoveAzureResourceGroup only deletes resource groups with this tag $resourceGroup = New-AzResourceGroup -Name $rgName -Location $Location -Tag @{ EntraOpsServiceEM = $ServiceName } $confirmed = Wait-EntraOpsServiceEMCondition -Activity "Azure resource group $rgName" -logPrefix $logPrefix -Condition { (@(Get-AzResourceGroup -Name $rgName -ErrorAction SilentlyContinue) | Measure-Object).Count -eq 1 } if (-not $confirmed) { throw "Resource Group consistency with Azure not achieved" } } else { Write-Verbose "$logPrefix Failed to lookup Azure Resource Group" throw } } } $scopeId = if ($AzureScope -eq "Subscription") { $subscriptionScope } else { $resourceGroup.ResourceId } # Parent scopes (subscription, management groups, root) only; child and sibling scopes don't inherit $isInheritedScope = { param([string]$assignmentScope) if ([string]::IsNullOrEmpty($assignmentScope) -or $assignmentScope -eq $scopeId) { return $false } if ($assignmentScope.StartsWith("$subscriptionScope/", [System.StringComparison]::OrdinalIgnoreCase)) { return $scopeId.StartsWith("$assignmentScope/", [System.StringComparison]::OrdinalIgnoreCase) } return $true } try { # Use -WarningAction SilentlyContinue to suppress Az.Resources breaking-change warnings # about flattened properties (Actions, NotActions, etc.) that this code does not use. $reader = Get-AzRoleDefinition -Name Reader -WarningAction SilentlyContinue $userAccessAdmin = Get-AzRoleDefinition -Name "User Access Administrator" -WarningAction SilentlyContinue $contributor = Get-AzRoleDefinition -Name Contributor -WarningAction SilentlyContinue $rbacAdmin = Get-AzRoleDefinition -Name "Role Based Access Control Administrator" -WarningAction SilentlyContinue } catch { Write-Verbose "$logPrefix Failed to find role definitions" Write-Error $_ } if ($SkipControlPlaneDelegation) { Write-Verbose "$logPrefix Skipping Control Plane delegation setup" } else { $control = $ServiceGroups | Where-Object { $_.DisplayName -like "*-ControlPlane-Admins" } } $management = $ServiceGroups | Where-Object { $_.DisplayName -like "*-ManagementPlane-Admins" } $workloadAdmins = $ServiceGroups | Where-Object { $_.DisplayName -like "*-WorkloadPlane-Admins" } $workloadUsers = $ServiceGroups | Where-Object { $_.DisplayName -like "*-WorkloadPlane-Users" } # Load constrained delegation configuration from global config $constrainedDelegationConfig = $null if ($Global:EntraOpsConfig.ServiceEM.ConstrainedDelegation) { $constrainedDelegationConfig = $Global:EntraOpsConfig.ServiceEM.ConstrainedDelegation Write-Verbose "$logPrefix Loaded constrained delegation configuration from EntraOpsConfig" } else { Write-Verbose "$logPrefix No EntraOpsConfig.ServiceEM.ConstrainedDelegation found, using the built-in constrained delegation defaults" } $scheduleRequestParams = @{ Name = "" RoleDefinitionId = "" PrincipalId = "" Scope = $scopeId RequestType = "AdminAssign" Justification = "Initial Bootstrap" #ExpirationDuration = "P1Y" ExpirationType = "NoExpiration" #AfterDuration ScheduleInfoStartDateTime = (Get-Date -Format o) } $roleDefinitionPrefix = "/Subscriptions/$((Get-AzContext).Subscription.Id)/providers/Microsoft.Authorization/roleDefinitions/" $eligibleRbacSet = @() $toAdd = @() } process { Write-Verbose "$logPrefix Beginning AZ Container" # Permanent Reader for WorkloadPlane-Admins; all other Azure roles are PIM eligible if ($workloadAdmins) { $wlExisting = @() try { $wlExisting = Get-AzRoleAssignment -Scope $scopeId -ObjectId $workloadAdmins.Id } catch { Write-Verbose "$logPrefix Failed to check existing Reader assignment for WorkloadPlane-Admins: $_" } if ("$($workloadAdmins.Id)_$($reader.Id)" -notin ($wlExisting | ForEach-Object { "$($_.ObjectId)_$($_.RoleDefinitionId)" })) { try { Write-Verbose "$logPrefix Creating permanent Reader assignment for WorkloadPlane-Admins: $($workloadAdmins.Id)" New-AzRoleAssignment -Scope $scopeId -RoleDefinitionName $reader.Name -ObjectId $workloadAdmins.Id | Out-Null } catch { Write-Verbose "$logPrefix Failed to create Reader assignment for WorkloadPlane-Admins" Write-Error $_ } } } $existing = @() try { Write-Verbose "$logPrefix Getting PIM Eligible Assignments" $existing = @(Get-AzRoleEligibilitySchedule -Scope $scopeId) $eligibleRbacSet += $existing | Select-Object @{n = "c"; e = { $_.RoleDefinitionDisplayName + "_" + $_.PrincipalId } } | ForEach-Object c } catch { Write-Verbose "$logPrefix Failed to get PIM Eligible Assignments" Write-Error $_ } # Check for inherited PIM eligible assignments at parent scopes (Subscription, MG, Tenant Root) # so we skip redundant assignments for Contributor and UAA. # Check ManagementPlane-Admins for inherited Contributor eligibility $skipContributorForMgmt = $false if ($management) { try { Write-Verbose "$logPrefix Checking inherited PIM eligible assignments for ManagementPlane-Admins ($($management.Id))" $mgmtInherited = Get-AzRoleEligibilityScheduleInstance -Scope $subscriptionScope ` -Filter "principalId eq '$($management.Id)'" -ErrorAction Stop if ($mgmtInherited | Where-Object { $_.RoleDefinitionDisplayName -eq $contributor.Name -and (& $isInheritedScope $_.Scope) }) { Write-Verbose "$logPrefix ManagementPlane-Admins already has Contributor eligible at a higher scope — skipping assignment" $skipContributorForMgmt = $true } } catch { Write-Verbose "$logPrefix Failed to check inherited eligible assignments for ManagementPlane-Admins: $_" } } # Check ControlPlane-Admins for inherited UAA eligibility $skipUaaForControl = $false if ($control -and -not $SkipControlPlaneDelegation) { try { Write-Verbose "$logPrefix Checking inherited PIM eligible assignments for ControlPlane-Admins ($($control.Id))" $ctrlInherited = Get-AzRoleEligibilityScheduleInstance -Scope $subscriptionScope ` -Filter "principalId eq '$($control.Id)'" -ErrorAction Stop if ($ctrlInherited | Where-Object { $_.RoleDefinitionDisplayName -eq $userAccessAdmin.Name -and (& $isInheritedScope $_.Scope) }) { Write-Verbose "$logPrefix ControlPlane-Admins already has User Access Administrator eligible at a higher scope — skipping assignment" $skipUaaForControl = $true } } catch { Write-Verbose "$logPrefix Failed to check inherited eligible assignments for ControlPlane-Admins: $_" } } if ($management -and -not $skipContributorForMgmt -and "$($contributor.Name)_$($management.Id)" -notin $eligibleRbacSet) { $toAdd += @{ RoleDefinitionId = "$roleDefinitionPrefix/$($contributor.Id)" RoleId = $contributor.Id PrincipalId = $management.Id } } if ($control -and -not $skipUaaForControl -and "$($userAccessAdmin.Name)_$($control.Id)" -notin $eligibleRbacSet -and -not $SkipControlPlaneDelegation) { $toAdd += @{ RoleDefinitionId = "$roleDefinitionPrefix/$($userAccessAdmin.Id)" RoleId = $userAccessAdmin.Id PrincipalId = $control.Id } } if ($workloadAdmins -and -not $management) { Write-Warning "$logPrefix No ManagementPlane-Admins for $scopeId; nobody gets Contributor from ServiceEM and WorkloadPlane-Admins stay restricted to Reader and the constrained RBAC Administrator." } # Condition of the constrained RBAC Administrator: write and delete of role assignments limited to the # role definitions matched by RoleOperator and to the single target group $newConstrainedCondition = { param([string]$RoleOperator, [string]$RoleIds, [string]$TargetId) "((!(ActionMatches{'Microsoft.Authorization/roleAssignments/write'}))" + " OR " + "(@Request[Microsoft.Authorization/roleAssignments:RoleDefinitionId] $RoleOperator {$RoleIds}" + " AND " + "@Request[Microsoft.Authorization/roleAssignments:PrincipalId] ForAnyOfAnyValues:GuidEquals {$TargetId}))" + " AND " + "((!(ActionMatches{'Microsoft.Authorization/roleAssignments/delete'}))" + " OR " + "(@Resource[Microsoft.Authorization/roleAssignments:RoleDefinitionId] $RoleOperator {$RoleIds}" + " AND " + "@Resource[Microsoft.Authorization/roleAssignments:PrincipalId] ForAnyOfAnyValues:GuidEquals {$TargetId}))" } # Matches a condition created by $newConstrainedCondition with any role list and target group $getConstrainedConditionPattern = { param([string]$RoleOperator) $guid = '[0-9a-fA-F]{8}-(?:[0-9a-fA-F]{4}-){3}[0-9a-fA-F]{12}' $template = [regex]::Escape((& $newConstrainedCondition $RoleOperator '__ROLES__' '__TARGET__')) '^\s*' + $template.Replace('\ ', '\s*').Replace('__ROLES__', "\s*$guid(?:\s*,\s*$guid)*\s*").Replace('__TARGET__', "\s*(?<target>$guid)\s*") + '\s*$' } # Leftover schedules of a deleted resource group reference the old target group ID and don't count $hasConstrainedRbacAdmin = { param([string]$PrincipalId, [string]$TargetGroupId) [bool]($existing | Where-Object { $_.RoleDefinitionDisplayName -eq $rbacAdmin.Name -and $_.PrincipalId -eq $PrincipalId -and "$($_.Condition)" -like "*$TargetGroupId*" }) } # Constrained RBAC Administrator for ManagementPlane-Admins # May assign any role EXCEPT high-privileged roles, only to the WorkloadPlane-Admins group if ($workloadAdmins -and $management -and -not (& $hasConstrainedRbacAdmin $management.Id $workloadAdmins.Id)) { # Load excluded role IDs from configuration or use defaults $highPrivRoleIds = @( "8e3af657-a8ff-443c-a75c-2fe8c4bcb635", # Owner "18d7d88d-d35e-4fb5-a5c3-7773c20a72d9", # User Access Administrator "f58310d9-a9f6-439a-9e8d-f62e7b41a168" # Role Based Access Control Administrator ) if ($constrainedDelegationConfig -and $constrainedDelegationConfig.ManagementPlane.ExcludedRoleDefinitionIds) { $highPrivRoleIds = $constrainedDelegationConfig.ManagementPlane.ExcludedRoleDefinitionIds Write-Verbose "$logPrefix Using ManagementPlane excluded role IDs from configuration: $($highPrivRoleIds -join ', ')" } $excludeRolesStr = ($highPrivRoleIds -join ", ") $invalidRoleIds = @($highPrivRoleIds | Where-Object { -not [guid]::TryParse([string]$_, [ref][guid]::Empty) }) if ($invalidRoleIds.Count -gt 0) { throw "$logPrefix ServiceEM.ConstrainedDelegation.ManagementPlane.ExcludedRoleDefinitionIds contains invalid GUIDs: $($invalidRoleIds -join ', ')" } # ForAnyOfAllValues: the requested role must differ from every excluded role (ForAllOfAnyValues would always be true) $mgmtRoleOperator = "ForAnyOfAllValues:GuidNotEquals" $toAdd += @{ RoleDefinitionId = "$roleDefinitionPrefix/$($rbacAdmin.Id)" RoleId = $rbacAdmin.Id PrincipalId = $management.Id Condition = & $newConstrainedCondition $mgmtRoleOperator $excludeRolesStr $workloadAdmins.Id ConditionVersion = "2.0" ConditionPattern = & $getConstrainedConditionPattern $mgmtRoleOperator } } elseif ($workloadAdmins -and $management) { Write-Verbose "$logPrefix ManagementPlane-Admins already has the constrained Role Based Access Control Administrator eligible assignment for $($workloadAdmins.Id) — skipping" } # Constrained RBAC Administrator for WorkloadPlane-Admins # May assign only data-plane roles (Key Vault, Storage) to the WorkloadPlane-Users group if ($workloadAdmins -and $workloadUsers -and -not (& $hasConstrainedRbacAdmin $workloadAdmins.Id $workloadUsers.Id)) { # Load allowed role IDs from configuration or use defaults $dataPlaneRoleIds = @( # Key Vault "00482a5a-887f-4fb3-b363-3b7fe8e74483", # Key Vault Administrator "a4417e6f-fecd-4de8-b567-7b0420556985", # Key Vault Certificates Officer "14b46e9e-c2b7-41b4-b07b-48a6ebf60603", # Key Vault Crypto Officer "12338af0-0e69-4776-bea7-57ae8d297424", # Key Vault Crypto User "21090545-7ca7-4776-b22c-e363652d74d2", # Key Vault Reader "b86a8fe4-44ce-4948-aee5-eccb2c155cd7", # Key Vault Secrets Officer "4633458b-17de-408a-b874-0445c86b69e6", # Key Vault Secrets User # Storage "ba92f5b4-2d11-453d-a403-e96b0029c9fe", # Storage Blob Data Contributor "b7e6dc6d-f1e8-4753-8033-0f276bb0955b", # Storage Blob Data Owner "2a2b9908-6ea1-4ae2-8e65-a410df84e7d1", # Storage Blob Data Reader "0a9a7e1f-b9d0-4cc4-a60d-0319b160aaa3", # Storage Table Data Contributor "76199698-9eea-4c19-bc75-cec21354c6b6", # Storage Table Data Reader "974c5e8b-45b9-4653-ba55-5f855dd0fb88", # Storage Queue Data Contributor "19e7f393-937e-4f77-808e-94535e297925", # Storage Queue Data Reader "8a0f0c08-91a1-4084-bc3d-661d67233fed", # Storage Queue Data Message Processor "c6a89b2d-59bc-44d0-9896-0f6e12d7b80a" # Storage Queue Data Message Sender ) if ($constrainedDelegationConfig -and $constrainedDelegationConfig.WorkloadPlane.AllowedRoleDefinitionIds) { $dataPlaneRoleIds = $constrainedDelegationConfig.WorkloadPlane.AllowedRoleDefinitionIds Write-Verbose "$logPrefix Using WorkloadPlane allowed role IDs from configuration: $($dataPlaneRoleIds.Count) roles" } $allowRolesStr = ($dataPlaneRoleIds -join ", ") $invalidRoleIds = @($dataPlaneRoleIds | Where-Object { -not [guid]::TryParse([string]$_, [ref][guid]::Empty) }) if ($invalidRoleIds.Count -gt 0) { throw "$logPrefix ServiceEM.ConstrainedDelegation.WorkloadPlane.AllowedRoleDefinitionIds contains invalid GUIDs: $($invalidRoleIds -join ', ')" } $wlRoleOperator = "ForAnyOfAnyValues:GuidEquals" $toAdd += @{ RoleDefinitionId = "$roleDefinitionPrefix/$($rbacAdmin.Id)" RoleId = $rbacAdmin.Id PrincipalId = $workloadAdmins.Id Condition = & $newConstrainedCondition $wlRoleOperator $allowRolesStr $workloadUsers.Id ConditionVersion = "2.0" ConditionPattern = & $getConstrainedConditionPattern $wlRoleOperator } } elseif ($workloadAdmins -and $workloadUsers) { Write-Verbose "$logPrefix WorkloadPlane-Admins already has the constrained Role Based Access Control Administrator eligible assignment for $($workloadUsers.Id) — skipping" } $keptConflicts = [System.Collections.Generic.List[string]]::new() foreach ($add in $toAdd) { $scheduleRequestParams.Name = [guid]::NewGuid() $scheduleRequestParams.RoleDefinitionId = $add.RoleDefinitionId $scheduleRequestParams.PrincipalId = $add.PrincipalId if ($add.Condition) { $scheduleRequestParams.Condition = $add.Condition $scheduleRequestParams.ConditionVersion = $add.ConditionVersion } else { $scheduleRequestParams.Remove('Condition') | Out-Null $scheduleRequestParams.Remove('ConditionVersion') | Out-Null } try { Write-Verbose "$logPrefix Getting role management policy for: $($add.RoleId)" $policy = Get-AzRoleManagementPolicy -Scope $scheduleRequestParams.Scope -Name $add.RoleId -WarningAction SilentlyContinue } catch { Write-Verbose "$logPrefix Failed to get role management policy" Write-Error $_ } # Use @() to handle both Array (legacy) and List (Az.Resources 9+) rule collections $policyRules = @($policy.Rule) if (($policyRules | Where-Object { $_.Id -eq "Expiration_Admin_Eligibility" }).IsExpirationRequired) { # The role management policy applies to every principal of the scope, not only to the service groups Write-Warning "$logPrefix The PIM policy of role '$($add.RoleId)' on '$scopeId' requires an expiration of eligible assignments. It is changed to allow permanent eligible assignments, which applies to all eligible assignments of this role on the scope." $roleManagementPolicySplat = @{ Scope = $scopeId Name = $add.RoleId Rule = @( @{ id = "Expiration_Admin_Eligibility" IsExpirationRequired = $false ruleType = "RoleManagementPolicyExpirationRule" } ) } try { Update-AzRoleManagementPolicy @roleManagementPolicySplat -WarningAction SilentlyContinue } catch { Write-Verbose "$logPrefix Failed to update role management policy rules" Write-Error $_ } } if ($add.Condition) { # Azure allows one eligible assignment per principal, role and scope, so a leftover of a deleted resource group has to go first $isStale = { "$($_.RoleDefinitionId)" -like "*$($add.RoleId)" -and $_.PrincipalId -eq $add.PrincipalId -and $_.Scope -ieq $scopeId } $conflicting = @($existing | Where-Object $isStale) if ($conflicting.Count -gt 0) { # Only remove assignments created by ServiceEM for a target group that no longer exists; in the Centralized model the # principal is a tenant-wide group whose other assignments (customer or another landing zone) must be kept $keepReason = $null foreach ($assignment in $conflicting) { $conditionMatch = [regex]::Match("$($assignment.Condition)", $add.ConditionPattern, [System.Text.RegularExpressions.RegexOptions]::IgnoreCase) if (-not $conditionMatch.Success) { $keepReason = "its condition wasn't created by ServiceEM" break } $previousTargetId = $conditionMatch.Groups['target'].Value try { $previousTarget = Invoke-EntraOpsMsGraphQuery -Method GET -Uri "/v1.0/groups/$($previousTargetId)?`$select=id" -OutputType PSObject -DisableCache -ThrowOnFailure -SuppressNotFoundWarning } catch { if ($_.Exception.Data['StatusCode'] -ne 404) { throw "$logPrefix Failed to look up target group $previousTargetId of the existing eligible assignment of $($add.PrincipalId) on ${scopeId}: $($_.Exception.Message)" } $previousTarget = $null } if ($previousTarget.Id) { $keepReason = "it delegates to the existing group $previousTargetId (e.g. another landing zone on the same scope)" break } } if ($keepReason) { Write-Warning "$logPrefix Keeping the existing eligible Role Based Access Control Administrator assignment of $($add.PrincipalId) on ${scopeId}: $keepReason. The constrained delegation for this landing zone isn't assigned; remove or adjust the existing assignment and run the deployment again." $keptConflicts.Add("$($add.PrincipalId) on ${scopeId} ($keepReason)") continue } Write-Warning "$logPrefix Removing leftover eligible assignment of $($add.PrincipalId) on $scopeId; its target group no longer exists" try { New-AzRoleEligibilityScheduleRequest -Name ([guid]::NewGuid()) -Scope $scopeId -RoleDefinitionId $add.RoleDefinitionId -PrincipalId $add.PrincipalId -RequestType AdminRemove -ErrorAction Stop | Out-Null } catch { throw "$logPrefix Failed to remove leftover eligible assignment of $($add.PrincipalId) on ${scopeId}: $($_.Exception.Message)" } $removed = Wait-EntraOpsServiceEMCondition -Activity "Removal of leftover eligible assignment $($add.PrincipalId)" -logPrefix $logPrefix -Condition { @(Get-AzRoleEligibilitySchedule -Scope $scopeId | Where-Object $isStale).Count -eq 0 } if (-not $removed) { throw "$logPrefix Leftover eligible assignment of $($add.PrincipalId) on $scopeId was not removed in time" } } } try { Write-Verbose "$logPrefix Creating PIM Eligible Assignment for PrincipalId: $($add.PrincipalId)" New-AzRoleEligibilityScheduleRequest @scheduleRequestParams -ErrorAction Stop | Out-Null } catch { Write-Verbose "$logPrefix Failed to create PIM Eligible Assignment" throw "$logPrefix Failed to create PIM eligible assignment for $($add.PrincipalId) on ${scopeId}: $($_.Exception.Message)" } } if ($keptConflicts.Count -gt 0) { throw "$logPrefix The constrained Role Based Access Control Administrator delegation wasn't assigned because existing eligible assignments were kept: $($keptConflicts -join '; '). Remove or adjust them and run the deployment again." } } end { if ($AzureScope -eq "Subscription") { return [psobject]@{ ResourceId = $scopeId; SubscriptionId = $scopeId -replace '^/subscriptions/' } } return [psobject]$resourceGroup } } # SIG # Begin signature block # MIIniAYJKoZIhvcNAQcCoIIneTCCJ3UCAQExDzANBglghkgBZQMEAgEFADB5Bgor # BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG # KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCALd/WR2thlz5Y3 # e+pyDBlm3uRlKsXgKmlYytcInR0466CCIDwwggXJMIIEsaADAgECAhAbtY8lKt8j # AEkoya49fu0nMA0GCSqGSIb3DQEBDAUAMH4xCzAJBgNVBAYTAlBMMSIwIAYDVQQK # ExlVbml6ZXRvIFRlY2hub2xvZ2llcyBTLkEuMScwJQYDVQQLEx5DZXJ0dW0gQ2Vy # dGlmaWNhdGlvbiBBdXRob3JpdHkxIjAgBgNVBAMTGUNlcnR1bSBUcnVzdGVkIE5l # dHdvcmsgQ0EwHhcNMjEwNTMxMDY0MzA2WhcNMjkwOTE3MDY0MzA2WjCBgDELMAkG # A1UEBhMCUEwxIjAgBgNVBAoTGVVuaXpldG8gVGVjaG5vbG9naWVzIFMuQS4xJzAl # BgNVBAsTHkNlcnR1bSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTEkMCIGA1UEAxMb # Q2VydHVtIFRydXN0ZWQgTmV0d29yayBDQSAyMIICIjANBgkqhkiG9w0BAQEFAAOC # Ag8AMIICCgKCAgEAvfl4+ObVgAxknYYblmRnPyI6HnUBfe/7XGeMycxca6mR5rlC # 5SBLm9qbe7mZXdmbgEvXhEArJ9PoujC7Pgkap0mV7ytAJMKXx6fumyXvqAoAl4Va # qp3cKcniNQfrcE1K1sGzVrihQTib0fsxf4/gX+GxPw+OFklg1waNGPmqJhCrKtPQ # 0WeNG0a+RzDVLnLRxWPa52N5RH5LYySJhi40PylMUosqp8DikSiJucBb+R3Z5yet # /5oCl8HGUJKbAiy9qbk0WQq/hEr/3/6zn+vZnuCYI+yma3cWKtvMrTscpIfcRnNe # GWJoRVfkkIJCu0LW8GHgwaM9ZqNd9BjuiMmNF0UpmTJ1AjHuKSbIawLmtWJFfzcV # WiNoidQ+3k4nsPBADLxNF8tNorMe0AZa3faTz1d1mfX6hhpneLO/lv403L3nUlbl # s+V1e9dBkQXcXWnjlQ1DufyDljmVe2yAWk8TcsbXfSl6RLpSpCrVQUYJIP4ioLZb # MI28iQzV13D4h1L92u+sUS4Hs07+0AnacO+Y+lbmbdu1V0vc5SwlFcieLnhO+Nqc # noYsylfzGuXIkosagpZ6w7xQEmnYDlpGizrrJvojybawgb5CAKT41v4wLsfSRvbl # jnX98sy50IdbzAYQYLuDNbdeZ95H7JlI8aShFf6tjGKOOVVPORa5sWOd/7cCAwEA # AaOCAT4wggE6MA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFLahVDkCw6A/joq8 # +tT4HKbROg79MB8GA1UdIwQYMBaAFAh2zcsH/yT2xc3tu5C84oQ3RnX3MA4GA1Ud # DwEB/wQEAwIBBjAvBgNVHR8EKDAmMCSgIqAghh5odHRwOi8vY3JsLmNlcnR1bS5w # bC9jdG5jYS5jcmwwawYIKwYBBQUHAQEEXzBdMCgGCCsGAQUFBzABhhxodHRwOi8v # c3ViY2Eub2NzcC1jZXJ0dW0uY29tMDEGCCsGAQUFBzAChiVodHRwOi8vcmVwb3Np # dG9yeS5jZXJ0dW0ucGwvY3RuY2EuY2VyMDkGA1UdIAQyMDAwLgYEVR0gADAmMCQG # CCsGAQUFBwIBFhhodHRwOi8vd3d3LmNlcnR1bS5wbC9DUFMwDQYJKoZIhvcNAQEM # BQADggEBAFHCoVgWIhCL/IYx1MIy01z4S6Ivaj5N+KsIHu3V6PrnCA3st8YeDrJ1 # BXqxC/rXdGoABh+kzqrya33YEcARCNQOTWHFOqj6seHjmOriY/1B9ZN9DbxdkjuR # mmW60F9MvkyNaAMQFtXx0ASKhTP5N+dbLiZpQjy6zbzUeulNndrnQ/tjUoCFBMQl # lVXwfqefAcVbKPjgzoZwpic7Ofs4LphTZSJ1Ldf23SIikZbr3WjtP6MZl9M7JYjs # NhI9qX7OAo0FmpKnJ25FspxihjcNpDOO16hO0EoXQ0zF8ads0h5YbBRRfopUofbv # n3l6XYGaFpAP4bvxSgD5+d2+7arszgowggZrMIIEU6ADAgECAhBX5gfP8LabvnxC # XoItEBa/MA0GCSqGSIb3DQEBCwUAMFYxCzAJBgNVBAYTAlBMMSEwHwYDVQQKExhB # c3NlY28gRGF0YSBTeXN0ZW1zIFMuQS4xJDAiBgNVBAMTG0NlcnR1bSBDb2RlIFNp # Z25pbmcgMjAyMSBDQTAeFw0yNjEwMDYwODI4MjNaFw0yNzEwMDYwODI4MjJaMIGQ # MQswCQYDVQQGEwJERTEdMBsGA1UECAwUUmhpbmVsYW5kLVBhbGF0aW5hdGUxEjAQ # BgNVBAcMCUxhaG5zdGVpbjEeMBwGA1UECgwVT3BlbiBTb3VyY2UgRGV2ZWxvcGVy # MS4wLAYDVQQDDCVPcGVuIFNvdXJjZSBEZXZlbG9wZXIgVGhvbWFzIE5hdW5oZWlt # MIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEAphp2ljSmVsRB6gvkEq7A # DUMENpKZiDFoAfF1ktD1nCwaVyI+bJ0ROjHwAn4iJgS+vlBDPOUIw2GLHkiqUhOg # HSwzg6gj45GNmt6zxsKP0zPlhMB/fb0zmz7/F7e3yfKCXLfhbHuk0Es9gJ+8Wmet # 9StVwwSE/K0MpW06GKA9nPWyE4N/ADwigDIj9rsTKJR4cve9w8G1b3zohpUc/qMr # onab+HVw5r9b3sOBnnaXC6zvrX4fovRz1ztwfWjRjILLoFG6mW/TuK5F2ICD3Rhl # 9R2rJWWWhzTDMsQlzvX+/SaM74q3OFpIVNdhvtmWU9pKUBRAyYMijcDqKEFGSzrc # 8JFmGMpP5TuroWF+N21d3px2eARvW1mV0DCfFAH+Og20SH/gVW6/ltQQEkH/z/Zt # 49ARyJrgESLOQtlMCIYSoKISdkk4ml4VUS1E/PcbzXEYj3a7m6DOxcd3WuY03UT9 # cnk3p0LMN6LeLYAZgVY+HRloZlH+kW2SkOHNuWgtXvZhAgMBAAGjggF4MIIBdDAM # BgNVHRMBAf8EAjAAMD0GA1UdHwQ2MDQwMqAwoC6GLGh0dHA6Ly9jY3NjYTIwMjEu # Y3JsLmNlcnR1bS5wbC9jY3NjYTIwMjEuY3JsMHMGCCsGAQUFBwEBBGcwZTAsBggr # BgEFBQcwAYYgaHR0cDovL2Njc2NhMjAyMS5vY3NwLWNlcnR1bS5jb20wNQYIKwYB # BQUHMAKGKWh0dHA6Ly9yZXBvc2l0b3J5LmNlcnR1bS5wbC9jY3NjYTIwMjEuY2Vy # MB8GA1UdIwQYMBaAFN10XUwA23ufoHTKsW73PMAywHDNMB0GA1UdDgQWBBTBvGyH # MXj1dMbVnxuof9oP4PY7cDBLBgNVHSAERDBCMAgGBmeBDAEEATA2BgsqhGgBhvZ3 # AgUBBDAnMCUGCCsGAQUFBwIBFhlodHRwczovL3d3dy5jZXJ0dW0ucGwvQ1BTMBMG # A1UdJQQMMAoGCCsGAQUFBwMDMA4GA1UdDwEB/wQEAwIHgDANBgkqhkiG9w0BAQsF # AAOCAgEAkC165c7/r6y3VeEEpNXas9bQs6ZpKPdJffHkf9xoPi4kWvAIWGc4ocG1 # DJLb18RXiDYShDmT5sb/6I08FeipKi2DNJkuOq6yOTJ3S7+hNFIZCyd6i7WKO8ea # P5I+FAbyJP4+5coZoMg45bUa9JoocaJCm+5n7WiCfN7/EQpClW5Trm4+ZMbp7EMN # F1uLJgGCMnN0SsoUarp7PHlqupMxMC4Uhhe+lAP2ytcSIU81L97/qjrADbLWqICk # 9il5iGM+aZndguq/cmfH9rJj4GRZHWnG6WwnzYD9+KgYlRI6em+/9dq+IRiXe2Bs # thUuIaFW7U2gWw8tBTEYm1UBLHdJx+L1gjJCLs5KT/wy5wU32wZxNDqPUQejUC1x # GvU3cKqmLgRq+yNf3gFVfzqIoggyM3U9wQvnk7nBtCkpjXS2hdx4yxzcmrlWsDHr # z6+6/jeyQySzbYUlFmKnc4n9Mw2soHOajEua5poFARVRS5KgUBuiiQ21YFNYzWBo # qGuECaaAI/C5rGxapMJ3dAOBnHw6NLqBet+ZXBJEccX5y89MLlOQu+jDmJP8HieR # qDLlwfhVZnw4RY8Rus43NZBwhNlVuOUXqocHKmJhKNlekda3qsJj93eQBQOxKJcc # w5O08q9jukpH2+yzFAj+7ll33DuNUgWptogc4Awtbx3glzcEvJQwggaCMIIEaqAD # AgECAhAo8HfBHDa9/l90MkdwJy4DMA0GCSqGSIb3DQEBDAUAMFYxCzAJBgNVBAYT # AlBMMSEwHwYDVQQKExhBc3NlY28gRGF0YSBTeXN0ZW1zIFMuQS4xJDAiBgNVBAMT # G0NlcnR1bSBUaW1lc3RhbXBpbmcgMjAyMSBDQTAeFw0yNjAzMTEwNzM0NTRaFw0z # NjAyMjcwNzM0NTRaMFAxCzAJBgNVBAYTAlBMMSEwHwYDVQQKDBhBc3NlY28gRGF0 # YSBTeXN0ZW1zIFMuQS4xHjAcBgNVBAMMFUNlcnR1bSBUaW1lc3RhbXAgMjAyNjCC # AiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBALi9zfBw+xI12hi2F9uNQBNO # STkTIi13fuh3DiWybI5392bBfJw6tO7zDs7UqP/rejVf4xFTU3XNOPsuOFkP5SPF # snWwn3VJL+NhNhsuujvYsQtaQnvNo1wk46reTgws1L8OFEdKaxgyqT4zOQx81mLw # NZSqXNU9Rrb9oAJWJF4Mydz+C1ebu0D0+vgM3tqAQ8TU203fqgKCPqIdHloaFqH9 # eyR+8tnpmy2hnnN59ZOtmsuHeRwf5iTOhlhf0d/qqquLOSIGPgGVL1Hj5XBUox2O # E7tQRr+l/xxNbL6OuwBO4Aa2EbuxYg37cpXv5i2/lTL4NASB/duf9W4fjA3Ro+GP # +bC54n2OYiM3Cq8z92ejx8F5HWO2e4/75eC95B9tMw/eq7109GDyW2+2ut5Bn0o8 # MXJTERkyystWbqcnUkomA97qfiDCwvpWuOMdfTOksXe9S+xDxOJaCbiyTivgPulx # cT+tfQTRXBr5UwtPvc+INnafOEilzVHA11tv+pHcylryxBc08cUihf/ttqkZS0bm # x3VxQay8uohaTp0//ECA9vYGW51SKdJMJhXvKyIdBLy8WYNchTt8yRITv9Adxg9J # qGujh+TnP1bW3aQjFtlEl7SzgNeI8OVA/CJAWWUuRT3Au9ToOpHy/flrrknhN4he # Ew7/6ILMv4HSsj0YZ4otAgMBAAGjggFQMIIBTDB1BggrBgEFBQcBAQRpMGcwOwYI # KwYBBQUHMAKGL2h0dHA6Ly9zdWJjYS5yZXBvc2l0b3J5LmNlcnR1bS5wbC9jdHNj # YTIwMjEuY2VyMCgGCCsGAQUFBzABhhxodHRwOi8vc3ViY2Eub2NzcC1jZXJ0dW0u # Y29tMB8GA1UdIwQYMBaAFL5UAi+/QGxzQ86sCSVOnkNEGu7gMAwGA1UdEwEB/wQC # MAAwOQYDVR0fBDIwMDAuoCygKoYoaHR0cDovL3N1YmNhLmNybC5jZXJ0dW0ucGwv # Y3RzY2EyMDIxLmNybDAWBgNVHSUBAf8EDDAKBggrBgEFBQcDCDAOBgNVHQ8BAf8E # BAMCB4AwIgYDVR0gBBswGTAIBgZngQwBBAIwDQYLKoRoAYb2dwIFAQswHQYDVR0O # BBYEFCM5aiirmhKnpKfyH/lFmr+N/oIDMA0GCSqGSIb3DQEBDAUAA4ICAQBm/pOb # rAveTqYFPCSthxNVwBHTW4TfuBQY2KGCdMlH6ALjVPYf2XARNGMTXScR0JQ2/c2L # xLpoHHhRwxFf3d933DhFS165rYj6iPSQKm41rc9AMpTCjMdmai1aK9o870pOzTzL # RhWFVIxm+5Qf+t4V5z14tPoUsAWdfRKD1Zzvzfe5AHlE9rxOZcGROiULxs3Edf7Z # J8RZI0jzD9juj2oEX2XS7BVKuNCK1W0h1mjapv028yqo8UtZh5/1Ib48Yyp88BDY # Shh5PSGUUuS5tYXVfcduIPBe3PdNTYRYdT5VNxkwLgjMzIcr/6SH2fejFJDjnCxj # vXLuarErJg/IdI+hXdGIBDVOGv1zrkF4ktoQvFZIp5DIAYh0GwyUhc87qqn5UaYT # j2+iyiBDSNh7aGCrkuAnqg7Qh7frhX+QhMGeJ9SpLpnG76qN85hLyjvYR6gRYiLT # j8G4fxjqV400/buPw0rBo9yJZyq/8cGdCOLPOYf9qpefTcrZKeO9pg+UJHx8UABb # aTqizRxm8sUSEycccCCFhIit3AI/3wGuSOADeiA676NcQ7+dsP04YvzvdN7pTZNy # g672WpBDJdg0eItHl4yomvNeIOWh1qs4QqOW6TI8Ocsi4uSDTLorxLUDnBTwKQzz # pxd1YUctEgRh32R0J9FkFkeEruJU94RljFr1uDCCBrkwggShoAMCAQICEQCZo4AK # JlU7ZavcboSms+o5MA0GCSqGSIb3DQEBDAUAMIGAMQswCQYDVQQGEwJQTDEiMCAG # A1UEChMZVW5pemV0byBUZWNobm9sb2dpZXMgUy5BLjEnMCUGA1UECxMeQ2VydHVt # IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MSQwIgYDVQQDExtDZXJ0dW0gVHJ1c3Rl # ZCBOZXR3b3JrIENBIDIwHhcNMjEwNTE5MDUzMjE4WhcNMzYwNTE4MDUzMjE4WjBW # MQswCQYDVQQGEwJQTDEhMB8GA1UEChMYQXNzZWNvIERhdGEgU3lzdGVtcyBTLkEu # MSQwIgYDVQQDExtDZXJ0dW0gQ29kZSBTaWduaW5nIDIwMjEgQ0EwggIiMA0GCSqG # SIb3DQEBAQUAA4ICDwAwggIKAoICAQCdI88EMCM7wUYs5zNzPmNdenW6vlxNur3r # Lfi+5OZ+U3iZIB+AspO+CC/bj+taJUbMbFP1gQBJUzDUCPx7BNLgid1TyztVLn52 # NKgxxu8gpyTr6EjWyGzKU/gnIu+bHAse1LCitX3CaOE13rbuHbtrxF2tPU8f253Q # gX6eO8yTbGps1Mg+yda3DcTsOYOhSYNCJiL+5wnjZ9weoGRtvFgMHtJg6i671OPX # IciiHO4Lwo2p9xh/tnj+JmCQEn5QU0NxzrOiRna4kjFaA9ZcwSaG7WAxeC/xoZSx # F1oK1UPZtKVt+yrsGKqWONoK6f5EmBOAVEK2y4ATDSkb34UD7JA32f+Rm0wsr5aj # zftDhA5mBipVZDjHpwzv8bTKzCDUSUuUmPo1govD0RwFcTtMXcfJtm1i+P2UNXad # PyYVKRxKQATHN3imsfBiNRdN5kiVVeqP55piqgxOkyt+HkwIA4gbmSc3hD8ke66t # 9MjlcNg73rZZlrLHsAIV/nJ0mmgSjBI/TthoGJDydekOQ2tQD2Dup/+sKQptalDl # ui59SerVSJg8gAeV7N/ia4mrGoiez+SqV3olVfxyLFt3o/OQOnBmjhKUANoKLYlK # mUpKEFI0PfoT8Q1W/y6s9LTI6ekbi0igEbFUIBE8KDUGfIwnisEkBw5KcBZ3XwnH # mfznwlKo8QIDAQABo4IBVTCCAVEwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU # 3XRdTADbe5+gdMqxbvc8wDLAcM0wHwYDVR0jBBgwFoAUtqFUOQLDoD+Oirz61Pgc # ptE6Dv0wDgYDVR0PAQH/BAQDAgEGMBMGA1UdJQQMMAoGCCsGAQUFBwMDMDAGA1Ud # HwQpMCcwJaAjoCGGH2h0dHA6Ly9jcmwuY2VydHVtLnBsL2N0bmNhMi5jcmwwbAYI # KwYBBQUHAQEEYDBeMCgGCCsGAQUFBzABhhxodHRwOi8vc3ViY2Eub2NzcC1jZXJ0 # dW0uY29tMDIGCCsGAQUFBzAChiZodHRwOi8vcmVwb3NpdG9yeS5jZXJ0dW0ucGwv # Y3RuY2EyLmNlcjA5BgNVHSAEMjAwMC4GBFUdIAAwJjAkBggrBgEFBQcCARYYaHR0 # cDovL3d3dy5jZXJ0dW0ucGwvQ1BTMA0GCSqGSIb3DQEBDAUAA4ICAQB1iFgP5Y9Q # KJpTnxDsQ/z0O23JmoZifZdEOEmQvo/79PQg9nLF/GJe6ZiUBEyDBHMtFRK0mXj3 # Qv3gL0sYXe+PPMfwmreJHvgFGWQ7XwnfMh2YIpBrkvJnjwh8gIlNlUl4KENTK5DL # qsYPEtRQCw7R6p4s2EtWyDDr/M58iY2UBEqfUU/ujR9NuPyKk0bEcEi62JGxauFY # zZ/yld13fHaZskIoq2XazjaD0pQkcQiIueL0HKiohS6XgZuUtCKA7S6CHttZEsOb # QJ1j2s0urIDdqF7xaXFVaTHKtAuMfwi0jXtF3JJphrJfc+FFILgCbX/uYBPBlbBI # P4Ht4xxk2GmfzMn7oxPITpigQFJFWuzTMUUgdRHTxaTSKRJ/6Uh7ki/pFjf9sUAS # WgxT69QF9Ki4JF5nBIujxZ2sOU9e1HSCJwOfK07t5nnzbs1LbHuAIGJsRJiQ6HX/ # DW1XFOlXY1rc9HufFhWU+7Uk+hFkJsfzqBz3pRO+5aI6u5abI4Qws4YaeJH7H7M8 # X/YNoaArZbV4Ql+jarKsE0+8XvC4DJB+IVcvC9Ydqahi09mjQse4fxfef0L7E3hh # o2O3bLDM6v60rIRUCi2fJT2/IRU5ohgyTch4GuYWefSBsp5NPJh4QRTP9DC3gc5Q # EKtbrTY0Ka87Web7/zScvLmvQBm8JDFpDjCCBrkwggShoAMCAQICEQDn/2nHOzXO # S5Em2HR8aKWHMA0GCSqGSIb3DQEBDAUAMIGAMQswCQYDVQQGEwJQTDEiMCAGA1UE # ChMZVW5pemV0byBUZWNobm9sb2dpZXMgUy5BLjEnMCUGA1UECxMeQ2VydHVtIENl # cnRpZmljYXRpb24gQXV0aG9yaXR5MSQwIgYDVQQDExtDZXJ0dW0gVHJ1c3RlZCBO # ZXR3b3JrIENBIDIwHhcNMjEwNTE5MDUzMjA3WhcNMzYwNTE4MDUzMjA3WjBWMQsw # CQYDVQQGEwJQTDEhMB8GA1UEChMYQXNzZWNvIERhdGEgU3lzdGVtcyBTLkEuMSQw # IgYDVQQDExtDZXJ0dW0gVGltZXN0YW1waW5nIDIwMjEgQ0EwggIiMA0GCSqGSIb3 # DQEBAQUAA4ICDwAwggIKAoICAQDpEh8ENe25XXrFppVBvoplf0530W0lddNmjtv4 # YSh/f7eDQKFaIqc7tHj7ox+u8vIsJZlroakUeMS3i3T8aJRC+eQs4FF0GqvkM6+W # ZO8kmzZfxmZaBYmMLs8FktgFYCzywmXeQ1fEExflee2OpbHVk665eXRHjH7MYZIz # Nnjl2m8Hy8ulB9mR8wL/W0v0pjKNT6G0sfrx1kk+3OGosFUb7yWNnVkWKU4qSxLv # 16kJ6oVJ4BSbZ4xMak6JLeB8szrK9vwGDpvGDnKCUMYL3NuviwH1x4gZG0JAXU3x # 2pOAz91JWKJSAmRy/l0s0l5bEYKolg+DMqVhlOANd8Yh5mkQWaMEvBRE/kAGzIqg # WhwzN2OsKIVtO8mf5sPWSrvyplSABAYa13rMYnzwfg08nljZHghquCJYCa/xHK9a # cev9UD7Y+usr15d7mrszzxhF1JOr1Mpup2chNSBlyOObhlSO16rwrffVrg/SzaKf # SndS5swRhr8bnDqNJY9TNyEYvBYpgF95K7p0g4LguR4A++Z1nFIHWVY5v0fNVZmg # zxD9uVo/gta3onGOQj3JCxgYx0KrCXu4yc9QiVwTFLWbNdHFSjBCt5/8Q9pLuRhV # ocdCunhcHudMS1CGQ/Rn0+7P+fzMgWdRKfEOh/hjLrnQ8BdJiYrZNxvIOhM2aa3z # EDHNwwIDAQABo4IBVTCCAVEwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUvlQC # L79AbHNDzqwJJU6eQ0Qa7uAwHwYDVR0jBBgwFoAUtqFUOQLDoD+Oirz61PgcptE6 # Dv0wDgYDVR0PAQH/BAQDAgEGMBMGA1UdJQQMMAoGCCsGAQUFBwMIMDAGA1UdHwQp # MCcwJaAjoCGGH2h0dHA6Ly9jcmwuY2VydHVtLnBsL2N0bmNhMi5jcmwwbAYIKwYB # BQUHAQEEYDBeMCgGCCsGAQUFBzABhhxodHRwOi8vc3ViY2Eub2NzcC1jZXJ0dW0u # Y29tMDIGCCsGAQUFBzAChiZodHRwOi8vcmVwb3NpdG9yeS5jZXJ0dW0ucGwvY3Ru # Y2EyLmNlcjA5BgNVHSAEMjAwMC4GBFUdIAAwJjAkBggrBgEFBQcCARYYaHR0cDov # L3d3dy5jZXJ0dW0ucGwvQ1BTMA0GCSqGSIb3DQEBDAUAA4ICAQC4k1l3yUwV/ZQH # CKCneqAs8EGTnwEUJLdDpokN/dMhKjK0rR5qX8nIIHzxpQR3TAw2IRw1Uxsr2Pli # G3bCFqSdQTUbfaTq6V3vBzEebDru9QFjqlKnxCF2h1jhLNFFplbPJiW+JSnJTh1f # KEqEdKdxgl9rVTvlxfEJ7exOn25MGbd/wGPwuSmMxRJVO0wnqgS7kmoJjNF9zqee # hFSDDP8ZVkWg4EZ2tIS0M3uZmByRr+1Lkwjjt8AtW83mVnZTyTsOb+FNfwJY7DS4 # FmWhkRbgcHRetreoTirPOr/ozyDKhT8MTSTf6Lttg6s6T/u08mDWw6HK04ZRDfQ9 # sb77QV8mKgO44WGP31vXnVKoWVJpFBjPvjL8/Zck/5wXX2iqjOaLStFOR/IQki+E # hn4zlcgVm22ZVCBPF+l8nAwUUShCtKuSU7GmZLKCmmxQMkSiWILTm8EtVD6AxnJh # oq8EnhjEEyUoflkeRF2WhFiVQOmWTwZRr44IxWGkNJC6tTorW5rl2Zl+2e9JLPYf # 3pStAPMDoPKIjVXd6NW2+fZrNUBeDo2eOa5Fn7Brs/HLQff5Xgris5MeUbdVgDrF # 8uxO6cLPvZPo63j62SsNg55pTWk9fUIF9iPoRbb4QurjoY/woI1RAOKtYtTic6aA # Jq3u83RIPpGXBSJKwx4KJAOZnCDCtTGCBqIwggaeAgEBMGowVjELMAkGA1UEBhMC # UEwxITAfBgNVBAoTGEFzc2VjbyBEYXRhIFN5c3RlbXMgUy5BLjEkMCIGA1UEAxMb # Q2VydHVtIENvZGUgU2lnbmluZyAyMDIxIENBAhBX5gfP8LabvnxCXoItEBa/MA0G # CWCGSAFlAwQCAQUAoIGEMBgGCisGAQQBgjcCAQwxCjAIoAKAAKECgAAwGQYJKoZI # hvcNAQkDMQwGCisGAQQBgjcCAQQwHAYKKwYBBAGCNwIBCzEOMAwGCisGAQQBgjcC # ARUwLwYJKoZIhvcNAQkEMSIEIJxeYbxIfc+1pzUYcuyvvMmxSVgGC9VGjGzMRrug # D1LRMA0GCSqGSIb3DQEBAQUABIIBgIfOwUtMA/X8i/R4DwKp1Cf+V8gQIuzVgj3z # 4EU8Rp/mpemWYTjuBqi1tNH/ug15+S8YT+uZx8xHYJ8ZdguH9tDetP5GcVlSGxHB # 5BICgKwIbgslXF8SpAygpYjlUffngjoT3IaseiC4+xRD4chMMG/yWeiWXkfwT+50 # QdkjK+Ck+JV3eh+jY8WUqF9QT55jgQZP0PLBQ4I4d22bP1oTnFYQVKluDXM/wU0Y # YP5Rgxjx8gUleyuqc0jUtkG+gdKBm9uYrIqvY4kuE/h4kpAv2ydAXKwz1aqdOfLN # xDo5gwL/P0oKmcnxU70AeZ32+FLWSL28xZloUS8X1n7l6dcWVv5w3YIA4qzoTm66 # h+AeMrKgBskGQPfE4M5t79YCupyKckMiGAsBM0+Fdlmd3utPwEGYVbH1yu2rOd/F # OSFT9EghKYueAEkmiEzFZWFyph671v3R7zybCJ5x0T3X+iAt0mLTlVo4W/bSFSnn # 7jIEjVp9P4wnQd1KAQtyeJnVq3E13aGCBAIwggP+BgkqhkiG9w0BCQYxggPvMIID # 6wIBATBqMFYxCzAJBgNVBAYTAlBMMSEwHwYDVQQKExhBc3NlY28gRGF0YSBTeXN0 # ZW1zIFMuQS4xJDAiBgNVBAMTG0NlcnR1bSBUaW1lc3RhbXBpbmcgMjAyMSBDQQIQ # KPB3wRw2vf5fdDJHcCcuAzANBglghkgBZQMEAgIFAKCCAVYwGgYJKoZIhvcNAQkD # MQ0GCyqGSIb3DQEJEAEEMBwGCSqGSIb3DQEJBTEPFw0yNjEwMjIxMTQwMzFaMDcG # CyqGSIb3DQEJEAIvMSgwJjAkMCIEIIW+kOEK0kONfMkotq9IsJqyCBd87PiwEmxY # 05EFJcQ8MD8GCSqGSIb3DQEJBDEyBDCHDeOA1LZ/VC+HzvoOudTII+no9mxVdR4J # uBnfBifm6CLEUvEXvAzkg5nL7NU0J10wgZ8GCyqGSIb3DQEJEAIMMYGPMIGMMIGJ # MIGGBBRXFGhBDKha80JO+RZKUTYQ9NONmDBuMFqkWDBWMQswCQYDVQQGEwJQTDEh # MB8GA1UEChMYQXNzZWNvIERhdGEgU3lzdGVtcyBTLkEuMSQwIgYDVQQDExtDZXJ0 # dW0gVGltZXN0YW1waW5nIDIwMjEgQ0ECECjwd8EcNr3+X3QyR3AnLgMwDQYJKoZI # hvcNAQEBBQAEggIAAz/zI8dK2h5T1BcCinLS4UtcrV3ztLQgbnz6v3K40bQA9ult # aDvmUmU+CfmFb/t7mtVWtoNXtiyS99u418fBDteTxQc3L0VQqtEaHO27LgpWxWkH # ds3KQ27ht78C1ug/GKDB3QtBPgeawOERaMe2DiiXVueXtn1EJS4zI1qiqB58PvhY # 3P65ZktL5mRCEIM/vxdKw94abnv+tjdFhcFjJC7k94rFOqne/+JyxHlnnwNSGFIW # V3wbS/ZXkqvDLHYflrfVfxAKUmv0Tpd6YDk5fIboM77kpB4y8AQ6zyCdrLw3N3KJ # Mm9q0Bm7PPTsSstujWpjqGgiX9mxE2mQjKBPrRBgHONiVDKk/22Lwg+q0xQAIn6h # wPIFwgJ+Pxwcrj81WvzWDIps82nKQg05kWK38yP3w4cNQTVzdHtGyrjzU+MXNVqY # R/A1pzZ51gSh2MF+6JikWKFQU0ESL3pwfNMiAse6jbyRL0jAh6sZ+iBKiFL1yRwt # RkwYaDtOzixA3PSYlkcoEPsZX94gdUNGKxfISZ/S1P6v89yimXfBpW1Af35LZP8Z # NSi6F32NEkKJQgVdkVDN2zt2Lw8WysRPkjti6qbqFGpQd6wUxqi/0u29kTlVGV0R # zyqhHObgQVN93BZlqDxkXzkIWMx5E47SCOctyFqqi42kWK7npN5SN6jJRb8= # SIG # End signature block |