Framework/Listeners/RemoteReports/RemoteReportsListener.ps1

Set-StrictMode -Version Latest

#This is used to send events to the controls API (to directly save to org DB)
class RemoteReportsListener: ListenerBase {

    hidden RemoteReportsListener() {
    }

    hidden static [RemoteReportsListener] $Instance = $null;

    static [RemoteReportsListener] GetInstance() {
        if ( $null  -eq [RemoteReportsListener]::Instance  ) {
            [RemoteReportsListener]::Instance = [RemoteReportsListener]::new();
        }
        return [RemoteReportsListener]::Instance
    }

    [void] RegisterEvents() {
        $this.UnregisterEvents();

        $this.RegisterEvent([AzSKRootEvent]::GenerateRunIdentifier, {
            $currentInstance = [RemoteReportsListener]::GetInstance();
            try
            {
                $runIdentifier = [AzSKRootEventArgument] ($Event.SourceArgs | Select-Object -First 1)
                $currentInstance.SetRunIdentifier($runIdentifier);
            }
            catch
            {
                $currentInstance.PublishException($_);
            }
        });

        $this.RegisterEvent([SVTEvent]::CommandStarted, {
             $currentInstance = [RemoteReportsListener]::GetInstance();
            try
            {

                $scanSource = [RemoteReportHelper]::GetScanSource();
                if($scanSource -ne [ScanSource]::Runbook) { return; }
                [ResourceInventory]::FetchResources();
                [RemoteReportsListener]::ReportAllResources();                
                $invocationContext = [System.Management.Automation.InvocationInfo] $currentInstance.InvocationContext
                if(!$invocationContext.BoundParameters.ContainsKey("tenantId")) {return;}
                $resources = "" | Select-Object "tenantId", "ResourceGroups"
                $resources.tenantId = $invocationContext.BoundParameters["tenantId"]
                $resources.ResourceGroups = [System.Collections.ArrayList]::new()
                $supportedResourceTypes = [SVTMapping]::GetSupportedResourceMap()
                # # Not considering nested resources to reduce complexity
                $filteredResources = [ResourceInventory]::FilteredResources | Where-Object { $supportedResourceTypes.ContainsKey($_.ResourceType.ToLower()) }
                $grouped = $filteredResources | Group-Object {$_.ResourceGroupName} | Select-Object Name, Group                
                foreach($group in $grouped){
                    $resourceGroup = "" | Select-Object Name, Resources
                    $resourceGroup.Name = $group.Name
                    $resourceGroup.Resources = [System.Collections.ArrayList]::new()
                    foreach($item in $group.Group){
                        $resource = "" | Select-Object Name, ResourceId, Feature
                        if($item.Name.Contains("/")){
                            $splitName = $item.Name.Split("/")
                            $resource.Name = $splitName[$splitName.Length - 1]
                        }
                        else{
                            $resource.Name = $item.Name;
                        }
                        $resource.ResourceId = $item.ResourceId
                        $resource.Feature = $supportedResourceTypes[$item.ResourceType.ToLower()]
                        $resourceGroup.Resources.Add($resource) | Out-Null
                    }
                    $resources.ResourceGroups.Add($resourceGroup) | Out-Null
                }
                [RemoteApiHelper]::PostResourceInventory($resources)
            }
            catch
            {
                $currentInstance.PublishException($_);
            }
        });

        $this.RegisterEvent([SVTEvent]::EvaluationCompleted, {
            $currentInstance = [RemoteReportsListener]::GetInstance();
            try
            {
                $settings = [ConfigurationManager]::GetAzSKConfigData();
                if(!$settings.PublishVulnDataToApi) {return;}
                $invocationContext = [System.Management.Automation.InvocationInfo] $currentInstance.InvocationContext
                $SVTEventContexts = [SVTEventContext[]] $Event.SourceArgs
                $featureGroup = [RemoteReportHelper]::GetFeatureGroup($SVTEventContexts)
                if($featureGroup -eq [FeatureGroup]::Subscription){
                    [RemoteReportsListener]::ReportSubscriptionScan($currentInstance, $invocationContext, $SVTEventContexts)
                }elseif($featureGroup -eq [FeatureGroup]::Service){
                    [RemoteReportsListener]::ReportServiceScan($currentInstance, $invocationContext, $SVTEventContexts)
                }else{

                }
            }
            catch
            {
                $currentInstance.PublishException($_);
            }
        });

        $this.RegisterEvent([AzSKRootEvent]::PublishCustomData, {
            $currentInstance = [RemoteReportsListener]::GetInstance();
            try
            {                
                $CustomDataObj =  $Event.SourceArgs
                $CustomObjectData=$CustomDataObj| Select-Object -exp Messages|select -exp DataObject
                if($CustomObjectData.Name -eq "SubSVTObject")
                {
                    $subSVTObject = $CustomObjectData.Value;
                    $currentInstance.FetchRBACTelemetry($subSVTObject);                    
                    [RemoteApiHelper]::PostRBACTelemetry(($subSVTObject.CustomObject.Value));
                }
                elseif($CustomObjectData.Name -eq "FeatureControlTelemetry")
                {                     
                     [RemoteApiHelper]::PushFeatureControlsTelemetry($CustomObjectData.Value);
                }
                #| select -exp Value;
                
            }
            catch
            {
                $currentInstance.PublishException($_);
            }
        });

        
    }


