Modules/Providers/ExportAADProvider.psm1
# Many of the commandlets can be replaced with direct API access, but we are starting the transition with the ones # below because they have slow imports that affect performance more than the others. Some commandlets are fast # and there is no obvoius performance advantage to using the API beyond maybe batching. $GraphEndpoints = @{ "Get-MgBetaRoleManagementDirectoryRoleEligibilityScheduleInstance" = "/beta/roleManagement/directory/roleEligibilityScheduleInstances" "Get-MgBetaRoleManagementDirectoryRoleAssignmentScheduleInstance" = "/beta/roleManagement/directory/roleAssignmentScheduleInstances" "Get-MgBetaIdentityGovernancePrivilegedAccessGroupEligibilityScheduleInstance" = "/beta/identityGovernance/privilegedAccess/group/eligibilityScheduleInstances" "Get-MgBetaPrivilegedAccessResource" = "/beta/privilegedAccess/aadGroups/resources" } function Invoke-GraphDirectly { param ( [ValidateNotNullOrEmpty()] [string] $commandlet, [ValidateNotNullOrEmpty()] [string] $M365Environment, [System.Collections.Hashtable] $queryParams ) Write-Debug "Replacing Cmdlet: $commandlet" try { $endpoint = $GraphEndpoints[$commandlet] } catch { Write-Error "The commandlet $commandlet can't be used with the Invoke-GraphDirectly function yet." } if ($M365Environment -eq "gcchigh") { $endpoint = "https://graph.microsoft.us" + $endpoint } elseif ($M365Environment -eq "dod") { $endpoint = "https://dod-graph.microsoft.us" + $endpoint } else { $endpoint = "https://graph.microsoft.com" + $endpoint } if ($queryParams) { <# If query params are passed in, we need to augment the endpoint URI to include the params. Paperwork below. We can't use string formatting easily because the graph API expects parameters that are prefixed with the dollar symbol. Maybe someone smarter than me can figure that out. #> $q = [System.Web.HttpUtility]::ParseQueryString([string]::Empty) foreach ($item in $queryParams.GetEnumerator()) { $q.Add($item.Key, $item.Value) } $uri = [System.UriBuilder]::new("", "", 443, $endpoint) $uri.Query = $q.ToString() $endpoint = $uri.ToString() } Write-Debug "Graph Api direct: $endpoint" $resp = Invoke-MgGraphRequest -Uri $endpoint return $resp.Value } function Export-AADProvider { <# .Description Gets the Azure Active Directory (AAD) settings that are relevant to the SCuBA AAD baselines using a subset of the modules under the overall Microsoft Graph PowerShell Module .Functionality Internal #> [CmdletBinding()] param ( [ValidateNotNullOrEmpty()] [string] $M365Environment ) Import-Module $PSScriptRoot/ProviderHelpers/CommandTracker.psm1 $Tracker = Get-CommandTracker # The below cmdlet covers the following baselines # - 1.1 # - 2.1 # - 3.1 # - 4.2 # - 3.7 $AllPolicies = $Tracker.TryCommand("Get-MgBetaIdentityConditionalAccessPolicy") Import-Module $PSScriptRoot/ProviderHelpers/AADConditionalAccessHelper.psm1 $CapHelper = Get-CapTracker $CapTableData = $CapHelper.ExportCapPolicies($AllPolicies) # pre-processed version of the CAPs used in generating # the CAP html in the report if ($CapTableData -eq "") { # Quick sanity check, did ExportCapPolicies return something? Write-Warning "Error parsing CAP data, empty json returned from ExportCapPolicies." $CapTableData = "[]" } try { # Final sanity check, did ExportCapPolicies return valid json? ConvertFrom-Json $CapTableData -ErrorAction "Stop" | Out-Null } catch { Write-Warning "Error parsing CAP data, invalid json returned from ExportCapPolicies." $CapTableData = "[]" } $AllPolicies = ConvertTo-Json -Depth 10 @($AllPolicies) $SubscribedSku = $Tracker.TryCommand("Get-MgBetaSubscribedSku") # Get a list of the tenant's provisioned service plans - used to see if the tenant has AAD premium p2 license required for some checks # The Rego looks at the service_plans in the JSON $ServicePlans = $SubscribedSku.ServicePlans | Where-Object -Property ProvisioningStatus -eq -Value "Success" #Obtains license information for tenant and total number of active users $LicenseInfo = $SubscribedSku | Select-Object -Property Sku*, ConsumedUnits, PrepaidUnits | ConvertTo-Json -Depth 3 if ($ServicePlans) { # The RequiredServicePlan variable is used so that PIM Cmdlets are only executed if the tenant has the premium license $RequiredServicePlan = $ServicePlans | Where-Object -Property ServicePlanName -eq -Value "AAD_PREMIUM_P2" # Get-PrivilegedUser provides a list of privileged users and their role assignments. Used for 2.11 and 2.12 if ($RequiredServicePlan) { # If the tenant has the premium license then we want to also include PIM Eligible role assignments - otherwise we don't to avoid an API error $PrivilegedUsers = $Tracker.TryCommand("Get-PrivilegedUser", @{"TenantHasPremiumLicense"=$true; "M365Environment"=$M365Environment}) } else{ $PrivilegedUsers = $Tracker.TryCommand("Get-PrivilegedUser", @{"TenantHasPremiumLicense"=$false; "M365Environment"=$M365Environment}) } $PrivilegedUsers = $PrivilegedUsers | ConvertTo-Json # The above Converto-Json call doesn't need to have the input wrapped in an # array (e.g, "ConvertTo-Json (@PrivilegedUsers)") because $PrivilegedUsers is # a dictionary, not an array, and ConvertTo-Json doesn't mess up dictionaries # like it does arrays (just observe the difference in output between # "@{} | ConvertTo-Json" and # "@() | ConvertTo-Json" ) $PrivilegedUsers = if ($null -eq $PrivilegedUsers) {"{}"} else {$PrivilegedUsers} # While ConvertTo-Json won't mess up a dict as described in the above comment, # on error, $TryCommand returns an empty list, not a dictionary. The if/else # above corrects the $null ConvertTo-Json would return in that case to an empty # dictionary # Get-PrivilegedRole provides a list of privileged roles referenced in 2.13 when checking if MFA is required for those roles # Get-PrivilegedRole provides data for 2.14 - 2.16, policies that evaluate conditions related to Azure AD PIM if ($RequiredServicePlan){ # If the tenant has the premium license then we want to also include PIM Eligible role assignments - otherwise we don't to avoid an API error $PrivilegedRoles = $Tracker.TryCommand("Get-PrivilegedRole", @{"TenantHasPremiumLicense"=$true; "M365Environment"=$M365Environment}) } else { $PrivilegedRoles = $Tracker.TryCommand("Get-PrivilegedRole", @{"TenantHasPremiumLicense"=$false; "M365Environment"=$M365Environment}) } $PrivilegedRoles = ConvertTo-Json -Depth 10 @($PrivilegedRoles) # Depth required to get policy rule object details } else { Write-Warning "Omitting calls to Get-PrivilegedRole and Get-PrivilegedUser." $PrivilegedUsers = ConvertTo-Json @() $PrivilegedRoles = ConvertTo-Json @() $Tracker.AddUnSuccessfulCommand("Get-PrivilegedRole") $Tracker.AddUnSuccessfulCommand("Get-PrivilegedUser") } $ServicePlans = ConvertTo-Json -Depth 3 @($ServicePlans) # Checking to ensure command runs successfully $UserCount = $Tracker.TryCommand("Get-MgBetaUserCount", @{"ConsistencyLevel"='eventual'}) if(-Not $UserCount -is [int]) { $UserCount = "NaN" } # 5.1, 5.2, 8.1 & 8.3 $AuthZPolicies = ConvertTo-Json @($Tracker.TryCommand("Get-MgBetaPolicyAuthorizationPolicy")) # 5.3, 5.4 $DirectorySettings = ConvertTo-Json -Depth 10 @($Tracker.TryCommand("Get-MgBetaDirectorySetting")) ##### This block of code below supports 3.3, 3.4, 3.5 $AuthenticationMethodPolicyRootObject = $Tracker.TryCommand("Get-MgBetaPolicyAuthenticationMethodPolicy") $AuthenticationMethodFeatureSettings = @($AuthenticationMethodPolicyRootObject.AuthenticationMethodConfigurations | Where-Object { $_.Id}) # Exclude the AuthenticationMethodConfigurations so we do not duplicate it in the JSON $AuthenticationMethodPolicy = $AuthenticationMethodPolicyRootObject | ForEach-Object { $_ | Select-Object * -ExcludeProperty AuthenticationMethodConfigurations } $AuthenticationMethodObjects = @{ authentication_method_policy = $AuthenticationMethodPolicy authentication_method_feature_settings = $AuthenticationMethodFeatureSettings } $AuthenticationMethod = ConvertTo-Json -Depth 10 @($AuthenticationMethodObjects) ##### End block # 6.1 $DomainSettings = ConvertTo-Json @($Tracker.TryCommand("Get-MgBetaDomain")) $SuccessfulCommands = ConvertTo-Json @($Tracker.GetSuccessfulCommands()) $UnSuccessfulCommands = ConvertTo-Json @($Tracker.GetUnSuccessfulCommands()) # Note the spacing and the last comma in the json is important $json = @" "conditional_access_policies": $AllPolicies, "cap_table_data": $CapTableData, "authorization_policies": $AuthZPolicies, "privileged_users": $PrivilegedUsers, "privileged_roles": $PrivilegedRoles, "service_plans": $ServicePlans, "directory_settings": $DirectorySettings, "authentication_method": $AuthenticationMethod, "domain_settings": $DomainSettings, "license_information": $LicenseInfo, "total_user_count": $UserCount, "aad_successful_commands": $SuccessfulCommands, "aad_unsuccessful_commands": $UnSuccessfulCommands, "@ $json } #"authentication_method_policy": $AuthenticationMethodPolicy, #"authentication_method_configuration": $AuthenticationMethodConfiguration, #"authentication_method_feature_settings": $AuthenticationMethodFeatureSettings, function Get-AADTenantDetail { <# .Description Gets the tenant details using the Microsoft Graph PowerShell Module .Functionality Internal #> try { $OrgInfo = Get-MgBetaOrganization -ErrorAction "Stop" $InitialDomain = $OrgInfo.VerifiedDomains | Where-Object {$_.isInitial} if (-not $InitialDomain) { $InitialDomain = "AAD: Domain Unretrievable" } $AADTenantInfo = @{ "DisplayName" = $OrgInfo.DisplayName; "DomainName" = $InitialDomain.Name; "TenantId" = $OrgInfo.Id; "AADAdditionalData" = $OrgInfo; } $AADTenantInfo = ConvertTo-Json @($AADTenantInfo) -Depth 4 $AADTenantInfo } catch { Write-Warning "Error retrieving Tenant details using Get-AADTenantDetail $($_)" $AADTenantInfo = @{ "DisplayName" = "Error retrieving Display name"; "DomainName" = "Error retrieving Domain name"; "TenantId" = "Error retrieving Tenant ID"; "AADAdditionalData" = "Error retrieving additional data"; } $AADTenantInfo = ConvertTo-Json @($AADTenantInfo) -Depth 4 $AADTenantInfo } } function Get-PrivilegedUser { <# .Description Gets the array of the highly privileged users .Functionality Internal #> param ( [ValidateNotNullOrEmpty()] [switch] $TenantHasPremiumLicense, [ValidateNotNullOrEmpty()] [string] $M365Environment ) # A hashtable of privileged users $PrivilegedUsers = @{} $PrivilegedRoles = [ScubaConfig]::ScubaDefault('DefaultPrivilegedRoles') # Get a list of the Id values for the privileged roles in the list above. # The Id value is passed to other cmdlets to construct a list of users assigned to privileged roles. $AADRoles = Get-MgBetaDirectoryRole -All -ErrorAction Stop | Where-Object { $_.DisplayName -in $PrivilegedRoles } # Construct a list of privileged users based on the Active role assignments foreach ($Role in $AADRoles) { # Get a list of all the users and groups Actively assigned to this role $UsersAssignedRole = Get-MgBetaDirectoryRoleMember -All -ErrorAction Stop -DirectoryRoleId $Role.Id foreach ($User in $UsersAssignedRole) { $Objecttype = $User.AdditionalProperties."@odata.type" -replace "#microsoft.graph." if ($Objecttype -eq "user") { # If the user's data has not been fetched from graph, go get it if (-Not $PrivilegedUsers.ContainsKey($User.Id)) { $AADUser = Get-MgBetaUser -ErrorAction Stop -UserId $User.Id $PrivilegedUsers[$AADUser.Id] = @{"DisplayName"=$AADUser.DisplayName; "OnPremisesImmutableId"=$AADUser.OnPremisesImmutableId; "roles"=@()} } # If the current role has not already been added to the user's roles array then add the role if ($PrivilegedUsers[$User.Id].roles -notcontains $Role.DisplayName) { $PrivilegedUsers[$User.Id].roles += $Role.DisplayName } } elseif ($Objecttype -eq "group") { # In this context $User.Id is a group identifier $GroupId = $User.Id # Get all of the group members that are Active assigned to the current role $GroupMembers = Get-MgBetaGroupMember -All -ErrorAction Stop -GroupId $GroupId foreach ($GroupMember in $GroupMembers) { $Membertype = $GroupMember.AdditionalProperties."@odata.type" -replace "#microsoft.graph." if ($Membertype -eq "user") { # If the user's data has not been fetched from graph, go get it if (-Not $PrivilegedUsers.ContainsKey($GroupMember.Id)) { $AADUser = Get-MgBetaUser -ErrorAction Stop -UserId $GroupMember.Id $PrivilegedUsers[$AADUser.Id] = @{"DisplayName"=$AADUser.DisplayName; "OnPremisesImmutableId"=$AADUser.OnPremisesImmutableId; "roles"=@()} } # If the current role has not already been added to the user's roles array then add the role if ($PrivilegedUsers[$GroupMember.Id].roles -notcontains $Role.DisplayName) { $PrivilegedUsers[$GroupMember.Id].roles += $Role.DisplayName } } } # If the premium license for PIM is there, process the users that are "member" of the PIM group as Eligible if ($TenantHasPremiumLicense) { # Get the users that are assigned to the PIM group as Eligible members $graphArgs = @{ "commandlet" = "Get-MgBetaIdentityGovernancePrivilegedAccessGroupEligibilityScheduleInstance" "queryParams" = @{'$filter' = "groupId eq '$GroupId'"} "M365Environment" = $M365Environment } $PIMGroupMembers = Invoke-GraphDirectly @graphArgs foreach ($GroupMember in $PIMGroupMembers) { # If the user is not a member of the PIM group (i.e. they are an owner) then skip them if ($GroupMember.AccessId -ne "member") { continue } $PIMEligibleUserId = $GroupMember.PrincipalId # If the user's data has not been fetched from graph, go get it if (-not $PrivilegedUsers.ContainsKey($PIMEligibleUserId)) { $AADUser = Get-MgBetaUser -ErrorAction Stop -UserId $PIMEligibleUserId $PrivilegedUsers[$PIMEligibleUserId] = @{"DisplayName"=$AADUser.DisplayName; "OnPremisesImmutableId"=$AADUser.OnPremisesImmutableId; "roles"=@()} } # If the current role has not already been added to the user's roles array then add the role if ($PrivilegedUsers[$PIMEligibleUserId].roles -notcontains $Role.DisplayName) { $PrivilegedUsers[$PIMEligibleUserId].roles += $Role.DisplayName } } } } } } # Process the Eligible role assignments if the premium license for PIM is there if ($TenantHasPremiumLicense) { # Get a list of all the users and groups that have Eligible assignments $graphArgs = @{ "commandlet" = "Get-MgBetaRoleManagementDirectoryRoleEligibilityScheduleInstance" "M365Environment" = $M365Environment } $AllPIMRoleAssignments = Invoke-GraphDirectly @graphArgs # Add to the list of privileged users based on Eligible assignments foreach ($Role in $AADRoles) { $PrivRoleId = $Role.RoleTemplateId # Get a list of all the users and groups Eligible assigned to this role $PIMRoleAssignments = $AllPIMRoleAssignments | Where-Object { $_.RoleDefinitionId -eq $PrivRoleId } foreach ($PIMRoleAssignment in $PIMRoleAssignments) { $UserObjectId = $PIMRoleAssignment.PrincipalId try { $UserType = "user" # If the user's data has not been fetched from graph, go get it if (-Not $PrivilegedUsers.ContainsKey($UserObjectId)) { $AADUser = Get-MgBetaUser -ErrorAction Stop -Filter "Id eq '$UserObjectId'" $PrivilegedUsers[$AADUser.Id] = @{"DisplayName"=$AADUser.DisplayName; "OnPremisesImmutableId"=$AADUser.OnPremisesImmutableId; "roles"=@()} } # If the current role has not already been added to the user's roles array then add the role if ($PrivilegedUsers[$UserObjectId].roles -notcontains $Role.DisplayName) { $PrivilegedUsers[$UserObjectId].roles += $Role.DisplayName } } # Catch the specific error which indicates Get-MgBetaUser does not find the user, therefore it is a group catch { if ($_.FullyQualifiedErrorId.Contains("Request_ResourceNotFound")) { $UserType = "group" } else { throw $_ } } # This if statement handles when the object eligible assigned to the current role is a Group if ($UserType -eq "group") { # Process the the users that are directly assigned to the group (not through PIM groups) $GroupMembers = Get-MgBetaGroupMember -All -ErrorAction Stop -GroupId $UserObjectId foreach ($GroupMember in $GroupMembers) { $Membertype = $GroupMember.AdditionalProperties."@odata.type" -replace "#microsoft.graph." if ($Membertype -eq "user") { # If the user's data has not been fetched from graph, go get it if (-Not $PrivilegedUsers.ContainsKey($GroupMember.Id)) { $AADUser = Get-MgBetaUser -ErrorAction Stop -UserId $GroupMember.Id $PrivilegedUsers[$AADUser.Id] = @{"DisplayName"=$AADUser.DisplayName; "OnPremisesImmutableId"=$AADUser.OnPremisesImmutableId; "roles"=@()} } # If the current role has not already been added to the user's roles array then add the role if ($PrivilegedUsers[$GroupMember.Id].roles -notcontains $Role.DisplayName) { $PrivilegedUsers[$GroupMember.Id].roles += $Role.DisplayName } } } # Get the users that are assigned to the PIM group as Eligible members $graphArgs = @{ "commandlet" = "Get-MgBetaIdentityGovernancePrivilegedAccessGroupEligibilityScheduleInstance" "queryParams" = @{'$filter' = "groupId eq '$UserObjectId'"} "M365Environment" = $M365Environment} $PIMGroupMembers = Invoke-GraphDirectly @graphArgs foreach ($GroupMember in $PIMGroupMembers) { # If the user is not a member of the PIM group (i.e. they are an owner) then skip them if ($GroupMember.AccessId -ne "member") { continue } $PIMEligibleUserId = $GroupMember.PrincipalId # If the user's data has not been fetched from graph, go get it if (-not $PrivilegedUsers.ContainsKey($PIMEligibleUserId)) { $AADUser = Get-MgBetaUser -ErrorAction Stop -UserId $PIMEligibleUserId $PrivilegedUsers[$PIMEligibleUserId] = @{"DisplayName"=$AADUser.DisplayName; "OnPremisesImmutableId"=$AADUser.OnPremisesImmutableId; "roles"=@()} } # If the current role has not already been added to the user's roles array then add the role if ($PrivilegedUsers[$PIMEligibleUserId].roles -notcontains $Role.DisplayName) { $PrivilegedUsers[$PIMEligibleUserId].roles += $Role.DisplayName } } } } } } $PrivilegedUsers } function AddRuleSource{ <# .NOTES Internal helper function to add a source to policy rule for reporting purposes. Source should be either PIM Group Name or Role Name #> param( [ValidateNotNullOrEmpty()] [string] $Source, [ValidateNotNullOrEmpty()] [string] $SourceType = "Directory Role", [ValidateNotNullOrEmpty()] [array] $Rules ) foreach ($Rule in $Rules){ $Rule | Add-Member -Name "RuleSource" -Value $Source -MemberType NoteProperty $Rule | Add-Member -Name "RuleSourceType" -Value $SourceType -MemberType NoteProperty } } # This cache keeps track of PIM groups that we've already processed class GroupTypeCache{ static [hashtable]$CheckedGroups = @{} } function GetConfigurationsForPimGroups{ param ( [ValidateNotNullOrEmpty()] [array] $PrivilegedRoleHashtable, [ValidateNotNullOrEmpty()] [array] $AllRoleAssignments, [ValidateNotNullOrEmpty()] [string] $M365Environment ) # Get a list of the groups that are enrolled in PIM - we want to ignore the others $graphArgs = @{ "commandlet" = "Get-MgBetaPrivilegedAccessResource" "queryParams" = @{'$PrivilegedAccessId' = "aadGroups"} "M365Environment" = $M365Environment } $PIMGroups = Invoke-GraphDirectly @graphArgs foreach ($RoleAssignment in $AllRoleAssignments){ # Check if the assignment in current loop iteration is assigned to a privileged role $Role = $PrivilegedRoleHashtable | Where-Object RoleTemplateId -EQ $($RoleAssignment.RoleDefinitionId) # If this is a privileged role if ($Role){ # Store the Id of the object assigned to the role (could be user,group,service principal) $PrincipalId = $RoleAssignment.PrincipalId # If the current object is not a PIM group we skip it $FoundPIMGroup = $PIMGroups | Where-Object { $_.Id -eq $PrincipalId } if ($null -eq $FoundPIMGroup) { continue } # If we haven't processed the current group before, add it to the cache and proceed If ($null -eq [GroupTypeCache]::CheckedGroups[$PrincipalId]){ [GroupTypeCache]::CheckedGroups.Add($PrincipalId, $true) } # If we have processed it before, then skip it to avoid unnecessary cycles else { continue } # Get all the configuration rules for the current PIM group - get member not owner configs $PolicyAssignment = Get-MgBetaPolicyRoleManagementPolicyAssignment -All -ErrorAction Stop -Filter "scopeId eq '$PrincipalId' and scopeType eq 'Group' and roleDefinitionId eq 'member'" | Select-Object -Property PolicyId # Add each configuration rule to the hashtable. There are usually about 17 configurations for a group. # Get the detailed configuration settings $MemberPolicyRules = Get-MgBetaPolicyRoleManagementPolicyRule -All -ErrorAction Stop -UnifiedRoleManagementPolicyId $PolicyAssignment.PolicyId # Filter for the PIM group so we can grab its name $PIMGroup = $PIMGroups | Where-Object {$_.Id -eq $PrincipalId} # $SourceGroup = Get-MgBetaGroup -Filter "id eq '$PrincipalId' " | Select-Object -Property DisplayName AddRuleSource -Source $PIMGroup.DisplayName -SourceType "PIM Group" -Rules $MemberPolicyRules $RoleRules = $Role.psobject.Properties | Where-Object {$_.Name -eq 'Rules'} if ($RoleRules){ # Appending rules $Role.Rules += $MemberPolicyRules } else { # Adding rules node if it is not already present $Role | Add-Member -Name "Rules" -Value $MemberPolicyRules -MemberType NoteProperty } } } } function GetConfigurationsForRoles{ param ( [ValidateNotNullOrEmpty()] [array] $PrivilegedRoleHashtable, [ValidateNotNullOrEmpty()] [array] $AllRoleAssignments ) # Get all the configuration settings (aka rules) for all the roles in the tenant $RolePolicyAssignments = Get-MgBetaPolicyRoleManagementPolicyAssignment -All -ErrorAction Stop -Filter "scopeId eq '/' and scopeType eq 'DirectoryRole'" foreach ($Role in $PrivilegedRoleHashtable) { $RolePolicies = @() $RoleTemplateId = $Role.RoleTemplateId # Get a list of the configuration rules assigned to this role $PolicyAssignment = $RolePolicyAssignments | Where-Object -Property RoleDefinitionId -eq -Value $RoleTemplateId # Get the detailed configuration settings $RolePolicies = Get-MgBetaPolicyRoleManagementPolicyRule -All -ErrorAction Stop -UnifiedRoleManagementPolicyId $PolicyAssignment.PolicyId # Get a list of the users / groups assigned to this role $RoleAssignments = @($AllRoleAssignments | Where-Object { $_.RoleDefinitionId -eq $RoleTemplateId }) # Store the data that we retrieved in the Role object which is part of the hashtable that will be returned from this function $Role | Add-Member -Name "Assignments" -Value $RoleAssignments -MemberType NoteProperty $RoleRules = $Role.psobject.Properties | Where-Object {$_.Name -eq 'Rules'} AddRuleSource -Source $Role.DisplayName -SourceType "Directory Role" -Rules $RolePolicies if ($RoleRules){ $Role.Rules += $RolePolicies } else { $Role | Add-Member -Name "Rules" -Value $RolePolicies -MemberType NoteProperty } } } function Get-PrivilegedRole { <# .Description Creates an array of the highly privileged roles along with the users assigned to the role and the security policies (aka rules) applied to it .Functionality Internal #> param ( [ValidateNotNullOrEmpty()] [switch] $TenantHasPremiumLicense, [ValidateNotNullOrEmpty()] [string] $M365Environment ) $PrivilegedRoles = [ScubaConfig]::ScubaDefault('DefaultPrivilegedRoles') # Get a list of the RoleTemplateId values for the privileged roles in the list above. # The RoleTemplateId value is passed to other cmdlets to retrieve role/group security configuration rules and user/group assignments. $PrivilegedRoleHashtable = Get-MgBetaDirectoryRoleTemplate -All -ErrorAction Stop | Where-Object { $_.DisplayName -in $PrivilegedRoles } | Select-Object "DisplayName", @{Name='RoleTemplateId'; Expression={$_.Id}} # If the tenant has the premium license then you can access the PIM service to get the role configuration policies and the active role assigments if ($TenantHasPremiumLicense) { # Clear the cache of already processed PIM groups because this is a static variable [GroupTypeCache]::CheckedGroups.Clear() # Get ALL the roles and users actively assigned to them $graphArgs = @{ "commandlet" = "Get-MgBetaRoleManagementDirectoryRoleAssignmentScheduleInstance" "M365Environment" = $M365Environment } $AllRoleAssignments = Invoke-GraphDirectly @graphArgs # Each of the helper functions below add configuration settings (aka rules) to the role hashtable. # Get the PIM configurations for the roles GetConfigurationsForRoles -PrivilegedRoleHashtable $PrivilegedRoleHashtable -AllRoleAssignments $AllRoleAssignments # Get the PIM configurations for the groups GetConfigurationsForPimGroups -PrivilegedRoleHashtable $PrivilegedRoleHashtable -AllRoleAssignments $AllRoleAssignments -M365Environment $M365Environment } # Return the hashtable $PrivilegedRoleHashtable } # SIG # Begin signature block # MIIuwAYJKoZIhvcNAQcCoIIusTCCLq0CAQExDzANBglghkgBZQMEAgEFADB5Bgor # BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG # KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCAfAdO0jRsQafSP # fxJfuqFsD5qo49lBXp2o1F3olrdQ5KCCE6MwggWQMIIDeKADAgECAhAFmxtXno4h # MuI5B72nd3VcMA0GCSqGSIb3DQEBDAUAMGIxCzAJBgNVBAYTAlVTMRUwEwYDVQQK # EwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xITAfBgNV # BAMTGERpZ2lDZXJ0IFRydXN0ZWQgUm9vdCBHNDAeFw0xMzA4MDExMjAwMDBaFw0z # ODAxMTUxMjAwMDBaMGIxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJ # bmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xITAfBgNVBAMTGERpZ2lDZXJ0 # IFRydXN0ZWQgUm9vdCBHNDCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIB # AL/mkHNo3rvkXUo8MCIwaTPswqclLskhPfKK2FnC4SmnPVirdprNrnsbhA3EMB/z # G6Q4FutWxpdtHauyefLKEdLkX9YFPFIPUh/GnhWlfr6fqVcWWVVyr2iTcMKyunWZ # anMylNEQRBAu34LzB4TmdDttceItDBvuINXJIB1jKS3O7F5OyJP4IWGbNOsFxl7s # Wxq868nPzaw0QF+xembud8hIqGZXV59UWI4MK7dPpzDZVu7Ke13jrclPXuU15zHL # 2pNe3I6PgNq2kZhAkHnDeMe2scS1ahg4AxCN2NQ3pC4FfYj1gj4QkXCrVYJBMtfb # BHMqbpEBfCFM1LyuGwN1XXhm2ToxRJozQL8I11pJpMLmqaBn3aQnvKFPObURWBf3 # JFxGj2T3wWmIdph2PVldQnaHiZdpekjw4KISG2aadMreSx7nDmOu5tTvkpI6nj3c # AORFJYm2mkQZK37AlLTSYW3rM9nF30sEAMx9HJXDj/chsrIRt7t/8tWMcCxBYKqx # YxhElRp2Yn72gLD76GSmM9GJB+G9t+ZDpBi4pncB4Q+UDCEdslQpJYls5Q5SUUd0 # viastkF13nqsX40/ybzTQRESW+UQUOsxxcpyFiIJ33xMdT9j7CFfxCBRa2+xq4aL # T8LWRV+dIPyhHsXAj6KxfgommfXkaS+YHS312amyHeUbAgMBAAGjQjBAMA8GA1Ud # EwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMB0GA1UdDgQWBBTs1+OC0nFdZEzf # Lmc/57qYrhwPTzANBgkqhkiG9w0BAQwFAAOCAgEAu2HZfalsvhfEkRvDoaIAjeNk # aA9Wz3eucPn9mkqZucl4XAwMX+TmFClWCzZJXURj4K2clhhmGyMNPXnpbWvWVPjS # PMFDQK4dUPVS/JA7u5iZaWvHwaeoaKQn3J35J64whbn2Z006Po9ZOSJTROvIXQPK # 7VB6fWIhCoDIc2bRoAVgX+iltKevqPdtNZx8WorWojiZ83iL9E3SIAveBO6Mm0eB # cg3AFDLvMFkuruBx8lbkapdvklBtlo1oepqyNhR6BvIkuQkRUNcIsbiJeoQjYUIp # 5aPNoiBB19GcZNnqJqGLFNdMGbJQQXE9P01wI4YMStyB0swylIQNCAmXHE/A7msg # dDDS4Dk0EIUhFQEI6FUy3nFJ2SgXUE3mvk3RdazQyvtBuEOlqtPDBURPLDab4vri # RbgjU2wGb2dVf0a1TD9uKFp5JtKkqGKX0h7i7UqLvBv9R0oN32dmfrJbQdA75PQ7 # 9ARj6e/CVABRoIoqyc54zNXqhwQYs86vSYiv85KZtrPmYQ/ShQDnUBrkG5WdGaG5 # nLGbsQAe79APT0JsyQq87kP6OnGlyE0mpTX9iV28hWIdMtKgK1TtmlfB2/oQzxm3 # i0objwG2J5VT6LaJbVu8aNQj6ItRolb58KaAoNYes7wPD1N1KarqE3fk3oyBIa0H # EEcRrYc9B9F1vM/zZn4wggawMIIEmKADAgECAhAIrUCyYNKcTJ9ezam9k67ZMA0G # CSqGSIb3DQEBDAUAMGIxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJ # bmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xITAfBgNVBAMTGERpZ2lDZXJ0 # IFRydXN0ZWQgUm9vdCBHNDAeFw0yMTA0MjkwMDAwMDBaFw0zNjA0MjgyMzU5NTla # MGkxCzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5EaWdpQ2VydCwgSW5jLjFBMD8GA1UE # AxM4RGlnaUNlcnQgVHJ1c3RlZCBHNCBDb2RlIFNpZ25pbmcgUlNBNDA5NiBTSEEz # ODQgMjAyMSBDQTEwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDVtC9C # 0CiteLdd1TlZG7GIQvUzjOs9gZdwxbvEhSYwn6SOaNhc9es0JAfhS0/TeEP0F9ce # 2vnS1WcaUk8OoVf8iJnBkcyBAz5NcCRks43iCH00fUyAVxJrQ5qZ8sU7H/Lvy0da # E6ZMswEgJfMQ04uy+wjwiuCdCcBlp/qYgEk1hz1RGeiQIXhFLqGfLOEYwhrMxe6T # SXBCMo/7xuoc82VokaJNTIIRSFJo3hC9FFdd6BgTZcV/sk+FLEikVoQ11vkunKoA # FdE3/hoGlMJ8yOobMubKwvSnowMOdKWvObarYBLj6Na59zHh3K3kGKDYwSNHR7Oh # D26jq22YBoMbt2pnLdK9RBqSEIGPsDsJ18ebMlrC/2pgVItJwZPt4bRc4G/rJvmM # 1bL5OBDm6s6R9b7T+2+TYTRcvJNFKIM2KmYoX7BzzosmJQayg9Rc9hUZTO1i4F4z # 8ujo7AqnsAMrkbI2eb73rQgedaZlzLvjSFDzd5Ea/ttQokbIYViY9XwCFjyDKK05 # huzUtw1T0PhH5nUwjewwk3YUpltLXXRhTT8SkXbev1jLchApQfDVxW0mdmgRQRNY # mtwmKwH0iU1Z23jPgUo+QEdfyYFQc4UQIyFZYIpkVMHMIRroOBl8ZhzNeDhFMJlP # /2NPTLuqDQhTQXxYPUez+rbsjDIJAsxsPAxWEQIDAQABo4IBWTCCAVUwEgYDVR0T # AQH/BAgwBgEB/wIBADAdBgNVHQ4EFgQUaDfg67Y7+F8Rhvv+YXsIiGX0TkIwHwYD # VR0jBBgwFoAU7NfjgtJxXWRM3y5nP+e6mK4cD08wDgYDVR0PAQH/BAQDAgGGMBMG # A1UdJQQMMAoGCCsGAQUFBwMDMHcGCCsGAQUFBwEBBGswaTAkBggrBgEFBQcwAYYY # aHR0cDovL29jc3AuZGlnaWNlcnQuY29tMEEGCCsGAQUFBzAChjVodHRwOi8vY2Fj # ZXJ0cy5kaWdpY2VydC5jb20vRGlnaUNlcnRUcnVzdGVkUm9vdEc0LmNydDBDBgNV # HR8EPDA6MDigNqA0hjJodHRwOi8vY3JsMy5kaWdpY2VydC5jb20vRGlnaUNlcnRU # cnVzdGVkUm9vdEc0LmNybDAcBgNVHSAEFTATMAcGBWeBDAEDMAgGBmeBDAEEATAN # BgkqhkiG9w0BAQwFAAOCAgEAOiNEPY0Idu6PvDqZ01bgAhql+Eg08yy25nRm95Ry # sQDKr2wwJxMSnpBEn0v9nqN8JtU3vDpdSG2V1T9J9Ce7FoFFUP2cvbaF4HZ+N3HL # IvdaqpDP9ZNq4+sg0dVQeYiaiorBtr2hSBh+3NiAGhEZGM1hmYFW9snjdufE5Btf # Q/g+lP92OT2e1JnPSt0o618moZVYSNUa/tcnP/2Q0XaG3RywYFzzDaju4ImhvTnh # OE7abrs2nfvlIVNaw8rpavGiPttDuDPITzgUkpn13c5UbdldAhQfQDN8A+KVssIh # dXNSy0bYxDQcoqVLjc1vdjcshT8azibpGL6QB7BDf5WIIIJw8MzK7/0pNVwfiThV # 9zeKiwmhywvpMRr/LhlcOXHhvpynCgbWJme3kuZOX956rEnPLqR0kq3bPKSchh/j # wVYbKyP/j7XqiHtwa+aguv06P0WmxOgWkVKLQcBIhEuWTatEQOON8BUozu3xGFYH # Ki8QxAwIZDwzj64ojDzLj4gLDb879M4ee47vtevLt/B3E+bnKD+sEq6lLyJsQfmC # XBVmzGwOysWGw/YmMwwHS6DTBwJqakAwSEs0qFEgu60bhQjiWQ1tygVQK+pKHJ6l # /aCnHwZ05/LWUpD9r4VIIflXO7ScA+2GRfS0YW6/aOImYIbqyK+p/pQd52MbOoZW # eE4wggdXMIIFP6ADAgECAhANkQ8dPvvR0q3Ytt4H0T3aMA0GCSqGSIb3DQEBCwUA # MGkxCzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5EaWdpQ2VydCwgSW5jLjFBMD8GA1UE # AxM4RGlnaUNlcnQgVHJ1c3RlZCBHNCBDb2RlIFNpZ25pbmcgUlNBNDA5NiBTSEEz # ODQgMjAyMSBDQTEwHhcNMjQwMTMwMDAwMDAwWhcNMjUwMTI5MjM1OTU5WjBfMQsw # CQYDVQQGEwJVUzEdMBsGA1UECBMURGlzdHJpY3Qgb2YgQ29sdW1iaWExEzARBgNV # BAcTCldhc2hpbmd0b24xDTALBgNVBAoTBENJU0ExDTALBgNVBAMTBENJU0EwggIi # MA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCT1y7uJCQax8JfiDEYgpiU9URj # EXCTRqtZbDALM9rPUudiuM3mj6A1SUSAAWYv6DTsvGPvxyMI2Idg0mQunl4Ms9DJ # yVwe5k4+Anj/73Nx1AbOPYP8xRZcD10FkctKGhV0PzvrDcwU15hsQWtiepFgg+bX # fHkGMeu426oc69f43vKE43DiqKTf0/UBX/qgpj3JZvJ3zc1kilBOv4sBCksfCjbW # tLZD0tqAgBsNPo3Oy5mQG31E1eZdTNvrdTnEXacSwb3k615z7mHy7nqBUkOruZ9E # tnvC2qla+uL3ks91O/e/LnKzH9Lj1JmEBf6jwPN/MYR9Dymni4Mi3AQ8mpQMyFmi # XcSHymibSNbtTMavpdBWjFfrcvPETX7krROUOoLzMQmNgHArceSh55tgvDRdSU5c # WK3BTvK3l3mgCdgjre7XGYxV3W8apyxk5+RKfHdbv9cpRwpSuDnI8sHeqmB3fnfo # Cr1PPu4WhKegt20CobhDVybiBdhDVqUdR53ful4N/coQOEHDrIExB5nJf9Pvdrza # DyIGKAMIXD79ba5/rQEo+2cA66oJkPlvB5hEGI/jtDcYwDBgalbwB7Kc8zAAhl6+ # JvHfYpXOkppSfEQbaRXZI+LGXWQAFa5pJDfDEAyZSXprStgw594sWUOysp+UOxFe # kSA4mBr0o1jVpdaulwIDAQABo4ICAzCCAf8wHwYDVR0jBBgwFoAUaDfg67Y7+F8R # hvv+YXsIiGX0TkIwHQYDVR0OBBYEFAmyTB5bcWyA+8+rq540jPRLJ1nYMD4GA1Ud # IAQ3MDUwMwYGZ4EMAQQBMCkwJwYIKwYBBQUHAgEWG2h0dHA6Ly93d3cuZGlnaWNl # cnQuY29tL0NQUzAOBgNVHQ8BAf8EBAMCB4AwEwYDVR0lBAwwCgYIKwYBBQUHAwMw # gbUGA1UdHwSBrTCBqjBToFGgT4ZNaHR0cDovL2NybDMuZGlnaWNlcnQuY29tL0Rp # Z2lDZXJ0VHJ1c3RlZEc0Q29kZVNpZ25pbmdSU0E0MDk2U0hBMzg0MjAyMUNBMS5j # cmwwU6BRoE+GTWh0dHA6Ly9jcmw0LmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydFRydXN0 # ZWRHNENvZGVTaWduaW5nUlNBNDA5NlNIQTM4NDIwMjFDQTEuY3JsMIGUBggrBgEF # BQcBAQSBhzCBhDAkBggrBgEFBQcwAYYYaHR0cDovL29jc3AuZGlnaWNlcnQuY29t # MFwGCCsGAQUFBzAChlBodHRwOi8vY2FjZXJ0cy5kaWdpY2VydC5jb20vRGlnaUNl # cnRUcnVzdGVkRzRDb2RlU2lnbmluZ1JTQTQwOTZTSEEzODQyMDIxQ0ExLmNydDAJ # BgNVHRMEAjAAMA0GCSqGSIb3DQEBCwUAA4ICAQAh2Jnt9IPoBvOQlYQUlCP9iJ5y # XAvEWe1camOwedqMZsHEPpT2yd6+fMzPZmV3/bYJgaN2OrDS1snf62S7yc+AulVw # PAXSp1lSAiFEbZ6PFEdEBIag9B65Mp/cvRtJsIWQIc//jWqFMHpkU6r3MW9YARRu # vaIf5/0qlM4VEwn3lTf+jJdxhhyoOFTWWd3BrlMPcT06z6F6hFfyycQkZ3Y9wEJ3 # uOU9bCNLZL1HCjlKT+oI0WsgeRdbe2sYrnvv9NmDY9oEi8PEq+DGjiTgLbY5OcAX # uUogPPw6gbcuNn8Hq6FFKPIQxaksB8dF8Gw4m2lQoUWESPRF8Zaq9lmZN3+QzA79 # yskfJtAFqz3gUP5wJBdNfi/u1sGbLI0QnJQkIKfFuz7DfDPldw0gIl05BIYwZBmj # TpFRu1/+gIlP1Ul4L/wt9Lxk6pglObLsdxHP2UQrG30JaUN0gv3xZMBBByHGVVTe # cyU4qwJ0ulMdv/kjHwh+m58uOF8gHXLfyBmOjYpohN3+l0rS0qdArZMNSmLTA7N8 # n3V3AZLKB//1yhPt++gR4pCFdXmgwYDDLRxjlV0cMsG1UeSQUdI0aieh/grg5TQO # CergVXS5h3sz5U0ZQPWND41LJhA0gF2OGZNHdUc9+0dwTsfxAERrjaTdeZp0/rdZ # 9iGBoiRsS4U86S8xkDGCGnMwghpvAgEBMH0waTELMAkGA1UEBhMCVVMxFzAVBgNV # BAoTDkRpZ2lDZXJ0LCBJbmMuMUEwPwYDVQQDEzhEaWdpQ2VydCBUcnVzdGVkIEc0 # IENvZGUgU2lnbmluZyBSU0E0MDk2IFNIQTM4NCAyMDIxIENBMQIQDZEPHT770dKt # 2LbeB9E92jANBglghkgBZQMEAgEFAKCBhDAYBgorBgEEAYI3AgEMMQowCKACgACh # AoAAMBkGCSqGSIb3DQEJAzEMBgorBgEEAYI3AgEEMBwGCisGAQQBgjcCAQsxDjAM # BgorBgEEAYI3AgEVMC8GCSqGSIb3DQEJBDEiBCAwtwynmWXwewL3vv8ZaYTfC06Q # MN/IL5RAaxrv/uVU/TANBgkqhkiG9w0BAQEFAASCAgBIB2VxCajSz82x8nqOEDzo # p8MmlRbJ6aMQnXec5Hc3pcOLk1Vy/k7CsdZhcDFDD3HxPFSBL/L+aM1+fU7sOHD0 # z3nihDlVz/4bf4320gWdRIwZqLbbTaPSqnST5sh6jXXG75D8knSSJtzd8fJEH/lw # uKcZFZt+h46Dgkf3g0WnnhjcSFCEB40e4QtvwLnEChuXBVSl8z0IA3qmcS9s2Nqu # 0FYiFwFsHQAiPopZZgsH0SpjSv+4Np+5bn5SMhngcWbrW3V0JolkgkYt2A0IN9Tw # 0pNZYfYmGcOqN6JXt9al+dMZxSF03qy3z7Tn4CHzu+I7+3Py6MGv8b1UuP3PQW/c # AiteGSULDtyFZ3cODNnondoUSGUIoz0zUi69lnh8ArST8MZ2E6lgHXW/zGH+CDsF # fuGlrXpyLz/gz+Bfwu8GOKqRIo774I6BVRqr0a6cozjdfd0OhXjbX06WGXUXnZC8 # qLReAIpgk1Rb/MceXZv+3sHBn0yNhNv0U4Dfszdnx519XugnDJ7K/CCvsWnroSUe # cP6zib3J5+S1vjosEUT6/bjKWD302ETROBzWIz0fxzZEx9pzcrg08we5XJegPqpq # M13kVjxNI+KMFKraHdVCZOdsBZY2aHu4Zh01h4Dbp+Zu11s8Phc0/zqbOHRnkIZS # NhHjKX0Imcf4cEShxkhRz6GCF0Awghc8BgorBgEEAYI3AwMBMYIXLDCCFygGCSqG # SIb3DQEHAqCCFxkwghcVAgEDMQ8wDQYJYIZIAWUDBAIBBQAweAYLKoZIhvcNAQkQ # AQSgaQRnMGUCAQEGCWCGSAGG/WwHATAxMA0GCWCGSAFlAwQCAQUABCCA7KNcRXnC # ecPPKYSb6QemE0/kNld2QgBfEtdSvVzRPQIRAJ/p5u4ldq6WL9+oiW4FrDMYDzIw # MjQwOTA0MTM0NDIwWqCCEwkwggbCMIIEqqADAgECAhAFRK/zlJ0IOaa/2z9f5WEW # MA0GCSqGSIb3DQEBCwUAMGMxCzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5EaWdpQ2Vy # dCwgSW5jLjE7MDkGA1UEAxMyRGlnaUNlcnQgVHJ1c3RlZCBHNCBSU0E0MDk2IFNI # QTI1NiBUaW1lU3RhbXBpbmcgQ0EwHhcNMjMwNzE0MDAwMDAwWhcNMzQxMDEzMjM1 # OTU5WjBIMQswCQYDVQQGEwJVUzEXMBUGA1UEChMORGlnaUNlcnQsIEluYy4xIDAe # BgNVBAMTF0RpZ2lDZXJ0IFRpbWVzdGFtcCAyMDIzMIICIjANBgkqhkiG9w0BAQEF # AAOCAg8AMIICCgKCAgEAo1NFhx2DjlusPlSzI+DPn9fl0uddoQ4J3C9Io5d6Oyqc # Z9xiFVjBqZMRp82qsmrdECmKHmJjadNYnDVxvzqX65RQjxwg6seaOy+WZuNp52n+ # W8PWKyAcwZeUtKVQgfLPywemMGjKg0La/H8JJJSkghraarrYO8pd3hkYhftF6g1h # bJ3+cV7EBpo88MUueQ8bZlLjyNY+X9pD04T10Mf2SC1eRXWWdf7dEKEbg8G45lKV # tUfXeCk5a+B4WZfjRCtK1ZXO7wgX6oJkTf8j48qG7rSkIWRw69XloNpjsy7pBe6q # 9iT1HbybHLK3X9/w7nZ9MZllR1WdSiQvrCuXvp/k/XtzPjLuUjT71Lvr1KAsNJvj # 3m5kGQc3AZEPHLVRzapMZoOIaGK7vEEbeBlt5NkP4FhB+9ixLOFRr7StFQYU6mII # E9NpHnxkTZ0P387RXoyqq1AVybPKvNfEO2hEo6U7Qv1zfe7dCv95NBB+plwKWEwA # PoVpdceDZNZ1zY8SdlalJPrXxGshuugfNJgvOuprAbD3+yqG7HtSOKmYCaFxsmxx # rz64b5bV4RAT/mFHCoz+8LbH1cfebCTwv0KCyqBxPZySkwS0aXAnDU+3tTbRyV8I # pHCj7ArxES5k4MsiK8rxKBMhSVF+BmbTO77665E42FEHypS34lCh8zrTioPLQHsC # AwEAAaOCAYswggGHMA4GA1UdDwEB/wQEAwIHgDAMBgNVHRMBAf8EAjAAMBYGA1Ud # JQEB/wQMMAoGCCsGAQUFBwMIMCAGA1UdIAQZMBcwCAYGZ4EMAQQCMAsGCWCGSAGG # /WwHATAfBgNVHSMEGDAWgBS6FtltTYUvcyl2mi91jGogj57IbzAdBgNVHQ4EFgQU # pbbvE+fvzdBkodVWqWUxo97V40kwWgYDVR0fBFMwUTBPoE2gS4ZJaHR0cDovL2Ny # bDMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0VHJ1c3RlZEc0UlNBNDA5NlNIQTI1NlRp # bWVTdGFtcGluZ0NBLmNybDCBkAYIKwYBBQUHAQEEgYMwgYAwJAYIKwYBBQUHMAGG # GGh0dHA6Ly9vY3NwLmRpZ2ljZXJ0LmNvbTBYBggrBgEFBQcwAoZMaHR0cDovL2Nh # Y2VydHMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0VHJ1c3RlZEc0UlNBNDA5NlNIQTI1 # NlRpbWVTdGFtcGluZ0NBLmNydDANBgkqhkiG9w0BAQsFAAOCAgEAgRrW3qCptZgX # vHCNT4o8aJzYJf/LLOTN6l0ikuyMIgKpuM+AqNnn48XtJoKKcS8Y3U623mzX4WCc # K+3tPUiOuGu6fF29wmE3aEl3o+uQqhLXJ4Xzjh6S2sJAOJ9dyKAuJXglnSoFeoQp # mLZXeY/bJlYrsPOnvTcM2Jh2T1a5UsK2nTipgedtQVyMadG5K8TGe8+c+njikxp2 # oml101DkRBK+IA2eqUTQ+OVJdwhaIcW0z5iVGlS6ubzBaRm6zxbygzc0brBBJt3e # WpdPM43UjXd9dUWhpVgmagNF3tlQtVCMr1a9TMXhRsUo063nQwBw3syYnhmJA+rU # kTfvTVLzyWAhxFZH7doRS4wyw4jmWOK22z75X7BC1o/jF5HRqsBV44a/rCcsQdCa # M0qoNtS5cpZ+l3k4SF/Kwtw9Mt911jZnWon49qfH5U81PAC9vpwqbHkB3NpE5jre # ODsHXjlY9HxzMVWggBHLFAx+rrz+pOt5Zapo1iLKO+uagjVXKBbLafIymrLS2Dq4 # sUaGa7oX/cR3bBVsrquvczroSUa31X/MtjjA2Owc9bahuEMs305MfR5ocMB3CtQC # 4Fxguyj/OOVSWtasFyIjTvTs0xf7UGv/B3cfcZdEQcm4RtNsMnxYL2dHZeUbc7aZ # +WssBkbvQR7w8F/g29mtkIBEr4AQQYowggauMIIElqADAgECAhAHNje3JFR82Ees # /ShmKl5bMA0GCSqGSIb3DQEBCwUAMGIxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxE # aWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xITAfBgNVBAMT # GERpZ2lDZXJ0IFRydXN0ZWQgUm9vdCBHNDAeFw0yMjAzMjMwMDAwMDBaFw0zNzAz # MjIyMzU5NTlaMGMxCzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5EaWdpQ2VydCwgSW5j # LjE7MDkGA1UEAxMyRGlnaUNlcnQgVHJ1c3RlZCBHNCBSU0E0MDk2IFNIQTI1NiBU # aW1lU3RhbXBpbmcgQ0EwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDG # hjUGSbPBPXJJUVXHJQPE8pE3qZdRodbSg9GeTKJtoLDMg/la9hGhRBVCX6SI82j6 # ffOciQt/nR+eDzMfUBMLJnOWbfhXqAJ9/UO0hNoR8XOxs+4rgISKIhjf69o9xBd/ # qxkrPkLcZ47qUT3w1lbU5ygt69OxtXXnHwZljZQp09nsad/ZkIdGAHvbREGJ3Hxq # V3rwN3mfXazL6IRktFLydkf3YYMZ3V+0VAshaG43IbtArF+y3kp9zvU5EmfvDqVj # bOSmxR3NNg1c1eYbqMFkdECnwHLFuk4fsbVYTXn+149zk6wsOeKlSNbwsDETqVcp # licu9Yemj052FVUmcJgmf6AaRyBD40NjgHt1biclkJg6OBGz9vae5jtb7IHeIhTZ # girHkr+g3uM+onP65x9abJTyUpURK1h0QCirc0PO30qhHGs4xSnzyqqWc0Jon7ZG # s506o9UD4L/wojzKQtwYSH8UNM/STKvvmz3+DrhkKvp1KCRB7UK/BZxmSVJQ9FHz # NklNiyDSLFc1eSuo80VgvCONWPfcYd6T/jnA+bIwpUzX6ZhKWD7TA4j+s4/TXkt2 # ElGTyYwMO1uKIqjBJgj5FBASA31fI7tk42PgpuE+9sJ0sj8eCXbsq11GdeJgo1gJ # ASgADoRU7s7pXcheMBK9Rp6103a50g5rmQzSM7TNsQIDAQABo4IBXTCCAVkwEgYD # VR0TAQH/BAgwBgEB/wIBADAdBgNVHQ4EFgQUuhbZbU2FL3MpdpovdYxqII+eyG8w # HwYDVR0jBBgwFoAU7NfjgtJxXWRM3y5nP+e6mK4cD08wDgYDVR0PAQH/BAQDAgGG # MBMGA1UdJQQMMAoGCCsGAQUFBwMIMHcGCCsGAQUFBwEBBGswaTAkBggrBgEFBQcw # AYYYaHR0cDovL29jc3AuZGlnaWNlcnQuY29tMEEGCCsGAQUFBzAChjVodHRwOi8v # Y2FjZXJ0cy5kaWdpY2VydC5jb20vRGlnaUNlcnRUcnVzdGVkUm9vdEc0LmNydDBD # BgNVHR8EPDA6MDigNqA0hjJodHRwOi8vY3JsMy5kaWdpY2VydC5jb20vRGlnaUNl # cnRUcnVzdGVkUm9vdEc0LmNybDAgBgNVHSAEGTAXMAgGBmeBDAEEAjALBglghkgB # hv1sBwEwDQYJKoZIhvcNAQELBQADggIBAH1ZjsCTtm+YqUQiAX5m1tghQuGwGC4Q # TRPPMFPOvxj7x1Bd4ksp+3CKDaopafxpwc8dB+k+YMjYC+VcW9dth/qEICU0MWfN # thKWb8RQTGIdDAiCqBa9qVbPFXONASIlzpVpP0d3+3J0FNf/q0+KLHqrhc1DX+1g # tqpPkWaeLJ7giqzl/Yy8ZCaHbJK9nXzQcAp876i8dU+6WvepELJd6f8oVInw1Ypx # dmXazPByoyP6wCeCRK6ZJxurJB4mwbfeKuv2nrF5mYGjVoarCkXJ38SNoOeY+/um # nXKvxMfBwWpx2cYTgAnEtp/Nh4cku0+jSbl3ZpHxcpzpSwJSpzd+k1OsOx0ISQ+U # zTl63f8lY5knLD0/a6fxZsNBzU+2QJshIUDQtxMkzdwdeDrknq3lNHGS1yZr5Dhz # q6YBT70/O3itTK37xJV77QpfMzmHQXh6OOmc4d0j/R0o08f56PGYX/sr2H7yRp11 # LB4nLCbbbxV7HhmLNriT1ObyF5lZynDwN7+YAN8gFk8n+2BnFqFmut1VwDophrCY # oCvtlUG3OtUVmDG0YgkPCr2B2RP+v6TR81fZvAT6gt4y3wSJ8ADNXcL50CN/AAvk # dgIm2fBldkKmKYcJRyvmfxqkhQ/8mJb2VVQrH4D6wPIOK+XW+6kvRBVK5xMOHds3 # OBqhK/bt1nz8MIIFjTCCBHWgAwIBAgIQDpsYjvnQLefv21DiCEAYWjANBgkqhkiG # 9w0BAQwFADBlMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkw # FwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1 # cmVkIElEIFJvb3QgQ0EwHhcNMjIwODAxMDAwMDAwWhcNMzExMTA5MjM1OTU5WjBi # MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 # d3cuZGlnaWNlcnQuY29tMSEwHwYDVQQDExhEaWdpQ2VydCBUcnVzdGVkIFJvb3Qg # RzQwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC/5pBzaN675F1KPDAi # MGkz7MKnJS7JIT3yithZwuEppz1Yq3aaza57G4QNxDAf8xukOBbrVsaXbR2rsnny # yhHS5F/WBTxSD1Ifxp4VpX6+n6lXFllVcq9ok3DCsrp1mWpzMpTREEQQLt+C8weE # 5nQ7bXHiLQwb7iDVySAdYyktzuxeTsiT+CFhmzTrBcZe7FsavOvJz82sNEBfsXpm # 7nfISKhmV1efVFiODCu3T6cw2Vbuyntd463JT17lNecxy9qTXtyOj4DatpGYQJB5 # w3jHtrHEtWoYOAMQjdjUN6QuBX2I9YI+EJFwq1WCQTLX2wRzKm6RAXwhTNS8rhsD # dV14Ztk6MUSaM0C/CNdaSaTC5qmgZ92kJ7yhTzm1EVgX9yRcRo9k98FpiHaYdj1Z # XUJ2h4mXaXpI8OCiEhtmmnTK3kse5w5jrubU75KSOp493ADkRSWJtppEGSt+wJS0 # 0mFt6zPZxd9LBADMfRyVw4/3IbKyEbe7f/LVjHAsQWCqsWMYRJUadmJ+9oCw++hk # pjPRiQfhvbfmQ6QYuKZ3AeEPlAwhHbJUKSWJbOUOUlFHdL4mrLZBdd56rF+NP8m8 # 00ERElvlEFDrMcXKchYiCd98THU/Y+whX8QgUWtvsauGi0/C1kVfnSD8oR7FwI+i # sX4KJpn15GkvmB0t9dmpsh3lGwIDAQABo4IBOjCCATYwDwYDVR0TAQH/BAUwAwEB # /zAdBgNVHQ4EFgQU7NfjgtJxXWRM3y5nP+e6mK4cD08wHwYDVR0jBBgwFoAUReui # r/SSy4IxLVGLp6chnfNtyA8wDgYDVR0PAQH/BAQDAgGGMHkGCCsGAQUFBwEBBG0w # azAkBggrBgEFBQcwAYYYaHR0cDovL29jc3AuZGlnaWNlcnQuY29tMEMGCCsGAQUF # BzAChjdodHRwOi8vY2FjZXJ0cy5kaWdpY2VydC5jb20vRGlnaUNlcnRBc3N1cmVk # SURSb290Q0EuY3J0MEUGA1UdHwQ+MDwwOqA4oDaGNGh0dHA6Ly9jcmwzLmRpZ2lj # ZXJ0LmNvbS9EaWdpQ2VydEFzc3VyZWRJRFJvb3RDQS5jcmwwEQYDVR0gBAowCDAG # BgRVHSAAMA0GCSqGSIb3DQEBDAUAA4IBAQBwoL9DXFXnOF+go3QbPbYW1/e/Vwe9 # mqyhhyzshV6pGrsi+IcaaVQi7aSId229GhT0E0p6Ly23OO/0/4C5+KH38nLeJLxS # A8hO0Cre+i1Wz/n096wwepqLsl7Uz9FDRJtDIeuWcqFItJnLnU+nBgMTdydE1Od/ # 6Fmo8L8vC6bp8jQ87PcDx4eo0kxAGTVGamlUsLihVo7spNU96LHc/RzY9HdaXFSM # b++hUD38dglohJ9vytsgjTVgHAIDyyCwrFigDkBjxZgiwbJZ9VVrzyerbHbObyMt # 9H5xaiNrIv8SuFQtJ37YOtnwtoeW/VvRXKwYw02fc7cBqZ9Xql4o4rmUMYIDdjCC # A3ICAQEwdzBjMQswCQYDVQQGEwJVUzEXMBUGA1UEChMORGlnaUNlcnQsIEluYy4x # OzA5BgNVBAMTMkRpZ2lDZXJ0IFRydXN0ZWQgRzQgUlNBNDA5NiBTSEEyNTYgVGlt # ZVN0YW1waW5nIENBAhAFRK/zlJ0IOaa/2z9f5WEWMA0GCWCGSAFlAwQCAQUAoIHR # MBoGCSqGSIb3DQEJAzENBgsqhkiG9w0BCRABBDAcBgkqhkiG9w0BCQUxDxcNMjQw # OTA0MTM0NDIwWjArBgsqhkiG9w0BCRACDDEcMBowGDAWBBRm8CsywsLJD4JdzqqK # ycZPGZzPQDAvBgkqhkiG9w0BCQQxIgQgDOXNhqK+qLpzhYG1BcZ8t8+dV3Qcfozv # SxJs4I/v4JUwNwYLKoZIhvcNAQkQAi8xKDAmMCQwIgQg0vbkbe10IszR1EBXaEE2 # b4KK2lWarjMWr00amtQMeCgwDQYJKoZIhvcNAQEBBQAEggIAK5XdshxKhrX6kBiN # xEZwud1MoiF6Cn/QbtTl2wvnKYiE4Agx/2TxFk4VjBHtKzM2nxvIXAbYW3RSe5uu # M4jwDCJO4XetWsB1ZKEjTAIgConOkAnv/syXri5zH4G2m8w9mTkkW9+C5IS8dW6t # PQ8INonNYqhtTZ7cQmT9Jk0XEB+C4yUnRZ7TqtqsrvHsGGNDehwvdNJtd4t+kgqY # WtWQlAW20UKjRTUBTmd0Y2x0mA9cYJsQKy5ku4ZtHxaLiCCR6JySQztHlnbAA54h # P/OcXhUIjTmhxr5PQXzwbk6+cvtWyyw11104IJoFUML733JwlftoOvI8p2X96+a4 # aEtSYY2Jrng75U13oeVEMWw8AwMnNjpK8SI0t6EJZ1E4/BL2q67i7MLk+pUNC31t # PLUZu8F1d+p2Jm8jHLu2jbLFxMktFCwZBFZQklo4r28NYZ1z3IuBzFGjDc/G8fwS # tPGXeQrVI6NKAymR22nf9Wo/+Xz46FD6ipdM60uEwnOdNAlH4fGldjsO48Lk0ZbR # ru5fnQdexVCaSItkHaCV41Y576sSU/ZS2hDC8ac+8bU5WNPLJa3UJJfvVsKoNbhy # InTfS+dKf+2pYqbFrB1pIPpVBrrRwIZIyrZJb9k4s3ZXCr+ItwtjU2oQC0Fu7HOG # ut6NdgGQ+12m6vHK74WQn8pRQZM= # SIG # End signature block |