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) } } } |