Framework/Core/SVT/ServicesSecurityStatus.ps1

Set-StrictMode -Version Latest 
class ServicesSecurityStatus: SVTCommandBase
{
    [SVTResourceResolver] $Resolver = $null;
    ServicesSecurityStatus([string] $subscriptionId, [InvocationInfo] $invocationContext, [SVTResourceResolver] $resolver): 
        Base($subscriptionId, $invocationContext)
    { 
        if(-not $resolver)
        {
            throw [System.ArgumentException] ("The argument 'resolver' is null");
        }

        $this.Resolver = $resolver;
        $this.Resolver.LoadAzureResources();
        
        #BaseLineControlFilter with control ids
        $this.UsePartialCommits =$invocationContext.BoundParameters["UsePartialCommits"];
        $this.UseBaselineControls = $invocationContext.BoundParameters["UseBaselineControls"];
        $this.BaselineFilterCheck();
        $this.UsePartialCommitsCheck();
        
    }

    hidden [SVTEventContext[]] RunAllControls()
    {
        [SVTEventContext[]] $result = @();
        $this.PublishCustomMessage("Number of resources: $($this.Resolver.SVTResources.Count)");
        $nonAutomatedResources = @();
        $nonAutomatedResources += ($this.Resolver.SVTResources | Where-Object { $null -eq $_.ResourceTypeMapping });
        $automatedResources = @();
        $automatedResources += ($this.Resolver.SVTResources | Where-Object { $_.ResourceTypeMapping });
        
        $this.PublishCustomMessage("Number of resources for which security controls will be evaluated: $($automatedResources.Count)");

        if($nonAutomatedResources.Count -gt 0)
        {
            $this.PublishCustomMessage("Number of resources for which security controls will NOT be evaluated: $($nonAutomatedResources.Count)", [MessageType]::Warning);
        
            $nonAutomatedResTypes = [array] ($nonAutomatedResources | Select-Object -Property ResourceType -Unique);
            $this.PublishCustomMessage([MessageData]::new("Security controls are yet to be automated for the following service types: ", $nonAutomatedResTypes));

            $this.PublishAzSdkRootEvent([AzSdkRootEvent]::UnsupportedResources, $nonAutomatedResources); 
        }
        
        $totalResources = $automatedResources.Count;
        [int] $currentCount = 0;
        $automatedResources | ForEach-Object {
            $exceptionMessage = "Exception for resource: [ResourceType: $($_.ResourceTypeMapping.ResourceTypeName)] [ResourceGroupName: $($_.ResourceGroupName)] [ResourceName: $($_.ResourceName)]"
            try 
            {
                $currentCount += 1;
                if($totalResources -gt 1)
                {
                    $this.PublishCustomMessage(" `r`nChecking resource [$currentCount/$totalResources] ");
                }
                $svtClassName = $_.ResourceTypeMapping.ClassName;

                $svtObject = $null;

                try
                {
                    $svtObject = New-Object -TypeName $svtClassName -ArgumentList $this.SubscriptionContext.SubscriptionId, $_
                }
                catch
                {
                    $this.PublishCustomMessage($exceptionMessage);
                    # Unwrapping the first layer of exception which is added by New-Object function
                    $this.CommandError($_.Exception.InnerException.ErrorRecord);
                }

                if($svtObject)
                {
                    $this.SetSVTBaseProperties($svtObject);

                    $result += $svtObject.EvaluateAllControls();
                }

                # Register/Deregister all listeners to cleanup the memory
                [ListenerHelper]::RegisterListeners();
            }
            catch 
            {
                $this.PublishCustomMessage($exceptionMessage);
                $this.CommandError($_);
            }   
        }

        return $result;
    }


    #BaseLineControlFilter Function
    [void] BaselineFilterCheck()
    {
        #Load ControlSetting Resource Types and Filter resources
        $scanSource = [AzSdkSettings]::GetInstance().GetScanSource();
        #Load ControlSetting Resource Types and Filter resources
        [ControlBaselineManager] $controlBaselineMngr = [ControlBaselineManager]::GetInstance();        
        $baselineControlsDetails = $controlBaselineMngr.GetBaselineControlDetails()
        #If Scan source is in suported sources or baselineControls switch is available
        if ($null -ne $baselineControlsDetails -and ($baselineControlsDetails.SupportedSources -contains $scanSource -or $this.UseBaselineControls))
        {
            #Get resource type and control ids mapping from controlsetting object
            #$this.PublishCustomMessage("Running cmdlet with baseline resource types and controls.", [MessageType]::Warning);
            $baselineResourceTypes = $baselineControlsDetails.ResourceTypeControlIdMappingList | Select ResourceType | Foreach-Object {$_.ResourceType}
            #Filter SVT resources based on baseline resource types
            $ResourcesWithBaselineFilter =$this.Resolver.SVTResources | Where-Object {$null -ne $_.ResourceTypeMapping -and   $_.ResourceTypeMapping.ResourceTypeName -in $baselineResourceTypes }
            #Get the list of control ids
            $controlIds = $baselineControlsDetails.ResourceTypeControlIdMappingList | Select ControlIds | ForEach-Object {  $_.ControlIds }
            $BaselineControlIds = [system.String]::Join(",",$controlIds);
            if(-not [system.String]::IsNullOrEmpty($BaselineControlIds))
            {
                $this.ControlIds = $BaselineControlIds;
            
            }
            $this.Resolver.SVTResources = $ResourcesWithBaselineFilter
        }

    }

    [void] UsePartialCommitsCheck()
    {
        #Load ControlSetting Resource Types and Filter resources
        $scanSource = [AzSdkSettings]::GetInstance().GetScanSource();
        #Load ControlSetting Resource Types and Filter resources
        [ControlBaselineManager] $controlBaselineMngr = [ControlBaselineManager]::GetInstance();        
        $baselineControlsDetails = $controlBaselineMngr.GetBaselineControlDetails()
        #If Scan source is in suported sources or UsePartialCommits switch is available
        if ($this.UsePartialCommits -or ($baselineControlsDetails.SupportedSources -contains $scanSource))
        {
            #$this.PublishCustomMessage("Running cmdlet under transactional mode. This will scan resources and store intermittent scan progress to Storage. It resume scan in next run if something breaks inbetween.", [MessageType]::Warning);
            [ControlBaselineManager] $controlBaselineMngr = [ControlBaselineManager]::GetInstance();
            #Validate if active resources list already available in store
            #If list not available in store. Get resources filtered by baseline resource types and store it storage
            if(($controlBaselineMngr.IsMasterListActive() -eq [ActiveStatus]::Yes)  )
            {
                $allResourcesList = $controlBaselineMngr.GetAllListedResources()
                # Get list of non scanned active resources
                $nonScannedResourcesList = $controlBaselineMngr.GetNonScannedResources();
                $this.PublishCustomMessage("Resuming scan from last commit. $(($nonScannedResourcesList | Measure-Object).Count) out of $(($allResourcesList | Measure-Object).Count) resources will be scanned.", [MessageType]::Warning);
                $nonScannedResourceIdList = $nonScannedResourcesList | Select Id | ForEach-Object { $_.Id}
                #Filter SVT resources based on master resources list available and scan completed
                $this.Resolver.SVTResources = $this.Resolver.SVTResources | Where-Object {$_.ResourceId -in $nonScannedResourceIdList }
            }
            else{
                $resourceIdList =  $this.Resolver.SVTResources | Select ResourceId | ForEach-Object {  $_.ResourceId }
                $controlBaselineMngr.CreateResourceMasterList($resourceIdList);
            }
        }
    }
}