Framework/Core/SVT/SubscriptionCore/SubscriptionCore.ps1
#using namespace Microsoft.Azure.Commands.Search.Models Set-StrictMode -Version Latest class SubscriptionCore: SVTBase { hidden [AzureSecurityCenter] $ASCSettings hidden [ManagementCertificate[]] $ManagementCertificates hidden [PSObject] $RoleAssignments hidden [PSObject] $ApprovedAdmins; hidden [PSObject] $ApprovedSPNs; hidden [PSObject] $MandatoryAccounts; hidden [PSObject] $DeprecatedAccounts; hidden [PSObject] $CurrentContext; hidden [bool] $HasGraphAPIAccess; hidden [string[]] $SubscriptionMandatoryTags = @() SubscriptionCore([string] $subscriptionId): Base($subscriptionId) { $this.GetResourceObject(); } hidden [void] GetResourceObject() { $this.ASCSettings = [AzureSecurityCenter]::new() $this.CurrentContext = Get-AzureRmContext -ErrorAction Ignore $this.MandatoryAccounts = $null $this.RoleAssignments = $null $this.ApprovedAdmins = $null $this.ApprovedSPNs = $null $this.DeprecatedAccounts = $null $this.HasGraphAPIAccess = [RoleAssignmentHelper]::HasGraphAccess(); $owners = @(); $SubAdmins = @(); $this.GetRoleAssignments(); $scope = $this.SubscriptionContext.Scope; $SubAdmins += $this.RoleAssignments | Where-Object { $_.RoleDefinitionName -eq 'CoAdministrator' ` -or $_.RoleDefinitionName -like '*ServiceAdministrator*' ` -or ($_.RoleDefinitionName -eq 'Owner' -and $_.Scope -eq $scope)} $SubAdmins | ForEach-Object{ $tempAdmin = $_ if($this.HasGraphAPIAccess -eq $false) { $owners += $tempAdmin.ObjectId } else { $owners += $tempAdmin.DisplayName } } [hashtable] $subscriptionMetada = @{} $subscriptionMetada.Add("HasGraphAccess",$this.HasGraphAPIAccess); $subscriptionMetada.Add("Owners", $owners); $this.SubscriptionContext.SubscriptionMetadata = $subscriptionMetada; $this.SubscriptionMandatoryTags += [ConfigurationManager]::GetAzSdkConfigData().SubscriptionMandatoryTags; } hidden [ControlResult] CheckSubscriptionAdminCount([ControlResult] $controlResult) { $this.GetRoleAssignments() $this.LoadRBACConfig() #Excessive number of admins (> 5) $scope = $this.SubscriptionContext.Scope; $SubAdmins = @(); $SubAdmins += $this.RoleAssignments | Where-Object { $_.RoleDefinitionName -eq 'CoAdministrator' ` -or $_.RoleDefinitionName -like '*ServiceAdministrator*' ` -or ($_.RoleDefinitionName -eq 'Owner' -and $_.Scope -eq $scope)} if($this.HasGraphAPIAccess -eq $false) { $this.PublishCustomMessage("Current Azure login context doesn't have graph api access"); } $ClientSubAdmins = @() $ApprovedSubAdmins = @() $SubAdmins | ForEach-Object{ $tempAdmin = $_ $objId = $_.ObjectId $isApprovedAdmin = $false foreach($admin in $this.ApprovedAdmins) { $tempObjId = $admin.ObjectId if($admin.ObjectType -eq "ServicePrincipal") { $out = $null #do we need to check for scope try { $out = $this.RoleAssignments | Where-Object { $_.ObjectId -eq $admin.ObjectId} } catch {} if($null -ne $out) { $tempObjId = $out[0].ObjectId } } if($objId -eq $tempObjId) { $ApprovedSubAdmins += $tempAdmin $isApprovedAdmin = $true } } if(-not $isApprovedAdmin) { $ClientSubAdmins += $tempAdmin } } $controlResult.AddMessage("There are a total of $($SubAdmins.Count) admin/owner accounts in your subscription`r`nOf these, the following $($ClientSubAdmins.Count) admin/owner accounts are not from a central team.", ($ClientSubAdmins | Select-Object DisplayName,SignInName,ObjectType, ObjectId)); $stateData = @{ Owners = @(); CoAdmins = @(); }; $stateData.Owners += $ClientSubAdmins | Where-Object { -not ($_.RoleDefinitionName -eq 'CoAdministrator' -or $_.RoleDefinitionName -like '*ServiceAdministrator*') }; $stateData.CoAdmins += $ClientSubAdmins | Where-Object { $_.RoleDefinitionName -eq 'CoAdministrator' -or $_.RoleDefinitionName -like '*ServiceAdministrator*' }; $controlResult.SetStateData("All Subscription Owners/CoAdministrators/ServiceAdministrators (excludes accounts from central team)", $stateData); if(($ApprovedSubAdmins | Measure-Object).Count -gt 0) { $controlResult.AddMessage("The following $($ApprovedSubAdmins.Count) admin/owner (approved) accounts are from a central team:`r`n", ($ApprovedSubAdmins | Select-Object DisplayName, SignInName, ObjectType, ObjectId)); } $controlResult.AddMessage("Note: Approved central team accounts don't count against your limit"); if($ClientSubAdmins.Count -gt $this.ControlSettings.NoOfApprovedAdmins) { $controlResult.VerificationResult = [VerificationResult]::Failed $controlResult.AddMessage("Number of admins/owners configured at subscription scope are more than the approved limit: $($this.ControlSettings.NoOfApprovedAdmins). Total: " + $ClientSubAdmins.Count); } else { $controlResult.AddMessage([VerificationResult]::Passed, "Number of admins/owners configured at subscription scope are with in approved limit: $($this.ControlSettings.NoOfApprovedAdmins). Total: " + $ClientSubAdmins.Count); } return $controlResult; } hidden [ControlResult] CheckApprovedCentralAccountsRBAC([ControlResult] $controlResult) { $this.GetRoleAssignments() $this.LoadRBACConfig() $state = $true $scope = $this.SubscriptionContext.Scope $out = $null $missingMandatoryAccount = @() $foundMandatoryAccount = @() if($null -ne $this.MandatoryAccounts) { foreach($admin in $this.MandatoryAccounts) { try{ $out = $this.RoleAssignments | Where-Object { $_.ObjectId -eq $admin.ObjectId -and $_.Scope -eq $scope -and $_.RoleDefinitionName -eq $admin.RoleDefinitionName }} catch { } if($null -eq $out) { $missingMandatoryAccount+= $admin $state = $false } else { $foundMandatoryAccount += $admin } } if(($foundMandatoryAccount | Measure-Object).Count -gt 0) { $controlResult.AddMessage("Found mandatory accounts:",$foundMandatoryAccount) } if(($missingMandatoryAccount | Measure-Object).Count -gt 0) { $controlResult.SetStateData("Mandatory accounts which are not added to subscription", $missingMandatoryAccount); $controlResult.AddMessage("Missing mandatory accounts:",$missingMandatoryAccount) } } if(-not $state) { $controlResult.EnableFixControl = $true; if($controlResult.FixControlParameters) { $controlResult.FixControlParameters.Tags = $this.SubscriptionMandatoryTags; } $controlResult.VerificationResult = [VerificationResult]::Failed; } else { $controlResult.VerificationResult = [VerificationResult]::Passed } return $controlResult } hidden [ControlResult] CheckDeprecatedAccountsRBAC([ControlResult] $controlResult) { $this.GetRoleAssignments() $this.LoadRBACConfig() $state = $true $scope = $this.SubscriptionContext.Scope $out = $null $foundDeprecatedAccounts = @() if($null -ne $this.DeprecatedAccounts) { foreach($depAcct in $this.DeprecatedAccounts) { try{ $out = $this.RoleAssignments | Where-Object { $_.ObjectId -eq $depAcct.ObjectId} } catch { } if($null -ne $out) { #$controlResult.AddMessage("`r`nFound deprecated account: [User] [$($depAcct.Name )] Type:[$($depAcct.ObjectType)]",$out) $foundDeprecatedAccounts += $depAcct $state = $false } } } if(-not $state) { $controlResult.EnableFixControl = $true; #$controlResult.AddMessage([VerificationResult]::Failed, "Found deprecated accounts on the subscription:", $foundDeprecatedAccounts, $true, "DeprecatedAccounts") $controlResult.SetStateData("Deprecated accounts which have access to subscription", $foundDeprecatedAccounts); $controlResult.AddMessage([VerificationResult]::Failed, "Found deprecated accounts on the subscription:", $foundDeprecatedAccounts) } else { $controlResult.VerificationResult = [VerificationResult]::Passed } return $controlResult } hidden [ControlResult] CheckNonAADAccountsRBAC([ControlResult] $controlResult) { if($this.HasGraphAPIAccess) { $this.GetRoleAssignments() Set-Variable -Name liveAccounts -Scope Local $liveAccounts = [array]($this.RoleAssignments | Where-Object {$_.SignInName -like '*#EXT#@*.onmicrosoft.com'} ) if(($liveAccounts | Measure-Object).Count -gt 0) { $controlResult.SetStateData("Non-AAD accounts which have access to subscription", $liveAccounts); $controlResult.AddMessage([VerificationResult]::Failed, "Found non-AAD account access present on the subscription:",($liveAccounts | Select-Object SignInName,DisplayName, Scope, RoleDefinitionName)) #$controlResult.AddMessage([VerificationResult]::Failed, "Found non-AAD account access present on the subscription:",($liveAccounts | Select-Object SignInName,DisplayName, Scope, RoleDefinitionName), $true, "NonAADAccounts") $controlResult.VerificationResult =[VerificationResult]::Failed } else { $controlResult.VerificationResult =[VerificationResult]::Passed } } else { $controlResult.AddMessage([VerificationResult]::Manual, "Not able to query Graph API. This has to be manually verified."); } return $controlResult } hidden [ControlResult] CheckSVCAccountsRBAC([ControlResult] $controlResult) { if($this.HasGraphAPIAccess) { $this.GetRoleAssignments() $serviceAccounts = @() if($null -ne $this.CurrentContext) { $GraphAccessToken = [Helpers]::GetAccessToken([WebRequestHelper]::GraphAPIUri) } $uniqueUsers = @(); $uniqueUsers += $this.RoleAssignments | Sort-Object SignInName -Unique | Select-Object DisplayName, SignInName,ObjectId, ObjectType $uniqueUsers | ForEach-Object{ Set-Variable -Name user -Scope Local -Value $_ Set-Variable -Name ObjectId -Scope Local -Value $_.ObjectId Set-Variable -Name SignInName -Scope Local -Value $_.SignInName Set-Variable -Name ObjectType -Scope Local -Value $_.ObjectType $isServiceAccount = [IdentityHelpers]::IsServiceAccount($_.ObjectId, $_.SignInName, $_.ObjectType, $GraphAccessToken) if($isServiceAccount) { $userScopes = $this.RoleAssignments | Where-Object {$_.SignInName -eq $SignInName} $userScopes | ForEach-Object{ Set-Variable -Name userScope -Scope Local -Value $_ $serviceAccounts += $userScope } } } if(($serviceAccounts | Measure-Object).Count -gt 0) { $serviceAccounts = $serviceAccounts | Where-Object {-not ($_.SignInName -like 'Sc-*')} } if(($serviceAccounts | Measure-Object).Count -gt 0) { $controlResult.SetStateData("Non-MFA enabled accounts present in the subscription", $serviceAccounts); #$controlResult.AddMessage([VerificationResult]::Failed, "Found non-MFA enabled accounts present on the subscription",($serviceAccounts | Select-Object Scope, DisplayName, SignInName, RoleDefinitionName, ObjectId, ObjectType), $true, "NonMFAAccounts") $controlResult.AddMessage([VerificationResult]::Failed, "Found non-MFA enabled accounts present on the subscription",($serviceAccounts | Select-Object Scope, DisplayName, SignInName, RoleDefinitionName, ObjectId, ObjectType)); } else { $controlResult.VerificationResult =[VerificationResult]::Passed } } else { $controlResult.AddMessage([VerificationResult]::Manual, "Not able to query Graph API. This has to be manually verified."); } return $controlResult } hidden [ControlResult] CheckCoAdminCount([ControlResult] $controlResult) { $this.GetRoleAssignments() Set-Variable -Name classicCoAdmins -Scope Local $classicCoAdmins = $this.RoleAssignments | Where-Object { $_.RoleDefinitionName -eq 'CoAdministrator' ` -or $_.RoleDefinitionName -like '*ServiceAdministrator*' } $count = ($classicCoAdmins | Measure-Object).Count #$controlResult.AddMessage("No. of CoAdministrators found: $count", ($classicCoAdmins | Select-Object DisplayName, Scope, ObjectType, ObjectId), $true, "CoAdminsList") $controlResult.AddMessage("No. of CoAdministrators found: $count", ($classicCoAdmins | Select-Object DisplayName, Scope, ObjectType, ObjectId)) $controlResult.SetStateData("Classic co-admins present in the subscription", $classicCoAdmins); if($count -gt $this.ControlSettings.NoOfClassicAdminsLimit) { $controlResult.VerificationResult = [VerificationResult]::Failed } else { $controlResult.VerificationResult =[VerificationResult]::Passed } return $controlResult } hidden [ControlResult] CheckManagementCertsPresence([ControlResult] $controlResult) { try { $this.GetManagementCertificates() if($this.ControlSettings.WhitelistedMgmtCerts | Get-Member -Name "Thumbprints") { $this.ManagementCertificates | ForEach-Object { Set-Variable -Name certObject -Value $_ -Scope Local if(($this.ControlSettings.WhitelistedMgmtCerts.Thumbprints | Where-Object {$_ -eq $certObject.CertThumbprint} | Measure-Object).Count -gt 0 -and $certObject.Difference.Days -le $this.ControlSettings.WhitelistedMgmtCerts.ApprovedValidityRangeInDays) { $certObject.Whitelisted = $true } } } $FilteredMgmtCerts = @(); $FilteredMgmtCerts += $this.ManagementCertificates | Where {-not $_.Whitelisted} Set-Variable -Name isCompliant -Scope Local $whitelistedMgmtCerts = @(); $whitelistedMgmtCerts += $this.ManagementCertificates | Where { $_.Whitelisted} if($whitelistedMgmtCerts.Count -gt 0) { $controlResult.AddMessage("Whitelisted management certificates on the subscription.",($whitelistedMgmtCerts | Select-Object CertThumbprint, SubjectName, Issuer, Created , ExpiryDate , IsExpired, Whitelisted)) } if($null -ne $FilteredMgmtCerts -and $FilteredMgmtCerts.Count -gt 0) { $controlResult.SetStateData("Management certificates in the subscription", $FilteredMgmtCerts); #$controlResult.AddMessage([VerificationResult]::Failed,"Found Management certificates on the subscription.",($this.ManagementCertificates | Select-Object CertThumbprint, SubjectName, Issuer, Created , ExpiryDate , IsExpired, Whitelisted), $true, "MgmtCerts") $controlResult.AddMessage([VerificationResult]::Failed,"Management certificates which needs to be removed.",($FilteredMgmtCerts | Select-Object CertThumbprint, SubjectName, Issuer, Created , ExpiryDate , IsExpired, Whitelisted)) } else { $controlResult.VerificationResult = [VerificationResult]::Passed } } catch { $controlResult.AddMessage([VerificationResult]::Manual,"You do not have required permissions to check for management certificates on this subscription.") } return $controlResult } hidden [ControlResult] CheckAzureSecurityCenterSettings([ControlResult] $controlResult) { $secCenter = [SecurityCenter]::new($this.SubscriptionContext.SubscriptionId); if ($secCenter) { $controlResult.AddMessage([MessageData]::new("Security center policies must be configured with settings mentioned below:", $secCenter.Policy.properties)); $misConfiguredPolicies = $secCenter.GetMisconfiguredPolicies(); if($misConfiguredPolicies.Count -ne 0) { $controlResult.EnableFixControl = $true; $controlResult.SetStateData("Security Center misconfigured policies", $misConfiguredPolicies); $controlResult.AddMessage([VerificationResult]::Failed, [MessageData]::new("Following security center policies are not correctly configured. Please update the policies in order to comply.", $misConfiguredPolicies)); } else { $controlResult.AddMessage([VerificationResult]::Passed, [MessageData]::new("All security center policies are correctly configured.")); } } return $controlResult } hidden [ControlResult] CheckAzureSecurityCenterAlerts([ControlResult] $controlResult) { $this.GetASCAlerts() $activeAlerts = ($this.ASCSettings.Alerts | Where-Object {$_.State -eq "Active"}) if(($activeAlerts | Measure-Object).Count -gt 0) { $controlResult.SetStateData("Active alert in Security Center", $activeAlerts); $controlResult.AddMessage([VerificationResult]::Failed,"Azure Security Center have active alerts that need to resolved.") } else { $controlResult.VerificationResult =[VerificationResult]::Passed } $controlResult.AddMessage(($activeAlerts | Select-Object State, AlertDisplayName, AlertName, Description, ReportedTimeUTC, RemediationSteps)) return $controlResult } hidden [ControlResult] CheckAzureSecurityCenterRecommendations([ControlResult] $controlResult) { $this.GetASCTasks() $requiredSecurityCenterRecommendations = @() $activeRecommendations = @() $requiredSecurityCenterRecommendations += $this.ControlSettings.RequiredSecurityCenterRecommendations $found = $false; if($null -ne $requiredSecurityCenterRecommendations -and $null -ne $this.ASCSettings.Tasks) { $this.ASCSettings.Tasks | ForEach-Object { $recommendation = $_; if(($requiredSecurityCenterRecommendations | Where-Object { $_ -eq $recommendation.Name -and $recommendation.State -eq "Active" } | Measure-Object).Count -gt 0) { $found = $true; $activeRecommendations += $_; } } } elseif($null -ne $this.ASCSettings.Tasks -and ($this.ASCSettings.Tasks | Where-Object { $_.State -eq "Active" } | Measure-Object).Count -gt 0) { $found = $true; } if($found) { $controlResult.SetStateData("Active recommendations in Security Center", $activeRecommendations); $controlResult.AddMessage([VerificationResult]::Failed,"Azure Security Center have active recommendations that need to resolved.") } else { $controlResult.VerificationResult =[VerificationResult]::Passed } $controlResult.AddMessage(($activeRecommendations | Select-Object Name, State, ResourceId)); return $controlResult } hidden [ControlResult] CheckSPNsRBAC([ControlResult] $controlResult) { if($this.HasGraphAPIAccess) { $this.GetRoleAssignments() $this.LoadRBACConfig() $scope = $this.SubscriptionContext.Scope $approvedIds = @(); $approvedIds += $this.ApprovedSPNs | Select-Object -Property ObjectId | Select-Object -ExpandProperty ObjectId; $servicePrincipalNames = $this.RoleAssignments | Where-Object {$_.ObjectType -eq "ServicePrincipal" -and ($approvedIds -notcontains $_.ObjectId ) -and ($_.RoleDefinitionName -eq "Owner" -or $_.RoleDefinitionName -eq "Contributor") -and $_.Scope -eq $scope} if(($servicePrincipalNames | Measure-Object).Count -gt 0) { $controlResult.SetStateData("Service Principals (excluding approved central accounts) having owner or contributor access on subscription", $servicePrincipalNames); $controlResult.VerificationResult = [VerificationResult]::Failed #$controlResult.AddMessage("Below is the list SPNs which have either owner or contributor access on subscription:", ($servicePrincipalNames | Select-Object DisplayName, SignInName,ObjectType), $true, "CriticalSPNs") $controlResult.AddMessage("Below is the list SPNs (excluding approved central accounts) which have either owner or contributor access on subscription:", $servicePrincipalNames) } else { $controlResult.VerificationResult =[VerificationResult]::Passed } } else { $controlResult.AddMessage([VerificationResult]::Manual, "Not able to query Graph API. This has to be manually verified."); } return $controlResult } hidden [ControlResult] CheckResourceLocksUsage([ControlResult] $controlResult) { $foundLocks = $true $lockDtls = $null #Command will throw exception if no locks found try { $lockDtls = Get-AzureRmResourceLock -ErrorAction Stop # -Scope "/subscriptions/$SubscriptionId" } catch { $foundLocks = $false } if($null -eq $lockDtls) { $foundLocks = $false } if($foundLocks) { $controlResult.SetStateData("Resource Locks on subscription", $lockDtls); #$controlResult.AddMessage([VerificationResult]::Verify, "Subscription lock details :", ($lockDtls | Select-Object Name, @{Name="Lock Level";Expression={$_.Properties.level}}, LockId, @{Name="Notes";Expression={$_.Properties.notes}} ), $true, "SubscriptionLocks") $controlResult.AddMessage([VerificationResult]::Verify, "Subscription lock details :", ($lockDtls | Select-Object Name, @{Name="Lock Level";Expression={$_.Properties.level}}, LockId, @{Name="Notes";Expression={$_.Properties.notes}} )) } else { $controlResult.AddMessage([VerificationResult]::Failed, "There are no resource locks present on the subscription."); } return $controlResult } hidden [ControlResult] CheckARMPoliciesCompliance([ControlResult] $controlResult) { $subARMPolConfig = $this.LoadServerConfigFile("Subscription.ARMPolicies.json") $output = @() $foundMandatoryPolicies = $true if($null -ne $subARMPolConfig) { $subARMPolConfig = [array]($subARMPolConfig) $subARMPolConfig | ForEach-Object{ Set-Variable -Name pol -Scope Local -Value $_ Set-Variable -Name polEnabled -Scope Local -Value $_.enabled Set-Variable -Name policyDefinitionName -Scope Local -Value $_.policyDefinitionName Set-Variable -Name tags -Scope Local -Value $_.tags $haveMatchedTags = ((($tags | Where-Object { $this.SubscriptionMandatoryTags -contains $_ }) | Measure-Object).Count -gt 0) if($polEnabled -and $haveMatchedTags) { $mandatoryPolicies = [array](Get-AzureRMPolicyAssignment | Where-Object {$_.Name -eq $policyDefinitionName}) if($null -eq $mandatoryPolicies -or ($mandatoryPolicies | Measure-Object).Count -le 0) { $foundMandatoryPolicies = $false $output += $pol } } } } if($foundMandatoryPolicies) { $controlResult.AddMessage([VerificationResult]::Passed, "Found all the mandatory policies on the Subscription."); } else { $controlResult.EnableFixControl = $true; if($controlResult.FixControlParameters) { $controlResult.FixControlParameters.Tags = $this.SubscriptionMandatoryTags; } $controlResult.SetStateData("Missing ARM policies", $output); $controlResult.AddMessage([VerificationResult]::Failed, "Some of the mandatory policies are missing which are demanded by the control tags [$([string]::Join(", ", $this.SubscriptionMandatoryTags))]", $output); } return $controlResult } hidden [ControlResult] CheckCriticalAlertsPresence([ControlResult] $controlResult) { $output = @() $subInsightsAlertsConfig = $this.LoadServerConfigFile("Subscription.InsAlerts.json") $foundRequiredAlerts = $true if($null -ne $subInsightsAlertsConfig) { $subInsightsAlertsConfig =[array]($subInsightsAlertsConfig) $AlertsPkgRG = "AzSDKAlertsRG" $alertsRG = [array] (Get-AzureRmResourceGroup | Where-Object {$_.ResourceGroupName -match "^$AlertsPkgRG"}) $configuredAlerts = $null if (($alertsRG | Measure-Object).Count -eq 1) { $configuredAlerts = Get-AzureRmAlertRule -ResourceGroup $AlertsPkgRG -WarningAction SilentlyContinue } if((($alertsRG | Measure-Object).Count -eq 1) -and ($null -ne $configuredAlerts)){ $subInsightsAlertsConfig | ForEach-Object{ Set-Variable -Name alert -Scope Local -Value $_ Set-Variable -Name alertEnabled -Scope Local -Value $_.Enabled Set-Variable -Name alertName -Scope Local -Value $_.Name Set-Variable -Name tags -Scope Local -Value $_.Tags $haveMatchedTags = ((($tags | Where-Object { $this.SubscriptionMandatoryTags -contains $_ }) | Measure-Object).Count -gt 0) if($alertEnabled -and $haveMatchedTags) { $foundAlert = [array]($configuredAlerts | Where-Object {$_.Name -eq $alertName}) if($null -eq $foundAlert -or ($foundAlert | Measure-Object).Count -le 0) { $foundRequiredAlerts = $false $output += $alert } } } } else { $foundRequiredAlerts = $false } } if($foundRequiredAlerts) { $controlResult.AddMessage([VerificationResult]::Passed, "Insights alerts has been configured on the subscription."); } else { $controlResult.EnableFixControl = $true; if($controlResult.FixControlParameters) { $controlResult.FixControlParameters.Tags = $this.SubscriptionMandatoryTags; } if($output.Count -ne 0) { $controlResult.SetStateData("Missing mandatory critical alerts", $output); } $controlResult.AddMessage([VerificationResult]::Failed, "Missing mandatory critical alerts on the subscription.", $output); } return $controlResult } hidden [ControlResult] CheckCustomRBACRolesPresence([ControlResult] $controlResult) { $this.GetRoleAssignments() $out = @() $customRoles = @(); $customRolesWithAssignment = @() $whitelistedCustomRoleIds = @(); $whitelistedCustomRoleIds += $this.ControlSettings.WhitelistedCustomRBACRoles | Select-Object -Property Id | Select-Object -ExpandProperty Id $customRoles += Get-AzureRmRoleDefinition -Custom | Where-Object { $whitelistedCustomRoleIds -notcontains $_.Id }; $customRoles | ForEach-Object { $role = $_; $roleWithAssignment = $role | Select-Object *, RoleAssignmentCount; $roleWithAssignment.RoleAssignmentCount = ($this.RoleAssignments | Where-Object { $_.RoleDefinitionId -eq $role.Id } | Measure-Object).Count; $customRolesWithAssignment += $roleWithAssignment; } if($whitelistedCustomRoleIds.Count -ne 0) { $controlResult.AddMessage("No. of whitelisted custom RBAC roles: $($whitelistedCustomRoleIds.Count)", $this.ControlSettings.WhitelistedCustomRBACRoles); } if($customRoles.Count -gt 0) { $controlResult.SetStateData("Custom RBAC definitions", $customRoles); $controlResult.AddMessage([VerificationResult]::Verify, "Found custom RBAC definitions`r`nNo. of custom RBAC roles with role assignment count: $($customRolesWithAssignment.Count)", $customRolesWithAssignment) } else { $controlResult.AddMessage([VerificationResult]::Passed, "No custom RBAC definitions found. ") } return $controlResult } hidden [ControlResult] CheckPresenceOfClassicResources([ControlResult] $controlResult) { $classicResources = [array] (Get-AzureRMResource | Where-Object {$_.ResourceType -like "*classic*"} ) if(($classicResources | Measure-Object).Count -gt 0) { $controlResult.SetStateData("Classic resources on subscription", $classicResources); #$controlResult.AddMessage([VerificationResult]::Failed, "Found classic resources on the subscription.", $classicResources, $true, "ClassicResources") $controlResult.AddMessage([VerificationResult]::Failed, "Found classic resources on the subscription.", $classicResources) } else { $controlResult.VerificationResult = [VerificationResult]::Passed } return $controlResult } hidden [ControlResult] CheckPresenceOfClassicVMs([ControlResult] $controlResult) { $classicVMResources = [array] (Find-AzureRmResource -ResourceType Microsoft.ClassicCompute/virtualMachines) if(($classicVMResources | Measure-Object).Count -gt 0) { $controlResult.SetStateData("Classic virtual machines on subscription", $classicVMResources); #$controlResult.AddMessage([VerificationResult]::Failed, "Found classic resources on the subscription.", $classicResources, $true, "ClassicResources") $controlResult.AddMessage([VerificationResult]::Failed, "Found classic virtual machines on the subscription.", $classicVMResources) } else { $controlResult.VerificationResult = [VerificationResult]::Passed } return $controlResult } hidden [ControlResult] CheckPublicIpUsage([ControlResult] $controlResult) { $publicIps = Get-AzureRmPublicIpAddress $ipFlatList = [System.Collections.ArrayList]::new() foreach($publicIp in $publicIps){ $ip = $publicIp | Select-Object ResourceGroupName, Name, Location, PublicIpAllocationMethod, IpAddress, PublicIpAddressVersion, AssociatedResourceType, AssociatedResourceId, AssociatedResourceName, Fqdn $ip.AssociatedResourceType = "Not Associated" $ip.AssociatedResourceName = "Not Associated" $ip.Fqdn = "Not Set" $ipConfig = $publicIp.IpConfiguration if($null -ne $ipConfig -and ![string]::IsNullOrWhiteSpace($ipConfig.Id)) { $ip.AssociatedResourceId = $ipConfig.Id try { $providerIndex = $ipConfig.Id.IndexOf("/providers/") $associatedResourceTypeStart = $providerIndex + 11 $associatedResourceTypeEnd = $ipConfig.Id.IndexOf("/", $ipConfig.Id.IndexOf("/", $associatedResourceTypeStart) + 1) $associatedResourceTypeLength = $associatedResourceTypeEnd - $associatedResourceTypeStart $ip.AssociatedResourceType = $ipConfig.Id.SubString($associatedResourceTypeStart, $associatedResourceTypeLength) $associatedResourceNameStart = $associatedResourceTypeEnd + 1 $associatedResourceNameLength = $ipConfig.Id.IndexOf("/", $associatedResourceNameStart) - $associatedResourceNameStart $ip.AssociatedResourceName = $ipConfig.Id.SubString($associatedResourceNameStart, $associatedResourceNameLength) } catch {} } if($null -ne $publicIp.DnsSettings -and ![string]::IsNullOrWhiteSpace($publicIp.DnsSettings.Fqdn)) { $ip.Fqdn = $publicIp.DnsSettings.Fqdn } $ipFlatList.Add($ip) | Out-Null } if($ipFlatList.Count -gt 0) { $controlResult.SetStateData("Public IPs on the subscription", $ipFlatList); $controlResult.AddMessage([VerificationResult]::Verify, "Found public IPs on the subscription.", $ipFlatList) } else { $controlResult.VerificationResult = [VerificationResult]::Passed } return $controlResult } hidden [void] LoadRBACConfig() { if($null -eq $this.MandatoryAccounts` -or $null -eq $this.ApprovedAdmins` -or $null -eq $this.DeprecatedAccounts` ) { $this.MandatoryAccounts = @() $this.ApprovedAdmins = @() $this.ApprovedSPNs = @() $subRBACConfig = $this.LoadServerConfigFile("Subscription.RBAC.json") if($null -ne $subRBACConfig) { $subRBACConfig.ValidActiveAccounts | Where-Object {$_.Enabled} | ForEach-Object{ if($_.RoleDefinitionName -eq "Owner") { $this.ApprovedAdmins += $_ } if(($_.Tags | Where-Object {$_ -eq $this.SubscriptionMandatoryTags } | Measure-Object).Count -gt 0) { $this.MandatoryAccounts += $_ } if($_.ObjectType -eq "ServicePrincipal") { $this.ApprovedSPNs += $_ } } } $this.DeprecatedAccounts = $subRBACConfig.DeprecatedAccounts | Where-Object {$_.Enabled} } } hidden [void] GetRoleAssignments() { if($null -eq $this.RoleAssignments) { $this.RoleAssignments = [RoleAssignmentHelper]::GetAzSDKRoleAssignment($true,$true) } } hidden [void] GetManagementCertificates() { $ResourceAppIdURI = [WebRequestHelper]::ClassicManagementUri; $ClassicAccessToken = [Helpers]::GetAccessToken($ResourceAppIdURI) if($null -ne $ClassicAccessToken) { $header = "Bearer " + $ClassicAccessToken $headers = @{"Authorization"=$header;"Content-Type"="application/json"; "x-ms-version" ="2013-08-01"} $uri = [string]::Format("{0}/{1}/certificates","https://management.core.windows.net",$this.SubscriptionContext.SubscriptionId) $mgmtCertsResponse = Invoke-WebRequest -Method GET -Uri $uri -Headers $headers -UseBasicParsing if($mgmtCertsResponse.StatusCode -ge 200 -and $mgmtCertsResponse.StatusCode -le 399) { if($null -ne $mgmtCertsResponse.Content) { [xml] $mgmtCerts = $mgmtCertsResponse.Content; $this.ManagementCertificates = @(); if($null -ne $mgmtCerts -and [Helpers]::CheckMember($mgmtCerts, "SubscriptionCertificates.SubscriptionCertificate")) { $this.ManagementCertificates = [ManagementCertificate]::ListManagementCertificates($mgmtCerts.SubscriptionCertificates.SubscriptionCertificate) } } } } } hidden [void] GetASCAlerts() { $ResourceAppIdURI = [WebRequestHelper]::AzureManagementUri; $AccessToken = [Helpers]::GetAccessToken($ResourceAppIdURI) if($null -ne $AccessToken) { $header = "Bearer " + $AccessToken $headers = @{"Authorization"=$header;"Content-Type"="application/json";} [SecurityCenterHelper]::RegisterResourceProvider(); $uri=[system.string]::Format("https://management.azure.com/subscriptions/{0}/providers/microsoft.Security/alerts?api-version=2015-06-01-preview",$this.SubscriptionContext.SubscriptionId) $result = "" $err = $null $output = $null try { $result = Invoke-WebRequest -Method GET -Uri $uri -Headers $headers -UseBasicParsing if($result.StatusCode -ge 200 -and $result.StatusCode -le 399){ if($null -ne $result.Content){ $json = (ConvertFrom-Json $result.Content) if($null -ne $json){ if(($json | Get-Member -Name "value")) { $output += $json.value; } else { $output += $json; } } } } } catch{ $err = $_ if($null -ne $err) { if($null -ne $err.ErrorDetails.Message){ $json = (ConvertFrom-Json $err.ErrorDetails.Message) if($null -ne $json){ $return = $json if($json.'odata.error'.code -eq "Request_ResourceNotFound") { $return = $json.'odata.error'.message } } } } } $this.ASCSettings.Alerts = [AzureSecurityCenter]::GetASCAlerts($output) } } hidden [void] GetASCTasks() { $ResourceAppIdURI = [WebRequestHelper]::AzureManagementUri; $AccessToken = [Helpers]::GetAccessToken($ResourceAppIdURI) if($null -ne $AccessToken) { $header = "Bearer " + $AccessToken $headers = @{"Authorization"=$header;"Content-Type"="application/json";} [SecurityCenterHelper]::RegisterResourceProvider(); $uri=[system.string]::Format("https://management.azure.com/subscriptions/{0}/providers/microsoft.Security/tasks?api-version=2015-06-01-preview", $this.SubscriptionContext.SubscriptionId) $result = "" $err = $null $output = $null try { $result = Invoke-WebRequest -Method GET -Uri $uri -Headers $headers -UseBasicParsing if($result.StatusCode -ge 200 -and $result.StatusCode -le 399){ if($null -ne $result.Content){ $json = (ConvertFrom-Json $result.Content) if($null -ne $json){ if($json | Get-Member -Name "value") { $output += $json.value; } else { $output += $json; } } } } } catch{ $err = $_ if($null -ne $err) { if($null -ne $err.ErrorDetails.Message){ $json = (ConvertFrom-Json $err.ErrorDetails.Message) if($null -ne $json){ $return = $json if($json.'odata.error'.code -eq "Request_ResourceNotFound") { $return = $json.'odata.error'.message } } } } } $this.ASCSettings.Tasks = [AzureSecurityCenter]::GetASCTasks($output) } } } # SIG # Begin signature block # MIIkAgYJKoZIhvcNAQcCoIIj8zCCI+8CAQExDzANBglghkgBZQMEAgEFADB5Bgor # BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG # KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCC/g87ztQ/Z0rf5 # SkIIq+QCqgHT9bzE1oVVDHwWQOCsA6CCDZMwggYRMIID+aADAgECAhMzAAAAjoeR # pFcaX8o+AAAAAACOMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMRMwEQYD # VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy # b3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25p # bmcgUENBIDIwMTEwHhcNMTYxMTE3MjIwOTIxWhcNMTgwMjE3MjIwOTIxWjCBgzEL # MAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1v # bmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjENMAsGA1UECxMETU9Q # UjEeMBwGA1UEAxMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMIIBIjANBgkqhkiG9w0B # AQEFAAOCAQ8AMIIBCgKCAQEA0IfUQit+ndnGetSiw+MVktJTnZUXyVI2+lS/qxCv # 6cnnzCZTw8Jzv23WAOUA3OlqZzQw9hYXtAGllXyLuaQs5os7efYjDHmP81LfQAEc # wsYDnetZz3Pp2HE5m/DOJVkt0slbCu9+1jIOXXQSBOyeBFOmawJn+E1Zi3fgKyHg # 78CkRRLPA3sDxjnD1CLcVVx3Qv+csuVVZ2i6LXZqf2ZTR9VHCsw43o17lxl9gtAm # +KWO5aHwXmQQ5PnrJ8by4AjQDfJnwNjyL/uJ2hX5rg8+AJcH0Qs+cNR3q3J4QZgH # uBfMorFf7L3zUGej15Tw0otVj1OmlZPmsmbPyTdo5GPHzwIDAQABo4IBgDCCAXww # HwYDVR0lBBgwFgYKKwYBBAGCN0wIAQYIKwYBBQUHAwMwHQYDVR0OBBYEFKvI1u2y # FdKqjvHM7Ww490VK0Iq7MFIGA1UdEQRLMEmkRzBFMQ0wCwYDVQQLEwRNT1BSMTQw # MgYDVQQFEysyMzAwMTIrYjA1MGM2ZTctNzY0MS00NDFmLWJjNGEtNDM0ODFlNDE1 # ZDA4MB8GA1UdIwQYMBaAFEhuZOVQBdOCqhc3NyK1bajKdQKVMFQGA1UdHwRNMEsw # SaBHoEWGQ2h0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2lvcHMvY3JsL01pY0Nv # ZFNpZ1BDQTIwMTFfMjAxMS0wNy0wOC5jcmwwYQYIKwYBBQUHAQEEVTBTMFEGCCsG # AQUFBzAChkVodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2NlcnRzL01p # Y0NvZFNpZ1BDQTIwMTFfMjAxMS0wNy0wOC5jcnQwDAYDVR0TAQH/BAIwADANBgkq # hkiG9w0BAQsFAAOCAgEARIkCrGlT88S2u9SMYFPnymyoSWlmvqWaQZk62J3SVwJR # avq/m5bbpiZ9CVbo3O0ldXqlR1KoHksWU/PuD5rDBJUpwYKEpFYx/KCKkZW1v1rO # qQEfZEah5srx13R7v5IIUV58MwJeUTub5dguXwJMCZwaQ9px7eTZ56LadCwXreUM # tRj1VAnUvhxzzSB7pPrI29jbOq76kMWjvZVlrkYtVylY1pLwbNpj8Y8zon44dl7d # 8zXtrJo7YoHQThl8SHywC484zC281TllqZXBA+KSybmr0lcKqtxSCy5WJ6PimJdX # jrypWW4kko6C4glzgtk1g8yff9EEjoi44pqDWLDUmuYx+pRHjn2m4k5589jTajMW # UHDxQruYCen/zJVVWwi/klKoCMTx6PH/QNf5mjad/bqQhdJVPlCtRh/vJQy4njpI # BGPveJiiXQMNAtjcIKvmVrXe7xZmw9dVgh5PgnjJnlQaEGC3F6tAE5GusBnBmjOd # 7jJyzWXMT0aYLQ9RYB58+/7b6Ad5B/ehMzj+CZrbj3u2Or2FhrjMvH0BMLd7Hald # G73MTRf3bkcz1UDfasouUbi1uc/DBNM75ePpEIzrp7repC4zaikvFErqHsEiODUF # he/CBAANa8HYlhRIFa9+UrC4YMRStUqCt4UqAEkqJoMnWkHevdVmSbwLnHhwCbww # ggd6MIIFYqADAgECAgphDpDSAAAAAAADMA0GCSqGSIb3DQEBCwUAMIGIMQswCQYD # VQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEe # MBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMTIwMAYDVQQDEylNaWNyb3Nv # ZnQgUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkgMjAxMTAeFw0xMTA3MDgyMDU5 # MDlaFw0yNjA3MDgyMTA5MDlaMH4xCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNo # aW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29y # cG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25pbmcgUENBIDIw # MTEwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCr8PpyEBwurdhuqoIQ # TTS68rZYIZ9CGypr6VpQqrgGOBoESbp/wwwe3TdrxhLYC/A4wpkGsMg51QEUMULT # iQ15ZId+lGAkbK+eSZzpaF7S35tTsgosw6/ZqSuuegmv15ZZymAaBelmdugyUiYS # L+erCFDPs0S3XdjELgN1q2jzy23zOlyhFvRGuuA4ZKxuZDV4pqBjDy3TQJP4494H # DdVceaVJKecNvqATd76UPe/74ytaEB9NViiienLgEjq3SV7Y7e1DkYPZe7J7hhvZ # PrGMXeiJT4Qa8qEvWeSQOy2uM1jFtz7+MtOzAz2xsq+SOH7SnYAs9U5WkSE1JcM5 # bmR/U7qcD60ZI4TL9LoDho33X/DQUr+MlIe8wCF0JV8YKLbMJyg4JZg5SjbPfLGS # rhwjp6lm7GEfauEoSZ1fiOIlXdMhSz5SxLVXPyQD8NF6Wy/VI+NwXQ9RRnez+ADh # vKwCgl/bwBWzvRvUVUvnOaEP6SNJvBi4RHxF5MHDcnrgcuck379GmcXvwhxX24ON # 7E1JMKerjt/sW5+v/N2wZuLBl4F77dbtS+dJKacTKKanfWeA5opieF+yL4TXV5xc # v3coKPHtbcMojyyPQDdPweGFRInECUzF1KVDL3SV9274eCBYLBNdYJWaPk8zhNqw # iBfenk70lrC8RqBsmNLg1oiMCwIDAQABo4IB7TCCAekwEAYJKwYBBAGCNxUBBAMC # AQAwHQYDVR0OBBYEFEhuZOVQBdOCqhc3NyK1bajKdQKVMBkGCSsGAQQBgjcUAgQM # HgoAUwB1AGIAQwBBMAsGA1UdDwQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB8GA1Ud # IwQYMBaAFHItOgIxkEO5FAVO4eqnxzHRI4k0MFoGA1UdHwRTMFEwT6BNoEuGSWh0 # dHA6Ly9jcmwubWljcm9zb2Z0LmNvbS9wa2kvY3JsL3Byb2R1Y3RzL01pY1Jvb0Nl # ckF1dDIwMTFfMjAxMV8wM18yMi5jcmwwXgYIKwYBBQUHAQEEUjBQME4GCCsGAQUF # BzAChkJodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpL2NlcnRzL01pY1Jvb0Nl # ckF1dDIwMTFfMjAxMV8wM18yMi5jcnQwgZ8GA1UdIASBlzCBlDCBkQYJKwYBBAGC # Ny4DMIGDMD8GCCsGAQUFBwIBFjNodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtp # b3BzL2RvY3MvcHJpbWFyeWNwcy5odG0wQAYIKwYBBQUHAgIwNB4yIB0ATABlAGcA # YQBsAF8AcABvAGwAaQBjAHkAXwBzAHQAYQB0AGUAbQBlAG4AdAAuIB0wDQYJKoZI # hvcNAQELBQADggIBAGfyhqWY4FR5Gi7T2HRnIpsLlhHhY5KZQpZ90nkMkMFlXy4s # PvjDctFtg/6+P+gKyju/R6mj82nbY78iNaWXXWWEkH2LRlBV2AySfNIaSxzzPEKL # UtCw/WvjPgcuKZvmPRul1LUdd5Q54ulkyUQ9eHoj8xN9ppB0g430yyYCRirCihC7 # pKkFDJvtaPpoLpWgKj8qa1hJYx8JaW5amJbkg/TAj/NGK978O9C9Ne9uJa7lryft # 0N3zDq+ZKJeYTQ49C/IIidYfwzIY4vDFLc5bnrRJOQrGCsLGra7lstnbFYhRRVg4 # MnEnGn+x9Cf43iw6IGmYslmJaG5vp7d0w0AFBqYBKig+gj8TTWYLwLNN9eGPfxxv # FX1Fp3blQCplo8NdUmKGwx1jNpeG39rz+PIWoZon4c2ll9DuXWNB41sHnIc+BncG # 0QaxdR8UvmFhtfDcxhsEvt9Bxw4o7t5lL+yX9qFcltgA1qFGvVnzl6UJS0gQmYAf # 0AApxbGbpT9Fdx41xtKiop96eiL6SJUfq/tHI4D1nvi/a7dLl+LrdXga7Oo3mXkY # S//WsyNodeav+vyL6wuA6mk7r/ww7QRMjt/fdW1jkT3RnVZOT7+AVyKheBEyIXrv # QQqxP/uozKRdwaGIm1dxVk5IRcBCyZt2WwqASGv9eZ/BvW1taslScxMNelDNMYIV # xTCCFcECAQEwgZUwfjELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24x # EDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlv # bjEoMCYGA1UEAxMfTWljcm9zb2Z0IENvZGUgU2lnbmluZyBQQ0EgMjAxMQITMwAA # AI6HkaRXGl/KPgAAAAAAjjANBglghkgBZQMEAgEFAKCBsDAZBgkqhkiG9w0BCQMx # DAYKKwYBBAGCNwIBBDAcBgorBgEEAYI3AgELMQ4wDAYKKwYBBAGCNwIBFTAvBgkq # hkiG9w0BCQQxIgQgVDWmNApWaxXM9Rc1Ni5E4yQCBVlK0D+Kr3Se5ssuddUwRAYK # KwYBBAGCNwIBDDE2MDSgEoAQAEEAegBTAEQASwAyADUAMqEegBxodHRwczovL2Fr # YS5tcy9henNka29zc2RvY3MgMA0GCSqGSIb3DQEBAQUABIIBAEKdPto4jQfcXB6k # 2G11WAOwdReDg4paxGqa5RADFVJtWP0G7aFcCWmnq8W83rZn1fllWOQZF52K92Yn # OJ9SDfjlS575WkUDMuOf2vnJrEURc+bv8J8mB84nVvIV6brBKJ7oamYpURcv6bhe # li1pojZOWcUOkMZl4xtG0dQvGGxj0IQYjLz/WHz4jD803UmXZHEwugFEwQ3tr+Xj # b4gqJ1fxj8Q4OCrnhBPr9B35kOvoBqPH1QvL7lTc08nReSVbfgSKJd2uuBGAxvc/ # tLlGCqoe7A89JWwXSjD8W/jn4ThnIpN5xIruqB9dGgNfT5oxT4oKnXe0LsOXBDzD # 7cQOZHShghNNMIITSQYKKwYBBAGCNwMDATGCEzkwghM1BgkqhkiG9w0BBwKgghMm # MIITIgIBAzEPMA0GCWCGSAFlAwQCAQUAMIIBPQYLKoZIhvcNAQkQAQSgggEsBIIB # KDCCASQCAQEGCisGAQQBhFkKAwEwMTANBglghkgBZQMEAgEFAAQgZWDO1hPaD3or # EHGE7sJxmFCBsox3wb8ZiJu1uGFKjp8CBlmSMx1T5hgTMjAxNzA5MDUwOTM3MTcu # MTU5WjAHAgEBgAIB9KCBuaSBtjCBszELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldh # c2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBD # b3Jwb3JhdGlvbjENMAsGA1UECxMETU9QUjEnMCUGA1UECxMebkNpcGhlciBEU0Ug # RVNOOjMxQzUtMzBCQS03QzkxMSUwIwYDVQQDExxNaWNyb3NvZnQgVGltZS1TdGFt # cCBTZXJ2aWNloIIO0DCCBnEwggRZoAMCAQICCmEJgSoAAAAAAAIwDQYJKoZIhvcN # AQELBQAwgYgxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYD # VQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xMjAw # BgNVBAMTKU1pY3Jvc29mdCBSb290IENlcnRpZmljYXRlIEF1dGhvcml0eSAyMDEw # MB4XDTEwMDcwMTIxMzY1NVoXDTI1MDcwMTIxNDY1NVowfDELMAkGA1UEBhMCVVMx # EzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoT # FU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUt # U3RhbXAgUENBIDIwMTAwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCp # HQ28dxGKOiDs/BOX9fp/aZRrdFQQ1aUKAIKF++18aEssX8XD5WHCdrc+Zitb8BVT # JwQxH0EbGpUdzgkTjnxhMFmxMEQP8WCIhFRDDNdNuDgIs0Ldk6zWczBXJoKjRQ3Q # 6vVHgc2/JGAyWGBG8lhHhjKEHnRhZ5FfgVSxz5NMksHEpl3RYRNuKMYa+YaAu99h # /EbBJx0kZxJyGiGKr0tkiVBisV39dx898Fd1rL2KQk1AUdEPnAY+Z3/1ZsADlkR+ # 79BL/W7lmsqxqPJ6Kgox8NpOBpG2iAg16HgcsOmZzTznL0S6p/TcZL2kAcEgCZN4 # zfy8wMlEXV4WnAEFTyJNAgMBAAGjggHmMIIB4jAQBgkrBgEEAYI3FQEEAwIBADAd # BgNVHQ4EFgQU1WM6XIoxkPNDe3xGG8UzaFqFbVUwGQYJKwYBBAGCNxQCBAweCgBT # AHUAYgBDAEEwCwYDVR0PBAQDAgGGMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgw # FoAU1fZWy4/oolxiaNE9lJBb186aGMQwVgYDVR0fBE8wTTBLoEmgR4ZFaHR0cDov # L2NybC5taWNyb3NvZnQuY29tL3BraS9jcmwvcHJvZHVjdHMvTWljUm9vQ2VyQXV0 # XzIwMTAtMDYtMjMuY3JsMFoGCCsGAQUFBwEBBE4wTDBKBggrBgEFBQcwAoY+aHR0 # cDovL3d3dy5taWNyb3NvZnQuY29tL3BraS9jZXJ0cy9NaWNSb29DZXJBdXRfMjAx # MC0wNi0yMy5jcnQwgaAGA1UdIAEB/wSBlTCBkjCBjwYJKwYBBAGCNy4DMIGBMD0G # CCsGAQUFBwIBFjFodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vUEtJL2RvY3MvQ1BT # L2RlZmF1bHQuaHRtMEAGCCsGAQUFBwICMDQeMiAdAEwAZQBnAGEAbABfAFAAbwBs # AGkAYwB5AF8AUwB0AGEAdABlAG0AZQBuAHQALiAdMA0GCSqGSIb3DQEBCwUAA4IC # AQAH5ohRDeLG4Jg/gXEDPZ2joSFvs+umzPUxvs8F4qn++ldtGTCzwsVmyWrf9efw # eL3HqJ4l4/m87WtUVwgrUYJEEvu5U4zM9GASinbMQEBBm9xcF/9c+V4XNZgkVkt0 # 70IQyK+/f8Z/8jd9Wj8c8pl5SpFSAK84Dxf1L3mBZdmptWvkx872ynoAb0swRCQi # PM/tA6WWj1kpvLb9BOFwnzJKJ/1Vry/+tuWOM7tiX5rbV0Dp8c6ZZpCM/2pif93F # SguRJuI57BlKcWOdeyFtw5yjojz6f32WapB4pm3S4Zz5Hfw42JT0xqUKloakvZ4a # rgRCg7i1gJsiOCC1JeVk7Pf0v35jWSUPei45V3aicaoGig+JFrphpxHLmtgOR5qA # xdDNp9DvfYPw4TtxCd9ddJgiCGHasFAeb73x4QDf5zEHpJM692VHeOj4qEir995y # fmFrb3epgcunCaw5u+zGy9iCtHLNHfS4hQEegPsbiSpUObJb2sgNVZl6h3M7COaY # LeqN4DMuEin1wC9UJyH3yKxO2ii4sanblrKnQqLJzxlBTeCG+SqaoxFmMNO7dDJL # 32N79ZmKLxvHIa9Zta7cRDyXUHHXodLFVeNp3lfB0d4wwP3M5k37Db9dT+mdHhk4 # L7zPWAUu7w2gUDXa7wknHNWzfjUeCLraNtvTX4/edIhJEjCCBNowggPCoAMCAQIC # EzMAAACgGph4PmbYqtcAAAAAAKAwDQYJKoZIhvcNAQELBQAwfDELMAkGA1UEBhMC # VVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNV # BAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRp # bWUtU3RhbXAgUENBIDIwMTAwHhcNMTYwOTA3MTc1NjQ4WhcNMTgwOTA3MTc1NjQ4 # WjCBszELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcT # B1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjENMAsGA1UE # CxMETU9QUjEnMCUGA1UECxMebkNpcGhlciBEU0UgRVNOOjMxQzUtMzBCQS03Qzkx # MSUwIwYDVQQDExxNaWNyb3NvZnQgVGltZS1TdGFtcCBTZXJ2aWNlMIIBIjANBgkq # hkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA52wU4jDRZ9KtBK622yI96CopNe6hQakb # U8w8PfgtvRENeMUEEQVrWEOU5GMkHWUj6cUz05BrFQ7RcEnqs+QiapTlbWMS5s9o # 1H1oNneIeM1ZtkbGvMhKOX/0a4QxOnGr9Ajeaxy3JO4fi1+H6HphTDlmr06jco1D # DXWcuw5AqgsT5SzQdaXICnaH8d0V6T7ovybN4WpSGAKTd/0PEV6J8bYIXqNTJVK+ # lWVDAPS9O6o6tEtsBFJpKi2fzjh/Hjc/OoeQsR5jHlah2GDLv9jRWkR8cIoeU/on # qawxpmsKnXysMJejJraEpcSl8bx1yMmM8BgRQeXPz9gYP00zBCOcGwIDAQABo4IB # GzCCARcwHQYDVR0OBBYEFMiwxPehFQIOYhdwOn5SNpFalHYBMB8GA1UdIwQYMBaA # FNVjOlyKMZDzQ3t8RhvFM2hahW1VMFYGA1UdHwRPME0wS6BJoEeGRWh0dHA6Ly9j # cmwubWljcm9zb2Z0LmNvbS9wa2kvY3JsL3Byb2R1Y3RzL01pY1RpbVN0YVBDQV8y # MDEwLTA3LTAxLmNybDBaBggrBgEFBQcBAQROMEwwSgYIKwYBBQUHMAKGPmh0dHA6 # Ly93d3cubWljcm9zb2Z0LmNvbS9wa2kvY2VydHMvTWljVGltU3RhUENBXzIwMTAt # MDctMDEuY3J0MAwGA1UdEwEB/wQCMAAwEwYDVR0lBAwwCgYIKwYBBQUHAwgwDQYJ # KoZIhvcNAQELBQADggEBABAJeQeXD5MucL1J1jbwVDnyQPaPP9IJdRzzWudobl06 # 1TgqE2sirzCtFR9B1MxTRbn4v+coz1BfKzXm6h77818Q9ievvSB50lpsvkMITije # +mTemKXeWyZNw2lJJztKPLCgwEyTfUadJoI8vC6Bfca1gjILJald4Grg2+Lhe6oi # YssFtwY9bkW+8sGZfrAL0CyuggARt6snq64iDIQJM9B1ATsKsMuqIi/kBE2nrpD1 # ZHT4zFZsYS6+IVefhBdcu3KRrXngpCDrLgH0H9L/KQyb2vgm4striVBVomm15MBY # aaQoLn0AZfK9ScP6vzPGmUc+G2aJzVZqqzvrZ7V9UlKhggN5MIICYQIBATCB46GB # uaSBtjCBszELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNV # BAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjENMAsG # A1UECxMETU9QUjEnMCUGA1UECxMebkNpcGhlciBEU0UgRVNOOjMxQzUtMzBCQS03 # QzkxMSUwIwYDVQQDExxNaWNyb3NvZnQgVGltZS1TdGFtcCBTZXJ2aWNloiUKAQEw # CQYFKw4DAhoFAAMVAIQVUWUii4Xre1VA0VgDHA3Fcm/AoIHCMIG/pIG8MIG5MQsw # CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u # ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMQ0wCwYDVQQLEwRNT1BS # MScwJQYDVQQLEx5uQ2lwaGVyIE5UUyBFU046NTdGNi1DMUUwLTU1NEMxKzApBgNV # BAMTIk1pY3Jvc29mdCBUaW1lIFNvdXJjZSBNYXN0ZXIgQ2xvY2swDQYJKoZIhvcN # AQEFBQACBQDdWK1ZMCIYDzIwMTcwOTA1MDQ1ODAxWhgPMjAxNzA5MDYwNDU4MDFa # MHcwPQYKKwYBBAGEWQoEATEvMC0wCgIFAN1YrVkCAQAwCgIBAAICCvkCAf8wBwIB # AAICGDswCgIFAN1Z/tkCAQAwNgYKKwYBBAGEWQoEAjEoMCYwDAYKKwYBBAGEWQoD # AaAKMAgCAQACAxbjYKEKMAgCAQACAwehIDANBgkqhkiG9w0BAQUFAAOCAQEAMiwG # xMv2sBvhEofejqAcEtUvQWYNW9NN81UIv4OySJZz08kx50azc6afv9kwoKM+hTeo # 70gH0Fi+6QFAnhEHgCmBKfaSD7NLpARgOWGdQHXkKoxVuBtBjKJp7jYXgguoYodZ # KLkePFiEgunXZI+EXsrhdmMkeAOeXZtOR0WSU4DzCRxKoeBtUiLN4ouSaQQ5+TPa # gbQrT5R5+HlsrZXQUO/Q059SJuz6nBGKY5pmZW8RFiy+lPyiNwgghDR2fOsRDjbO # 1XChhcDvrPZcECXua4zfS/M/qT48GLPJLgFt+qnj5C7xNiicVn9fMtDk4Gp++lEM # k5SJTl1ZNSPw1lpJ3TGCAvUwggLxAgEBMIGTMHwxCzAJBgNVBAYTAlVTMRMwEQYD # VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy # b3NvZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1w # IFBDQSAyMDEwAhMzAAAAoBqYeD5m2KrXAAAAAACgMA0GCWCGSAFlAwQCAQUAoIIB # MjAaBgkqhkiG9w0BCQMxDQYLKoZIhvcNAQkQAQQwLwYJKoZIhvcNAQkEMSIEICbt # b3BNI6F2k0J+F3TLf/S7NjhHAnh8liXhd0tgdxJaMIHiBgsqhkiG9w0BCRACDDGB # 0jCBzzCBzDCBsQQUhBVRZSKLhet7VUDRWAMcDcVyb8AwgZgwgYCkfjB8MQswCQYD # VQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEe # MBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQDEx1NaWNyb3Nv # ZnQgVGltZS1TdGFtcCBQQ0EgMjAxMAITMwAAAKAamHg+Ztiq1wAAAAAAoDAWBBTA # 85YJQWPAB1Gd0BMAT0WYJRkPXTANBgkqhkiG9w0BAQsFAASCAQBOGDbBRzy/voey # uoXzjMxlLIEy8ci9Te8/BCYM7Q7V2sQxi0gzAwjHe66uC8dsPYmUplR394lGsj8v # rTKpC8VsMt8VxB8YDGTpnQBtufbFeTQqXxxqsXhTEMT/ZNPXFYOSotUXUGkURDU6 # HQLkuTTFGRjKDMkzi18sy2rDQhP9ZitQ/oF3+cTbcLOxlm6249Ow1exSvOZ0Ui7Y # pnSoLoCDbn0dZ+TfqqnVjwgU6aZj+8zhA2uK9QJYCTl3QOQsNliYhQXtLx1ylz3b # nBheaxiDKabW//hyyRwm9VvOSIg1c2Qb0SEYZ9AbSyL5Cg0Wq6UId1XBonLX2Evo # u6ahmS/4 # SIG # End signature block |