    static [void] ReportAllResources()
    {
        $currentInstance = [RemoteReportsListener]::GetInstance();
        $invocationContext = [System.Management.Automation.InvocationInfo] $currentInstance.InvocationContext
        $tenantId = ([AccountHelper]::GetCurrentRmContext()).Subscription.Id;
        $resourceGroups = Get-AzResourceGroup
        $resourcesDetails = @();
        $resourcesFlat = [ResourceInventory]::RawResources
        foreach($res in $resourcesFlat){
            $resourceGroup = ($resourceGroups | where-object {$_.ResourceGroupName -eq $res.ResourceGroupName});
            $resEnv = "";
            $resComponentId = "";
            $rgEnv = "";
            $rgComponentId = "";
            if([Helpers]::CheckMember($resourceGroup, "Tags")) {
                $rgTags = $resourceGroup.Tags;
                if($rgTags.ContainsKey("Env")) 
                {
                   $rgEnv = $rgTags.Env;
                }
                if($rgTags.ContainsKey("ComponentID")) 
                {
                    $rgComponentId = $rgTags.ComponentID;
                }
            }
            if([Helpers]::CheckMember($res, "Tags"))
            {
                $resTags = $res.Tags;
                if($resTags.ContainsKey("Env"))
                {
                    $resEnv = $resTags.Env;
                }
                if($resTags.ContainsKey("ComponentID"))
                {
                    $resComponentId = $resTags.ComponentID;
                }
            }
            $resourceProperties = @{
                "Name" = $res.Name;
                "ResourceId" = $res.ResourceId;
                "ResourceName" = $res.Name;
                "ResourceType" = $res.ResourceType;
                "ResourceGroupName" = $res.ResourceGroupName;
                "Location" = $res.Location;
                "tenantId" = $tenantId;
                "Sku" = $res.Sku;
                "Tags" = [Helpers]::FetchTagsString($res.Tags);
                "Env" = $resEnv;
                "ComponentID" = $resComponentId;
                "RGComponentID" = $rgComponentId;
                "RGEnv" = $rgEnv;
                }
                $resourcesDetails += $resourceProperties;
            }
        [RemoteApiHelper]::PostResourceFlatInventory($resourcesDetails)
    }


    static [void] ReportSubscriptionScan(
        [RemoteReportsListener] $publisher, `
        [System.Management.Automation.InvocationInfo]  $invocationContext, `
        [SVTEventContext[]] $SVTEventContexts)
    {
        $SVTEventContext = $SVTEventContexts[0]
        $scanResult = [SubscriptionScanInfo]::new()
        $scanResult.ScanKind = [RemoteReportHelper]::GetSubscriptionScanKind($invocationContext.MyCommand.Name, $invocationContext.BoundParameters)
        $scanResult.tenantId = $SVTEventContext.TenantContext.tenantId
        $scanResult.TenantName = $SVTEventContext.TenantContext.TenantName
        $scanResult.Source = [RemoteReportHelper]::GetScanSource()
        $scanResult.ScannerVersion = $publisher.GetCurrentModuleVersion()
        # Using module version as control version by default
        $scanResult.ControlVersion = $publisher.GetCurrentModuleVersion()
        $scanResult.Metadata = [Helpers]::ConvertToJsonCustomCompressed($SVTEventContext.TenantContext.SubscriptionMetadata)
        if(($SVTEventContexts | Measure-Object).Count -gt 0 -and ($SVTEventContexts[0].ControlResults | Measure-Object).Count -gt 0)
        {
            $TempCtrlResult = $SVTEventContexts[0].ControlResults[0];
            $scanResult.HasAttestationWritePermissions = $TempCtrlResult.CurrentSessionContext.Permissions.HasAttestationWritePermissions
            $scanResult.HasAttestationReadPermissions = $TempCtrlResult.CurrentSessionContext.Permissions.HasAttestationReadPermissions
            $scanResult.IsLatestPSModule = $TempCtrlResult.CurrentSessionContext.IsLatestPSModule
        }
        $results = [System.Collections.ArrayList]::new()
        $SVTEventContexts | ForEach-Object {
            $context = $_
            if ($context.ControlItem.Enabled) {
                $result = [RemoteReportHelper]::BuildSubscriptionControlResult($context.ControlResults[0], $context.ControlItem)
                $results.Add($result)
            }
            else {
                $result = [SubscriptionControlResult]::new()
                $result.ControlId = $context.ControlItem.ControlID
                $result.ControlIntId = $context.ControlItem.Id
                $result.ActualVerificationResult = [VerificationResult]::Disabled
                $result.AttestationStatus = [AttestationStatus]::None
                $result.VerificationResult = [VerificationResult]::Disabled
                $result.MaximumAllowedGraceDays = $context.MaximumAllowedGraceDays
                $results.Add($result)
            }
        }
        $scanResult.ControlResults = [SubscriptionControlResult[]] $results
        [RemoteApiHelper]::PostSubscriptionScanResult($scanResult)
    }

