Framework/Helpers/RemoteReportHelper.ps1

Set-StrictMode -Version Latest

#Helper functions used by various listeners that send events remotely (e.g., OMS, AIOrg/Control-Telemetry, User/Anon-Telemetry, RemoteReportsListener, etc.)
class RemoteReportHelper
{
    hidden static [string[]] $IgnoreScanParamList = "DoNotOpenOutputFolder";
    hidden static [string[]] $AllowedServiceScanParamList = "tenantId", "ResourceGroupNames";
    hidden static [string[]] $AllowedSubscriptionScanParamList = "tenantId";
    hidden static [int] $MaxServiceParamCount = [RemoteReportHelper]::IgnoreScanParamList.Count + [RemoteReportHelper]::AllowedServiceScanParamList.Count;
    hidden static [int] $MaxSubscriptionParamCount = [RemoteReportHelper]::IgnoreScanParamList.Count + [RemoteReportHelper]::AllowedSubscriptionScanParamList.Count;

    static [FeatureGroup] GetFeatureGroup([SVTEventContext[]] $SVTEventContexts)
    {
        if(($SVTEventContexts | Measure-Object).Count -eq 0 -or $null -eq $SVTEventContexts[0].FeatureName) {
            return [FeatureGroup]::Unknown
        }
        $feature = $SVTEventContexts[0].FeatureName.ToLower()
        if($feature.Contains("subscription")){
            return [FeatureGroup]::Subscription
        } else{
            return [FeatureGroup]::Service
        }
    }

    static [ServiceScanKind] GetServiceScanKind([string] $command, [hashtable] $parameters)
    {
        $parameterNames = [array] $parameters.Keys
        if($parameterNames.Count -gt [RemoteReportHelper]::MaxServiceParamCount)
        {
            return [ServiceScanKind]::Partial;
        }
        $validParamCounter = 0;
        foreach($parameterName in $parameterNames)
        {
            if ([RemoteReportHelper]::AllowedServiceScanParamList.Contains($parameterName))
            {
                $validParamCounter += 1
            }
            elseif ([RemoteReportHelper]::IgnoreScanParamList.Contains($parameterName))
            {
                # Ignoring
            }
            else
            {
                return [ServiceScanKind]::Partial;
            }
        }

        if ($validParamCounter -eq 1)
        {
            return [ServiceScanKind]::Subscription;
        }
        elseif ($validParamCounter -eq 2)
        {
            return [ServiceScanKind]::ResourceGroup;
        }
        else
        {
            return [ServiceScanKind]::Partial;
        }
    }

    static [SubscriptionScanKind] GetSubscriptionScanKind([string] $command, [hashtable] $parameters)
    {
        $parameterNames = [array] $parameters.Keys
        if($parameterNames.Count -gt [RemoteReportHelper]::MaxSubscriptionParamCount)
        {
            return [SubscriptionScanKind]::Partial;
        }
        $validParamCounter = 0;
        foreach($parameterName in $parameterNames)
        {
            if ([RemoteReportHelper]::AllowedSubscriptionScanParamList.Contains($parameterName))
            {
                $validParamCounter += 1
            }
            elseif ([RemoteReportHelper]::IgnoreScanParamList.Contains($parameterName))
            {
                # Ignoring
            }
            else
            {
                return [SubscriptionScanKind]::Partial;
            }
        }

        if ($validParamCounter -eq 1)
        {
            return [SubscriptionScanKind]::Complete;
        }
        else
        {
            return [SubscriptionScanKind]::Partial;
        }
    }

    static [SubscriptionControlResult] BuildSubscriptionControlResult([ControlResult] $controlResult, [ControlItem] $control)
    {
        $result = [SubscriptionControlResult]::new()
        $result.ControlId = $control.ControlId
        $result.ControlIntId = $control.Id
        $result.ControlSeverity = $control.ControlSeverity
        $result.ActualVerificationResult = $controlResult.ActualVerificationResult
        $result.AttestationStatus = $controlResult.AttestationStatus
        $result.VerificationResult = $controlResult.VerificationResult
        $result.HasRequiredAccess = $controlResult.CurrentSessionContext.Permissions.HasRequiredAccess
        $result.IsBaselineControl = $control.IsBaselineControl
        $result.MaximumAllowedGraceDays = $controlResult.MaximumAllowedGraceDays
        if($control.Tags.Contains("OwnerAccess")  -or $control.Tags.Contains("GraphRead"))
        {
            $result.HasOwnerAccessTag = $true
        }

        $result.UserComments = $controlResult.UserComments

        if($null -ne $controlResult.StateManagement -and $null -ne $controlResult.StateManagement.AttestedStateData) {
            $result.AttestedBy = $controlResult.StateManagement.AttestedStateData.AttestedBy
            $result.Justification = $controlResult.StateManagement.AttestedStateData.Justification
            $result.AttestedState = [Helpers]::ConvertToJsonCustomCompressed($controlResult.StateManagement.AttestedStateData.DataObject)
            $result.AttestedDate = $controlResult.StateManagement.AttestedStateData.AttestedDate

        }
        if($null -ne $controlResult.StateManagement -and $null -ne $controlResult.StateManagement.CurrentStateData) {
            $result.CurrentState = [Helpers]::ConvertToJsonCustomCompressed($controlResult.StateManagement.CurrentStateData.DataObject)
        }
        return $result;
    }

