Framework/Core/AzSKInfo/ComplianceInfo.ps1
using namespace System.Management.Automation Set-StrictMode -Version Latest class ComplianceInfo: AzCommandBase { hidden [ComplianceMessageSummary[]] $ComplianceMessageSummary = @(); hidden [ComplianceResult[]] $ComplianceScanResult = @(); hidden [string] $SubscriptionId hidden [bool] $Full hidden $baselineControls = @(); hidden [PSObject] $ControlSettings hidden [PSObject] $EmptyResource = @(); ComplianceInfo([string] $subscriptionId, [InvocationInfo] $invocationContext, [bool] $full): Base($subscriptionId, $invocationContext) { $this.SubscriptionId = $subscriptionId $this.Full = $full $this.ControlSettings = $this.LoadServerConfigFile("ControlSettings.json"); } hidden [void] GetComplianceScanData($UseBaselineControls, $UsePreviewBaselineControls) { $ComplianceRptHelper = [ComplianceReportHelper]::new($this.SubscriptionContext, $this.GetCurrentModuleVersion()); $ComplianceReportData = $null if ($ComplianceRptHelper.HaveRequiredPermissions() -and -not($UseBaselineControls -or $UsePreviewBaselineControls)) { $ComplianceReportData = $ComplianceRptHelper.GetSubscriptionComplianceReport(); } else { $selectColumns = New-Object System.Collections.ArrayList; $query = ""; $filterSr = 0 if ($UsePreviewBaselineControls) { $selectColumns.Add('IsPreviewBaselineControl%20eq%20true') } if ($UseBaselineControls ) { $selectcolumns.Add('IsBaselineControl%20eq%20true') } if (($selectColumns | Measure-Object).Count -gt 0) { $selectColumns.ToArray()| ForEach-Object { if (-not $filterSr) { $query+=$_ ++$filterSr } else { $query+="%20or%20"+$_ ++$filterSr } } } $ComplianceReportData = $ComplianceRptHelper.GetSubscriptionComplianceReport($query, $null); } $this.ComplianceScanResult = @(); if (($ComplianceReportData | Measure-Object).Count -gt 0) { $ComplianceReportData | ForEach-Object { $this.ComplianceScanResult += [ComplianceResult]::new($_); } } } hidden [void] GetComplianceInfo($UseBaselineControls, $UsePreviewBaselineControls) { $this.PublishCustomMessage([Constants]::DoubleDashLine, [MessageType]::Default); $azskConfig = [ConfigurationManager]::GetAzSKConfigData(); $settingStoreComplianceSummaryInUserSubscriptions = [ConfigurationManager]::GetAzSKSettings().StoreComplianceSummaryInUserSubscriptions; #return if feature is turned off at server config if (-not $azskConfig.StoreComplianceSummaryInUserSubscriptions -and -not $settingStoreComplianceSummaryInUserSubscriptions) { $this.PublishCustomMessage("NOTE: This feature is currently disabled in your environment. Please contact the cloud security team for your org. ", [MessageType]::Warning); return; } $this.PublishCustomMessage([Constants]::DoubleDashLine, [MessageType]::Default); $this.PublishCustomMessage("`r`nFetching compliance info for subscription [" + $this.SubscriptionId + "] ...", [MessageType]::Default); $this.PublishCustomMessage([Constants]::SingleDashLine, [MessageType]::Default); $this.GetComplianceScanData($UseBaselineControls, $UsePreviewBaselineControls); if (($this.ComplianceScanResult | Measure-Object).Count -le 0) { $this.PublishCustomMessage("Could not find compliance data that matches specified criteria. Try with a more permissive switch.", [MessageType]::Default); return; } #$this.GetControlDetails(); $this.ComputeCompliance(); $this.GetComplianceSummary($UseBaselineControls, $UsePreviewBaselineControls) $this.ExportComplianceResultCSV() } hidden [void] ComputeCompliance() { $this.ComplianceScanResult | ForEach-Object { # ToDo: Add condition to check whether control in grace if ($_.FeatureName -eq "AzSKCfg" -or $_.VerificationResult -eq [VerificationResult]::Disabled -or $_.VerificationResult -eq [VerificationResult]::Error) { $_.EffectiveResult = [VerificationResult]::Skipped } elseif (-not [string]::IsNullOrEmpty($_.ChildResourceName)) { $_.EffectiveResult = [VerificationResult]::Skipped } else { if ($_.VerificationResult -eq [VerificationResult]::Passed) { $_.EffectiveResult = ([VerificationResult]::Passed).ToString(); $lastScannedDate = [datetime] $_.LastScannedOn $days = [DateTime]::UtcNow.Subtract($lastScannedDate).Days [int]$allowedDays = [Constants]::ControlResultComplianceInDays if (($null -ne $this.ControlSettings) -and [Helpers]::CheckMember($this.ControlSettings, "ResultComplianceInDays.DefaultControls")) { [int32]::TryParse($this.ControlSettings.ResultComplianceInDays.DefaultControls, [ref]$allowedDays) } if ($_.HasOwnerAccessTag) { if (($null -ne $this.ControlSettings) -and [Helpers]::CheckMember($this.ControlSettings, "ResultComplianceInDays.OwnerAccessControls")) { [int32]::TryParse($this.ControlSettings.ResultComplianceInDays.OwnerAccessControls, [ref]$allowedDays) } } #revert back to actual result if control result is stale if ($days -ge $allowedDays) { $_.EffectiveResult = ([VerificationResult]::Failed).ToString(); } } else { $_.EffectiveResult = ([VerificationResult]::Failed).ToString(); } } } } hidden [void] GetComplianceSummary($UseBaselineControls, $UsePreviewBaselineControls) { $totalCompliance = 0.0 $baselineCompliance = 0.0 $passControlCount = 0 $passControlCountwithGrace = 0 $failedControlCount = 0 $failedControlCountwithGrace = 0 $baselinePassedControlCount = 0 $baselinePassedControlCountWithGrace = 0 $baselineFailedControlCount = 0 $baselineFailedControlCountWithGrace = 0 $previewBaselinePassedControlCount = 0 $previewBaselinePassedControlCountWithGrace = 0 $previewBaselineFailedControlCount = 0 $previewBaselineFailedControlCountWithGrace = 0 $attestedControlCount = 0 $totalControlCount = 0 $baselineControlCount = 0 $baselineControlCountWithGrace = 0 $previewBaselineControlCount = 0 $previewBaselineControlCountWithGrace = 0 $attestedControlCount = 0 $gracePeriodControlCount = 0 $totalComplianceWithGrace = 0.0 if (($this.ComplianceScanResult | Measure-Object).Count -gt 0) { $this.ComplianceScanResult | ForEach-Object { $result = $_ #ideally every proper control should fall under effective result in passed/failed/skipped if ($result.EffectiveResult -eq [VerificationResult]::Passed -or $result.EffectiveResult -eq [VerificationResult]::Failed) { # total count has been kept inside to exclude not-scanned and skipped controls $totalControlCount++ if ($result.IsControlInGrace -eq $false) { if ($result.EffectiveResult -eq [VerificationResult]::Passed) { $passControlCountWithGrace++ #baseline controls condition shouldnot increment if it wont fall in passed/ failed state if ($result.IsBaselineControl -eq "True") { $baselineControlCountWithGrace++ $baselinePassedControlCountWithGrace++ } if ($result.IsPreviewBaselineControl -eq "True") { $previewBaselineControlCountWithGrace++ $previewBaselinePassedControlCountWithGrace++ } } elseif ($result.EffectiveResult -eq [VerificationResult]::Failed) { $failedControlCountwithGrace++ if ($result.IsBaselineControl -eq "True") { $baselineControlCountWithGrace++ $baselineFailedControlCountWithGrace++ } if ($result.IsPreviewBaselineControl -eq "True") { $previewBaselineControlCountWithGrace++ $previewBaselineFailedControlCountWithGrace++ } } } else { if ($result.EffectiveResult -eq [VerificationResult]::Passed) { $passControlCount++ #baseline controls condition shouldnot increment if it wont fall in passed/ failed state if ($result.IsBaselineControl -eq "True") { $baselineControlCount++ $baselinePassedControlCount++ } if ($result.IsPreviewBaselineControl -eq "True") { $previewBaselineControlCount++ $previewBaselinePassedControlCount++ } } elseif ($result.EffectiveResult -eq [VerificationResult]::Failed) { $failedControlCount++ if ($result.IsBaselineControl -eq "True") { $baselineControlCount++ $baselineFailedControlCount++ } if ($result.IsBaselineControl -eq "True") { $previewBaselineControlCount++ $previewBaselineFailedControlCount++ } } } } if (-not [string]::IsNullOrEmpty($result.AttestationStatus) -and ($result.AttestationStatus -ne [AttestationStatus]::None)) { $attestedControlCount++ } if ($result.IsControlInGrace -eq $true) { $gracePeriodControlCount++ } } if (($passControlCountwithGrace + $failedControlCountwithGrace) -ne 0) { $totalComplianceWithGrace = (100 * $passControlCountwithGrace) / ($passControlCountwithGrace + $failedControlCountwithGrace) } else { $totalComplianceWithGrace = 0; } if (($totalControlCount) -ne 0) { $totalCompliance = (100 * ($passControlCountwithGrace + $passControlCount) / $totalControlCount) } $ComplianceStats = @(); $summary= @(); if ($UseBaselineControls -and ($baselinePassedControlCountWithGrace + $baselineFailedControlCountWithGrace) -ne 0) { $ComplianceStat = "" | Select-Object "ComplianceType", "Total Passed", "Total Failed" , "Passed (excl. grace)", "Failed (excl. grace)" $ComplianceStat.ComplianceType = "Baseline" $ComplianceStat."Total Passed" = ($baselinePassedControlCountWithGrace + $baselinePassedControlCount) $ComplianceStat."Total Failed" = ($baselineFailedControlCount + $baselineFailedControlCountWithGrace) $ComplianceStat."Passed (excl. grace)" = $baselinePassedControlCountWithGrace $ComplianceStat."Failed (excl. grace)" = $baselineFailedControlCountWithGrace $ComplianceStats += $ComplianceStat $summary+="`r`nTotal baseline controls: " + ($baselineControlCount + $baselineControlCountWithGrace) } if ($UsePreviewBaselineControls -and ($previewBaselinePassedControlCountWithGrace + $previewBaselineFailedControlCountWithGrace) -ne 0) { $ComplianceStat = "" | Select-Object "ComplianceType", "Total Passed", "Total Failed" , "Passed (excl. grace)", "Failed (excl. grace)" $ComplianceStat.ComplianceType = "PreviewBaseline" $ComplianceStat."Total Passed" += ($previewBaselinePassedControlCountWithGrace + $previewBaselinePassedControlCount) $ComplianceStat."Total Failed" += ($previewBaselineFailedControlCount + $previewBaselineFailedControlCountWithGrace) $ComplianceStat."Passed (excl. grace)" += $previewBaselinePassedControlCountWithGrace $ComplianceStat."Failed (excl. grace)" += $previewBaselineFailedControlCountWithGrace $ComplianceStats += $ComplianceStat $summary+="`r`nTotal PreviewBaseline controls: " + ($previewBaselineControlCount + $previewBaselineControlCountWithGrace) } if(-not ($UsePreviewBaselineControls -or $UseBaselineControls )){ $ComplianceStat = "" | Select-Object "ComplianceType", "Total Passed", "Total Failed" , "Passed (excl. grace)", "Failed (excl. grace)" $ComplianceStat.ComplianceType = "Full" $ComplianceStat."Total Passed" += ($passControlCountwithGrace + $passControlCount) $ComplianceStat."Total Failed" += ($failedControlCountwithGrace + $failedControlCount) $ComplianceStat."Passed (excl. grace)" += ($passControlCountwithGrace) $ComplianceStat."Failed (excl. grace)" += ($failedControlCountwithGrace) $ComplianceStats += $ComplianceStat $summary+= "`r`nTotal controls: " + $totalControlCount } $this.PublishCustomMessage(($ComplianceStats | Format-Table | Out-String -Width 2048), [MessageType]::Default) $this.PublishCustomMessage([Constants]::SingleDashLine, [MessageType]::Default); $this.PublishCustomMessage($summary, [MessageType]::Default) $this.PublishCustomMessage("`r`nAttested controls: " + $attestedControlCount , [MessageType]::Default); $this.PublishCustomMessage("`r`nControls in grace: " + $gracePeriodControlCount , [MessageType]::Default); $this.PublishCustomMessage([Constants]::DoubleDashLine, [MessageType]::Default); $this.PublishCustomMessage("`r`n`r`n`r`nDisclaimer: Compliance summary/control counts may differ slightly from the central telemetry/dashboard due to various timing/sync lags.", [MessageType]::Default); } } hidden [void] GetControlsInGracePeriod() { $this.PublishCustomMessage("List of control in grace period", [MessageType]::Default); } hidden [void] ExportComplianceResultCSV() { $this.ComplianceScanResult | ForEach-Object { if ($_.IsBaselineControl -eq "True") { $_.IsBaselineControl = "Yes" } else { $_.IsBaselineControl = "No" } if ($_.IsPreviewBaselineControl -eq "True") { $_.IsPreviewBaselineControl = "Yes" } else { $_.IsPreviewBaselineControl = "No" } if ($_.IsPreviewBaselineControl -eq $true) { $_.IsControlInGrace = "Yes" } else { $_.IsControlInGrace = "No" } if ($_.AttestationStatus.ToLower() -eq "none") { $_.AttestationStatus = "" } if ($_.HasOwnerAccessTag -eq "True") { $_.HasOwnerAccessTag = "Yes" } else { $_.HasOwnerAccessTag = "No" } $ControlSeverity = $_.ControlSeverity if ([Helpers]::CheckMember($this.ControlSettings, "ControlSeverity.$ControlSeverity")) { $_.ControlSeverity = $this.ControlSettings.ControlSeverity.$ControlSeverity } else { $_.ControlSeverity = $ControlSeverity } } $objectToExport = $this.ComplianceScanResult if (-not $this.Full) { $objectToExport = $this.ComplianceScanResult | Select-Object "ControlId", "VerificationResult", "ActualVerificationResult", "FeatureName", "ResourceGroupName", "ResourceName", "ChildResourceName", "IsBaselineControl", "IsPreviewBaselineControl", ` "ControlSeverity", "AttestationStatus", "AttestedBy", "Justification", "LastScannedOn", "ScanSource", "ScannedBy", "ScannerModuleName", "ScannerVersion", "IsControlInGrace" } $controlCSV = New-Object -TypeName WriteCSVData $controlCSV.FileName = 'ComplianceDetails_' + $this.RunIdentifier $controlCSV.FileExtension = 'csv' $controlCSV.FolderPath = '' $controlCSV.MessageData = $objectToExport $this.PublishAzSKRootEvent([AzSKRootEvent]::WriteCSV, $controlCSV); } AddComplianceMessage([string] $ComplianceType, [string] $ComplianceCount, [string] $ComplianceComment) { $ComplianceMessage = New-Object -TypeName ComplianceMessageSummary $ComplianceMessage.ComplianceType = $ComplianceType $ComplianceMessage.ComplianceCount = $ComplianceCount $this.ComplianceMessageSummary += $ComplianceMessage } hidden [void] UpdateStorageComplianceData() { $azskConfig = [ConfigurationManager]::GetAzSKConfigData(); $settingStoreComplianceSummaryInUserSubscriptions = [ConfigurationManager]::GetAzSKSettings().StoreComplianceSummaryInUserSubscriptions; if (-not $azskConfig.StoreComplianceSummaryInUserSubscriptions -and -not $settingStoreComplianceSummaryInUserSubscriptions) { $this.PublishCustomMessage("NOTE: This feature is currently disabled in your environment. Please contact the cloud security team for your org. ", [MessageType]::Warning); return; } $ComplianceRptHelper = [ComplianceReportHelper]::new($this.SubscriptionContext, $this.GetCurrentModuleVersion()); $this.PublishCustomMessage("Fetching data from backend. This may take a while..."); try { $message = $ComplianceRptHelper.FetchComplianceStateFromDb(); $this.PublishCustomMessage($message); } catch { [EventBase]::PublishGenericException($_); } } } class ComplianceMessageSummary { [string] $ComplianceType = "" [string] $ComplianceCount = "" #[string] $ComplianceComment = "" } class ComplianceResult { [string] $ControlId = "" [string] $VerificationResult = ([VerificationResult]::Manual).ToString(); [string] $ActualVerificationResult = ([VerificationResult]::Manual).ToString(); [string] $FeatureName = "" [string] $ResourceGroupName = "" [string] $ResourceName = "" [string] $ChildResourceName = "" [string] $IsBaselineControl = "" [string] $IsPreviewBaselineControl = "" [string] $ControlSeverity = ([ControlSeverity]::High).ToString(); [string] $AttestationCounter = "" [string] $AttestationStatus = "" [string] $AttestedBy = "" [string] $AttestedDate = "" [string] $Justification = "" [String] $UserComments = "" [string] $LastScannedOn = "" [string] $FirstScannedOn = "" [string] $FirstFailedOn = "" [string] $FirstAttestedOn = "" [string] $LastResultTransitionOn = "" [string] $ScanSource = "" [string] $ScannedBy = "" [string] $ScannerModuleName = "" [string] $ScannerVersion = "" [string] $IsControlInGrace ; [string] $HasOwnerAccessTag = "" [string] $ResourceId = "" [string] $EffectiveResult = ([VerificationResult]::NotScanned).ToString(); ComplianceResult([ComplianceStateTableEntity] $persistedEntity) { $this.ControlId = $persistedEntity.ControlId; $this.VerificationResult = $persistedEntity.VerificationResult; $this.ActualVerificationResult = $persistedEntity.ActualVerificationResult; $this.FeatureName = $persistedEntity.FeatureName; $this.ResourceGroupName = $persistedEntity.ResourceGroupName; $this.ResourceName = $persistedEntity.ResourceName; $this.ChildResourceName = $persistedEntity.ChildResourceName; $this.IsBaselineControl = $persistedEntity.IsBaselineControl; $this.IsPreviewBaselineControl = $persistedEntity.IsPreviewBaselineControl; $this.ControlSeverity = $persistedEntity.ControlSeverity; $this.AttestationCounter = $persistedEntity.AttestationCounter; $this.AttestationStatus = $persistedEntity.AttestationStatus; $this.AttestedBy = $persistedEntity.AttestedBy; $this.AttestedDate = $persistedEntity.AttestedDate; $this.Justification = $persistedEntity.Justification; $this.UserComments = $persistedEntity.UserComments; $this.LastScannedOn = $persistedEntity.LastScannedOn; $this.FirstScannedOn = $persistedEntity.FirstScannedOn; $this.FirstFailedOn = $persistedEntity.FirstFailedOn; $this.FirstAttestedOn = $persistedEntity.FirstAttestedOn; $this.LastResultTransitionOn = $persistedEntity.LastResultTransitionOn; $this.ScanSource = $persistedEntity.ScanSource; $this.ScannedBy = $persistedEntity.ScannedBy; $this.ScannerModuleName = $persistedEntity.ScannerModuleName; $this.ScannerVersion = $persistedEntity.ScannerVersion; $this.IsControlInGrace = $persistedEntity.IsControlInGrace $this.HasOwnerAccessTag = $persistedEntity.HasOwnerAccessTag; $this.ResourceId = $persistedEntity.ResourceId; #this.$EffectiveResult = if($persistedEntity.VerificationResult; } ComplianceResult($featureName, $resourceId, $resourceGroupName, $resourceName, $controlId, $verificationResult, $isBaselineControl, $isPreviewBaselineControl, $controlSeverity, $effectiveResult) { $this.ControlId = $controlId $this.FeatureName = $featureName $this.VerificationResult = $verificationResult $this.ResourceGroupName = $resourceGroupName $this.ResourceName = $resourceName $this.IsBaselineControl = $isBaselineControl $this.IsPreviewBaselineControl = $isPreviewBaselineControl $this.ControlSeverity = $controlSeverity $this.ResourceId = $resourceId $this.EffectiveResult = $effectiveResult } } |