Framework/Core/AzSKInfo/ComplianceInfo.ps1
using namespace System.Management.Automation Set-StrictMode -Version Latest class ComplianceInfo: CommandBase { 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() { $ComplianceRptHelper = [ComplianceReportHelper]::new($this.SubscriptionContext, $this.GetCurrentModuleVersion()); if($ComplianceRptHelper.HaveRequiredPermissions()) { $ComplianceReportData = $ComplianceRptHelper.GetSubscriptionComplianceReport(); $this.ComplianceScanResult = @(); if(($ComplianceReportData | Measure-Object).Count -gt 0) { $ComplianceReportData | ForEach-Object{ $this.ComplianceScanResult += [ComplianceResult]::new($_); } } } } hidden [void] GetComplianceInfo() { $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(); if(($this.ComplianceScanResult | Measure-Object).Count -le 0) { $this.PublishCustomMessage("No previously persisted compliance data found in the subscription ["+ $this.SubscriptionId +"]`nCompliance data will get persisted as you perform scans (or when scans happen from CA/CICD).", [MessageType]::Default); return; } #$this.GetControlDetails(); $this.ComputeCompliance(); $this.GetComplianceSummary() $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() { $totalCompliance = 0.0 $baselineCompliance = 0.0 $passControlCount = 0 $passControlCountwithGrace=0 $failedControlCount = 0 $failedControlCountwithGrace=0 $baselinePassedControlCount = 0 $baselinePassedControlCountWithGrace=0 $baselineFailedControlCount = 0 $baselineFailedControlCountWithGrace=0 $attestedControlCount = 0 $totalControlCount = 0 $baselineControlCount = 0 $baselineControlCountWithGrace = 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++ } } elseif($result.EffectiveResult -eq [VerificationResult]::Failed) { $failedControlCountwithGrace++ if($result.IsBaselineControl -eq "True") { $baselineControlCountWithGrace++ $baselineFailedControlCountWithGrace++ } } }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++ } }elseif($result.EffectiveResult -eq [VerificationResult]::Failed) { $failedControlCount++ if($result.IsBaselineControl -eq "True") { $baselineControlCount++ $baselineFailedControlCount++ } } } } 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 = @(); if(($baselinePassedControlCountWithGrace + $baselineFailedControlCountWithGrace) -ne 0) { $baselineCompliancewithGrace = (100 * $baselinePassedControlCountWithGrace)/($baselineControlCountWithGrace) $baselineCompliancewithoutGrace=0.0 if( ($baselinePassedControlCountWithGrace + $baselineControlCount) -ne 0) { $baselineCompliancewithoutGrace=(100 * ($baselinePassedControlCountWithGrace+ $baselinePassedControlCount))/($baselineControlCount+$baselineControlCountWithGrace) } $ComplianceStat = "" | Select-Object "ComplianceType", "Pass-%( grace)","Pass-%( no grace)","# of Passed Controls( grace)", "# of Failed Controls( grace)" , "# of Passed Controls( no grace)", "# of Failed Controls( no grace)" $ComplianceStat.ComplianceType = "Baseline" $ComplianceStat."Pass-%( grace)"= [math]::Round($baselineCompliancewithGrace,2) $ComplianceStat."Pass-%( no grace)"= [math]::Round($baselineCompliancewithoutGrace,2) $ComplianceStat."# of Passed Controls( grace)" = $baselinePassedControlCountWithGrace $ComplianceStat."# of Failed Controls( grace)" = $baselineFailedControlCountWithGrace $ComplianceStat."# of Passed Controls( no grace)"=($baselinePassedControlCountWithGrace+$baselinePassedControlCount) $ComplianceStat."# of Failed Controls( no grace)"=($baselineFailedControlCount+$baselineFailedControlCountWithGrace) $ComplianceStats += $ComplianceStat } $ComplianceStat = "" | Select-Object "ComplianceType", "Pass-%( grace)","Pass-%( no grace)","# of Passed Controls( grace)", "# of Failed Controls( grace)" , "# of Passed Controls( no grace)", "# of Failed Controls( no grace)" $ComplianceStat.ComplianceType = "Full" $ComplianceStat."Pass-%( grace)"= [math]::Round($totalComplianceWithGrace,2) $ComplianceStat."Pass-%( no grace)"= [math]::Round($totalCompliance,2) $ComplianceStat."# of Passed Controls( grace)" = ($passControlCountwithGrace) $ComplianceStat."# of Failed Controls( grace)" = ($failedControlCountwithGrace) $ComplianceStat."# of Passed Controls( no grace)" = ($passControlCountwithGrace+$passControlCount) $ComplianceStat."# of Failed Controls( no grace)" = ($failedControlCountwithGrace+$failedControlCount) $ComplianceStats += $ComplianceStat $this.PublishCustomMessage(($ComplianceStats | Format-Table | Out-String -Width 2048), [MessageType]::Default) $this.PublishCustomMessage([Constants]::SingleDashLine, [MessageType]::Default); $this.PublishCustomMessage("`r`nTotal controls: "+ $totalControlCount, [MessageType]::Default); $this.PublishCustomMessage("`r`nTotal baseline controls: "+ ($baselineControlCount+$baselineControlCountWithGrace), [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($_.IsControlInGrace -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", ` "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] $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.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, $controlSeverity, $effectiveResult) { $this.ControlId = $controlId $this.FeatureName = $featureName $this.VerificationResult = $verificationResult $this.ResourceGroupName = $resourceGroupName $this.ResourceName = $resourceName $this.IsBaselineControl = $isBaselineControl $this.ControlSeverity = $controlSeverity $this.ResourceId = $resourceId $this.EffectiveResult = $effectiveResult } } |