    static [ServiceControlResult] BuildServiceControlResult([ControlResult] $controlResult, [bool] $isNestedResource, [ControlItem] $control)
    {
        $result = [ServiceControlResult]::new()
        $result.IsNestedResource = $isNestedResource
        if ($isNestedResource)
        {
            $result.NestedResourceName = $controlResult.ChildResourceName
        }
        else
        {
            $result.NestedResourceName = $null
        }
        $result.ControlId = $control.ControlID
        $result.ControlIntId = $control.Id
        $result.ControlSeverity = $control.ControlSeverity
        $result.ActualVerificationResult = $controlResult.ActualVerificationResult
        $result.AttestationStatus = $controlResult.AttestationStatus
        $result.VerificationResult = $controlResult.VerificationResult
        $result.HasRequiredAccess = $controlResult.CurrentSessionContext.Permissions.HasRequiredAccess
        $result.IsBaselineControl = $control.IsBaselineControl
        $result.UserComments = $controlResult.UserComments
        $result.MaximumAllowedGraceDays = $controlResult.MaximumAllowedGraceDays
        if($control.Tags.Contains("OwnerAccess")  -or $control.Tags.Contains("GraphRead"))
        {
            $result.HasOwnerAccessTag = $true
        }

        if($null -ne $controlResult.StateManagement -and $null -ne $controlResult.StateManagement.AttestedStateData) {
            $result.AttestedBy = $controlResult.StateManagement.AttestedStateData.AttestedBy
            $result.Justification = $controlResult.StateManagement.AttestedStateData.Justification
            $result.AttestedState = [Helpers]::ConvertToJsonCustomCompressed($controlResult.StateManagement.AttestedStateData.DataObject)
            $result.AttestedDate = $controlResult.StateManagement.AttestedStateData.AttestedDate
        }
        if($null -ne $controlResult.StateManagement -and $null -ne $controlResult.StateManagement.CurrentStateData) {
            $result.CurrentState = [Helpers]::ConvertToJsonCustomCompressed($controlResult.StateManagement.CurrentStateData.DataObject)
        }
        return $result;
    }

    static [ScanSource] GetScanSource()
    {        
        $settings = [ConfigurationManager]::GetAzSKSettings();
        [string] $omsSource = $settings.OMSSource;
        if([string]::IsNullOrWhiteSpace($omsSource)){
            return [ScanSource]::SpotCheck
        }
        if($omsSource.Equals("CICD", [System.StringComparison]::OrdinalIgnoreCase)){
            return [ScanSource]::VSO
        }
        if($omsSource.Equals("CC", [System.StringComparison]::OrdinalIgnoreCase) -or
            $omsSource.Equals("CA", [System.StringComparison]::OrdinalIgnoreCase)){
            return [ScanSource]::Runbook
        }
        return [ScanSource]::SpotCheck
    }

    static [string] GetAIOrgTelemetryKey()
    {
        $settings = [ConfigurationManager]::GetAzSKConfigData();
        $telemetryKey = $settings.ControlTelemetryKey

        #BUGBUG: Need to address same 'perf' concern as AIOrgTMKey below!
        [guid]$key = [guid]::NewGuid() 
        
        if([guid]::TryParse($telemetryKey, [ref] $key) -and ![guid]::Empty.Equals($key))
        {
            return $telemetryKey;
        }
        #BUGBUG: What is the intent here?
        #BUGBUG: It appears that if telemetryKey in config is 00000- (and no server setting) this will return 0000--...
        #TODO: This should work smoothly if we support locally forwarded OrgTelemetry in OSS mode...
        return [ConfigurationManager]::GetAzSKSettings().LocalControlTelemetryKey;
    }

    static [bool] IsAIOrgTelemetryEnabled()
    {
        $settings = [ConfigurationManager]::GetAzSKConfigData();
        $telemetryKey = $settings.ControlTelemetryKey
        #BUGBUG: We should not burn a Guid each time like this. Just check non-null and perhaps length...
        #If we need a mock guid, make one up 01234567-89ab-cdef-0123456789abcdef
        #Also, cache the result and the fact that it has been set/checked (upon first call)
        #TODO: Even otherwise, checking bEnabled first is much more optimal. Most people will have it as $false.
        [guid]$key = [guid]::NewGuid()
        if([guid]::TryParse($telemetryKey, [ref] $key) -and ![guid]::Empty.Equals($key))
        {
            return $settings.EnableControlTelemetry;
        } 
        #BUGBUG: Unclear why this would return LocalEnable...
        return [ConfigurationManager]::GetAzSKSettings().LocalEnableControlTelemetry;
    }

    static [string] Mask([psobject] $toMask)
    {
        $sha384 = [System.Security.Cryptography.SHA384Managed]::new()
        $maskBytes = [System.Text.Encoding]::UTF8.GetBytes($toMask.ToString())
        $maskBytes = $sha384.ComputeHash($maskBytes)
        $sha384.Dispose()
        $take = 16
        $sb = [System.Text.StringBuilder]::new($take)
        for($i = 0; $i -lt ($take/2); $i++){
            $x = $sb.Append($maskBytes[$i].ToString("x2"))
        }
        return $sb.ToString();
    }
}