Framework/Core/SubscriptionSecurity/SecurityCenter.ps1
using namespace System.Management.Automation Set-StrictMode -Version Latest class SecurityCenter: AzSKRoot { [PSObject] $PolicyObject = $null; [PSObject] $CurrentPolicyObject = $null; [bool] $PolicyAPIFail = $false; [string] $Off = "Off"; [string] $On = "On"; [string] $ContactPhoneNumber; [string] $ContactEmail; [string] $AlertNotifStatus; [string] $AlertNotifSev; [string] $NotificationRolesStatus; [string] $NotificationRoles; [string] $AutoProvisioningSettings = ""; [string] $ASCTier = ""; [string] $VMASCTier = ""; [string] $SQLASCTier = ""; [string] $AppSvcASCTier = ""; [string] $StorageASCTier = ""; [bool] $alertNotificationsstate = $false; [bool] $alertNotificationsminimalSeverity = $false; [bool] $notificationsByRolestate = $false; [bool] $notificationsByRole = $false; SecurityCenter([string] $subscriptionId,[bool]$registerASCProvider): Base($subscriptionId) { if($registerASCProvider) { [SecurityCenterHelper]::RegisterResourceProvider(); } $this.LoadPolicies(); $this.LoadCurrentPolicy(); } SecurityCenter([string] $subscriptionId): Base($subscriptionId) { [SecurityCenterHelper]::RegisterResourceProvider(); $this.LoadPolicies(); $this.LoadCurrentPolicy(); #calling this function as it would fetch the current contact phone number settings $this.CheckSecurityContactSettings(); #this function would fetch auto provisioning settings $this.CheckAutoProvisioningSettings(); #this function would fetch ASC Tier details $this.CheckASCTierSettings(); } SecurityCenter([string] $subscriptionId, [string] $securityContactEmail, [string] $securityContactPhoneNumber): Base($subscriptionId) { [SecurityCenterHelper]::RegisterResourceProvider(); $this.LoadPolicies(); $this.LoadCurrentPolicy(); #calling this function as it would fetch the current contact phone number settings $this.CheckSecurityContactSettings(); if(-not [string]::IsNullOrWhiteSpace($securityContactPhoneNumber)) { $this.ContactPhoneNumber = $securityContactPhoneNumber; } if(-not [string]::IsNullOrWhiteSpace($securityContactEmail)) { $this.ContactEmail = $securityContactEmail; } #this function would fetch auto provisioning settings $this.CheckAutoProvisioningSettings(); #this function would fetch ASC Tier details $this.CheckASCTierSettings(); } hidden [void] LoadCurrentPolicy() { if($null -ne $this.PolicyObject -and $null -ne $this.PolicyObject.policySettings) { #Fetching all the ASC initiative assignments. $policyDefinitionId = $this.PolicyObject.policySettings.properties.policyDefinitionId; try { $this.CurrentPolicyObject = Get-AzPolicyAssignment -PolicyDefinitionId $policyDefinitionId } catch { $this.PolicyAPIFail = $true; } } } hidden [void] LoadPolicies() { $this.PolicyObject = [ConfigurationManager]::LoadServerConfigFile("SecurityCenter.json"); } [MessageData[]] SetPolicies([bool] $updateProvisioningSettings, [bool] $updatePolicies, [bool] $updateSecurityContacts, [bool] $setOptionalPolicy, [bool] $SetASCTier) { [MessageData[]] $messages = @(); $this.PublishCustomMessage("Updating SecurityCenter policies...`n" + [Constants]::SingleDashLine, [MessageType]::Warning); $this.PublishCustomMessage("Updating Security Center version...", [MessageType]::Warning); $this.SetSecurityCenterVersion(); $this.PublishCustomMessage("Completed updating Security Center version.", [MessageType]::Update); if($updateProvisioningSettings) { $this.PublishCustomMessage("Updating AutoProvision settings...", [MessageType]::Warning); $this.SetAutoProvisioningSettings(); $this.PublishCustomMessage("Completed updating AutoProvision settings.", [MessageType]::Update); } if($updatePolicies) { $this.PublishCustomMessage("Updating SecurityPolicy settings...", [MessageType]::Warning); $this.SetSecurityPolicySettings(); $this.PublishCustomMessage("Completed updating SecurityPolicy settings.", [MessageType]::Update); } if($setOptionalPolicy) { $this.PublishCustomMessage("Updating optional SecurityPolicy settings...", [MessageType]::Warning); $this.SetSecurityOptionalPolicySettings(); $this.PublishCustomMessage("Completed optional SecurityPolicy settings.", [MessageType]::Update); } if($updateSecurityContacts) { $this.PublishCustomMessage("Updating SecurityContact settings...", [MessageType]::Warning); $this.SetSecurityContactSettings(); $this.PublishCustomMessage("Completed updating SecurityContact settings.", [MessageType]::Update); } if($SetASCTier) { $this.PublishCustomMessage("Updating ASC pricing tier...", [MessageType]::Warning); $this.SetASCTiers(); $this.PublishCustomMessage("Completed updating ASC pricing tier.", [MessageType]::Update); } $this.PublishCustomMessage([Constants]::SingleDashLine + "`nCompleted configuring SecurityCenter.", [MessageType]::Update); return $messages; } [MessageData[]] SetASCTiers() { [MessageData[]] $messages = @(); $AzSKRequiredResourceTypes=@() $ResourceTypesUpdateToStandard=@() #Loading ControlSettings.json file $ControlSettings = [ConfigurationManager]::LoadServerConfigFile("ControlSettings.json"); if([Helpers]::CheckMember($ControlSettings,"SubscriptionCore.ASCTier") -and ($ControlSettings.SubscriptionCore.ASCTier -eq 'Standard') -and [Helpers]::CheckMember($ControlSettings,"SubscriptionCore.Standard") ) { # Taking AzSK Supported resource types $AzSKRequiredResourceTypes = $ControlSettings.SubscriptionCore.Standard } $ResourceTierDetails = $this.CheckASCTierSettings() $ResTypeWithFreeTier = ($ResourceTierDetails.GetEnumerator() | Where-Object {$_.Value -eq "Free"}) if( -not ([string]::IsNullOrWhiteSpace($ResTypeWithFreeTier) ) ) { $FreeTierResourceTypes=$ResTypeWithFreeTier.Name if($AzSKRequiredResourceTypes -contains '*') { $ResourceTypesUpdateToStandard = $FreeTierResourceTypes } else { $ResourceTypesUpdateToStandard = @($AzSKRequiredResourceTypes | Where-Object { $FreeTierResourceTypes -contains $_ }) } $ResourceAppIdURI = [WebRequestHelper]::GetResourceManagerUrl() foreach($rsc in $ResourceTypesUpdateToStandard) { $PricingtierUri = $ResourceAppIdURI + "subscriptions/$($this.SubscriptionContext.SubscriptionId)/providers/$([SecurityCenterHelper]::ProviderNamespace)/pricings/$($rsc)?api-version=2018-06-01"; $body = '{"properties":{"pricingTier":"Standard"}}' | ConvertFrom-Json; try { [WebRequestHelper]::InvokeWebRequest([Microsoft.PowerShell.Commands.WebRequestMethod]::Put, $PricingtierUri, $body); } catch { throw [System.ArgumentException] ("Error occurred while configuring Pricing Tier."); } } } else { $this.PublishCustomMessage("All Resource types already have Standard Pricing Tier.", [MessageType]::Update); } return $messages; } [MessageData[]] SetSecurityCenterVersion() { [MessageData[]] $messages = @(); if($null -ne $this.PolicyObject -and $null -ne $this.PolicyObject.autoProvisioning) { $azskRGName = [ConfigurationManager]::GetAzSKConfigData().AzSKRGName; [ResourceGroupHelper]::SetResourceGroupTags($azskRGName,@{[Constants]::SecurityCenterConfigVersionTagName=$this.PolicyObject.Version},$false) } return $messages; } [MessageData[]] SetAutoProvisioningSettings() { [MessageData[]] $messages = @(); if($null -ne $this.PolicyObject -and $null -ne $this.PolicyObject.autoProvisioning) { $ResourceAppIdURI = [WebRequestHelper]::GetResourceManagerUrl() $autoProvisioningUri = $ResourceAppIdURI + "subscriptions/$($this.SubscriptionContext.SubscriptionId)/providers/$([SecurityCenterHelper]::ProviderNamespace)/$([SecurityCenterHelper]::AutoProvisioningSettingsApi)/default$([SecurityCenterHelper]::ApiVersionNew)"; $body = $this.PolicyObject.autoProvisioning | ConvertTo-Json -Depth 10 $body = $body.Replace("{0}",$this.SubscriptionContext.SubscriptionId) | ConvertFrom-Json; [WebRequestHelper]::InvokeWebRequest([Microsoft.PowerShell.Commands.WebRequestMethod]::Put, $autoProvisioningUri, $body); } return $messages; } [string] CheckAutoProvisioningSettings() { if($null -ne $this.PolicyObject -and $null -ne $this.PolicyObject.autoProvisioning) { $ResourceAppIdURI = [WebRequestHelper]::GetResourceManagerUrl() $autoProvisioningUri = $ResourceAppIdURI + "subscriptions/$($this.SubscriptionContext.SubscriptionId)/providers/$([SecurityCenterHelper]::ProviderNamespace)/$([SecurityCenterHelper]::AutoProvisioningSettingsApi)/default$([SecurityCenterHelper]::ApiVersionNew)"; try { $response = [WebRequestHelper]::InvokeGetWebRequest($autoProvisioningUri); if([Helpers]::CheckMember($response, "properties.autoProvision")) { $this.AutoProvisioningSettings = $response.properties.autoProvision; } } catch { #return failure status if api throws exception. return "AutoProvisioning: [ASC is either not configured or not able to fetch ASC provisioning status due to access issue]" } $autoProvisionObject = $this.PolicyObject.autoProvisioning if(-not (-not ([Helpers]::CheckMember($autoProvisionObject,"properties.autoProvision",$false)) -or ([Helpers]::CheckMember($response,"properties.autoProvision") -and ($response.properties.autoProvision -eq $autoProvisionObject.properties.autoProvision)))) { return "AutoProvisioning: [Failed]" } } return $null; } [MessageData[]] SetSecurityContactSettings() { [MessageData[]] $messages = @(); if($null -ne $this.PolicyObject -and $null -ne $this.PolicyObject.securityContacts) { $ResourceAppIdURI = [WebRequestHelper]::GetResourceManagerUrl() $securityContactsUri = $ResourceAppIdURI + "subscriptions/$($this.SubscriptionContext.SubscriptionId)/providers/$([SecurityCenterHelper]::ProviderNamespace)/$([SecurityCenterHelper]::SecurityContactsApi)/default$([SecurityCenterHelper]::NewApiVersionForSecContact)"; $body = $this.PolicyObject.securityContacts | ConvertTo-Json -Depth 10 $email = (($this.ContactEmail.split(",")).Trim()) -join ";" $body = $body.Replace("{0}",$this.SubscriptionContext.SubscriptionId).Replace("{1}",$email).Replace("{2}",$this.ContactPhoneNumber) | ConvertFrom-Json; try { [WebRequestHelper]::InvokeWebRequest([Microsoft.PowerShell.Commands.WebRequestMethod]::Put, $securityContactsUri, $body); } catch { throw [System.ArgumentException] ("Error occurred while configuring security contact settings."); } } return $messages; } [string] CheckSecurityContactSettings() { [string] $messages = @(); $ControlSettings = $this.LoadServerConfigFile("ControlSettings.json"); if($null -ne $this.PolicyObject -and $null -ne $this.PolicyObject.securityContacts) { $ResourceAppIdURI = [WebRequestHelper]::GetResourceManagerUrl() $securityContactsUri = $ResourceAppIdURI + "subscriptions/$($this.SubscriptionContext.SubscriptionId)/providers/$([SecurityCenterHelper]::ProviderNamespace)/$([SecurityCenterHelper]::SecurityContactsApi)/default$([SecurityCenterHelper]::NewApiVersionForSecContact)"; try { $response = [WebRequestHelper]::InvokeGetWebRequest($securityContactsUri); } catch { #return failure status if api throws exception. return "Security contact details is either not configured or not able to fetch configuration due to access issue." } $secContactObject = $this.PolicyObject.securityContacts #SecurityCenter Object if([Helpers]::CheckMember($response,"properties.emails") -and -not [string]::IsNullOrWhiteSpace($response.properties.emails) ` -and [Helpers]::CheckMember($response,"properties.notificationsByRole.roles") -and -not [string]::IsNullOrWhiteSpace($response.properties.notificationsByRole.roles) ` -and [Helpers]::CheckMember($response,"properties.notificationsByRole.state") -and -not [string]::IsNullOrWhiteSpace($response.properties.notificationsByRole.state) ` -and [Helpers]::CheckMember($response,"properties.alertNotifications.minimalSeverity") -and -not [string]::IsNullOrWhiteSpace($response.properties.alertNotifications.minimalSeverity) ` -and [Helpers]::CheckMember($response,"properties.alertNotifications.state") -and -not [string]::IsNullOrWhiteSpace($response.properties.alertNotifications.state)) { $this.ContactEmail = $response.properties.emails if([Helpers]::CheckMember($response,"properties.phone")) { $this.ContactPhoneNumber = $response.properties.phone } #checking if alerts are configured as expected i.e. (state = ON and Severity = "Medium or Low") if ([Helpers]::CheckMember($secContactObject,"properties.alertNotifications.state",$false) -and [Helpers]::CheckMember($secContactObject,"properties.alertNotifications.minimalSeverity",$false)) { $this.AlertNotifStatus = $response.properties.alertNotifications.state $this.AlertNotifSev = $response.properties.alertNotifications.minimalSeverity $this.alertNotificationsstate = $response.properties.alertNotifications.state -eq $secContactObject.properties.alertNotifications.state; $this.alertNotificationsminimalSeverity = $ControlSettings.SeverityForSecContactAlerts -contains $response.properties.alertNotifications.minimalSeverity; if (-not ($this.alertNotificationsstate -and $this.alertNotificationsminimalSeverity)) { $messages += "-Alert Notifications should be configured for alerts with severity "+ $secContactObject.properties.alertNotifications.minimalSeverity + " and higher.`n" } } #checking if roles for notification are configured as expected i.e. (state = ON and MinimumRoles = "Owner, ServiceAdmin") if ([Helpers]::CheckMember($secContactObject,"properties.notificationsByRole.state",$false) -and [Helpers]::CheckMember($secContactObject,"properties.notificationsByRole.roles",$false)) { $this.NotificationRolesStatus = $response.properties.notificationsByRole.state $this.NotificationRoles = $response.properties.notificationsByRole.roles $this.notificationsByRolestate = $response.properties.notificationsByRole.state -eq $secContactObject.properties.notificationsByRole.state $this.notificationsByRole = -not @($secContactObject.properties.notificationsByRole.roles| Where-Object {$response.properties.notificationsByRole.roles -notcontains $_}| Select-Object -first 1).Count if ( -not $this.notificationsByRole) { $messages += "-Roles that should be configured to receive alert notifications: $($secContactObject.properties.notificationsByRole.roles -Join ',')" } } if(-not ($this.alertNotificationsstate -and $this.alertNotificationsminimalSeverity -and $this.notificationsByRolestate -and $this.notificationsByRole)) { return $messages } } else { return "One of the configurations (Email,alertNotifications,notificationsByRole) is missing." } } return $null; } [hashtable] CheckASCTierSettings() { [string[]] $ResourceASCTier = @(); [string[]] $resourceName = @(); [hashtable] $hashvalue = @{} $ResourceUrl= [WebRequestHelper]::GetResourceManagerUrl() $validatedUri ="$ResourceUrl/subscriptions/$($this.SubscriptionContext.SubscriptionId)/providers/Microsoft.Security/pricings/default?api-version=2017-08-01-preview" try { $ascTierContentDetails = [WebRequestHelper]::InvokeGetWebRequest($validatedUri) if([Helpers]::CheckMember($ascTierContentDetails,"properties.pricingTier")) { $this.ASCTier = $ascTierContentDetails.properties.pricingTier } $validatedUri = "$ResourceUrl/subscriptions/$($this.SubscriptionContext.SubscriptionId)/providers/Microsoft.Security/pricings?api-version=2018-06-01" $ascTierResourceWiseDetails = [WebRequestHelper]::InvokeGetWebRequest($validatedUri) foreach($resourceDetails in $ascTierResourceWiseDetails) { if([Helpers]::CheckMember($resourceDetails,"properties.pricingTier")) { $resourceName = $resourceDetails.name $ResourceASCTier = $resourceDetails.properties.pricingTier $hashvalue.Add($resourceName,$ResourceASCTier) } } } catch { #TODO: need to handle it gracefully } return $hashvalue } [MessageData[]] SetSecurityPolicySettings() { [MessageData[]] $messages = @(); if($null -ne $this.PolicyObject -and $null -ne $this.PolicyObject.policySettings) { $ResourceAppIdURI = [WebRequestHelper]::GetResourceManagerUrl() $defaultPoliciesNames = Get-Member -InputObject $this.PolicyObject.policySettings.properties.parameters -MemberType NoteProperty | Select-Object Name $configuredPolicyObject = $this.PolicyObject.policySettings.properties.parameters; $this.UpdatePolicyObject(); $policyLocation = $null #Get existing Policysetting to check if Location parameter is available (since policies set via Portal have an extra Location parameter) try{ $policySettingsUri = $ResourceAppIdURI + "subscriptions/$($this.SubscriptionContext.SubscriptionId)/providers/Microsoft.Authorization/policyAssignments$([SecurityCenterHelper]::ApiVersionLatest)"; $existingsettings = [WebRequestHelper]::InvokeWebRequest([Microsoft.PowerShell.Commands.WebRequestMethod]::Get, $policySettingsUri, $null ) $scInitiative = [ConfigurationManager]::GetAzSKConfigData().AzSKSecurityCenterInitiativeName foreach ($setting in $existingsettings) { if($setting.properties.policyDefinitionId -match $scInitiative){ if([Helpers]::CheckMember($setting,'Location')) { $policyLocation = $setting.Location } } } } catch{ #eat exception, do not break existing flow } $policySettingsUri = $ResourceAppIdURI + "subscriptions/$($this.SubscriptionContext.SubscriptionId)/providers/Microsoft.Authorization/policyAssignments/SecurityCenterBuiltIn$([SecurityCenterHelper]::ApiVersionLatest)"; $body = $this.PolicyObject.policySettings | ConvertTo-Json -Depth 10 $body = $body.Replace("{0}",$this.SubscriptionContext.SubscriptionId) | ConvertFrom-Json; #If Location parameter is present in policy then append the property in the request body if ($null -ne $policyLocation) { $body | Add-Member -Name "Location" -value $policyLocation -MemberType NoteProperty } [WebRequestHelper]::InvokeWebRequest([Microsoft.PowerShell.Commands.WebRequestMethod]::Put, $policySettingsUri, $body); if($null -ne $this.CurrentPolicyObject) { [PSObject] $defaultDisabledPoliciesNames = @(); # This will store the mandatory policies name that require 'Disabled' policy effect. $defaultPoliciesNames | ForEach-Object{ $policyName = $_.Name; if($configuredPolicyObject.$policyName.value -eq "Disabled"){ $defaultDisabledPoliciesNames += $policyName; } } $this.CurrentPolicyObject | where{ if($_.ResourceType -eq 'Microsoft.Authorization/policyAssignments' -and (-not [Helpers]::CheckMember($_,'ResourceGroupName'))) #Filtering out ASC policy assignments only at subscription scope. We are currently skipping MG & RG level scoped assignments. { $currentPolicyObj = $_.Properties.parameters #For each ASC policy assignment, we will set the policies in $defaultDisabledPoliciesNames to 'Disabled' effect so that overall effect across sub is 'Disabled'. $defaultDisabledPoliciesNames | where{ $policyName = $_; if([Helpers]::CheckMember($currentPolicyObj,$policyName) -and ($currentPolicyObj.$policyName.value -ne $configuredPolicyObject.$policyName.value)) { $currentPolicyObj.$policyName.value = $configuredPolicyObject.$policyName.value } } $_.Properties.parameters = $currentPolicyObj; $policySettingsUri = $ResourceAppIdURI + "subscriptions/$($this.SubscriptionContext.SubscriptionId)/providers/Microsoft.Authorization/policyAssignments/$($_.Name)$([SecurityCenterHelper]::ApiVersionLatest)"; $body = New-Object PSObject $body | Add-Member -NotePropertyName sku -NotePropertyValue $_.sku $body | Add-Member -NotePropertyName id -NotePropertyValue $_.PolicyAssignmentId $body | Add-Member -NotePropertyName type -NotePropertyValue $_.ResourceType $body | Add-Member -NotePropertyName name -NotePropertyValue $_.Name $body | Add-Member -NotePropertyName properties -NotePropertyValue $_.properties #If Location parameter is present in policy then append the property in the request body if($body.properties.policyDefinitionId -match $scInitiative) { if ($null -ne $policyLocation) { $body | Add-Member -NotePropertyName Location -NotePropertyValue $policyLocation } } [WebRequestHelper]::InvokeWebRequest([Microsoft.PowerShell.Commands.WebRequestMethod]::Put, $policySettingsUri, $body); } else { $this.PublishCustomMessage("Skipping policy validation & update for the assignment [$($_.ResourceId)]. This support will be added in an upcoming release." , [MessageType]::Warning); } } } } return $messages; } [MessageData[]] SetSecurityOptionalPolicySettings() { [MessageData[]] $messages = @(); if($null -ne $this.PolicyObject -and $null -ne $this.PolicyObject.optionalPolicySettings) { $ResourceAppIdURI = [WebRequestHelper]::GetResourceManagerUrl() $optionalPoliciesNames = Get-Member -InputObject $this.PolicyObject.optionalPolicySettings.properties.parameters -MemberType NoteProperty | Select-Object Name $configuredOptionalPolicyObject = $this.PolicyObject.optionalPolicySettings.properties.parameters; $this.UpdateOptionalPolicyObject(); $policySettingsUri = $ResourceAppIdURI + "subscriptions/$($this.SubscriptionContext.SubscriptionId)/providers/Microsoft.Authorization/policyAssignments/SecurityCenterBuiltIn$([SecurityCenterHelper]::ApiVersionLatest)"; $body = $this.PolicyObject.optionalPolicySettings | ConvertTo-Json -Depth 10 $body = $body.Replace("{0}",$this.SubscriptionContext.SubscriptionId) | ConvertFrom-Json; [WebRequestHelper]::InvokeWebRequest([Microsoft.PowerShell.Commands.WebRequestMethod]::Put, $policySettingsUri, $body); if($null -ne $this.CurrentPolicyObject) { [PSObject] $optionalDisabledPoliciesNames = @(); # This will store the optional policies name that require 'Disabled' policy effect. $optionalPoliciesNames | ForEach-Object{ $policyName = $_.Name; if($configuredOptionalPolicyObject.$policyName.value -eq "Disabled"){ $optionalDisabledPoliciesNames += $policyName; } } $this.CurrentPolicyObject | where{ if($_.ResourceType -eq 'Microsoft.Authorization/policyAssignments' -and (-not [Helpers]::CheckMember($_,'ResourceGroupName'))) #Filtering out ASC policy assignments only at subscription scope. We are currently skipping MG & RG level scoped assignments. { $currentPolicyObj = $_.Properties.parameters #For each ASC policy assignment, we will set the policies in $optionalDisabledPoliciesNames to 'Disabled' effect so that overall effect across sub is 'Disabled'. $optionalDisabledPoliciesNames | where{ $policyName = $_; if([Helpers]::CheckMember($currentPolicyObj,$policyName) -and ($currentPolicyObj.$policyName.value -ne $configuredOptionalPolicyObject.$policyName.value)) { $currentPolicyObj.$policyName.value = $configuredOptionalPolicyObject.$policyName.value } } $_.Properties.parameters = $currentPolicyObj; $policySettingsUri = $ResourceAppIdURI + "subscriptions/$($this.SubscriptionContext.SubscriptionId)/providers/Microsoft.Authorization/policyAssignments/$($_.Name)$([SecurityCenterHelper]::ApiVersionLatest)"; $body = New-Object PSObject $body | Add-Member -NotePropertyName sku -NotePropertyValue $_.sku $body | Add-Member -NotePropertyName id -NotePropertyValue $_.PolicyAssignmentId $body | Add-Member -NotePropertyName type -NotePropertyValue $_.ResourceType $body | Add-Member -NotePropertyName name -NotePropertyValue $_.Name $body | Add-Member -NotePropertyName properties -NotePropertyValue $_.properties [WebRequestHelper]::InvokeWebRequest([Microsoft.PowerShell.Commands.WebRequestMethod]::Put, $policySettingsUri, $body); } else { $this.PublishCustomMessage("Skipping policy validation & update for the assignment [$($_.ResourceId)]. This support will be added in an upcoming release." , [MessageType]::Warning); } } } } return $messages; } [string[]] CheckSecurityPolicySettings() { if($null -ne $this.PolicyObject -and $null -ne $this.PolicyObject.policySettings) { return $this.ValidatePolicyObject(); } return $null; } [string[]] CheckOptionalSecurityPolicySettings() { if($null -ne $this.PolicyObject -and $null -ne $this.PolicyObject.optionalPolicySettings) { return $this.ValidateOptionalPolicyObject(); } return $null; } [void] UpdatePolicyObject() { if($null -ne $this.CurrentPolicyObject -and $null -ne $this.PolicyObject.policySettings) { $ASCAssignment = $this.CurrentPolicyObject | where {$_.Name -eq $this.PolicyObject.policySettings.name} $ASCcount = ($ASCAssignment | Measure-Object).Count if($ASCcount -eq 1) { $currentPolicyObj = $ASCAssignment.Properties.parameters; $defaultPoliciesNames = Get-Member -InputObject $this.PolicyObject.policySettings.properties.parameters -MemberType NoteProperty | Select-Object Name $configuredPolicyObject = $this.PolicyObject.policySettings.properties.parameters; $defaultPoliciesNames | ForEach-Object { $policyName = $_.Name; if([Helpers]::CheckMember($currentPolicyObj,$policyName)) { $currentPolicyObj.$policyName.value = $configuredPolicyObject.$policyName.value }else { $currentPolicyObj | Add-Member -NotePropertyName $policyName -NotePropertyValue $configuredPolicyObject.$policyName } } $this.PolicyObject.policySettings.properties.parameters = $currentPolicyObj; } } } [void] UpdateOptionalPolicyObject() { if($null -ne $this.CurrentPolicyObject -and $null -ne $this.PolicyObject.optionalPolicySettings) { $ASCAssignment = $this.CurrentPolicyObject | where {$_.Name -eq $this.PolicyObject.policySettings.name} $ASCcount = ($ASCAssignment | Measure-Object).Count if($ASCcount -eq 1) { $currentPolicyObj = $ASCAssignment.Properties.parameters; $optionalPoliciesNames = Get-Member -InputObject $this.PolicyObject.optionalPolicySettings.properties.parameters -MemberType NoteProperty | Select-Object Name $configuredOptionalPolicyObject = $this.PolicyObject.optionalPolicySettings.properties.parameters; $optionalPoliciesNames | ForEach-Object { $policyName = $_.Name; if([Helpers]::CheckMember($currentPolicyObj,$policyName)) { $currentPolicyObj.$policyName.value = $configuredOptionalPolicyObject.$policyName.value }else { $currentPolicyObj | Add-Member -NotePropertyName $policyName -NotePropertyValue $configuredOptionalPolicyObject.$policyName } } $this.PolicyObject.optionalPolicySettings.properties.parameters = $currentPolicyObj; } } } [string[]] ValidatePolicyObject() { [string[]] $MisConfiguredPolicies = @(); if($null -ne $this.CurrentPolicyObject -and $null -ne $this.PolicyObject.policySettings) { $subPolicy = $this.CurrentPolicyObject | where {$_.ResourceType -eq 'Microsoft.Authorization/policyAssignments' -and (-not [Helpers]::CheckMember($_,'ResourceGroupName'))} #Filtering out ASC policy assignments only at subscription scope. We are currently skipping MG & RG level scoped assignments. $assignCount = ($subPolicy | Measure-Object).Count # Total no. of ASC initiative assignments (duplicates). $defaultPoliciesNames = Get-Member -InputObject $this.PolicyObject.policySettings.properties.parameters -MemberType NoteProperty | Select-Object Name $configuredPolicyObject = $this.PolicyObject.policySettings.properties.parameters; $defaultPoliciesNames | ForEach-Object { $policyName = $_.Name; $counter = 0; # count of misconfigured instances of the policy under iteration. $polNotFoundCounter = 0; # count of assignments in which the policy under iteration is absent. if($configuredPolicyObject.$policyName.value -ne "Disabled") # If the desired effect of a policy is not "Disabled" i.e. "Audit/AuditIfNotExists", then atleast one initiative assignment should have the correct effect so that the policy is correctly configured on the sub. { $enabledAssignments = $subPolicy | where{[Helpers]::CheckMember($_.Properties.parameters,$policyName) -and $_.Properties.parameters.$policyName.value -eq $configuredPolicyObject.$policyName.value} $counter = ($enabledAssignments | Measure-Object).Count if($counter -eq 0) # If policy is absent/misconfigured in all the assignments, then the overall effect of the policy is opposite of what is required (Here "Audit/AuditIfNotExists"). { $MisConfiguredPolicies += ("Misconfigured Mandatory Policy: [" + $policyName + "]"); } } elseif($configuredPolicyObject.$policyName.value -eq "Disabled") # If the desired effect of a policy is "Disabled", then no initiative assignments should have a stronger effect (Audit / AuditIfNotExists) for the policy under consideration. { $enabledAssignments = $subPolicy | where{[Helpers]::CheckMember($_.Properties.parameters,$policyName) -and $_.Properties.parameters.$policyName.value -ne $configuredPolicyObject.$policyName.value} $counter = ($enabledAssignments | Measure-Object).Count if($counter -gt 0) # This means in atleast one assignment, the effect is not 'Disabled', i.e it is "Audit/AuditIfNotExists" making the stronger effect win. { $MisConfiguredPolicies += ("Misconfigured Mandatory Policy: [" + $policyName + "]"); } else # This means either the policy is absent in all the assignments or it is configured correctly wherever present. { $absentAssignments = $subPolicy | where{-not [Helpers]::CheckMember($_.Properties.parameters,$policyName)} $polNotFoundCounter = ($absentAssignments | Measure-Object).Count if($polNotFoundCounter -eq $assignCount) # This means policy is absent across all assignments meaning it is not in effect on the sub. { $MisConfiguredPolicies += ("Misconfigured Mandatory Policy: [" + $policyName + "]"); } } } } } elseif($null -eq $this.CurrentPolicyObject -and $null -ne $this.PolicyObject.policySettings) { if($this.PolicyAPIFail) { $MisConfiguredPolicies += ("Mandatory ASC Policies information can't be fetched beacuse either mandatory ASC policies are not configured or due to API access failure."); } else { $MisConfiguredPolicies += ("Mandatory ASC Policies are not configured"); } } return $MisConfiguredPolicies; } [string[]] ValidateOptionalPolicyObject() { [string[]] $MisConfiguredOptionalPolicies = @(); if($null -ne $this.CurrentPolicyObject -and $null -ne $this.PolicyObject.optionalPolicySettings) { $subPolicy = $this.CurrentPolicyObject | where {$_.ResourceType -eq 'Microsoft.Authorization/policyAssignments' -and (-not [Helpers]::CheckMember($_,'ResourceGroupName'))} #Filtering out ASC policy assignments only at subscription scope. We are currently skipping MG & RG level scoped assignments. $assignCount = ($subPolicy | Measure-Object).Count # Total no. of ASC initiative assignments (duplicates). $optionalPoliciesNames = Get-Member -InputObject $this.PolicyObject.optionalPolicySettings.properties.parameters -MemberType NoteProperty | Select-Object Name $configuredOptionalPolicyObject = $this.PolicyObject.optionalPolicySettings.properties.parameters; $optionalPoliciesNames | ForEach-Object { $policyName = $_.Name; $counter = 0; # count of misconfigured instances of the policy under iteration. $polNotFoundCounter = 0; # count of assignments in which the policy under iteration is absent. if($configuredOptionalPolicyObject.$policyName.value -ne "Disabled") # If the desired effect of a policy is not "Disabled" i.e. "Audit/AuditIfNotExists", then atleast one initiative assignment should have the correct effect so that the policy is correctly configured on the sub. { $enabledAssignments = $subPolicy | where{[Helpers]::CheckMember($_.Properties.parameters,$policyName) -and $_.Properties.parameters.$policyName.value -eq $configuredOptionalPolicyObject.$policyName.value} $counter = ($enabledAssignments | Measure-Object).Count if($counter -eq 0) # If policy is absent/misconfigured in all the assignments, then the overall effect of the policy is opposite of what is required (Here "Audit/AuditIfNotExists"). { $MisConfiguredOptionalPolicies += ("Misconfigured Optional Policy: [" + $policyName + "]"); } } elseif($configuredOptionalPolicyObject.$policyName.value -eq "Disabled") # If the desired effect of a policy is "Disabled", then no initiative assignments should have a stronger effect (Audit / AuditIfNotExists) for the policy under consideration. { $enabledAssignments = $subPolicy | where{[Helpers]::CheckMember($_.Properties.parameters,$policyName) -and $_.Properties.parameters.$policyName.value -ne $configuredOptionalPolicyObject.$policyName.value} $counter = ($enabledAssignments | Measure-Object).Count if($counter -gt 0) # This means in atleast one assignment, the effect is not 'Disabled', i.e it is "Audit/AuditIfNotExists" making the stronger effect win. { $MisConfiguredOptionalPolicies += ("Misconfigured Optional Policy: [" + $policyName + "]"); } else # This means either the policy is absent in all the assignments or it is configured correctly wherever present. { $absentAssignments = $subPolicy | where{-not [Helpers]::CheckMember($_.Properties.parameters,$policyName)} $polNotFoundCounter = ($absentAssignments | Measure-Object).Count if($polNotFoundCounter -eq $assignCount) # This means policy is absent across all assignments meaning it is not in effect on the sub. { $MisConfiguredOptionalPolicies += ("Misconfigured Optional Policy: [" + $policyName + "]"); } } } } } elseif($null -eq $this.CurrentPolicyObject -and $null -ne $this.PolicyObject.optionalPolicySettings) { if($this.PolicyAPIFail) { $MisConfiguredOptionalPolicies += ("Optional ASC Policies information can't be fetched beacuse either mandatory ASC policies are not configured or due to API access failure."); } else { $MisConfiguredOptionalPolicies += ("Optional ASC Policies are not configured"); } } return $MisConfiguredOptionalPolicies; } # Get SecurtiySolution details for the subscription [PSObject[]] GetASCSecuritySolutionsDetails() { $SecuritySolutionsDetails = @(); $ResourceAppIdURI = [WebRequestHelper]::GetResourceManagerUrl() $securitySolutionsUri = $ResourceAppIdURI + "subscriptions/$($this.SubscriptionContext.SubscriptionId)/providers/$([SecurityCenterHelper]::ProviderNamespace)/securitySolutions?api-version=2015-06-01-preview"; try { $SecuritySolutionsDetails += [WebRequestHelper]::InvokeWebRequest("Get", $securitySolutionsUri, $null); } catch { #eat exception, do not break existing flow } return $SecuritySolutionsDetails; } } |