Internal/Collector/AzureAD/Get-AADManagementSummary.ps1
|
Function Get-AADManagementSummary(){ <# .SYNOPSIS This function provides a management summary for Entra ID (Azure AD). .DESCRIPTION The function collects key Entra ID summary metrics including identity inventory, conditional access posture, directory role assignments, and authentication method policy states. .EXAMPLE Get-AADManagementSummary Returns a management summary section for Entra ID. .NOTES NAME: Get-AADManagementSummary #> [OutputType('DocSection')] [cmdletbinding()] param() function ConvertTo-Array { param([object]$InputObject) if($null -eq $InputObject){ return @() } if($InputObject.PSObject.Properties.Name -contains "value"){ if($null -eq $InputObject.value){ return @() } if($InputObject.value -is [System.Array]){ return $InputObject.value } return @($InputObject.value) } if($InputObject -is [System.Array]){ return $InputObject } return @($InputObject) } function Get-PolicyStateGroup { param([string]$State) if([string]::IsNullOrWhiteSpace($State)){ return "Unknown" } $value = $State.ToLowerInvariant() if($value -eq "enabled"){ return "Enabled" } if($value -eq "disabled"){ return "Disabled" } if($value -match "report"){ return "ReportOnly" } return $State } function Get-MethodPolicyState { param([object]$MethodPolicy) if($null -eq $MethodPolicy){ return "Unknown" } if($MethodPolicy.PSObject.Properties.Name -contains "state" -and -not [string]::IsNullOrWhiteSpace("$($MethodPolicy.state)")){ return "$($MethodPolicy.state)" } if($MethodPolicy.PSObject.Properties.Name -contains "isUsableForSignIn" -and $null -ne $MethodPolicy.isUsableForSignIn){ if([bool]$MethodPolicy.isUsableForSignIn){ return "enabled" } return "disabled" } if($MethodPolicy.PSObject.Properties.Name -contains "isSoftwareOathEnabled" -and $null -ne $MethodPolicy.isSoftwareOathEnabled){ if([bool]$MethodPolicy.isSoftwareOathEnabled){ return "enabled" } return "disabled" } return "Unknown" } function ConvertTo-DateTimeOrNull { param([object]$Value) if($null -eq $Value -or [string]::IsNullOrWhiteSpace("$Value")){ return $null } try { return [datetime]$Value } catch { return $null } } $DocSec = New-Object DocSection $DocSec.Title = "Management Summary - Entra ID" $DocSec.Text = "This section contains an Entra ID management summary with key identity, policy, and role metrics." $DocSec.SubSections = @() $organization = $null $nowUtc = (Get-Date).ToUniversalTime() $staleGuestThresholdDays = 90 $credentialExpiryThresholdDays = 30 $users = @() $groups = @() $servicePrincipals = @() $applications = @() $conditionalAccessPolicies = @() $directoryRoles = @() $roleMemberSummary = @() $staleGuestUsers = 0 $neverSignedInStaleGuests = 0 $credentialsExpiringSoon = 0 $expiredCredentials = 0 $appsWithExpiringCredentials = @{} try { $organization = ConvertTo-Array (Invoke-DocGraph -Path "/organization?`$select=id,displayName,tenantType" -FollowNextLink $true) | Select-Object -First 1 } catch { Write-Warning "Unable to collect organization summary." } try { $users = ConvertTo-Array (Invoke-DocGraph -Path "/users?`$select=id,userType,accountEnabled" -FollowNextLink $true) } catch { Write-Warning "Unable to collect user inventory summary." } try { $guestUsersWithActivity = ConvertTo-Array (Invoke-DocGraph -Path "/users?`$select=id,displayName,userPrincipalName,userType,createdDateTime,signInActivity&`$filter=userType%20eq%20'Guest'" -Beta -FollowNextLink $true) foreach($guest in $guestUsersWithActivity){ $createdDate = ConvertTo-DateTimeOrNull -Value $guest.createdDateTime $lastSignInDate = $null if($guest.PSObject.Properties.Name -contains "signInActivity" -and $null -ne $guest.signInActivity){ $lastSignInDate = ConvertTo-DateTimeOrNull -Value $guest.signInActivity.lastSignInDateTime } if($null -ne $lastSignInDate){ $daysSinceSignIn = [Math]::Floor(($nowUtc - $lastSignInDate.ToUniversalTime()).TotalDays) if($daysSinceSignIn -ge $staleGuestThresholdDays){ $staleGuestUsers++ } } elseif($null -ne $createdDate) { $daysSinceCreated = [Math]::Floor(($nowUtc - $createdDate.ToUniversalTime()).TotalDays) if($daysSinceCreated -ge $staleGuestThresholdDays){ $staleGuestUsers++ $neverSignedInStaleGuests++ } } } } catch { Write-Warning "Unable to collect stale guest account findings." } try { $groups = ConvertTo-Array (Invoke-DocGraph -Path "/groups?`$select=id,groupTypes,mailEnabled,securityEnabled" -FollowNextLink $true) } catch { Write-Warning "Unable to collect group inventory summary." } try { $servicePrincipals = ConvertTo-Array (Invoke-DocGraph -Path "/servicePrincipals?`$select=id" -FollowNextLink $true) } catch { Write-Warning "Unable to collect service principal summary." } try { $applications = ConvertTo-Array (Invoke-DocGraph -Path "/applications?`$select=id" -FollowNextLink $true) } catch { Write-Warning "Unable to collect app registration summary." } try { $applicationsForCredentials = ConvertTo-Array (Invoke-DocGraph -Path "/applications?`$select=id,displayName,passwordCredentials,keyCredentials" -FollowNextLink $true) foreach($app in $applicationsForCredentials){ $appName = if([string]::IsNullOrWhiteSpace("$($app.displayName)")){ "$($app.id)" } else { "$($app.displayName)" } $expiringForApp = 0 $passwordCredentials = ConvertTo-Array $app.passwordCredentials foreach($credential in $passwordCredentials){ $endDate = ConvertTo-DateTimeOrNull -Value $credential.endDateTime if($null -eq $endDate){ continue } $daysToExpiry = [Math]::Floor(($endDate.ToUniversalTime() - $nowUtc).TotalDays) if($daysToExpiry -lt 0){ $expiredCredentials++ continue } if($daysToExpiry -le $credentialExpiryThresholdDays){ $credentialsExpiringSoon++ $expiringForApp++ } } $keyCredentials = ConvertTo-Array $app.keyCredentials foreach($credential in $keyCredentials){ $endDate = ConvertTo-DateTimeOrNull -Value $credential.endDateTime if($null -eq $endDate){ continue } $daysToExpiry = [Math]::Floor(($endDate.ToUniversalTime() - $nowUtc).TotalDays) if($daysToExpiry -lt 0){ $expiredCredentials++ continue } if($daysToExpiry -le $credentialExpiryThresholdDays){ $credentialsExpiringSoon++ $expiringForApp++ } } if($expiringForApp -gt 0){ $appsWithExpiringCredentials[$appName] = $expiringForApp } } } catch { Write-Warning "Unable to collect app credential expiry findings." } try { $conditionalAccessPolicies = ConvertTo-Array (Invoke-DocGraph -Path "/identity/conditionalAccess/policies" -Beta -FollowNextLink $true) } catch { Write-Warning "Unable to collect conditional access summary." } try { $directoryRoles = ConvertTo-Array (Invoke-DocGraph -Path "/directoryRoles" -FollowNextLink $true) foreach($role in $directoryRoles){ $members = ConvertTo-Array (Invoke-DocGraph -Path "/directoryRoles/$($role.id)/members?`$select=id" -FollowNextLink $true) $roleMemberSummary += [PSCustomObject]@{ Role = $role.displayName MemberCount = $members.Count } } } catch { Write-Warning "Unable to collect directory role membership summary." } $enabledUsers = ($users | Where-Object { $_.accountEnabled -eq $true }).Count $disabledUsers = ($users | Where-Object { $_.accountEnabled -eq $false }).Count $guestUsers = ($users | Where-Object { "$($_.userType)" -match "Guest" }).Count $memberUsers = ($users | Where-Object { "$($_.userType)" -match "Member" }).Count $dynamicGroups = ($groups | Where-Object { $_.groupTypes -contains "DynamicMembership" }).Count $securityGroups = ($groups | Where-Object { $_.securityEnabled -eq $true -and $_.mailEnabled -eq $false }).Count $microsoft365Groups = ($groups | Where-Object { $_.mailEnabled -eq $true -and $_.groupTypes -contains "Unified" }).Count $caEnabled = 0 $caDisabled = 0 $caReportOnly = 0 $caUnknown = 0 foreach($policy in $conditionalAccessPolicies){ switch(Get-PolicyStateGroup -State $policy.state){ "Enabled" { $caEnabled++ } "Disabled" { $caDisabled++ } "ReportOnly" { $caReportOnly++ } Default { $caUnknown++ } } } $globalAdminRole = $roleMemberSummary | Where-Object { $_.Role -eq "Global Administrator" } | Select-Object -First 1 $globalAdminCount = if($null -eq $globalAdminRole){ 0 } else { $globalAdminRole.MemberCount } $kpiSummary = [PSCustomObject]@{ Tenant = if($null -eq $organization){ "Unknown" } else { $organization.displayName } TenantType = if($null -eq $organization){ "Unknown" } else { $organization.tenantType } TotalUsers = $users.Count EnabledUsers = $enabledUsers DisabledUsers = $disabledUsers MemberUsers = $memberUsers GuestUsers = $guestUsers TotalGroups = $groups.Count DynamicGroups = $dynamicGroups SecurityGroups = $securityGroups Microsoft365Groups = $microsoft365Groups AppRegistrations = $applications.Count ServicePrincipals = $servicePrincipals.Count ConditionalAccessPoliciesEnabled = $caEnabled ConditionalAccessPoliciesReportOnly = $caReportOnly ConditionalAccessPoliciesDisabled = $caDisabled ConditionalAccessPoliciesUnknown = $caUnknown DirectoryRoles = $directoryRoles.Count GlobalAdministrators = $globalAdminCount StaleGuestAccounts90Days = $staleGuestUsers CredentialsExpiring30Days = $credentialsExpiringSoon ExpiredCredentials = $expiredCredentials } $DocSec.Objects = $kpiSummary $DocSec.Transpose = $true $DocSecIdentity = New-Object DocSection $DocSecIdentity.Title = "Identity Inventory" $DocSecIdentity.Text = "Counts of key identity object types in Entra ID." $DocSecIdentity.Objects = @( [PSCustomObject]@{ Metric = "Users"; Count = $users.Count }, [PSCustomObject]@{ Metric = "Enabled Users"; Count = $enabledUsers }, [PSCustomObject]@{ Metric = "Disabled Users"; Count = $disabledUsers }, [PSCustomObject]@{ Metric = "Member Users"; Count = $memberUsers }, [PSCustomObject]@{ Metric = "Guest Users"; Count = $guestUsers }, [PSCustomObject]@{ Metric = "Groups"; Count = $groups.Count }, [PSCustomObject]@{ Metric = "Dynamic Groups"; Count = $dynamicGroups }, [PSCustomObject]@{ Metric = "Security Groups"; Count = $securityGroups }, [PSCustomObject]@{ Metric = "Microsoft 365 Groups"; Count = $microsoft365Groups }, [PSCustomObject]@{ Metric = "App Registrations"; Count = $applications.Count }, [PSCustomObject]@{ Metric = "Service Principals"; Count = $servicePrincipals.Count } ) $DocSecIdentity.Transpose = $false $DocSecConditionalAccess = New-Object DocSection $DocSecConditionalAccess.Title = "Conditional Access Posture" $DocSecConditionalAccess.Text = "Count of Conditional Access policies by state." $DocSecConditionalAccess.Objects = @( [PSCustomObject]@{ State = "Enabled"; PolicyCount = $caEnabled }, [PSCustomObject]@{ State = "ReportOnly"; PolicyCount = $caReportOnly }, [PSCustomObject]@{ State = "Disabled"; PolicyCount = $caDisabled }, [PSCustomObject]@{ State = "Unknown"; PolicyCount = $caUnknown } ) $DocSecConditionalAccess.Transpose = $false $authMethodSummary = @() $authMethodDefinitions = @( @{ Name = "FIDO2"; Path = "/policies/authenticationMethodsPolicy/authenticationMethodConfigurations/fido2" }, @{ Name = "Microsoft Authenticator"; Path = "/policies/authenticationMethodsPolicy/authenticationMethodConfigurations/microsoftAuthenticator" }, @{ Name = "Temporary Access Pass"; Path = "/policies/authenticationMethodsPolicy/authenticationMethodConfigurations/TemporaryAccessPass" }, @{ Name = "Email OTP"; Path = "/policies/authenticationMethodsPolicy/authenticationMethodConfigurations/email" }, @{ Name = "SMS"; Path = "/policies/authenticationMethodsPolicy/authenticationMethodConfigurations/sms" } ) foreach($method in $authMethodDefinitions){ try { $methodPolicy = Invoke-DocGraph -Path $method.Path -Beta $authMethodSummary += [PSCustomObject]@{ Method = $method.Name State = Get-MethodPolicyState -MethodPolicy $methodPolicy } } catch { $authMethodSummary += [PSCustomObject]@{ Method = $method.Name State = "Unavailable" } } } $DocSecAuthMethods = New-Object DocSection $DocSecAuthMethods.Title = "Authentication Methods Policy" $DocSecAuthMethods.Text = "Current policy state for key authentication methods." $DocSecAuthMethods.Objects = $authMethodSummary $DocSecAuthMethods.Transpose = $false $DocSecRoleAssignments = New-Object DocSection $DocSecRoleAssignments.Title = "Top Directory Roles by Members" $DocSecRoleAssignments.Text = "Top 10 activated directory roles by assigned member count." $DocSecRoleAssignments.Objects = $roleMemberSummary | Sort-Object MemberCount,Role -Descending | Select-Object -First 10 if($null -eq $DocSecRoleAssignments.Objects -or $DocSecRoleAssignments.Objects.Count -eq 0){ $DocSecRoleAssignments.Objects = @( [PSCustomObject]@{ Role = "No data available" MemberCount = 0 } ) } $DocSecRoleAssignments.Transpose = $false $appsWithExpiringCredentialsTop = @() foreach($appName in $appsWithExpiringCredentials.Keys){ $appsWithExpiringCredentialsTop += [PSCustomObject]@{ Application = $appName ExpiringCredentials = $appsWithExpiringCredentials[$appName] } } $appsWithExpiringCredentialsTop = $appsWithExpiringCredentialsTop | Sort-Object ExpiringCredentials,Application -Descending | Select-Object -First 5 $expiringAppsText = if($null -ne $appsWithExpiringCredentialsTop -and $appsWithExpiringCredentialsTop.Count -gt 0){ ($appsWithExpiringCredentialsTop | ForEach-Object { "$($_.Application) ($($_.ExpiringCredentials))" }) -join ", " } else { "None" } $DocSecTopFindings = New-Object DocSection $DocSecTopFindings.Title = "Top Findings" $DocSecTopFindings.Text = "Compact risk findings for stale guest identities and application credential hygiene." $DocSecTopFindings.Objects = @( [PSCustomObject]@{ Finding = "Stale guest accounts (>= 90 days)" Severity = if($staleGuestUsers -gt 0){ "Medium" } else { "Low" } Count = $staleGuestUsers Details = "Guest accounts with last sign-in older than 90 days or never signed in since creation." }, [PSCustomObject]@{ Finding = "Never-signed-in stale guest accounts (>= 90 days)" Severity = if($neverSignedInStaleGuests -gt 0){ "Medium" } else { "Low" } Count = $neverSignedInStaleGuests Details = "Subset of stale guests with no recorded sign-in activity." }, [PSCustomObject]@{ Finding = "Credentials expiring in <= 30 days" Severity = if($credentialsExpiringSoon -gt 0){ "High" } else { "Low" } Count = $credentialsExpiringSoon Details = "Top apps: $expiringAppsText" }, [PSCustomObject]@{ Finding = "Expired application credentials" Severity = if($expiredCredentials -gt 0){ "High" } else { "Low" } Count = $expiredCredentials Details = "Application secrets/certificates already past end date." } ) $DocSecTopFindings.Transpose = $false $DocSec.SubSections += @( $DocSecIdentity, $DocSecConditionalAccess, $DocSecAuthMethods, $DocSecRoleAssignments, $DocSecTopFindings ) return $DocSec } # SIG # Begin signature block # MIIo0AYJKoZIhvcNAQcCoIIowTCCKL0CAQExDzANBglghkgBZQMEAgEFADB5Bgor # BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG # KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCAAFCt8giCawFWe # 0Yb1ZaY5DQDIh2XBqs5Md4YXUTmywqCCIc0wggWNMIIEdaADAgECAhAOmxiO+dAt # 5+/bUOIIQBhaMA0GCSqGSIb3DQEBDAUAMGUxCzAJBgNVBAYTAlVTMRUwEwYDVQQK # EwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xJDAiBgNV # BAMTG0RpZ2lDZXJ0IEFzc3VyZWQgSUQgUm9vdCBDQTAeFw0yMjA4MDEwMDAwMDBa # Fw0zMTExMDkyMzU5NTlaMGIxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2Vy # dCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xITAfBgNVBAMTGERpZ2lD # ZXJ0IFRydXN0ZWQgUm9vdCBHNDCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoC # ggIBAL/mkHNo3rvkXUo8MCIwaTPswqclLskhPfKK2FnC4SmnPVirdprNrnsbhA3E # MB/zG6Q4FutWxpdtHauyefLKEdLkX9YFPFIPUh/GnhWlfr6fqVcWWVVyr2iTcMKy # unWZanMylNEQRBAu34LzB4TmdDttceItDBvuINXJIB1jKS3O7F5OyJP4IWGbNOsF # xl7sWxq868nPzaw0QF+xembud8hIqGZXV59UWI4MK7dPpzDZVu7Ke13jrclPXuU1 # 5zHL2pNe3I6PgNq2kZhAkHnDeMe2scS1ahg4AxCN2NQ3pC4FfYj1gj4QkXCrVYJB # MtfbBHMqbpEBfCFM1LyuGwN1XXhm2ToxRJozQL8I11pJpMLmqaBn3aQnvKFPObUR # WBf3JFxGj2T3wWmIdph2PVldQnaHiZdpekjw4KISG2aadMreSx7nDmOu5tTvkpI6 # nj3cAORFJYm2mkQZK37AlLTSYW3rM9nF30sEAMx9HJXDj/chsrIRt7t/8tWMcCxB # YKqxYxhElRp2Yn72gLD76GSmM9GJB+G9t+ZDpBi4pncB4Q+UDCEdslQpJYls5Q5S # UUd0viastkF13nqsX40/ybzTQRESW+UQUOsxxcpyFiIJ33xMdT9j7CFfxCBRa2+x # q4aLT8LWRV+dIPyhHsXAj6KxfgommfXkaS+YHS312amyHeUbAgMBAAGjggE6MIIB # NjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBTs1+OC0nFdZEzfLmc/57qYrhwP # TzAfBgNVHSMEGDAWgBRF66Kv9JLLgjEtUYunpyGd823IDzAOBgNVHQ8BAf8EBAMC # AYYweQYIKwYBBQUHAQEEbTBrMCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5kaWdp # Y2VydC5jb20wQwYIKwYBBQUHMAKGN2h0dHA6Ly9jYWNlcnRzLmRpZ2ljZXJ0LmNv # bS9EaWdpQ2VydEFzc3VyZWRJRFJvb3RDQS5jcnQwRQYDVR0fBD4wPDA6oDigNoY0 # aHR0cDovL2NybDMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0QXNzdXJlZElEUm9vdENB # LmNybDARBgNVHSAECjAIMAYGBFUdIAAwDQYJKoZIhvcNAQEMBQADggEBAHCgv0Nc # Vec4X6CjdBs9thbX979XB72arKGHLOyFXqkauyL4hxppVCLtpIh3bb0aFPQTSnov # Lbc47/T/gLn4offyct4kvFIDyE7QKt76LVbP+fT3rDB6mouyXtTP0UNEm0Mh65Zy # oUi0mcudT6cGAxN3J0TU53/oWajwvy8LpunyNDzs9wPHh6jSTEAZNUZqaVSwuKFW # juyk1T3osdz9HNj0d1pcVIxv76FQPfx2CWiEn2/K2yCNNWAcAgPLILCsWKAOQGPF # mCLBsln1VWvPJ6tsds5vIy30fnFqI2si/xK4VC0nftg62fC2h5b9W9FcrBjDTZ9z # twGpn1eqXijiuZQwggawMIIEmKADAgECAhAIrUCyYNKcTJ9ezam9k67ZMA0GCSqG # SIb3DQEBDAUAMGIxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMx # GTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xITAfBgNVBAMTGERpZ2lDZXJ0IFRy # dXN0ZWQgUm9vdCBHNDAeFw0yMTA0MjkwMDAwMDBaFw0zNjA0MjgyMzU5NTlaMGkx # CzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5EaWdpQ2VydCwgSW5jLjFBMD8GA1UEAxM4 # RGlnaUNlcnQgVHJ1c3RlZCBHNCBDb2RlIFNpZ25pbmcgUlNBNDA5NiBTSEEzODQg # MjAyMSBDQTEwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDVtC9C0Cit # eLdd1TlZG7GIQvUzjOs9gZdwxbvEhSYwn6SOaNhc9es0JAfhS0/TeEP0F9ce2vnS # 1WcaUk8OoVf8iJnBkcyBAz5NcCRks43iCH00fUyAVxJrQ5qZ8sU7H/Lvy0daE6ZM # swEgJfMQ04uy+wjwiuCdCcBlp/qYgEk1hz1RGeiQIXhFLqGfLOEYwhrMxe6TSXBC # Mo/7xuoc82VokaJNTIIRSFJo3hC9FFdd6BgTZcV/sk+FLEikVoQ11vkunKoAFdE3 # /hoGlMJ8yOobMubKwvSnowMOdKWvObarYBLj6Na59zHh3K3kGKDYwSNHR7OhD26j # q22YBoMbt2pnLdK9RBqSEIGPsDsJ18ebMlrC/2pgVItJwZPt4bRc4G/rJvmM1bL5 # OBDm6s6R9b7T+2+TYTRcvJNFKIM2KmYoX7BzzosmJQayg9Rc9hUZTO1i4F4z8ujo # 7AqnsAMrkbI2eb73rQgedaZlzLvjSFDzd5Ea/ttQokbIYViY9XwCFjyDKK05huzU # tw1T0PhH5nUwjewwk3YUpltLXXRhTT8SkXbev1jLchApQfDVxW0mdmgRQRNYmtwm # KwH0iU1Z23jPgUo+QEdfyYFQc4UQIyFZYIpkVMHMIRroOBl8ZhzNeDhFMJlP/2NP # TLuqDQhTQXxYPUez+rbsjDIJAsxsPAxWEQIDAQABo4IBWTCCAVUwEgYDVR0TAQH/ # BAgwBgEB/wIBADAdBgNVHQ4EFgQUaDfg67Y7+F8Rhvv+YXsIiGX0TkIwHwYDVR0j # BBgwFoAU7NfjgtJxXWRM3y5nP+e6mK4cD08wDgYDVR0PAQH/BAQDAgGGMBMGA1Ud # JQQMMAoGCCsGAQUFBwMDMHcGCCsGAQUFBwEBBGswaTAkBggrBgEFBQcwAYYYaHR0 # cDovL29jc3AuZGlnaWNlcnQuY29tMEEGCCsGAQUFBzAChjVodHRwOi8vY2FjZXJ0 # cy5kaWdpY2VydC5jb20vRGlnaUNlcnRUcnVzdGVkUm9vdEc0LmNydDBDBgNVHR8E # PDA6MDigNqA0hjJodHRwOi8vY3JsMy5kaWdpY2VydC5jb20vRGlnaUNlcnRUcnVz # dGVkUm9vdEc0LmNybDAcBgNVHSAEFTATMAcGBWeBDAEDMAgGBmeBDAEEATANBgkq # hkiG9w0BAQwFAAOCAgEAOiNEPY0Idu6PvDqZ01bgAhql+Eg08yy25nRm95RysQDK # r2wwJxMSnpBEn0v9nqN8JtU3vDpdSG2V1T9J9Ce7FoFFUP2cvbaF4HZ+N3HLIvda # qpDP9ZNq4+sg0dVQeYiaiorBtr2hSBh+3NiAGhEZGM1hmYFW9snjdufE5BtfQ/g+ # lP92OT2e1JnPSt0o618moZVYSNUa/tcnP/2Q0XaG3RywYFzzDaju4ImhvTnhOE7a # brs2nfvlIVNaw8rpavGiPttDuDPITzgUkpn13c5UbdldAhQfQDN8A+KVssIhdXNS # y0bYxDQcoqVLjc1vdjcshT8azibpGL6QB7BDf5WIIIJw8MzK7/0pNVwfiThV9zeK # iwmhywvpMRr/LhlcOXHhvpynCgbWJme3kuZOX956rEnPLqR0kq3bPKSchh/jwVYb # KyP/j7XqiHtwa+aguv06P0WmxOgWkVKLQcBIhEuWTatEQOON8BUozu3xGFYHKi8Q # xAwIZDwzj64ojDzLj4gLDb879M4ee47vtevLt/B3E+bnKD+sEq6lLyJsQfmCXBVm # zGwOysWGw/YmMwwHS6DTBwJqakAwSEs0qFEgu60bhQjiWQ1tygVQK+pKHJ6l/aCn # HwZ05/LWUpD9r4VIIflXO7ScA+2GRfS0YW6/aOImYIbqyK+p/pQd52MbOoZWeE4w # gga0MIIEnKADAgECAhANx6xXBf8hmS5AQyIMOkmGMA0GCSqGSIb3DQEBCwUAMGIx # CzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3 # dy5kaWdpY2VydC5jb20xITAfBgNVBAMTGERpZ2lDZXJ0IFRydXN0ZWQgUm9vdCBH # NDAeFw0yNTA1MDcwMDAwMDBaFw0zODAxMTQyMzU5NTlaMGkxCzAJBgNVBAYTAlVT # MRcwFQYDVQQKEw5EaWdpQ2VydCwgSW5jLjFBMD8GA1UEAxM4RGlnaUNlcnQgVHJ1 # c3RlZCBHNCBUaW1lU3RhbXBpbmcgUlNBNDA5NiBTSEEyNTYgMjAyNSBDQTEwggIi # MA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC0eDHTCphBcr48RsAcrHXbo0Zo # dLRRF51NrY0NlLWZloMsVO1DahGPNRcybEKq+RuwOnPhof6pvF4uGjwjqNjfEvUi # 6wuim5bap+0lgloM2zX4kftn5B1IpYzTqpyFQ/4Bt0mAxAHeHYNnQxqXmRinvuNg # xVBdJkf77S2uPoCj7GH8BLuxBG5AvftBdsOECS1UkxBvMgEdgkFiDNYiOTx4OtiF # cMSkqTtF2hfQz3zQSku2Ws3IfDReb6e3mmdglTcaarps0wjUjsZvkgFkriK9tUKJ # m/s80FiocSk1VYLZlDwFt+cVFBURJg6zMUjZa/zbCclF83bRVFLeGkuAhHiGPMvS # GmhgaTzVyhYn4p0+8y9oHRaQT/aofEnS5xLrfxnGpTXiUOeSLsJygoLPp66bkDX1 # ZlAeSpQl92QOMeRxykvq6gbylsXQskBBBnGy3tW/AMOMCZIVNSaz7BX8VtYGqLt9 # MmeOreGPRdtBx3yGOP+rx3rKWDEJlIqLXvJWnY0v5ydPpOjL6s36czwzsucuoKs7 # Yk/ehb//Wx+5kMqIMRvUBDx6z1ev+7psNOdgJMoiwOrUG2ZdSoQbU2rMkpLiQ6bG # RinZbI4OLu9BMIFm1UUl9VnePs6BaaeEWvjJSjNm2qA+sdFUeEY0qVjPKOWug/G6 # X5uAiynM7Bu2ayBjUwIDAQABo4IBXTCCAVkwEgYDVR0TAQH/BAgwBgEB/wIBADAd # BgNVHQ4EFgQU729TSunkBnx6yuKQVvYv1Ensy04wHwYDVR0jBBgwFoAU7NfjgtJx # XWRM3y5nP+e6mK4cD08wDgYDVR0PAQH/BAQDAgGGMBMGA1UdJQQMMAoGCCsGAQUF # BwMIMHcGCCsGAQUFBwEBBGswaTAkBggrBgEFBQcwAYYYaHR0cDovL29jc3AuZGln # aWNlcnQuY29tMEEGCCsGAQUFBzAChjVodHRwOi8vY2FjZXJ0cy5kaWdpY2VydC5j # b20vRGlnaUNlcnRUcnVzdGVkUm9vdEc0LmNydDBDBgNVHR8EPDA6MDigNqA0hjJo # dHRwOi8vY3JsMy5kaWdpY2VydC5jb20vRGlnaUNlcnRUcnVzdGVkUm9vdEc0LmNy # bDAgBgNVHSAEGTAXMAgGBmeBDAEEAjALBglghkgBhv1sBwEwDQYJKoZIhvcNAQEL # BQADggIBABfO+xaAHP4HPRF2cTC9vgvItTSmf83Qh8WIGjB/T8ObXAZz8OjuhUxj # aaFdleMM0lBryPTQM2qEJPe36zwbSI/mS83afsl3YTj+IQhQE7jU/kXjjytJgnn0 # hvrV6hqWGd3rLAUt6vJy9lMDPjTLxLgXf9r5nWMQwr8Myb9rEVKChHyfpzee5kH0 # F8HABBgr0UdqirZ7bowe9Vj2AIMD8liyrukZ2iA/wdG2th9y1IsA0QF8dTXqvcnT # mpfeQh35k5zOCPmSNq1UH410ANVko43+Cdmu4y81hjajV/gxdEkMx1NKU4uHQcKf # ZxAvBAKqMVuqte69M9J6A47OvgRaPs+2ykgcGV00TYr2Lr3ty9qIijanrUR3anzE # wlvzZiiyfTPjLbnFRsjsYg39OlV8cipDoq7+qNNjqFzeGxcytL5TTLL4ZaoBdqbh # OhZ3ZRDUphPvSRmMThi0vw9vODRzW6AxnJll38F0cuJG7uEBYTptMSbhdhGQDpOX # gpIUsWTjd6xpR6oaQf/DJbg3s6KCLPAlZ66RzIg9sC+NJpud/v4+7RWsWCiKi9EO # LLHfMR2ZyJ/+xhCx9yHbxtl5TPau1j/1MIDpMPx0LckTetiSuEtQvLsNz3Qbp7wG # WqbIiOWCnb5WqxL3/BAPvIXKUjPSxyZsq8WhbaM2tszWkPZPubdcMIIG7TCCBNWg # AwIBAgIQCoDvGEuN8QWC0cR2p5V0aDANBgkqhkiG9w0BAQsFADBpMQswCQYDVQQG # EwJVUzEXMBUGA1UEChMORGlnaUNlcnQsIEluYy4xQTA/BgNVBAMTOERpZ2lDZXJ0 # IFRydXN0ZWQgRzQgVGltZVN0YW1waW5nIFJTQTQwOTYgU0hBMjU2IDIwMjUgQ0Ex # MB4XDTI1MDYwNDAwMDAwMFoXDTM2MDkwMzIzNTk1OVowYzELMAkGA1UEBhMCVVMx # FzAVBgNVBAoTDkRpZ2lDZXJ0LCBJbmMuMTswOQYDVQQDEzJEaWdpQ2VydCBTSEEy # NTYgUlNBNDA5NiBUaW1lc3RhbXAgUmVzcG9uZGVyIDIwMjUgMTCCAiIwDQYJKoZI # hvcNAQEBBQADggIPADCCAgoCggIBANBGrC0Sxp7Q6q5gVrMrV7pvUf+GcAoB38o3 # zBlCMGMyqJnfFNZx+wvA69HFTBdwbHwBSOeLpvPnZ8ZN+vo8dE2/pPvOx/Vj8Tch # TySA2R4QKpVD7dvNZh6wW2R6kSu9RJt/4QhguSssp3qome7MrxVyfQO9sMx6ZAWj # FDYOzDi8SOhPUWlLnh00Cll8pjrUcCV3K3E0zz09ldQ//nBZZREr4h/GI6Dxb2Uo # yrN0ijtUDVHRXdmncOOMA3CoB/iUSROUINDT98oksouTMYFOnHoRh6+86Ltc5zjP # KHW5KqCvpSduSwhwUmotuQhcg9tw2YD3w6ySSSu+3qU8DD+nigNJFmt6LAHvH3KS # uNLoZLc1Hf2JNMVL4Q1OpbybpMe46YceNA0LfNsnqcnpJeItK/DhKbPxTTuGoX7w # JNdoRORVbPR1VVnDuSeHVZlc4seAO+6d2sC26/PQPdP51ho1zBp+xUIZkpSFA8vW # doUoHLWnqWU3dCCyFG1roSrgHjSHlq8xymLnjCbSLZ49kPmk8iyyizNDIXj//cOg # rY7rlRyTlaCCfw7aSUROwnu7zER6EaJ+AliL7ojTdS5PWPsWeupWs7NpChUk555K # 096V1hE0yZIXe+giAwW00aHzrDchIc2bQhpp0IoKRR7YufAkprxMiXAJQ1XCmnCf # gPf8+3mnAgMBAAGjggGVMIIBkTAMBgNVHRMBAf8EAjAAMB0GA1UdDgQWBBTkO/zy # Me39/dfzkXFjGVBDz2GM6DAfBgNVHSMEGDAWgBTvb1NK6eQGfHrK4pBW9i/USezL # TjAOBgNVHQ8BAf8EBAMCB4AwFgYDVR0lAQH/BAwwCgYIKwYBBQUHAwgwgZUGCCsG # AQUFBwEBBIGIMIGFMCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5kaWdpY2VydC5j # b20wXQYIKwYBBQUHMAKGUWh0dHA6Ly9jYWNlcnRzLmRpZ2ljZXJ0LmNvbS9EaWdp # Q2VydFRydXN0ZWRHNFRpbWVTdGFtcGluZ1JTQTQwOTZTSEEyNTYyMDI1Q0ExLmNy # dDBfBgNVHR8EWDBWMFSgUqBQhk5odHRwOi8vY3JsMy5kaWdpY2VydC5jb20vRGln # aUNlcnRUcnVzdGVkRzRUaW1lU3RhbXBpbmdSU0E0MDk2U0hBMjU2MjAyNUNBMS5j # cmwwIAYDVR0gBBkwFzAIBgZngQwBBAIwCwYJYIZIAYb9bAcBMA0GCSqGSIb3DQEB # CwUAA4ICAQBlKq3xHCcEua5gQezRCESeY0ByIfjk9iJP2zWLpQq1b4URGnwWBdEZ # D9gBq9fNaNmFj6Eh8/YmRDfxT7C0k8FUFqNh+tshgb4O6Lgjg8K8elC4+oWCqnU/ # ML9lFfim8/9yJmZSe2F8AQ/UdKFOtj7YMTmqPO9mzskgiC3QYIUP2S3HQvHG1FDu # +WUqW4daIqToXFE/JQ/EABgfZXLWU0ziTN6R3ygQBHMUBaB5bdrPbF6MRYs03h4o # bEMnxYOX8VBRKe1uNnzQVTeLni2nHkX/QqvXnNb+YkDFkxUGtMTaiLR9wjxUxu2h # ECZpqyU1d0IbX6Wq8/gVutDojBIFeRlqAcuEVT0cKsb+zJNEsuEB7O7/cuvTQasn # M9AWcIQfVjnzrvwiCZ85EE8LUkqRhoS3Y50OHgaY7T/lwd6UArb+BOVAkg2oOvol # /DJgddJ35XTxfUlQ+8Hggt8l2Yv7roancJIFcbojBcxlRcGG0LIhp6GvReQGgMgY # xQbV1S3CrWqZzBt1R9xJgKf47CdxVRd/ndUlQ05oxYy2zRWVFjF7mcr4C34Mj3oc # CVccAvlKV9jEnstrniLvUxxVZE/rptb7IRE2lskKPIJgbaP5t2nGj/ULLi49xTcB # ZU8atufk+EMF/cWuiC7POGT75qaL6vdCvHlshtjdNXOCIUjsarfNZzCCB9swggXD # oAMCAQICEArIyQilds29NWOEexdFLZQwDQYJKoZIhvcNAQELBQAwaTELMAkGA1UE # BhMCVVMxFzAVBgNVBAoTDkRpZ2lDZXJ0LCBJbmMuMUEwPwYDVQQDEzhEaWdpQ2Vy # dCBUcnVzdGVkIEc0IENvZGUgU2lnbmluZyBSU0E0MDk2IFNIQTM4NCAyMDIxIENB # MTAeFw0yNjAxMDUwMDAwMDBaFw0yOTAzMzAyMzU5NTlaMIHjMRMwEQYLKwYBBAGC # NzwCAQMTAkNIMRowGAYLKwYBBAGCNzwCAQITCVNvbG90aHVybjEdMBsGA1UEDwwU # UHJpdmF0ZSBPcmdhbml6YXRpb24xGDAWBgNVBAUTD0NIRS0zMTQuNjM5LjUyMzEL # MAkGA1UEBhMCQ0gxEjAQBgNVBAgTCVNvbG90aHVybjEOMAwGA1UEBxMFT2x0ZW4x # FjAUBgNVBAoTDWJhc2VWSVNJT04gQUcxFjAUBgNVBAsTDWJhc2VWSVNJT04gQUcx # FjAUBgNVBAMTDWJhc2VWSVNJT04gQUcwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAw # ggIKAoICAQC3tXxu0JPpfl3vK/0opOZ3h4I0p1EblH7sg2CNOuuyAYUClu20QCPq # zKcyZyNr9/oHBnvfYAhP4yyMWQWzSyqLOjqma9GPTMhA92DF8VR45y0UGAPaq9MA # j9Pk28CRk9j3AGTkoTgyqyT9eWP9nx3hfpymB/RYCvX2EGHboOyJ4baaXsJhmpzN # slYBOv3UonHUMVbVlViLW3yJ9x21vi1xPjYPrB8QQggBg1e5CJzjcOYBdHQs91rO # wMz5IBpj4FCTIEFGYZyeNyayiylItBCRaxscCR8UKTX/YFHRmiLJEkEqH9O1HZM+ # 6rXZfvKAyJtbekDl6g1nWUz8sa5ctoNVsksoQUkO+aR/hhJ85OxSo2bDul4mZPtf # 4z7rU/TLdhi77xKqZTFwzBqsJPT66AOQILVpvPwQCxxz7pHKh/fxwph3/ky4cL8y # 4sg8myDk0iWUHbG5b3/oe2S0CEK0pBTrIpEd2bvGuS2due2F1DLW/Ryb5dBXc1FD # /1jRql3Dp9h7grt8hJ4zEiVwKorBtcM0olm5cCvhttycIBJ0P66onU1HB6NixKDW # pTl76GfAgjt3vHd+rnTLYoya3xsW0NDRijnJsn3YPrzPmT96L4RPl9JKnZ3dpow7 # NFl2GANVVkcSQ5RYt46172P90SjqcZ+6BJuL+acPYLQAUJ7+Q4alrwIDAQABo4IC # AjCCAf4wHwYDVR0jBBgwFoAUaDfg67Y7+F8Rhvv+YXsIiGX0TkIwHQYDVR0OBBYE # FFYXYLMvlLovduDyV/ny/M+PqhZiMD0GA1UdIAQ2MDQwMgYFZ4EMAQMwKTAnBggr # BgEFBQcCARYbaHR0cDovL3d3dy5kaWdpY2VydC5jb20vQ1BTMA4GA1UdDwEB/wQE # AwIHgDATBgNVHSUEDDAKBggrBgEFBQcDAzCBtQYDVR0fBIGtMIGqMFOgUaBPhk1o # dHRwOi8vY3JsMy5kaWdpY2VydC5jb20vRGlnaUNlcnRUcnVzdGVkRzRDb2RlU2ln # bmluZ1JTQTQwOTZTSEEzODQyMDIxQ0ExLmNybDBToFGgT4ZNaHR0cDovL2NybDQu # ZGlnaWNlcnQuY29tL0RpZ2lDZXJ0VHJ1c3RlZEc0Q29kZVNpZ25pbmdSU0E0MDk2 # U0hBMzg0MjAyMUNBMS5jcmwwgZQGCCsGAQUFBwEBBIGHMIGEMCQGCCsGAQUFBzAB # hhhodHRwOi8vb2NzcC5kaWdpY2VydC5jb20wXAYIKwYBBQUHMAKGUGh0dHA6Ly9j # YWNlcnRzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydFRydXN0ZWRHNENvZGVTaWduaW5n # UlNBNDA5NlNIQTM4NDIwMjFDQTEuY3J0MAkGA1UdEwQCMAAwDQYJKoZIhvcNAQEL # BQADggIBADE8VXBOhHT+nPBJgfR8W5B/hUq01DFiXWnscRoUh6UkMjpZNsksnV6R # FZ92e9j5PtMTXckN3N5Da6AauhYai3a4bhSpVLRqP0jaWDvrmloIDwU9j5ZeeSgw # uBVyJ/ZTdmPLXFlPFBuGu9x3G+Gm/IkqRz7fcDbrZJPjp0Wdsq+KT2YXqQVM2jlF # 6i537ub2UPJMJjbCS6vtuNJUTaU+gC7yzDOaievgCQHE5qqz354peNGMp5MgKrS9 # bsDv5LmO7tZ3cFspoBZ/dmZVggStQJqfPP7W6axtoTndwNrGtiwUVk0QI/h5qlwf # QFeO1L/RlKeSMVILA3kqHS84BZ22tYil82YK5YeqmWv+64WZpijbxC/g+P7xc3NU # +E9XAmX1QFPESIKGFgDc5+b6hDZ6+9pbdbfCk2WVsnhfJbubPlCGEzfkpVS+Ml1Y # QHyrC5d9qYHIMJDRIE/MaUL+5Tzakx6I6hH5HPTZ4mJ4XjLiMIREgyMniNo3DUdm # cJZqHu5quKGIXiD8emUbkk/jLjgnRMGwiL9pg7JJXMSN3nx4vEx6rEQIIQ1xD1ta # 3fG8D/Fk7C2tW90lU9LhE1xjn6K59/F/KCMAdedem4+BJiU/q7MxIjvYjFZW7zzK # egbfC4H04a93xmTv9g6VnsFW4NzBfF2PGDeNXO7S9FEtIUmGOFn7MYIGWTCCBlUC # AQEwfTBpMQswCQYDVQQGEwJVUzEXMBUGA1UEChMORGlnaUNlcnQsIEluYy4xQTA/ # BgNVBAMTOERpZ2lDZXJ0IFRydXN0ZWQgRzQgQ29kZSBTaWduaW5nIFJTQTQwOTYg # U0hBMzg0IDIwMjEgQ0ExAhAKyMkIpXbNvTVjhHsXRS2UMA0GCWCGSAFlAwQCAQUA # oIGEMBgGCisGAQQBgjcCAQwxCjAIoAKAAKECgAAwGQYJKoZIhvcNAQkDMQwGCisG # AQQBgjcCAQQwHAYKKwYBBAGCNwIBCzEOMAwGCisGAQQBgjcCARUwLwYJKoZIhvcN # AQkEMSIEIFXZzMPOjghbkxMQDyIMNx1l3dBtyhiaVz1bR+HObJ0tMA0GCSqGSIb3 # DQEBAQUABIICAJcSLyCPqC1ZECQF4F0RiQcNwl758KsOPxjnZyIVPJ9NyTJPrkFU # DPdIIp2pxvqG7fUR4plhiYBSitgrVDqBEzqBxsOO1QV6MGfaOiFkzwJGYv4xVvNU # aGwPpU0r3xU8GLPk1EIRQUztQKaqNdy/2fugl7sQdL8c/bh8VJXg8X6/ZWSAh1z6 # YAQs/ae3lEYOB5n3XaANeLrwylDAQSDwuz7tzZrJcgcJ5NI8YBnco+uHBeKCxHUl # 2d+Lfmg1H5C4QHDoTAlOkh6GlUfkigsFVpBlVJFze7yCWPH5Wqh81HFDsM+BfZ5S # YDH2Xg4p4fp+7s9g0B52/qCVUVWpXtp9mwU8Fpea+ExkkoPyhXVnldebuigw0qvm # AMu0RzeVE2FeBjUvZHFE7fTc7onxcj3oASOzUV8mM40zLiG/RePxm0SMvdv2lm9f # 8cvR5JQ0A3SFHi4x/BQAKCve/pcVE8WJ4KRhPHUoBUgCNUl1dRxiCETz2RI4c1d5 # bJ7ytnPHbiozQ6qg64cPd3DiLBwsajCf+N5mzEozXM0R13WvVyXD/8S0UUNsaKsy # Mn5DlPNzqQoBhdu9HQBz8WIy0N36ik8jeg5jpTJKA2B5oL3LCXVM0A48t5dkRBam # 1pbK3RDwrsPu8uIunlu3wD4KI+fbnYNlrl0gKVkSga4Jk0lsNZb6epO9oYIDJjCC # AyIGCSqGSIb3DQEJBjGCAxMwggMPAgEBMH0waTELMAkGA1UEBhMCVVMxFzAVBgNV # BAoTDkRpZ2lDZXJ0LCBJbmMuMUEwPwYDVQQDEzhEaWdpQ2VydCBUcnVzdGVkIEc0 # IFRpbWVTdGFtcGluZyBSU0E0MDk2IFNIQTI1NiAyMDI1IENBMQIQCoDvGEuN8QWC # 0cR2p5V0aDANBglghkgBZQMEAgEFAKBpMBgGCSqGSIb3DQEJAzELBgkqhkiG9w0B # BwEwHAYJKoZIhvcNAQkFMQ8XDTI2MDMxNTEzMjM0MFowLwYJKoZIhvcNAQkEMSIE # ICRS1Ye/7ZrIp5hfp1a6YkCOd6zGw4XZAxA02pGdFTq2MA0GCSqGSIb3DQEBAQUA # BIICAHWac6lCuSbjBqTu13NoVNjQBSRMhPObvDNrUdnVWaJ342CPegFejebg0UnA # DJbS62TSAVbp8TKM4jhNOcfSHXjvP3rkbLccbWVv2lvZlHB0TyIZbQzY3Z9RbWWI # AR/PE9bKI4Bee3WCyb5EBxEOkIqnTgiIGNA3hJT5DjQAuY4P/E2RHAePjq1ig0U/ # OjYoAcAtN9xDrb6z5zMsL7veoqvrmOGCu+JIGpi7d6Y5GrBVZ1cslcA2iRcX7CIW # 8A7dZae7vI0xB7V6Si+8LzNeGERhLgUgPqUCQt9ts9tRy47NKZPogbK0bz4Ie2P7 # daGBqK6+WZRh5TJi2MyvacnBmP6w+mEQQtR8yenyIQTphJ+/K2ASS2X29ntRunLn # PtnGTGUdC+jgyH6+zcLnRatujO93LY6PTaGUbeJY03g9DvVq5WHg+f+i89C+VWT2 # IOlacX+46C8Vo/fNr8QcgneJccgOeGRwCBNyX+1fGxZpdh1VyGlYaL1xu3vEJVol # BJ0Q02eKhpKom/1iS76sUJs6yyU1CSDxkEA2jd0qNf5VUUXnOXwJwJNYGlwD0DQc # sK7botLqLCFG9CnNvxgULwZZLAq2oFiZlFwLPFUEuIKM9+vDv75Td7mesLwCgaVG # uVgUAMBygk2v5br1ZTeiKRfujzY/eESo11t+WW1+yPjS5A6b # SIG # End signature block |