    static [void] ReportServiceScan(
        [RemoteReportsListener] $publisher, `
        [System.Management.Automation.InvocationInfo]  $invocationContext, `
        [SVTEventContext[]] $SVTEventContexts)
    {
        $SVTEventContextFirst = $SVTEventContexts[0]
        $scanResult = [ServiceScanInfo]::new()
        $scanResult.ScanKind = [RemoteReportHelper]::GetServiceScanKind($invocationContext.MyCommand.Name, $invocationContext.BoundParameters)
        $scanResult.tenantId = $SVTEventContextFirst.TenantContext.tenantId
        $scanResult.TenantName = $SVTEventContextFirst.TenantContext.TenantName
        $scanResult.Source = [RemoteReportHelper]::GetScanSource()
        $scanResult.ScannerVersion = $publisher.GetCurrentModuleVersion()
        # Using module version as control version by default
        $scanResult.ControlVersion = $publisher.GetCurrentModuleVersion()
        $scanResult.Feature = $SVTEventContextFirst.FeatureName
        $scanResult.ResourceGroup = $SVTEventContextFirst.ResourceContext.ResourceGroupName
        $scanResult.ResourceName = $SVTEventContextFirst.ResourceContext.ResourceName
        $scanResult.ResourceId = $SVTEventContextFirst.ResourceContext.ResourceId
        $scanResult.Metadata = [Helpers]::ConvertToJsonCustomCompressed($SVTEventContextFirst.ResourceContext.ResourceMetadata)
        
        if(($SVTEventContexts | Measure-Object).Count -gt 0 -and ($SVTEventContexts[0].ControlResults | Measure-Object).Count -gt 0)
        {
            $TempCtrlResult = $SVTEventContexts[0].ControlResults[0];
            $scanResult.HasAttestationWritePermissions = $TempCtrlResult.CurrentSessionContext.Permissions.HasAttestationWritePermissions
            $scanResult.HasAttestationReadPermissions = $TempCtrlResult.CurrentSessionContext.Permissions.HasAttestationReadPermissions
            $scanResult.IsLatestPSModule = $TempCtrlResult.CurrentSessionContext.IsLatestPSModule
        }
        $results = [System.Collections.ArrayList]::new()
        $SVTEventContexts | ForEach-Object {
            $SVTEventContext = $_
            if (!$SVTEventContext.ControlItem.Enabled) {
                $result = [ServiceControlResult]::new()
                $result.ControlId = $SVTEventContext.ControlItem.ControlID
                $result.ControlIntId = $SVTEventContext.ControlItem.Id
                $result.ControlSeverity = $SVTEventContext.ControlItem.ControlSeverity
                $result.ActualVerificationResult = [VerificationResult]::Disabled
                $result.AttestationStatus = [AttestationStatus]::None
                $result.VerificationResult = [VerificationResult]::Disabled                
                $results.Add($result)
            }
            elseif ($SVTEventContext.ControlResults.Count -eq 1 -and `
                ($scanResult.ResourceName -eq $SVTEventContext.ControlResults[0].ChildResourceName -or `
                    [string]::IsNullOrWhiteSpace($SVTEventContext.ControlResults[0].ChildResourceName)))
            {
                $result = [RemoteReportHelper]::BuildServiceControlResult($SVTEventContext.ControlResults[0], `
                    $false, $SVTEventContext.ControlItem)
                $results.Add($result)
            }
            elseif ($SVTEventContext.ControlResults.Count -eq 1 -and `
                $scanResult.ResourceName -ne $SVTEventContext.ControlResults[0].ChildResourceName)
            {
                $result = [RemoteReportHelper]::BuildServiceControlResult($SVTEventContext.ControlResults[0], `
                     $true, $SVTEventContext.ControlItem)
                $results.Add($result)
            }
            elseif ($SVTEventContext.ControlResults.Count -gt 1)
            {
                $SVTEventContext.ControlResults | Foreach-Object {
                    $result = [RemoteReportHelper]::BuildServiceControlResult($_ , `
                         $true, $SVTEventContext.ControlItem)
                    $results.Add($result)
                }
            }
        }

        $scanResult.ControlResults = [ServiceControlResult[]] $results
        [RemoteApiHelper]::PostServiceScanResult($scanResult)
    }

    hidden [void] FetchRBACTelemetry($svtObject)
    {
        $svtObject.GetRoleAssignments();
        $svtObject.PublishRBACTelemetryData();
        $svtObject.GetPIMRoles();

    }
}