Public/Reportings/New-EntraOpsTierBreachAnalyzerData.ps1
|
<# .SYNOPSIS Generate (refresh) the embedded dataset for the EntraOps Tier Breach Analyzer static web app. .DESCRIPTION Transforms EntraOps Privileged EAM data into the Tier Breach Analyzer dataset. Generates data/tier-breach-data.js exposing `window.ENTRAOPS_TB_DATA` so the static web app works both when served over HTTP (for example Azure Static Web Apps) and when opened directly from the file system (no fetch / CORS required). Flow modeled (left -> right): Object Tier -> Object -> Role -> Service -> Service Tier Tiering rules: * Object designated tier comes from `ObjectAdminTierLevel`. * If an object has no classification (empty / "Unclassified"), it is treated as Tier 2 (User Access) which is the lowest / least privileged tier. * A "tier breach" is a path where the object's tier is LESS privileged (higher tier number) than the service it can reach through a role. The headline case is a Tier 1 / Tier 2 object reaching a Tier 0 service. Source data is the Privileged EAM export written by Save-EntraOpsPrivilegedEAMJson: PrivilegedEAM/<RbacSystem>/<RbacSystem>.json (only JSON files directly in the RBAC system folder are read, not the per-object subfolders user/, group/, serviceprincipal/, ...). This function requires the module to be imported from a repository checkout that contains the Reports/TierBreachAnalyzer folder. .PARAMETER RepoRoot Path to the EntraOps repository root that contains the PrivilegedEAM export folder. Defaults to the repository this module lives in. .PARAMETER ImportPath Folder with the Privileged EAM export. Defaults to <RepoRoot>/PrivilegedEAM. .PARAMETER AppRoot Path to the TierBreachAnalyzer app folder (where the generated content is written). Defaults to Reports/TierBreachAnalyzer in the EntraOps repository. .PARAMETER OutFile Output file. Defaults to <AppRoot>/data/tier-breach-data.js. .PARAMETER PassThru Emit the generated payload object to the pipeline. .EXAMPLE New-EntraOpsTierBreachAnalyzerData Regenerates the Tier Breach Analyzer dataset from the PrivilegedEAM folder of this repository. .EXAMPLE New-EntraOpsTierBreachAnalyzerData -ImportPath "C:\Exports\PrivilegedEAM" -Verbose -WhatIf Shows what would be generated from an explicit Privileged EAM export without changing any files. #> function New-EntraOpsTierBreachAnalyzerData { [CmdletBinding(SupportsShouldProcess = $true)] param ( [Parameter(Mandatory = $false)] [System.String]$RepoRoot, [Parameter(Mandatory = $false)] [System.String]$ImportPath, [Parameter(Mandatory = $false)] [System.String]$AppRoot, [Parameter(Mandatory = $false)] [System.String]$OutFile, [Parameter(Mandatory = $false)] [switch]$PassThru ) # Resolve the app/repository location relative to the module location: # <repo>/EntraOps/Public/<subfolder> -> <repo>/Reports/TierBreachAnalyzer # Prefer the module's own ModuleBase (always populated for exported module # functions) over $PSScriptRoot, which can be empty depending on how the # function was invoked/loaded. $ModuleRoot = $MyInvocation.MyCommand.Module.ModuleBase if ([string]::IsNullOrWhiteSpace($ModuleRoot) -and -not [string]::IsNullOrWhiteSpace($PSScriptRoot)) { $ModuleRoot = Split-Path -Parent (Split-Path -Parent $PSScriptRoot) } if ([string]::IsNullOrWhiteSpace($ModuleRoot)) { throw "Unable to resolve the EntraOps module location. Import the module with 'Import-Module <path-to-EntraOps> -Force' and try again." } $RepositoryRoot = Split-Path -Parent $ModuleRoot if ([string]::IsNullOrWhiteSpace($RepoRoot)) { $RepoRoot = $RepositoryRoot } if ([string]::IsNullOrWhiteSpace($AppRoot)) { $AppRoot = Join-Path $RepositoryRoot 'Reports/TierBreachAnalyzer' } if (-not (Test-Path -LiteralPath $AppRoot -PathType Container)) { throw "Tier Breach Analyzer app folder not found: $AppRoot. Import the EntraOps module from a repository checkout that contains Reports/TierBreachAnalyzer." } if ([string]::IsNullOrWhiteSpace($ImportPath)) { $ImportPath = Join-Path $RepoRoot 'PrivilegedEAM' } if ([string]::IsNullOrWhiteSpace($OutFile)) { $OutFile = Join-Path $AppRoot 'data/tier-breach-data.js' } if (-not (Test-Path -LiteralPath $ImportPath)) { throw "Privileged EAM import path not found: $ImportPath. Run Save-EntraOpsPrivilegedEAMJson first or pass -ImportPath." } Write-Verbose "Reading Privileged EAM JSON from $ImportPath." Write-Verbose "Writing Tier Breach Analyzer data to $OutFile." # Tier numeric value -> label. Lower number = more privileged. $TierLabels = [ordered]@{ '0' = 'Tier 0 - Control Plane' '1' = 'Tier 1 - Management Plane' '2' = 'Tier 2 - User Access' } function Get-PropValue { # Safe property access for objects from ConvertFrom-Json whose schema can # vary between RBAC systems (also strict-mode proof). param($Object, [string] $Name) $p = $Object.PSObject.Properties[$Name] if ($null -ne $p) { return $p.Value } return $null } function ConvertTo-TierNumber { <# Normalize a raw tier value to a numeric tier (0/1/2). Empty string, $null and "Unclassified" map to the default (Tier 2 - lowest privilege), matching the handling of unclassified objects. #> param( $Value, [int] $Default = 2 ) if ($null -eq $Value) { return $Default } $s = "$Value".Trim() if ($s -eq '' -or $s -eq 'Unclassified') { return $Default } $n = 0 if (-not [int]::TryParse($s, [ref] $n)) { return $Default } if ($n -in 0, 1, 2) { return $n } return $Default } # Match PrivilegedEAM/<System>/<System>.json only (one level deep), not the # per-object subfolders (user/, group/, serviceprincipal/ ...). $files = Get-ChildItem -Path $ImportPath -Directory | ForEach-Object { Get-ChildItem -Path $_.FullName -Filter '*.json' -File } | Sort-Object FullName if (-not $files) { Write-Warning "No Privileged EAM JSON files found under $ImportPath (expected <RbacSystem>/<RbacSystem>.json). Generating an empty dataset." } else { Write-Verbose "Found $($files.Count) Privileged EAM JSON file(s) to process." } $objects = [System.Collections.Generic.List[object]]::new() $paths = [System.Collections.Generic.List[object]]::new() # Maps assignment scope IDs to reusable scope-reasoning entries. $scopeReasoningByScope = [ordered]@{} $scopeReasoningDetails = Import-EntraOpsAzureScopeReasoning -RepoRoot $RepoRoot Write-Verbose "Loaded $($scopeReasoningDetails.Count) Azure scope-reasoning detail record(s)." $fileNumber = 0 foreach ($file in @($files)) { $fileNumber++ $roleSystem = Split-Path -Leaf (Split-Path -Parent $file.FullName) Write-Verbose "[$fileNumber/$($files.Count)] Reading $roleSystem data from $($file.FullName)." try { $data = Get-Content -LiteralPath $file.FullName -Raw -Encoding UTF8 | ConvertFrom-Json } catch { # Stop generation when an enumerated export cannot be read or parsed. throw "Failed to read or parse Privileged EAM export file '$($file.FullName)' (RBAC system '$roleSystem'): $($_.Exception.Message)" } $sourceObjectCount = @($data).Count $processedObjectCount = 0 $pathsBeforeFile = $paths.Count foreach ($o in @($data)) { $processedObjectCount++ if ($processedObjectCount % 250 -eq 0) { Write-Verbose "[$fileNumber/$($files.Count)] Processed $processedObjectCount of $sourceObjectCount $roleSystem source object(s)." } $objType = Get-PropValue $o 'ObjectType' # Only users and service principals. if ($objType -notin 'user', 'serviceprincipal') { continue } $objectId = Get-PropValue $o 'ObjectId' $display = Get-PropValue $o 'ObjectDisplayName' if (-not $display) { $display = $objectId } $objTier = ConvertTo-TierNumber -Value (Get-PropValue $o 'ObjectAdminTierLevel') $objects.Add([ordered]@{ id = $objectId name = $display type = $objType system = $roleSystem tier = $objTier }) foreach ($ra in @(Get-PropValue $o 'RoleAssignments')) { if ($null -eq $ra) { continue } $roleName = Get-PropValue $ra 'RoleDefinitionName' if (-not $roleName) { $roleName = '(unnamed role)' } $isPriv = [bool] (Get-PropValue $ra 'RoleIsPrivileged') $classifications = @(Get-PropValue $ra 'Classification') if ($classifications.Count -eq 0) { continue } # Assignment-level context (explains which assignment causes the breach). $scopeId = Get-PropValue $ra 'RoleAssignmentScopeId' $scopeName = Get-PropValue $ra 'RoleAssignmentScopeName' if (-not $scopeName) { $scopeName = $scopeId } $assignType = "$(Get-PropValue $ra 'RoleAssignmentType')" $assignSub = "$(Get-PropValue $ra 'RoleAssignmentSubType')" $pimManaged = [bool] (Get-PropValue $ra 'PIMManagedRole') $pimType = "$(Get-PropValue $ra 'PIMAssignmentType')" $roleType = "$(Get-PropValue $ra 'RoleType')" $assignId = Get-PropValue $ra 'RoleAssignmentId' $assignInstanceId = if (Get-PropValue $ra 'RoleAssignmentInstanceId') { Get-PropValue $ra 'RoleAssignmentInstanceId' } else { Get-EntraOpsRoleAssignmentInstanceId -RoleSystem $roleSystem -RoleAssignment $ra } $transitiveBy = "$(Get-PropValue $ra 'TransitiveByObjectDisplayName')" # De-duplicate services within a single assignment. $seen = [System.Collections.Generic.HashSet[string]]::new() foreach ($c in $classifications) { if ($null -eq $c) { continue } $service = Get-PropValue $c 'Service' if (-not $service) { $service = '(unclassified)' } $svcTier = ConvertTo-TierNumber -Value (Get-PropValue $c 'AdminTierLevel') $key = "$service|$svcTier" if (-not $seen.Add($key)) { continue } $taggedBy = "$(Get-PropValue $c 'TaggedBy')" $taggedBySystem = "$(Get-PropValue $c 'TaggedByRoleSystem')" # Cache Azure scope reasoning once for each assignment scope. if ($roleSystem -eq 'Azure' -and $scopeReasoningDetails.Count -gt 0 -and -not [string]::IsNullOrEmpty("$scopeId") -and -not $scopeReasoningByScope.Contains("$scopeId")) { $scopeReasoning = [System.Collections.Generic.List[object]]::new() foreach ($sr in (Find-EntraOpsAzureScopeReasoning -ScopeReasoningDetails $scopeReasoningDetails -ScopeId "$scopeId")) { $scopeReasoning.Add([ordered]@{ resourceName = "$(Get-PropValue $sr 'ScopeName')" resourceId = "$(Get-PropValue $sr 'ScopeId')" eamTier = "$(Get-PropValue $sr 'EAMTier')" resultingScope = "$(Get-PropValue $sr 'ResultingScope')" reason = "$(Get-PropValue $sr 'Reason')" managedIdentityObjectId = "$(Get-PropValue $sr 'ManagedIdentityObjectId')" # ScopeRelation identifies the scope reason relative to the assignment scope. source = "$(Get-PropValue $sr 'Source')" scopeRelation = $(if ("$(Get-PropValue $sr 'ScopeId')".TrimEnd('/') -ieq "$scopeId".TrimEnd('/')) { 'Exact' } else { 'Descendant resource' }) }) } if ($scopeReasoning.Count -gt 0) { $scopeReasoningByScope["$scopeId"] = $scopeReasoning } else { # Cache scope IDs without matching reasoning entries. $scopeReasoningByScope["$scopeId"] = @() } } $paths.Add([ordered]@{ objectId = $objectId objectName = $display objectType = $objType objectTier = $objTier system = $roleSystem role = $roleName roleType = $roleType roleIsPrivileged = $isPriv service = $service serviceTier = $svcTier # Assignment detail. assignmentInstanceId = $assignInstanceId assignmentId = $assignId scopeName = $scopeName scopeId = $scopeId assignmentType = $assignType assignmentSubType = $assignSub pimManaged = $pimManaged pimAssignmentType = $pimType transitiveBy = $transitiveBy taggedBy = $taggedBy taggedByRoleSystem = $taggedBySystem # Breach: object less privileged than the service it reaches. breach = ($objTier -gt $svcTier) tier0Breach = ($objTier -gt 0 -and $svcTier -eq 0) # Scope reasoning is resolved through scopeReasoningByScope. }) } } } Write-Verbose "[$fileNumber/$($files.Count)] Finished ${roleSystem}: processed $sourceObjectCount source object(s), added $($paths.Count - $pathsBeforeFile) assignment path(s)." } $repoRootFull = (Resolve-Path -LiteralPath $RepoRoot).Path $tenantName = Get-EntraOpsReportingTenantName -RepoRoot $RepoRoot $payload = [ordered]@{ tenantName = $tenantName generatedFrom = @(@($files) | ForEach-Object { if ($_.FullName.StartsWith($repoRootFull)) { $_.FullName.Substring($repoRootFull.Length).TrimStart('\', '/') -replace '\\', '/' } else { $_.Name } }) tierLabels = $TierLabels objects = $objects paths = $paths # Scope-reasoning entries shared by assignment scope ID. scopeReasoningByScope = $scopeReasoningByScope } $json = $payload | ConvertTo-Json -Depth 10 -Compress $content = "// Auto-generated by New-EntraOpsTierBreachAnalyzerData - do not edit by hand.`n" + "window.ENTRAOPS_TB_DATA = $json;`n" Write-Verbose "Built Tier Breach Analyzer payload with $($objects.Count) object record(s) and $($paths.Count) assignment path(s)." if ($PSCmdlet.ShouldProcess($OutFile, 'Write Tier Breach Analyzer dataset')) { Save-EntraOpsReportDataFile -Content $content -LiteralPath $OutFile $nBreach = @($paths | Where-Object { $_.breach }).Count $nTier0 = @($paths | Where-Object { $_.tier0Breach }).Count Write-Host "Objects (user/sp): $($objects.Count)" Write-Host "Assignment paths: $($paths.Count)" Write-Host "Tier breaches: $nBreach" Write-Host "Tier 0 breaches: $nTier0" Write-Host "Wrote $OutFile" } if ($PassThru) { $payload } } # SIG # Begin signature block # MIIniAYJKoZIhvcNAQcCoIIneTCCJ3UCAQExDzANBglghkgBZQMEAgEFADB5Bgor # BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG # KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCBrO10qJdhxDx/u # jLlV8CDdOyxTt7oyUer8PwjMQB28haCCIDwwggXJMIIEsaADAgECAhAbtY8lKt8j # 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 # ARUwLwYJKoZIhvcNAQkEMSIEIMN98/gekOSkthPMGYF9S2xy35ueTILeIsiavW3w # ixvLMA0GCSqGSIb3DQEBAQUABIIBgBUFDn8bD97O6CeLiaXaET7xN+fh558DW3N6 # tEipdK+d7ckWCDAe+mQWV3CZ1Ue/RjylgJW0lz2v+JowD3CHVhxDBKgMwPkQyZBV # rFQpBmNeO8c6Kah7JigpfIdyM3fbReyCrZ45D0xED1q9+i3SPb+4HF3UDm9XKqY9 # Js2KNCf/ucfJQesrqA6SRsfiESx8NYPKfYwcu2UsYi3ct6Ma2w/KQDxKJw28kvv0 # +VxQLAoI83ytO2HtemCS5+JXwDIjW06NROQyWlEaapKALpxXEyLjF9AAv7rnRIu3 # F6eo32JSsLUXFuPCr0tVzQH8/HjnW/Dh3FHTkRf539lbF+wgpBZMKbNM1l+uk+So # 9ZXRos78vlHRm+APiTj4CuiGUl/omiwo+xmHC0X1vaA3XC97d2nWjQDoNsGXej4n # WlCanjYVkXBC+4OGm68+dsqKjNPdFSo5WRBCz6Q4CXcVqjTgbxJRwonSTmEUdVxo # XoBzWYLSnRcAY3e9edblgdck2mzhk6GCBAIwggP+BgkqhkiG9w0BCQYxggPvMIID # 6wIBATBqMFYxCzAJBgNVBAYTAlBMMSEwHwYDVQQKExhBc3NlY28gRGF0YSBTeXN0 # ZW1zIFMuQS4xJDAiBgNVBAMTG0NlcnR1bSBUaW1lc3RhbXBpbmcgMjAyMSBDQQIQ # KPB3wRw2vf5fdDJHcCcuAzANBglghkgBZQMEAgIFAKCCAVYwGgYJKoZIhvcNAQkD # MQ0GCyqGSIb3DQEJEAEEMBwGCSqGSIb3DQEJBTEPFw0yNjEwMTMwOTQxNTdaMDcG # CyqGSIb3DQEJEAIvMSgwJjAkMCIEIIW+kOEK0kONfMkotq9IsJqyCBd87PiwEmxY # 05EFJcQ8MD8GCSqGSIb3DQEJBDEyBDBB49tDr0uF5A/JadBZGyLqAZ2OD4ZYkCZj # irFDdBB/UXDzQGa7KQeB8p1IQF6PdPwwgZ8GCyqGSIb3DQEJEAIMMYGPMIGMMIGJ # MIGGBBRXFGhBDKha80JO+RZKUTYQ9NONmDBuMFqkWDBWMQswCQYDVQQGEwJQTDEh # MB8GA1UEChMYQXNzZWNvIERhdGEgU3lzdGVtcyBTLkEuMSQwIgYDVQQDExtDZXJ0 # dW0gVGltZXN0YW1waW5nIDIwMjEgQ0ECECjwd8EcNr3+X3QyR3AnLgMwDQYJKoZI # hvcNAQEBBQAEggIAJg2sKHVwCAgimFvtLkPIPp2KSzf/sVHQ+MykY5yZjihGWktJ # phM9lfOfh5cMXy6Xn0a/B+3zTlHxxubpBBqMudq3Kz/vHrEd0bju8M2rYWcRVD+r # Bvwd5WJaq9Rv3Qx6A6W4nNYctESyDNV3k4+ypi1ANbV0RoDSFXaRWYmE5Y9qW0s5 # SHOJA367VKGjq1lLGYPC2mz5nzEUC7iro4lPfGMbNSOCz4MVmdbg1giYk2c4YZyR # ZjE64TH1jZABwSTEmjfWJDQ4MWWGtmgIuoOYwD5SddN8cqaYluqKhuhbAEtIJow4 # 6vxCmRbTWGXN1esdP4pa1RlkYxbBXY5g+e+tACX9lcgJYyT2GzCUm5ezURM63EAL # 81CcgRiI+i8vPVfHJ3pcICQP3D3tpcsiOqQ/mPCVazZv70aT503fmxNprVHwm7zU # xwqLzxG4CgjwGFpR8udulYifYX8UuSKndKq6I8mOpSqS8+a4e5fp1rA0qhrXhhNv # bNiBesYoJPkwjk7h7LDrAbPJ0eMYyocfPB5EAk8/J6+5BNCgatGYJb53Q+hX54Bj # EmNoqG+/0+QhzFbCdK/Hs603jOVy1IdYwFw5XyW8cjNfOBRyFbJxqIRHJeaPFvv1 # vGmzzvkHACwCVY0UZHxek1dYQEScgJkA0PD2jVpoJA1x4t4B3T3VOCP+ezY= # SIG # End signature block |