Private/Get-EntraOpsPrivilegedEamAccessPackageResourceRoleScopeClassification.ps1
|
<# .SYNOPSIS Classify a list of Identity Governance access package resource role scopes (resources actually included in one or more access packages) against the classified RBAC systems and API permission definitions. .DESCRIPTION Shared by Get-EntraOpsPrivilegedEamIdGov for scopes narrower than a full catalog resource inventory - a single access package, or the union of resource role scopes across every access package of a catalog for the Access package assignment manager role, which can only assign users to existing access packages and never gains access to catalog resources outside of them. .PARAMETER ResourceRoleScopes Array of accessPackageResourceRoleScopes objects (expanded with accessPackageResourceRole and accessPackageResourceScope) to classify. .PARAMETER FilterClassifiedRbacs RBAC systems (e.g. Azure, EntraID, DeviceManagement) whose pre-loaded classification cache should be used to classify AadGroup resource scopes. .PARAMETER ClassificationCache Hashtable of pre-loaded classified objects, keyed by RBAC system name. .PARAMETER ApiPermissionsClassLookup Hashtable lookup ("<originId>|<app role display name>" -> classification) for Graph API app roles. .PARAMETER ApiResourceAppCategoryLookup Optional hashtable lookup (resource app id -> Category, e.g. "Microsoft.Graph") from the same Classification_ApiPermissions.json definitions, used to label OAuthApplication resource role scopes with a readable API name instead of the access package resource scope's own displayName (often just "Root"). Falls back to that displayName when not provided or the resource app id isn't found. .PARAMETER EntraIdRolesClassification Classified EntraID role assignments (flattened RoleAssignments of the classified EntraID EAM data) used to classify DirectoryRole resource scopes at directory level. .PARAMETER EntraRolesDefaultClassification Default classification of Entra ID directory roles (AzurePrivilegedIAM community repository) used as fallback when a directory role is not found in EntraIdRolesClassification. .PARAMETER AzureScopeReasoning Parsed payload of ScopeReasoning_Azure.json (Tier0Scope/Tier1Scope), written by Update-EntraOpsClassificationControlPlaneScope, used to classify AzureResources resource scopes. Without it, Azure resources fail closed to ControlPlane (conservative) with a warning - the same fail-closed convention used by the IdGov-side scope classifier (Get-EntraOpsIdGovScopeClassification), so both outputs agree for the same catalog. .PARAMETER ContextLabel Human-readable label of the scope being classified (access package or catalog id), used in warning messages only. .PARAMETER WarningMessages Optional list to collect warning messages raised during classification. .EXAMPLE Get-EntraOpsPrivilegedEamAccessPackageResourceRoleScopeClassification -ResourceRoleScopes $Scopes -FilterClassifiedRbacs $FilterClassifiedRbacs -ClassificationCache $ClassificationCache -ApiPermissionsClassLookup $ApiPermissionsClassLookup -ContextLabel "access package aaaaaaaa-0000-0000-0000-000000000000" #> function Get-EntraOpsPrivilegedEamAccessPackageResourceRoleScopeClassification { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [AllowEmptyCollection()] [array]$ResourceRoleScopes , [Parameter(Mandatory = $true)] [array]$FilterClassifiedRbacs , [Parameter(Mandatory = $true)] [hashtable]$ClassificationCache , [Parameter(Mandatory = $true)] [hashtable]$ApiPermissionsClassLookup , [Parameter(Mandatory = $false)] [AllowNull()] [hashtable]$ApiResourceAppCategoryLookup , [Parameter(Mandatory = $false)] [AllowNull()] [array]$EntraIdRolesClassification , [Parameter(Mandatory = $false)] [AllowNull()] [array]$EntraRolesDefaultClassification , [Parameter(Mandatory = $false)] [AllowNull()] [psobject]$AzureScopeReasoning , [Parameter(Mandatory = $true)] [string]$ContextLabel , [Parameter(Mandatory = $false)] [System.Collections.Generic.List[psobject]]$WarningMessages ) $MatchedClassification = New-Object System.Collections.Generic.List[psobject] foreach ($AssignedResourceRoleScope in $ResourceRoleScopes) { $ResourceRole = $AssignedResourceRoleScope.accessPackageResourceRole $ResourceScope = $AssignedResourceRoleScope.accessPackageResourceScope # A resource role added to an access package but not yet approved (e.g. by an Access package # assignment manager pending catalog owner approval) is still potential access - classify it, # but annotate the tagging so the pending state stays visible. $IsPending = ($null -ne $AssignedResourceRoleScope.PSObject.Properties['isPending'] -and $AssignedResourceRoleScope.isPending -eq $true) $PendingSuffix = if ($IsPending) { " [pending approval]" } else { "" } switch ($ResourceScope.originSystem) { 'AadGroup' { Write-Verbose -Message "Classifying resource role scope $($ResourceScope.displayName) from origin system $($ResourceScope.originSystem) by $FilterClassifiedRbacs" foreach ($RbacSystem in $FilterClassifiedRbacs) { if ($ClassificationCache.ContainsKey($RbacSystem)) { $ObjectIndexKey = "${RbacSystem}:ByObjectId" $ClassifiedObject = if ($ClassificationCache.ContainsKey($ObjectIndexKey)) { @($ClassificationCache[$ObjectIndexKey][$ResourceScope.originId]) } else { @($ClassificationCache[$RbacSystem] | Where-Object { $_.ObjectId -eq $ResourceScope.originId }) } if ($null -ne $($ClassifiedObject.Classification)) { $TaggedObjectDisplayName = $ClassifiedObject.ObjectDisplayName | Where-Object { -not [string]::IsNullOrWhiteSpace($_) } | Select-Object -First 1 if ([string]::IsNullOrWhiteSpace($TaggedObjectDisplayName)) { $TaggedObjectDisplayName = $ResourceScope.displayName } $MatchedRbacClassification = $ClassifiedObject.Classification foreach ($ClassItem in $MatchedRbacClassification) { # Clone before tagging - $ClassItem is a live reference into $ClassificationCache, # which is loaded once and reused for every catalog/access package scope in this # run. Mutating it in place would permanently rewrite the same cached object every # time any other scope's resource happens to match it. $TaggedClassItem = $ClassItem.PSObject.Copy() $TaggedClassItem | Add-Member -NotePropertyName "TaggedBy" -NotePropertyValue "AssignedAadGroup" -Force $TaggedClassItem | Add-Member -NotePropertyName "TaggedByObjectIds" -NotePropertyValue @($ResourceScope.originId) -Force $TaggedClassItem | Add-Member -NotePropertyName "TaggedByObjectDisplayNames" -NotePropertyValue @("$TaggedObjectDisplayName$PendingSuffix") -Force $TaggedClassItem | Add-Member -NotePropertyName "TaggedByRoleSystem" -NotePropertyValue $RbacSystem -Force $MatchedClassification.Add($TaggedClassItem) | Out-Null } } else { Write-Verbose "No classification for $($ResourceScope.displayName) $($ResourceScope.id) found in $RbacSystem" } } } } 'OAuthApplication' { Write-Verbose -Message "Classifying resource role $($ResourceRole.displayName) of resource $($ResourceScope.displayName) by Graph API App roles" $lookupKey = "$($ResourceScope.originId)|$($ResourceRole.displayName)" $AppRoleMatch = $ApiPermissionsClassLookup[$lookupKey] # $ResourceScope.displayName is the access package resource SCOPE's name (e.g. "Root" # for the root scope of the resource application), not the resource application # itself - resolve the resource app's Category from the same classification # definitions (Classification_ApiPermissions.json) instead, falling back to the # scope's own displayName if the resource app id isn't in there. $ResourceAppDisplayName = $null if ($null -ne $ApiResourceAppCategoryLookup) { $ResourceAppDisplayName = $ApiResourceAppCategoryLookup[$ResourceScope.originId] } if ([string]::IsNullOrEmpty($ResourceAppDisplayName)) { $ResourceAppDisplayName = $ResourceScope.displayName } if ($null -ne $AppRoleMatch) { $Classification = [PSCustomObject]@{ 'AdminTierLevel' = $AppRoleMatch.EAMTierLevelTagValue 'AdminTierLevelName' = $AppRoleMatch.EAMTierLevelName 'Service' = $AppRoleMatch.Service 'TaggedBy' = "AssignedOAuthApplicationResource" 'TaggedByObjectIds' = @($ResourceRole.originId) 'TaggedByObjectDisplayNames' = @("$($ResourceRole.displayName)$PendingSuffix") 'TaggedByRoleSystem' = $ResourceAppDisplayName } } else { if ($null -ne $WarningMessages) { $WarningMessages.Add([PSCustomObject]@{ Type = "Unclassified App Role" Message = "No classification for app role $($ResourceRole.displayName) assigned to resource $($ResourceScope.displayName) in $ContextLabel" Target = $ContextLabel }) } $Classification = [PSCustomObject]@{ 'AdminTierLevel' = "Unclassified" 'AdminTierLevelName' = "Unclassified" 'Service' = "Unclassified" 'TaggedBy' = "AssignedOAuthApplicationResource" 'TaggedByObjectIds' = @($ResourceRole.originId) 'TaggedByObjectDisplayNames' = @("$($ResourceRole.displayName)$PendingSuffix") 'TaggedByRoleSystem' = $ResourceAppDisplayName } } $MatchedClassification.Add($Classification) | Out-Null } 'AadApplication' { $Classifications = @(Get-EntraOpsAadApplicationClassification -ServicePrincipalObjectId $ResourceScope.originId -ClassificationCache $ClassificationCache -DisplayName $ResourceScope.displayName -ContextLabel $ContextLabel -WarningMessages $WarningMessages) foreach ($ClassItem in $Classifications) { $TaggedClassItem = $ClassItem.PSObject.Copy() $TaggedClassItem | Add-Member -NotePropertyName "TaggedBy" -NotePropertyValue "AssignedAadApplicationResource" -Force $TaggedClassItem | Add-Member -NotePropertyName "TaggedByObjectIds" -NotePropertyValue @($ResourceScope.originId) -Force $TaggedClassItem | Add-Member -NotePropertyName "TaggedByObjectDisplayNames" -NotePropertyValue @("$($ResourceScope.displayName) ($($ResourceRole.displayName))$PendingSuffix") -Force $TaggedClassItem | Add-Member -NotePropertyName "TaggedByRoleSystem" -NotePropertyValue "ResourceApps" -Force $MatchedClassification.Add($TaggedClassItem) | Out-Null } } 'SharePointOnline' { $SharePointTier = Resolve-EntraOpsSharePointOnlineRoleTier -RoleDisplayName $ResourceRole.displayName -RoleOriginId "$($ResourceRole.originId)" if ($SharePointTier.IsFallback -and $null -ne $WarningMessages) { $WarningMessages.Add([pscustomobject]@{ Type = "Unknown SharePoint Role" Message = "SharePoint role '$($ResourceRole.displayName)' (originId '$($ResourceRole.originId)') in $ContextLabel is not recognized - treated as ManagementPlane (conservative)." Target = $ContextLabel }) } $MatchedClassification.Add([pscustomobject]@{ AdminTierLevel = $SharePointTier.AdminTierLevel AdminTierLevelName = $SharePointTier.AdminTierLevelName Service = "SharePoint Online" TaggedBy = "AssignedSharePointOnlineResource" TaggedByObjectIds = @($ResourceScope.originId) TaggedByObjectDisplayNames = @("$($ResourceScope.displayName) ($($ResourceRole.displayName))$PendingSuffix") TaggedByRoleSystem = "SharePointOnline" }) | Out-Null } 'DirectoryRole' { # Directory role delegated via an access package: the resource scope's originId is the # role definition id (the expanded role object only carries the membership type, e.g. # "Active Member"). Classified like catalog-level directory roles - EntraID EAM data at # directory scope first, then the community default classification. Write-Verbose -Message "Classifying resource role scope $($ResourceScope.displayName) ($($ResourceScope.originId)) from origin system DirectoryRole by EntraID" $RoleDisplayName = $ResourceScope.displayName $Classification = $null $MatchedRole = if ($ClassificationCache.ContainsKey("EntraIDRoles:ByDefinitionId")) { $ClassificationCache["EntraIDRoles:ByDefinitionId"][$ResourceScope.originId] } else { @($EntraIdRolesClassification) | Where-Object { $_.RoleAssignmentScopeId -eq "/" -and $_.RoleDefinitionId -eq $ResourceScope.originId } | Select-Object -First 1 } if ($null -ne $MatchedRole) { if (-not [string]::IsNullOrEmpty($MatchedRole.RoleDefinitionName)) { $RoleDisplayName = $MatchedRole.RoleDefinitionName } $Classification = $MatchedRole.Classification } if ($null -eq $Classification) { if ($null -ne $WarningMessages) { $WarningMessages.Add([PSCustomObject]@{ Type = "Default Classification Fallback" Message = "No classification for directory role $($ResourceScope.originId) assigned in $ContextLabel found in EntraID! Fallback to default." Target = $ContextLabel }) } $MatchedDefaultRole = @($EntraRolesDefaultClassification) | Where-Object { $_.RoleId -eq $ResourceScope.originId } | Select-Object -First 1 if ($null -ne $MatchedDefaultRole -and $null -ne $MatchedDefaultRole.RolePermissions) { if (-not [string]::IsNullOrEmpty($MatchedDefaultRole.RoleName)) { $RoleDisplayName = $MatchedDefaultRole.RoleName } $DefaultRoleClassification = $MatchedDefaultRole.RolePermissions | Select-Object -Unique EAMTierLevelTagValue, EAMTierLevelName, Category $Classification = $DefaultRoleClassification | ForEach-Object { [PSCustomObject]@{ 'AdminTierLevel' = $_.EAMTierLevelTagValue 'AdminTierLevelName' = $_.EAMTierLevelName 'Service' = $_.Category | Select-Object -First 1 } } } } if ($null -eq $Classification.AdminTierLevel) { if ($null -ne $WarningMessages) { $WarningMessages.Add([PSCustomObject]@{ Type = "Unclassified Resource" Message = "No default classification for directory role $($ResourceScope.originId) assigned in $ContextLabel found!" Target = $ContextLabel }) } $Classification = [PSCustomObject]@{ 'AdminTierLevel' = "Unclassified" 'AdminTierLevelName' = "Unclassified" 'Service' = "Unclassified" } } foreach ($ClassItem in @($Classification)) { $TaggedClassItem = $ClassItem.PSObject.Copy() $TaggedClassItem | Add-Member -NotePropertyName "TaggedBy" -NotePropertyValue "AssignedDirectoryRoleResource" -Force $TaggedClassItem | Add-Member -NotePropertyName "TaggedByObjectIds" -NotePropertyValue @($ResourceScope.originId) -Force $TaggedClassItem | Add-Member -NotePropertyName "TaggedByObjectDisplayNames" -NotePropertyValue @("$RoleDisplayName$PendingSuffix") -Force $TaggedClassItem | Add-Member -NotePropertyName "TaggedByRoleSystem" -NotePropertyValue "EntraID" -Force $MatchedClassification.Add($TaggedClassItem) | Out-Null } } 'AzureResources' { # Azure resource (subscription, resource group, resource) delegated via an access # package: classified by the Tier0/Tier1 resource scope buckets from # ScopeReasoning_Azure.json (a role at the onboarded ARM scope reaches every resource # beneath it). The specific Azure role is kept visible in the tagging display name. Write-Verbose -Message "Classifying resource role scope $($ResourceScope.displayName) ($($ResourceScope.originId)) from origin system AzureResources by Azure resource scope reasoning" $AzureScopeTier = Resolve-EntraOpsAzureScopeReasoningTier -ArmScopeId $ResourceScope.originId -AzureScopeReasoning $AzureScopeReasoning if ($null -eq $AzureScopeTier) { if ($null -ne $WarningMessages) { $WarningMessages.Add([PSCustomObject]@{ Type = "Unresolved Azure Scope" Message = "Azure scope $($ResourceScope.originId) assigned in $ContextLabel could not be evaluated - ScopeReasoning_Azure.json not available. Run Update-EntraOpsClassificationControlPlaneScope for Azure first. Treated as ControlPlane (conservative)." Target = $ContextLabel }) } $AzureScopeTier = [PSCustomObject]@{ AdminTierLevel = "0"; AdminTierLevelName = "ControlPlane"; MatchedScope = $null } } $RoleLabel = if ([string]::IsNullOrEmpty($ResourceRole.displayName)) { "" } else { " ($($ResourceRole.displayName))" } $Classification = [PSCustomObject]@{ 'AdminTierLevel' = $AzureScopeTier.AdminTierLevel 'AdminTierLevelName' = $AzureScopeTier.AdminTierLevelName 'Service' = "Azure Resources" 'TaggedBy' = "AssignedAzureResourcesResource" 'TaggedByObjectIds' = @($ResourceScope.originId) 'TaggedByObjectDisplayNames' = @("$($ResourceScope.displayName)$RoleLabel$PendingSuffix") 'TaggedByRoleSystem' = "Azure" } $MatchedClassification.Add($Classification) | Out-Null } default { if ($null -ne $WarningMessages) { $WarningMessages.Add([PSCustomObject]@{ Type = "Unknown Origin System" Message = "Origin system $($ResourceScope.originSystem) not supported for access package classification!" Target = $ResourceScope.originSystem }) } } } } return $MatchedClassification } # SIG # Begin signature block # MIIniAYJKoZIhvcNAQcCoIIneTCCJ3UCAQExDzANBglghkgBZQMEAgEFADB5Bgor # BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG # KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCCjdpCGXiuTE3F0 # nFIBf4seB62RPkXWAA1o7npjHEGQEqCCIDwwggXJMIIEsaADAgECAhAbtY8lKt8j # 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 # ARUwLwYJKoZIhvcNAQkEMSIEIIOOQUh5zNOovGul/uiB1v1j6HZqCHQZ9D3xq/1m # t8m7MA0GCSqGSIb3DQEBAQUABIIBgE3pOItUuE2B8ZLSbZ4kV6x1utATJZXaUp/1 # kmp+WMhw1Qua1H64JgFahcKAOK4xZE6GKRjvVp68tP3o9L0y5zItF7Vbp2p/2/hD # dvpHhsuuf+HAYcja/VG85Vs8V6eWRoQO+YNMUVP1uV4k38az6dRqkKOcG0PZh0V7 # 3T8Z3ausTvIqbhpdVVpepDmRAiwSFU946kaoTGG1E6XBYy6UoalKwFqcX2wEy9Wh # HrbKX00pOFHjr3T7ZRVD0Xn4m6cDTQETypUxA4PWhC14YLPF3g684M7bpwsm99TM # dFQbgVDW5Mw9za3B2K1HaTr3w/tNIxL2tVkY+VYR34a8x/RDUY15dC7lWtRMyYvJ # oxzLL/Ta/+aAsjuyk/eWyQiwuM+rmOJxWm3WVuK04MZk9mz35lwPzIzzTeUUjoYZ # 4NKgN7ytYNr21KXLaOgn/8WQq5jalHRo2hNHtCEAoC9oJKyAMPSq4zcJFUy1rWKc # s2ezQZyeJ+UpmT/Sl8jeKsj5pA8PA6GCBAIwggP+BgkqhkiG9w0BCQYxggPvMIID # 6wIBATBqMFYxCzAJBgNVBAYTAlBMMSEwHwYDVQQKExhBc3NlY28gRGF0YSBTeXN0 # ZW1zIFMuQS4xJDAiBgNVBAMTG0NlcnR1bSBUaW1lc3RhbXBpbmcgMjAyMSBDQQIQ # KPB3wRw2vf5fdDJHcCcuAzANBglghkgBZQMEAgIFAKCCAVYwGgYJKoZIhvcNAQkD # MQ0GCyqGSIb3DQEJEAEEMBwGCSqGSIb3DQEJBTEPFw0yNjEwMTMwOTM5NDJaMDcG # CyqGSIb3DQEJEAIvMSgwJjAkMCIEIIW+kOEK0kONfMkotq9IsJqyCBd87PiwEmxY # 05EFJcQ8MD8GCSqGSIb3DQEJBDEyBDC49wKQz9p7cW9wBO0bOfR9RL+dZ32RLueW # j6mW/3XE/Yap/AcFS+5zBXUa7HjzzP0wgZ8GCyqGSIb3DQEJEAIMMYGPMIGMMIGJ # MIGGBBRXFGhBDKha80JO+RZKUTYQ9NONmDBuMFqkWDBWMQswCQYDVQQGEwJQTDEh # MB8GA1UEChMYQXNzZWNvIERhdGEgU3lzdGVtcyBTLkEuMSQwIgYDVQQDExtDZXJ0 # dW0gVGltZXN0YW1waW5nIDIwMjEgQ0ECECjwd8EcNr3+X3QyR3AnLgMwDQYJKoZI # hvcNAQEBBQAEggIAH2odgCIt5SoQpOXYsAJ4DRJ574xvrXsl4G8wmUu993rWdPoB # lIDhRFH7vrRCVKuMoAHE5SOyMw+5sS/MT+n65DCKivYLTx70pkUlNmCtkhMri6tW # fGdEheiV+BeC6rpNscSFAswPO2feX5//r7UTVIUDpXdcDTmrCyafQ1PPER7VP32q # KFzQu682zClOP5LNEaDnNLXoH3ypIYLTTeYwh4uoqozt9YjNChLXUzhEaxiXKvAL # Ny2NmUjUQcX/r8UXARTnWZ58wcUccWzeXG+qUEGFWaCNUKap8h8OPU+OYH8zC1ep # OtIKLF/8afjGMeWNid6qcErdwwlC/we8VnbR1vtNp9UXBEA2cWnHpi2Qhq+3oZeU # FsngPecmCLitMUrTiXvpeDEyGocsVPYpgTCBCCvOwAeTarK8J+mitLtrTn5P7dNQ # GliC2Lauzix5yUSfc1gxQ9v49Ld69jMXJemYKHfHQp4hsdgPW1u1mjn4LYErAUll # 2XxGB6BSeRkUTGFoTz3Nexx021gPin/+XpTrQ4IRTVP5FUzsHKwesd5vQHc38a26 # CQ0eg7O8h23otoy0UCT/24wUiVyZbXvPoEwvBHU1k7X9XKT3Pi+NGIWQQsoTjavw # AfR18inNEMAjwuls2ObIiF3UqgtSgIqXTl7OiCawu7zHDS4zqI1tPsQXnNU= # SIG # End signature block |