Public/PrivilegedAccess/Get-EntraOpsPrivilegedEAMAzure.ps1
|
<# .SYNOPSIS Get a list in EntraOps EAM schema of all privileged principals with Azure RBAC role assignments and their classifications. .DESCRIPTION Collects Azure RBAC role assignments via Get-EntraOpsPrivilegedAzureRoles, fetches role definition actions from the Azure Resource Graph, then classifies each unique (RoleDefinitionId, RoleAssignmentScopeId) pair against Classification_Azure.json. Scope matching uses PowerShell -like wildcard patterns (e.g. "/*" matches any ARM path). Action matching uses bidirectional wildcard matching so that a role with actions ["*"] correctly matches specific classification actions, and classification patterns like "Microsoft.Authorization/*/write" correctly match a role action "Microsoft.Authorization/roleAssignments/write". notActions exclusion is applied so that Contributor (actions=["*"], notActions=["Microsoft.Authorization/*/Write",...]) is not classified as having authorization write permissions. Run Update-EntraOpsClassificationControlPlaneScope with -ClassificationParameterScope including "Azure" before calling this function to get tenant-specific Tier0/Tier1 scope parameterization. .PARAMETER TenantId Tenant ID of the Microsoft Entra ID tenant. Default is the current Az context tenant. .PARAMETER FolderClassification Folder path to the classification definition files. Default is $DefaultFolderClassification. .PARAMETER SampleMode Use sample data for testing or offline mode. Default is $False. .PARAMETER GlobalExclusion Apply the global exclusion list. Default is $true. .PARAMETER EnableParallelProcessing Enable parallel processing for object resolution. Default is $true. .PARAMETER ParallelThrottleLimit Maximum number of parallel threads. Default is 10. .PARAMETER ControlPlaneScopeOnly Restrict the Azure RBAC role assignment scan to only the Control Plane ARM hierarchies defined as RoleAssignmentScopeName under the ControlPlane (EAMTierLevelTagValue "0") tier in Classification_Azure.json. The tenant-specific Classification/<TenantName>/Classification_Azure.json is used when it exists, otherwise the template is used. Wildcard entries ("/" and "/*") are ignored so only concrete management group, subscription, resource group or resource hierarchies limit the scan. Run Update-EntraOpsClassificationControlPlaneScope with -ClassificationParameterScope including "Azure" first to populate tenant-specific Tier0 scopes. Default is $false. .PARAMETER IncludeJustification Include the Justification property (documenting a manual classification overwrite) on all Classification entries of the returned objects. Default is $false, so the property is not present in the output at all. .PARAMETER UnresolvedRoleDefinitionFallbackTier Tier assigned to role assignments whose role definition could not be resolved from Azure Resource Graph (e.g. ARG replication lag for a new custom role, deleted role definition or a transient ARG failure). The fallback classification is marked with TaggedBy "FallbackUnresolvedRoleDefinition" and a warning is added to the summary for each affected (RoleDefinitionId, Scope) pair. Allowed values: "Unclassified" (default, consistent with the Unclassified fallback of the other RBAC systems), "ManagementPlane" or "ControlPlane" (stricter fail-closed tiers with Service "Unresolved Role Definition") or "None" (skip classification, previous behavior). Can be set via AzureRbacClassification.UnresolvedRoleDefinitionFallbackTier in EntraOpsConfig.json. .PARAMETER DeletedPrincipalAssignmentHandling Controls Azure RBAC role assignments whose principal is confirmed deleted. "Filter" (default) removes those assignments from EAM output. "Keep" preserves the fail-closed unresolved object only after both the Microsoft Graph directory-object and user lookups return HTTP 404. Permission, throttling, network and other resolution failures remain visible. Can be set via AzureRbacClassification.DeletedPrincipalAssignmentHandling in EntraOpsConfig.json. .PARAMETER IncludeObjectDetails Include descriptive object details in warning output. Defaults to ConsoleOutput.IncludeObjectDetails from EntraOpsConfig.json. Object IDs are always shown. .EXAMPLE Get-EntraOpsPrivilegedEAMAzure -TenantId "00000000-0000-0000-0000-000000000000" .EXAMPLE Scan only Control Plane hierarchies defined in Classification_Azure.json instead of the whole tenant. Get-EntraOpsPrivilegedEAMAzure -ControlPlaneScopeOnly $true #> function Get-EntraOpsPrivilegedEAMAzure { [cmdletbinding()] param ( [Parameter(Mandatory = $false)] [System.String]$TenantId = (Get-EntraOpsAzContextValue -Property TenantId) , [Parameter(Mandatory = $false)] [System.String]$FolderClassification = "$DefaultFolderClassification" , [Parameter(Mandatory = $false)] [System.Boolean]$SampleMode = $False , [Parameter(Mandatory = $false)] [System.Boolean]$GlobalExclusion = $true , [Parameter(Mandatory = $false)] [System.Boolean]$EnableParallelProcessing = $true , [Parameter(Mandatory = $false)] [System.Int32]$ParallelThrottleLimit = 10 , [Parameter(Mandatory = $false)] [switch]$IncludeJustification , [Parameter(Mandatory = $false)] [System.Boolean]$ControlPlaneScopeOnly = $false , [Parameter(Mandatory = $false)] [System.Boolean]$ClassifyConstrainedDelegationAlwaysAsControlPlane = $(if ($null -ne $EntraOpsConfig.AzureRbacClassification.ClassifyConstrainedDelegationAlwaysAsControlPlane) { [System.Convert]::ToBoolean($EntraOpsConfig.AzureRbacClassification.ClassifyConstrainedDelegationAlwaysAsControlPlane) } else { $false }) , [Parameter(Mandatory = $false)] [ValidateSet('Unclassified', 'ManagementPlane', 'ControlPlane', 'None')] [System.String]$UnresolvedRoleDefinitionFallbackTier = $(if (-not [string]::IsNullOrEmpty($EntraOpsConfig.AzureRbacClassification.UnresolvedRoleDefinitionFallbackTier)) { $EntraOpsConfig.AzureRbacClassification.UnresolvedRoleDefinitionFallbackTier } else { 'Unclassified' }) , [Parameter(Mandatory = $false)] [ValidateSet('Keep', 'Filter')] [System.String]$DeletedPrincipalAssignmentHandling = $(if (-not [string]::IsNullOrEmpty($EntraOpsConfig.AzureRbacClassification.DeletedPrincipalAssignmentHandling)) { $EntraOpsConfig.AzureRbacClassification.DeletedPrincipalAssignmentHandling } else { 'Filter' }) , [Parameter(Mandatory = $false)] [System.Boolean]$IncludeObjectDetails = [bool]$Global:EntraOpsIncludeObjectDetails ) $WarningMessages = New-Object -TypeName "System.Collections.Generic.List[psobject]" $Stage1Start = Get-Date #region Stage 1: Fetch Azure RBAC Role Assignments Write-Host "" Write-Host "═══════════════════════════════════════════════════════════════════════════════" -ForegroundColor Cyan Write-Host " Stage 1/4: Fetching Azure RBAC Role Assignments" -ForegroundColor Cyan Write-Host "═══════════════════════════════════════════════════════════════════════════════" -ForegroundColor Cyan Write-Host "Retrieving Azure RBAC role assignments across tenant scope..." -ForegroundColor Gray Write-Progress -Activity "Stage 1/4: Fetching Azure RBAC Roles" -Status "Loading role assignments and global exclusions..." -PercentComplete 10 # Optionally restrict the Azure RBAC scan to Control Plane hierarchies defined in the classification file $ControlPlaneScopeFilterPatterns = @() if ($ControlPlaneScopeOnly -eq $true) { $AzureClassificationFilterPath = Resolve-EntraOpsClassificationPath -ClassificationFileName "Classification_Azure.json" -FolderClassification $FolderClassification if ($null -eq $AzureClassificationFilterPath) { $WarningMessages.Add([PSCustomObject]@{ Type = "Stage1"; Message = "ControlPlaneScopeOnly enabled but Classification_Azure.json could not be resolved; scanning full tenant scope." }) } else { try { $AzureClassificationForFilter = Get-Content -Path $AzureClassificationFilterPath -Raw | ConvertFrom-Json -Depth 10 $ControlPlaneTier = $AzureClassificationForFilter | Where-Object { "$($_.EAMTierLevelTagValue)" -eq "0" } $ControlPlaneScopeFilterPatterns = @($ControlPlaneTier.TierLevelDefinition.RoleAssignmentScopeName | Where-Object { -not [string]::IsNullOrWhiteSpace($_) -and $_ -ne "/" -and $_ -ne "/*" } | Select-Object -Unique) Write-Host "Control Plane scope filter: $($ControlPlaneScopeFilterPatterns.Count) hierarchy scope(s) from $([System.IO.Path]::GetFileName($AzureClassificationFilterPath))." -ForegroundColor Gray if ($ControlPlaneScopeFilterPatterns.Count -eq 0) { $WarningMessages.Add([PSCustomObject]@{ Type = "Stage1"; Message = "ControlPlaneScopeOnly enabled but no specific Control Plane RoleAssignmentScopeName hierarchies found (only '/' or '/*'); scanning full tenant scope." }) } } catch { $WarningMessages.Add([PSCustomObject]@{ Type = "Stage1"; Message = "Failed to load Control Plane scope filter from classification file: $($_.Exception.Message); scanning full tenant scope." }) } } } if ($SampleMode -ne $True) { $AzureRbacAssignments = Get-EntraOpsPrivilegedAzureRoles -TenantId $TenantId -ControlPlaneScopeFilter $ControlPlaneScopeFilterPatterns -WarningMessages $WarningMessages } else { $WarningMessages.Add([PSCustomObject]@{ Type = "Stage1"; Message = "SampleMode is not supported for Azure RBAC." }) $AzureRbacAssignments = @() } #region Resolve ForeignGroup principals (cross-tenant Azure RBAC) via remoteTenantGroups # Azure RBAC assignments to a group from another tenant are returned with principalType 'ForeignGroup' # (ObjectType 'foreigngroup'). These have no directory object in the home tenant, so resolve them via # remoteTenantGroups to obtain the real remote group id, display name and owning tenant. The matching # assignments are re-keyed to the remote group identity so the rest of the pipeline (object resolution, # classification grouping) treats them as ordinary groups. When the remote tenant matches the configured # managing tenant, full details are resolved later in Stage 4b via Invoke-EntraOpsCrossTenantObjectResolution. $ForeignGroupDetailsCache = @{} $ForeignGroupAssignments = @($AzureRbacAssignments | Where-Object { "$($_.ObjectType)" -ieq 'foreigngroup' }) if ($ForeignGroupAssignments.Count -gt 0) { Write-Host "Resolving $($ForeignGroupAssignments.Count) ForeignGroup role assignment(s) via remoteTenantGroups..." -ForegroundColor Gray $ForeignGroupResolution = @{} foreach ($StubObjectId in @($ForeignGroupAssignments.ObjectId | Select-Object -Unique)) { $ResolvedForeignGroup = Get-EntraOpsPrivilegedEntraObject -AadObjectId $StubObjectId -TenantId $TenantId if ($null -ne $ResolvedForeignGroup -and "$($ResolvedForeignGroup.ObjectType)" -ieq 'group' -and -not [string]::IsNullOrEmpty($ResolvedForeignGroup.ObjectId)) { $ForeignGroupResolution[$StubObjectId] = $ResolvedForeignGroup $ForeignGroupDetailsCache[$ResolvedForeignGroup.ObjectId] = $ResolvedForeignGroup } else { $WarningMessages.Add([PSCustomObject]@{ Type = "Stage1-ForeignGroup"; Message = "ForeignGroup $StubObjectId could not be resolved via remoteTenantGroups." }) } } foreach ($ForeignGroupAssignment in $ForeignGroupAssignments) { $ResolvedForeignGroup = $ForeignGroupResolution[$ForeignGroupAssignment.ObjectId] if ($null -ne $ResolvedForeignGroup) { $ForeignGroupAssignment.ObjectId = $ResolvedForeignGroup.ObjectId $ForeignGroupAssignment.ObjectTenantId = $ResolvedForeignGroup.ObjectTenantId $ForeignGroupAssignment.ObjectType = 'group' } } } #endregion $GlobalExclusionList = Import-EntraOpsGlobalExclusions -Enabled $GlobalExclusion $Stage1Duration = ((Get-Date) - $Stage1Start).TotalSeconds Write-Host "✓ Stage 1 completed in $([Math]::Round($Stage1Duration, 2)) seconds ($(@($AzureRbacAssignments).Count) role assignments retrieved)" -ForegroundColor Green Write-Progress -Activity "Stage 1/4: Fetching Azure RBAC Roles" -Completed #endregion if ($null -eq $AzureRbacAssignments -or @($AzureRbacAssignments).Count -eq 0) { Write-Warning "No Azure RBAC role assignments found. Returning empty result." return @() } #region Stage 2: Load Classification File and Classify by Role Definition and Scope $Stage2Start = Get-Date Write-Host "" Write-Host "═══════════════════════════════════════════════════════════════════════════════" -ForegroundColor Cyan Write-Host " Stage 2/4: Loading Classification Rules and Classifying Roles" -ForegroundColor Cyan Write-Host "═══════════════════════════════════════════════════════════════════════════════" -ForegroundColor Cyan Write-Host "Loading Azure RBAC classification file and matching role definitions..." -ForegroundColor Gray Write-Progress -Activity "Stage 2/4: Loading Classification Rules" -Status "Reading classification file..." -PercentComplete 25 $AzureClassificationFilePath = Resolve-EntraOpsClassificationPath -ClassificationFileName "Classification_Azure.json" -FolderClassification $FolderClassification if ($null -eq $AzureClassificationFilePath) { Write-Error "Could not resolve Classification_Azure.json. Run Update-EntraOpsClassificationFiles first." return @() } $AzureResourcesByClassificationJSON = Expand-EntraOpsPrivilegedEAMJsonFile -FilePath $AzureClassificationFilePath | Select-Object EAMTierLevelName, EAMTierLevelTagValue, Category, Service, ActionType, RoleAssignmentScopeName, ExcludedRoleAssignmentScopeName, RoleDefinitionActions, ExcludedRoleDefinitionActions # Fetch role definitions from Azure Resource Graph for all referenced role definition GUIDs. # Results are deduped by name (GUID) since the same built-in role can appear at multiple scopes. $RoleDefinitionCache = @{} $UniqueRoleDefIds = @($AzureRbacAssignments | ForEach-Object { $_.RoleDefinitionId } | Where-Object { -not [string]::IsNullOrEmpty($_) } | Select-Object -Unique) if ($SampleMode -ne $True -and $UniqueRoleDefIds.Count -gt 0) { Write-Host "Fetching $($UniqueRoleDefIds.Count) unique role definition(s) from Azure Resource Graph..." -ForegroundColor Gray # Batch ARG queries in groups of 100 to stay within query length limits $ArgBatchSize = 100 for ($BatchStart = 0; $BatchStart -lt $UniqueRoleDefIds.Count; $BatchStart += $ArgBatchSize) { $BatchEnd = [Math]::Min($BatchStart + $ArgBatchSize - 1, $UniqueRoleDefIds.Count - 1) $BatchIds = @($UniqueRoleDefIds[$BatchStart..$BatchEnd]) $RoleDefIdFilter = ($BatchIds | ForEach-Object { "'$_'" }) -join ', ' $RoleDefKql = "authorizationresources | where type =~ 'microsoft.authorization/roledefinitions' | where name in ($RoleDefIdFilter) | project name, properties" try { # -ThrowOnFailure makes the catch below reachable. Without it the helper reports failure # with a non-terminating error and returns whatever it collected, so a throttled batch # silently left role definitions unresolved and no warning was recorded. $BatchResults = @(Invoke-EntraOpsAzGraphQuery -KqlQuery $RoleDefKql -ThrowOnFailure) foreach ($RoleDef in $BatchResults) { if (-not [string]::IsNullOrEmpty($RoleDef.name) -and -not $RoleDefinitionCache.ContainsKey($RoleDef.name)) { $RoleDefinitionCache[$RoleDef.name] = $RoleDef } } } catch { $WarningMessage = "Failed to fetch role definitions batch (start=$BatchStart): $($_.Exception.Message)" $WarningMessages.Add([PSCustomObject]@{ Type = "Stage2"; Message = $WarningMessage }) Write-Warning $WarningMessage } } Write-Host "Resolved $($RoleDefinitionCache.Count) of $($UniqueRoleDefIds.Count) role definition(s) from Azure Resource Graph." -ForegroundColor Gray # Guardrail: a high unresolved ratio points to an ARG outage rather than individual stale role # definitions, so fallback classifications from this run should not be trusted blindly. $UnresolvedRoleDefIds = @($UniqueRoleDefIds | Where-Object { -not $RoleDefinitionCache.ContainsKey($_) }) if ($UnresolvedRoleDefIds.Count -gt 0 -and ($UnresolvedRoleDefIds.Count / $UniqueRoleDefIds.Count) -gt 0.2) { $WarningMessages.Add([PSCustomObject]@{ Type = "Stage2" Message = "$($UnresolvedRoleDefIds.Count) of $($UniqueRoleDefIds.Count) role definitions could not be resolved from Azure Resource Graph. A transient ARG outage is suspected; fallback classifications of this run may be unreliable." }) } } # Resolve tier tag and service of the configured unresolved-role-definition fallback. # "Unclassified" mirrors the assignment-level Unclassified fallback used by the other RBAC systems. $FallbackTierTagValue = $null $FallbackServiceName = $null if ($UnresolvedRoleDefinitionFallbackTier -eq 'Unclassified') { $FallbackTierTagValue = 'Unclassified' $FallbackServiceName = 'Unclassified' } elseif ($UnresolvedRoleDefinitionFallbackTier -ne 'None') { $FallbackTierTagValue = @($AzureResourcesByClassificationJSON | Where-Object { $_.EAMTierLevelName -eq $UnresolvedRoleDefinitionFallbackTier })[0].EAMTierLevelTagValue if ([string]::IsNullOrEmpty("$FallbackTierTagValue")) { $FallbackTierTagValue = if ($UnresolvedRoleDefinitionFallbackTier -eq 'ControlPlane') { '0' } else { '1' } } $FallbackServiceName = 'Unresolved Role Definition' } # Classify each unique (RoleDefinitionId, RoleAssignmentScopeId) combination. # For each matching scope+action pair the result is one classification entry with all matched actions. $AzureRbacClassificationsByJSON = @() # Select-Object -Unique compares case-SENSITIVELY, and ARM does not guarantee consistent casing of # role definition GUIDs or scope ids across endpoints, so the same logical pair could survive dedup # twice and be classified twice. Group on a lowercased key and keep the first member of each group. $UniqueAssignmentPairs = @( $AzureRbacAssignments | Group-Object -Property { "$($_.RoleDefinitionId)".ToLowerInvariant() + '|' + "$($_.RoleAssignmentScopeId)".ToLowerInvariant() } | ForEach-Object { $_.Group[0] | Select-Object RoleDefinitionId, RoleAssignmentScopeId } ) $PairsProcessed = 0 $PairsTotal = $UniqueAssignmentPairs.Count $AzureRbacClassificationsByJSON += foreach ($AzureRbacAssignment in $UniqueAssignmentPairs) { $PairsProcessed++ if ($PairsProcessed % 20 -eq 0 -or $PairsProcessed -eq $PairsTotal) { Write-Progress -Activity "Stage 2/4: Loading Classification Rules" -Status "Classifying assignment $PairsProcessed of $PairsTotal" -PercentComplete (25 + ($PairsProcessed / [Math]::Max($PairsTotal, 1) * 25)) } $RoleAssignmentScopeId = $AzureRbacAssignment.RoleAssignmentScopeId $RoleDefinitionId = $AzureRbacAssignment.RoleDefinitionId $RoleDef = $RoleDefinitionCache[$RoleDefinitionId] if ($null -eq $RoleDef) { if ($UnresolvedRoleDefinitionFallbackTier -eq 'None') { $WarningMessages.Add([PSCustomObject]@{ Type = "Stage2" Message = "Role definition '$RoleDefinitionId' was not found in Azure Resource Graph; classification skipped for scope '$RoleAssignmentScopeId'." }) continue } # Emit a self-identifying fallback classification so the assignment stays visible instead of # silently losing classification when the role definition cannot be resolved. $WarningMessages.Add([PSCustomObject]@{ Type = "Stage2-UnresolvedFallback" Message = "Role definition '$RoleDefinitionId' was not found in Azure Resource Graph; assignment at scope '$RoleAssignmentScopeId' classified with $UnresolvedRoleDefinitionFallbackTier fallback." }) [PSCustomObject]@{ 'RoleDefinitionId' = $RoleDefinitionId 'RoleAssignmentScopeId' = $RoleAssignmentScopeId 'Classification' = @([PSCustomObject]@{ 'AdminTierLevel' = "$FallbackTierTagValue" 'AdminTierLevelName' = $UnresolvedRoleDefinitionFallbackTier 'Service' = $FallbackServiceName 'MatchedActions' = $null 'ScopedObjects' = $null 'TaggedBy' = "FallbackUnresolvedRoleDefinition" 'TaggedByObjectIds' = $null 'TaggedByObjectDisplayNames' = $null 'TaggedByRoleSystem' = "Azure" }) } continue } # --- Scope matching --- # Find classification entries whose RoleAssignmentScopeName pattern matches the assignment scope. $MatchedClassificationByScope = @() $MatchedClassificationByScope += foreach ($ClassEntry in $AzureResourcesByClassificationJSON) { if (-not ($RoleAssignmentScopeId -like $ClassEntry.RoleAssignmentScopeName)) { continue } # Check scope exclusions (one-directional: assignment scope -like exclusion pattern) $IsExcluded = $false foreach ($ExcludedScope in @($ClassEntry.ExcludedRoleAssignmentScopeName)) { if (-not [string]::IsNullOrEmpty($ExcludedScope) -and ($RoleAssignmentScopeId -like $ExcludedScope)) { $IsExcluded = $true break } } if (-not $IsExcluded) { $ClassEntry } } if ($MatchedClassificationByScope.Count -eq 0) { continue } # --- Action matching --- # Bidirectional wildcard: ($roleAction -like $classAction) handles specific role actions matching # a wildcard classification pattern (e.g. "Microsoft.Authorization/roleAssignments/write" -like # "Microsoft.Authorization/*/write"). ($classAction -like $roleAction) handles a broad role action # (e.g. "*") matching any specific classification action. # notActions exclusion: if the classification action is covered by a notAction, skip it # (e.g. Contributor's notActions prevent it from matching authorization write patterns). $ClassifiedWithMatchedActions = @() foreach ($ClassEntry in $MatchedClassificationByScope) { $IsDataActionRule = "$($ClassEntry.ActionType)" -ieq 'DataAction' foreach ($ClassAction in @($ClassEntry.RoleDefinitionActions)) { if ([string]::IsNullOrEmpty($ClassAction)) { continue } $ActionMatched = $false foreach ($Permission in @($RoleDef.properties.permissions)) { if ($ActionMatched) { break } if (-not $IsDataActionRule) { # Management/control plane Actions must not match data-plane rules. foreach ($RoleAction in @($Permission.actions)) { if ([string]::IsNullOrEmpty($RoleAction)) { continue } # Azure documents */read as control-plane metadata access. It must not # satisfy an explicit telemetry or other sensitive-read classification. if ($RoleAction -eq '*/read' -and $ClassAction -ne '*/read') { continue } if ($RoleAction -like $ClassAction -or $ClassAction -like $RoleAction) { $IsExcludedByClassification = @($ClassEntry.ExcludedRoleDefinitionActions | Where-Object { -not [string]::IsNullOrEmpty($_) -and $RoleAction -like $_ }).Count -gt 0 if ($IsExcludedByClassification) { continue } # Verify the classification action is not excluded by a notAction $IsNotActioned = $false foreach ($NotAction in @($Permission.notActions)) { if (-not [string]::IsNullOrEmpty($NotAction) -and ($ClassAction -like $NotAction)) { $IsNotActioned = $true break } } if (-not $IsNotActioned) { $ActionMatched = $true break } } } } if ($ActionMatched) { break } if ($IsDataActionRule) { # DataActions grant access to resource data and must not match Actions rules. foreach ($DataAction in @($Permission.dataActions)) { if ([string]::IsNullOrEmpty($DataAction)) { continue } if ($DataAction -like $ClassAction -or $ClassAction -like $DataAction) { $IsExcludedByClassification = @($ClassEntry.ExcludedRoleDefinitionActions | Where-Object { -not [string]::IsNullOrEmpty($_) -and $DataAction -like $_ }).Count -gt 0 if ($IsExcludedByClassification) { continue } $IsNotDataActioned = $false foreach ($NotDataAction in @($Permission.notDataActions)) { if (-not [string]::IsNullOrEmpty($NotDataAction) -and ($ClassAction -like $NotDataAction)) { $IsNotDataActioned = $true break } } if (-not $IsNotDataActioned) { $ActionMatched = $true break } } } } } if ($ActionMatched) { $ClassifiedWithMatchedActions += [PSCustomObject]@{ EAMTierLevelName = $ClassEntry.EAMTierLevelName EAMTierLevelTagValue = $ClassEntry.EAMTierLevelTagValue Service = $ClassEntry.Service MatchedAction = $ClassAction } } } } if ($ClassifiedWithMatchedActions.Count -gt 0) { $UniqueClassifications = $ClassifiedWithMatchedActions | Select-Object -Unique EAMTierLevelName, EAMTierLevelTagValue, Service $Classification = foreach ($UniqueClass in $UniqueClassifications) { [array]$MatchedActions = @($ClassifiedWithMatchedActions | Where-Object { $_.EAMTierLevelName -eq $UniqueClass.EAMTierLevelName -and $_.EAMTierLevelTagValue -eq $UniqueClass.EAMTierLevelTagValue -and $_.Service -eq $UniqueClass.Service } | ForEach-Object { $_.MatchedAction } | Select-Object -Unique) [PSCustomObject]@{ 'AdminTierLevel' = $UniqueClass.EAMTierLevelTagValue 'AdminTierLevelName' = $UniqueClass.EAMTierLevelName 'Service' = $UniqueClass.Service 'MatchedActions' = if ($MatchedActions.Count -gt 0) { , @($MatchedActions) } else { $null } 'ScopedObjects' = $null 'TaggedBy' = "JSONwithAction" 'TaggedByObjectIds' = $null 'TaggedByObjectDisplayNames' = $null 'TaggedByRoleSystem' = "Azure" } } [PSCustomObject]@{ 'RoleDefinitionId' = $RoleDefinitionId 'RoleAssignmentScopeId' = $RoleAssignmentScopeId 'Classification' = $Classification } } } $Stage2Duration = ((Get-Date) - $Stage2Start).TotalSeconds Write-Host "✓ Stage 2 completed in $([Math]::Round($Stage2Duration, 2)) seconds ($($AzureRbacClassificationsByJSON.Count) classified (RoleDefinitionId, Scope) pairs)" -ForegroundColor Green Write-Progress -Activity "Stage 2/4: Loading Classification Rules" -Completed #endregion #region Stage 2b: Resolve role definitions referenced by constrained-delegation conditions # Build a tier tag -> tier name lookup (ordered ascending; 0 = most privileged) used to downgrade # condition-based (ABAC) constrained-delegation Authorization classifications. $TierNameByTag = @{} foreach ($ClassEntry in $AzureResourcesByClassificationJSON) { if ($null -ne $ClassEntry.EAMTierLevelTagValue -and -not $TierNameByTag.ContainsKey("$($ClassEntry.EAMTierLevelTagValue)")) { $TierNameByTag["$($ClassEntry.EAMTierLevelTagValue)"] = $ClassEntry.EAMTierLevelName } } # Collect RoleDefinitionId GUIDs referenced inside RoleAssignmentCondition (constrained delegation) so we # can classify which roles the delegate may/may not grant. Fetch any that are not already cached. $ConditionRoleDefGuidPattern = "RoleDefinitionId\]\s*\w+:Guid(?:Not)?Equals\s*\{([^}]*)\}" $ReferencedRoleDefIds = [System.Collections.Generic.HashSet[string]]::new([System.StringComparer]::OrdinalIgnoreCase) foreach ($AzureRbacAssignment in $AzureRbacAssignments) { if ([string]::IsNullOrEmpty($AzureRbacAssignment.RoleAssignmentCondition)) { continue } foreach ($Match in [regex]::Matches($AzureRbacAssignment.RoleAssignmentCondition, $ConditionRoleDefGuidPattern)) { foreach ($Guid in ($Match.Groups[1].Value -split ',')) { $TrimmedGuid = $Guid.Trim() if (-not [string]::IsNullOrEmpty($TrimmedGuid)) { [void]$ReferencedRoleDefIds.Add($TrimmedGuid) } } } } # Built-in constrained-delegation roles (e.g. Key Vault Data Access Administrator) carry their ABAC # condition on the role definition's permissions instead of the assignment. Track those role definitions # so Stage 3 also invokes the downgrade helper for them, and cache the roles their conditions reference. $RoleDefIdsWithPermissionCondition = [System.Collections.Generic.HashSet[string]]::new([System.StringComparer]::OrdinalIgnoreCase) foreach ($CachedRoleDefId in @($RoleDefinitionCache.Keys)) { foreach ($Permission in @($RoleDefinitionCache[$CachedRoleDefId].properties.permissions)) { if ([string]::IsNullOrEmpty($Permission.condition)) { continue } [void]$RoleDefIdsWithPermissionCondition.Add($CachedRoleDefId) foreach ($Match in [regex]::Matches($Permission.condition, $ConditionRoleDefGuidPattern)) { foreach ($Guid in ($Match.Groups[1].Value -split ',')) { $TrimmedGuid = $Guid.Trim() if (-not [string]::IsNullOrEmpty($TrimmedGuid)) { [void]$ReferencedRoleDefIds.Add($TrimmedGuid) } } } } } $MissingConditionRoleDefIds = @($ReferencedRoleDefIds | Where-Object { -not $RoleDefinitionCache.ContainsKey($_) }) if ($SampleMode -ne $True -and $MissingConditionRoleDefIds.Count -gt 0) { Write-Host "Fetching $($MissingConditionRoleDefIds.Count) role definition(s) referenced by delegation conditions..." -ForegroundColor Gray for ($BatchStart = 0; $BatchStart -lt $MissingConditionRoleDefIds.Count; $BatchStart += 100) { $BatchEnd = [Math]::Min($BatchStart + 99, $MissingConditionRoleDefIds.Count - 1) $BatchIds = @($MissingConditionRoleDefIds[$BatchStart..$BatchEnd]) $RoleDefIdFilter = ($BatchIds | ForEach-Object { "'$_'" }) -join ', ' $RoleDefKql = "authorizationresources | where type =~ 'microsoft.authorization/roledefinitions' | where name in ($RoleDefIdFilter) | project name, properties" try { foreach ($RoleDef in @(Invoke-EntraOpsAzGraphQuery -KqlQuery $RoleDefKql -ThrowOnFailure)) { if (-not [string]::IsNullOrEmpty($RoleDef.name) -and -not $RoleDefinitionCache.ContainsKey($RoleDef.name)) { $RoleDefinitionCache[$RoleDef.name] = $RoleDef } } } catch { $WarningMessages.Add([PSCustomObject]@{ Type = "Stage2b"; Message = "Failed to fetch condition role definitions batch (start=$BatchStart): $($_.Exception.Message)" }) } } } #endregion #region Stage 3: Apply Classifications to All Role Assignments $Stage3Start = Get-Date Write-Host "" Write-Host "═══════════════════════════════════════════════════════════════════════════════" -ForegroundColor Cyan Write-Host " Stage 3/4: Classifying Principals" -ForegroundColor Cyan Write-Host "═══════════════════════════════════════════════════════════════════════════════" -ForegroundColor Cyan Write-Host "Matching principals against classified role assignments..." -ForegroundColor Gray Write-Progress -Activity "Stage 3/4: Classifying Principals" -Status "Processing role assignments..." -PercentComplete 50 $ClassificationsByRoleAndScope = @{} foreach ($ClassifiedPair in $AzureRbacClassificationsByJSON) { $ClassificationKey = "$($ClassifiedPair.RoleDefinitionId)|$($ClassifiedPair.RoleAssignmentScopeId)" if (-not $ClassificationsByRoleAndScope.ContainsKey($ClassificationKey)) { $ClassificationsByRoleAndScope[$ClassificationKey] = [System.Collections.Generic.List[object]]::new() } foreach ($ClassItem in @($ClassifiedPair.Classification)) { $ClassificationsByRoleAndScope[$ClassificationKey].Add($ClassItem) } } $AzureRbacClassifications = foreach ($AzureRbacAssignment in $AzureRbacAssignments) { $AzureRbacAssignment = $AzureRbacAssignment | Select-Object -ExcludeProperty Classification $ClassificationKey = "$($AzureRbacAssignment.RoleDefinitionId)|$($AzureRbacAssignment.RoleAssignmentScopeId)" $Classification = @($ClassificationsByRoleAndScope[$ClassificationKey]) $Classification = $Classification | Select-Object -Unique AdminTierLevel, AdminTierLevelName, MatchedActions, ScopedObjects, Service, TaggedBy, TaggedByObjectIds, TaggedByObjectDisplayNames, TaggedByRoleSystem | Sort-Object AdminTierLevel, AdminTierLevelName, Service, TaggedBy $AzureRbacAssignment | Add-Member -NotePropertyName "Classification" -NotePropertyValue $Classification -Force $AssignedRoleDefinitionForCondition = $RoleDefinitionCache["$($AzureRbacAssignment.RoleDefinitionId)"] $RoleDefinitionConditions = if ($null -eq $AssignedRoleDefinitionForCondition) { @() } else { @($AssignedRoleDefinitionForCondition.properties.permissions | Where-Object { -not [string]::IsNullOrEmpty($_.condition) } | ForEach-Object { [PSCustomObject]@{ Condition = $_.condition ConditionVersion = $_.conditionVersion } }) } $AzureRbacAssignment | Add-Member -NotePropertyName "RoleDefinitionConditions" -NotePropertyValue $RoleDefinitionConditions -Force $ConditionConstraints = @( @([PSCustomObject]@{ Source = "Assignment" Condition = $AzureRbacAssignment.RoleAssignmentCondition ConditionVersion = $AzureRbacAssignment.RoleAssignmentConditionVersion }) + @($RoleDefinitionConditions | ForEach-Object { [PSCustomObject]@{ Source = "RoleDefinition" Condition = $_.Condition ConditionVersion = $_.ConditionVersion } }) | Where-Object { -not [string]::IsNullOrEmpty($_.Condition) } | ForEach-Object { $ConditionSource = $_ foreach ($Match in [regex]::Matches($_.Condition, "RoleDefinitionId\]\s*\w+:(Guid(?:Not)?Equals)\s*\{([^}]*)\}")) { [PSCustomObject]@{ Source = $ConditionSource.Source Operator = $Match.Groups[1].Value RoleDefinitionIds = @($Match.Groups[2].Value -split ',' | ForEach-Object { $_.Trim() } | Where-Object { -not [string]::IsNullOrEmpty($_) }) } } } ) $HasCondition = -not [string]::IsNullOrEmpty($AzureRbacAssignment.RoleAssignmentCondition) -or $RoleDefinitionConditions.Count -gt 0 $HasControlPlaneAuthorization = @($Classification | Where-Object { "$($_.AdminTierLevel)" -eq "0" -and ( $_.Service -eq "Authorization" -or (@($_.MatchedActions) | Where-Object { -not [string]::IsNullOrEmpty($_) -and ($_ -like "*roleAssignments/write" -or $_ -like "*roleAssignments/delete") }).Count -gt 0 ) }).Count -gt 0 $ConditionEvaluation = if (-not $HasCondition) { $null } elseif ($ClassifyConstrainedDelegationAlwaysAsControlPlane) { [PSCustomObject]@{ Status = "RetainedControlPlaneByConfiguration" Summary = "Conditional delegation evaluation is disabled by configuration; the assignment remains Control Plane." Constraints = $ConditionConstraints } } else { [PSCustomObject]@{ Status = "Pending" Summary = "The condition was evaluated against delegable role definitions at the assignment scope." Constraints = $ConditionConstraints } } # Condition-based (ABAC) constrained delegation: if the role can write role assignments but a # condition (on the assignment or on the role definition's permissions) limits which roles it can # grant, downgrade the Authorization Control Plane classification based on the roles referenced in # the condition (classified against the same scope). # When ClassifyConstrainedDelegationAlwaysAsControlPlane is enabled (config flag), this dynamic downgrade is # skipped so constrained-delegation authorization assignments always remain classified as Control Plane. if (-not $ClassifyConstrainedDelegationAlwaysAsControlPlane -and ( -not [string]::IsNullOrEmpty($AzureRbacAssignment.RoleAssignmentCondition) -or $RoleDefIdsWithPermissionCondition.Contains("$($AzureRbacAssignment.RoleDefinitionId)") )) { $AzureRbacAssignment.Classification = @(Resolve-EntraOpsAzureConstrainedDelegationTier ` -Assignment $AzureRbacAssignment ` -ClassificationDefinitions $AzureResourcesByClassificationJSON ` -RoleDefinitionCache $RoleDefinitionCache ` -TierNameByTag $TierNameByTag) $DowngradedEntries = @($AzureRbacAssignment.Classification | Where-Object { $_.TaggedBy -eq "JSONwithConditionInScope" }) if ($DowngradedEntries.Count -gt 0) { $ConditionEvaluation.Status = "DowngradedConstrainedActions" $ConditionEvaluation.Summary = "The condition limits role-assignment delegation; constrained actions were classified at $(@($DowngradedEntries.AdminTierLevelName | Select-Object -Unique) -join ', ')." } elseif (-not $HasControlPlaneAuthorization) { $ConditionEvaluation.Status = "AuthorizationNotClassifiedAtScope" $ConditionEvaluation.Summary = "No Control Plane Authorization classification matched this assignment scope, so there was nothing to downgrade. Verify that Classification_Azure.json covers the Authorization service at '$($AzureRbacAssignment.RoleAssignmentScopeId)' - otherwise the role-assignment delegation capability is not represented in this classification." } else { $ConditionEvaluation.Status = "RetainedControlPlane" $ConditionEvaluation.Summary = "The condition did not prove that every Control Plane capability is constrained, so the applicable classification remains Control Plane." } } $AzureRbacAssignment | Add-Member -NotePropertyName "ConditionEvaluation" -NotePropertyValue $ConditionEvaluation -Force $AzureRbacAssignment } # Apply role definition classification overwrites (down-/upgrade by RoleDefinitionId or RoleDefinitionName). # Applied after the constrained-delegation evaluation so an explicit operator pin always wins. $ClassificationOverwrites = Import-EntraOpsClassificationOverwrites -RbacSystem "Azure" -FolderClassification $FolderClassification if ($ClassificationOverwrites.RoleDefinitionOverwrites.Count -gt 0) { Write-Host "Applying $($ClassificationOverwrites.RoleDefinitionOverwrites.Count) role definition classification overwrite(s) from Classification_RoleDefinitionOverwrites.json..." -ForegroundColor Yellow $AzureRbacClassifications = @(Invoke-EntraOpsClassificationRoleOverwrite -RbacClassifications $AzureRbacClassifications -RoleDefinitionOverwrites $ClassificationOverwrites.RoleDefinitionOverwrites -RoleSystem "Azure") } $Stage3Duration = ((Get-Date) - $Stage3Start).TotalSeconds Write-Host "✓ Stage 3 completed in $([Math]::Round($Stage3Duration, 2)) seconds ($($AzureRbacClassifications.Count) role assignments classified)" -ForegroundColor Green Write-Progress -Activity "Stage 3/4: Classifying Principals" -Completed #endregion #region Stage 4: Resolve Object Details and Finalize Output $Stage4Start = Get-Date Write-Host "" Write-Host "═══════════════════════════════════════════════════════════════════════════════" -ForegroundColor Cyan Write-Host " Stage 4/4: Resolving Object Details and Finalizing" -ForegroundColor Cyan Write-Host "═══════════════════════════════════════════════════════════════════════════════" -ForegroundColor Cyan Write-Host "Enriching principals with detailed attributes and applying exclusions..." -ForegroundColor Gray Write-Progress -Activity "Stage 4/4: Resolving Objects" -Status "Resolving object details..." -PercentComplete 75 $AzureRbacByObject = $AzureRbacClassifications | Group-Object ObjectId -AsHashTable -AsString # Case-insensitive dedup: a principal id returned with different casing by two ARM/Graph endpoints # (for example roleAssignmentScheduleInstances vs. the remoteTenantGroups re-keying in Stage 1) # would otherwise be resolved twice and emitted as two rows for the same object. $UniqueObjects = @( $AzureRbacAssignments | Where-Object { $null -ne $_.ObjectId } | Group-Object -Property { "$($_.ObjectId)".ToLowerInvariant() } | ForEach-Object { $_.Group[0] | Select-Object ObjectId, ObjectType } ) $ObjectDetailsCache = Invoke-EntraOpsParallelObjectResolution ` -UniqueObjects $UniqueObjects ` -TenantId $TenantId ` -EnableParallelProcessing $EnableParallelProcessing ` -ParallelThrottleLimit $ParallelThrottleLimit #region Stage 4b: Cross-tenant resolution of objects living in the managing tenant # Objects that could not be resolved in the home tenant (e.g. ForeignGroups re-keyed to their remote group # identity in Stage 1) are resolved against the configured managing tenant, mirroring the Entra ID RBAC flow. # No-op when no managing tenant is configured or all objects were already resolved in the home tenant. $ObjectDetailsCache = Invoke-EntraOpsCrossTenantObjectResolution ` -UniqueObjects $UniqueObjects ` -ObjectDetailsCache $ObjectDetailsCache ` -EnableParallelProcessing $EnableParallelProcessing ` -ParallelThrottleLimit $ParallelThrottleLimit # Fallback: any ForeignGroup still unresolved (managing tenant not configured, remote tenant differs, or # not reachable) uses the remoteTenantGroups details captured in Stage 1 so it still appears with its # remote display name and role-assignable group subtype instead of being dropped. if ($ForeignGroupDetailsCache.Count -gt 0) { foreach ($RemoteGroupId in $ForeignGroupDetailsCache.Keys) { $ExistingDetails = $ObjectDetailsCache[$RemoteGroupId] if ($null -eq $ExistingDetails -or "$($ExistingDetails.ObjectType)" -ieq 'unknown') { $ObjectDetailsCache[$RemoteGroupId] = $ForeignGroupDetailsCache[$RemoteGroupId] } } } #endregion if ($DeletedPrincipalAssignmentHandling -eq 'Filter') { $ObjectsBeforeDeletedPrincipalFilter = @($UniqueObjects) $UniqueObjects = @(Select-EntraOpsAzureRbacPrincipal ` -UniqueObjects $UniqueObjects ` -ObjectDetailsCache $ObjectDetailsCache ` -DeletedPrincipalAssignmentHandling $DeletedPrincipalAssignmentHandling) # O(1) cache lookup per principal instead of scanning the retained-id array per id # (O(N x M) in large tenants just to build this informational message). $FilteredDeletedPrincipalIds = @($ObjectsBeforeDeletedPrincipalFilter | Where-Object { $ObjectDetailsCache[$_.ObjectId].ResolutionStatus -eq 'NotFound' } | ForEach-Object { $_.ObjectId }) if ($FilteredDeletedPrincipalIds.Count -gt 0) { $FilteredAssignmentCount = @($AzureRbacClassifications | Where-Object { $_.ObjectId -in $FilteredDeletedPrincipalIds }).Count $FilteredPrincipalMessage = "Filtered $FilteredAssignmentCount Azure RBAC role assignment(s) for $($FilteredDeletedPrincipalIds.Count) principal(s) confirmed deleted by Microsoft Graph: $($FilteredDeletedPrincipalIds -join ', ')" Write-Host $FilteredPrincipalMessage -ForegroundColor Yellow } } # Explicit short-circuit: after the deleted-principal filter (or in an empty-scope tenant) no # principals may remain. Invoke-EntraOpsEAMClassificationAggregation deliberately keeps a strict # mandatory-collection contract, so an empty set is handled here with an empty result instead. if (@($UniqueObjects).Count -eq 0) { Write-Host "No Azure RBAC principals remain for classification aggregation - producing an empty export." -ForegroundColor Yellow $AzureRbacClassifiedObjects = @() } else { $AzureRbacClassifiedObjects = Invoke-EntraOpsEAMClassificationAggregation ` -UniqueObjects $UniqueObjects ` -ObjectDetailsCache $ObjectDetailsCache ` -RbacClassificationsByObject $AzureRbacByObject ` -RoleSystem "Azure" ` -EnableParallelProcessing $EnableParallelProcessing ` -ParallelThrottleLimit $ParallelThrottleLimit ` -WarningMessages $WarningMessages } Write-Progress -Activity "Stage 4/4: Resolving Objects" -Status "Applying global exclusions and sorting..." -PercentComplete 90 $FilteredAzureObjects = $AzureRbacClassifiedObjects | Where-Object { $GlobalExclusionList -notcontains $_.ObjectId } $Stage4Duration = ((Get-Date) - $Stage4Start).TotalSeconds $TotalDuration = ((Get-Date) - $Stage1Start).TotalSeconds Write-Progress -Activity "Stage 4/4: Resolving Objects" -Completed Write-Host "✓ Stage 4 completed in $([Math]::Round($Stage4Duration, 2)) seconds ($($FilteredAzureObjects.Count) privileged objects after exclusions)" -ForegroundColor Green Show-EntraOpsWarningSummary -WarningMessages $WarningMessages -IncludeObjectDetails $IncludeObjectDetails Write-Host "" Write-Host "═══════════════════════════════════════════════════════════════════════════════" -ForegroundColor Green Write-Host " ✓ All Stages Completed Successfully" -ForegroundColor Green Write-Host "═══════════════════════════════════════════════════════════════════════════════" -ForegroundColor Green Write-Host "Total execution time: $([Math]::Round($TotalDuration, 2)) seconds" -ForegroundColor Gray Write-Host "Final result: $($FilteredAzureObjects.Count) privileged objects ready for export" -ForegroundColor Gray #endregion $FilteredAzureObjects | Where-Object { $null -ne $_.ObjectType -and $null -ne $_.ObjectId } | Set-EntraOpsEAMClassificationJustification -IncludeJustification:$IncludeJustification | Sort-Object ObjectAdminTierLevel, ObjectDisplayName } # SIG # Begin signature block # MIIniAYJKoZIhvcNAQcCoIIneTCCJ3UCAQExDzANBglghkgBZQMEAgEFADB5Bgor # BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG # KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCAH7/e2+2ioKygB # t1omsiK6Ppcq1RlCp+6GAkU9axBSlaCCIDwwggXJMIIEsaADAgECAhAbtY8lKt8j # 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 # ARUwLwYJKoZIhvcNAQkEMSIEIB9AGxj6SfbXID0Xi1UsBCc9XZ7JnVzfNR0jQmRe # yFUaMA0GCSqGSIb3DQEBAQUABIIBgDoAOUQ6lZl8eDRVyxFxILZSlLOeicw3OhBR # QsGCtlzM5wHVO7ncwgrZgOAjwqW1zbWKX4DDaL0Ylr9qma2xXdHJLo8nwjGnzhzF # TiVN4vUG0edBTT0LCcBMv/eqIsrV5ZKayEmwQWkjtHsRHH/DUnuHPlAMS2hFBeLG # OleRZdMoPx70+8qVB870DvDGU7SDXt3SUgar4oqeRAjEPDLkx9OTcz3cmlRm4Tzx # b2EKU7RLFkcMh7vsiItdp4yxVYHDro3vUI06GYeRGiNvbDiG/oKNA+B+zH7933lH # WC9mHPpct5el7C9mLfCAEsYADVCdzJcZAglrSs0iN/e4vpEMAsJ6U5StCkqR0ONB # f/dq0mQjx+IQYTke3mnqqd9rTu/N5ZmckIOw53mjV2OlKUs4oI+tESH9/9TVGYcq # R6KGk6PqUxrnXFoWWcsEUhc8t+WnC+MHkGGpcgiMLSVu8cqlfbJ0bzADgLKlcPO/ # G3lWfsPRqEnbaHe/qR5HeXZGTtu/UqGCBAIwggP+BgkqhkiG9w0BCQYxggPvMIID # 6wIBATBqMFYxCzAJBgNVBAYTAlBMMSEwHwYDVQQKExhBc3NlY28gRGF0YSBTeXN0 # ZW1zIFMuQS4xJDAiBgNVBAMTG0NlcnR1bSBUaW1lc3RhbXBpbmcgMjAyMSBDQQIQ # KPB3wRw2vf5fdDJHcCcuAzANBglghkgBZQMEAgIFAKCCAVYwGgYJKoZIhvcNAQkD # MQ0GCyqGSIb3DQEJEAEEMBwGCSqGSIb3DQEJBTEPFw0yNjEwMTMwOTQxMTZaMDcG # CyqGSIb3DQEJEAIvMSgwJjAkMCIEIIW+kOEK0kONfMkotq9IsJqyCBd87PiwEmxY # 05EFJcQ8MD8GCSqGSIb3DQEJBDEyBDDt44LuRNq9EtM9yFlOpqeoOKCsujWYY9QS # InddqfEMTP8TmxxOkBl6//nQzLTzTVswgZ8GCyqGSIb3DQEJEAIMMYGPMIGMMIGJ # MIGGBBRXFGhBDKha80JO+RZKUTYQ9NONmDBuMFqkWDBWMQswCQYDVQQGEwJQTDEh # MB8GA1UEChMYQXNzZWNvIERhdGEgU3lzdGVtcyBTLkEuMSQwIgYDVQQDExtDZXJ0 # dW0gVGltZXN0YW1waW5nIDIwMjEgQ0ECECjwd8EcNr3+X3QyR3AnLgMwDQYJKoZI # hvcNAQEBBQAEggIAkvsqJpJdW/JcLUTO6a1ydBBf10BIBjwT+3VwXPOcetqybdxX # tZfNVJNpMp8LpC4V0aELUBzZqusi8AL5hpMO5RGeseCXdhXijxIh3ecxHmqb2aWp # 9TRfaVWmH8Geih5mU1vjl8PSMIHP7t8/UIv2C4xM9MzW/fM8ndiCellMqreEXkqB # qQ19tVaAaoncv+np1S8qkluEx7RhIVWp1CnKyRtA5rsGI1ops3BSHSttwymxIMvs # 4F86fREWi8RqdmNfv9yTnTaubMtdIXbrN4D701DwdAQwJRb8w7ZEs6DGV8EAg31g # OuEkQd/Q+YosmOtWyPam2Y0PCSIIU41mouBf5EUi2oZkUdetiblvnPCbZ/yjpiIG # sew4K2dQ1TvYkzBa0NrDOHFOIw76gUBG2sAnQUOvzj+k9FLvmBy+tSkhcSIq6zrL # GUTVuXaEYSD3jjhvCeD/RYmTkOK6U6EisY4dYLY+6DYgAhzzBRafVEPulhjxC5uk # n8Hu5oZmpZ8WYIqqaZRmj2+dY3cVk4sGM32w6CZtlojB/hnNNCFEemr0+SigDVwI # GQS247RpGaNSaSDlISoECqV3H+B7ghINd5Udwbmt4X8Wg1jJb2My/pGYNJJJV5lz # nGF4z/6kJSTRYoDn534c6+rYUTMw/ZpklhY+9gzGSgqEeSC+VsI89hfc4Sc= # SIG # End signature block |