Framework/Managers/ControlBaselineManager.ps1

Set-StrictMode -Version Latest

class ControlBaselineManager
{
    hidden [PSObject] $AzSKResourceGroup = $null;
    hidden [PSObject] $AzSKStorageAccount = $null;
    hidden [PSObject] $AzSKStorageResourceBaselineContainer = $null;
    hidden [int] $HasWritePermissions = -1;
    hidden [string]    $BaselineResourceBlobName = "BaselineResourceBlob.json"
    hidden [string] $CAScanProgressSnapshotsContainerName = [Constants]::CAScanProgressSnapshotsContainerName
    hidden [BaselineResourceMap] $BaselineControlObj = $null
    [PSObject] $ControlSettings;
    hidden [ActiveStatus] $ActiveStatus = [ActiveStatus]::NotStarted;

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

    ControlBaselineManager()
    {
        $this.ControlSettings = [ConfigurationManager]::LoadServerConfigFile("ControlSettings.json");
        #$this.GetAzSKControlBaselineContainer()
    }

    hidden [bool] HasControlBaselineStatusWritePermissions()
    {
        $hasPermissions = $false;
        return $hasPermissions;
    }

    hidden [void] GetAzSKControlBaselineContainer()
    {
        if($null -eq $this.AzSKStorageAccount)
        {
            $this.GetAzSKStorageAccount()
        }
        if($null -eq $this.AzSKStorageAccount)
        {
            return;
        }


        try
        {
            #Able to read the container then read permissions are good
            $containerObject = Get-AzureStorageContainer -Context $this.AzSKStorageAccount.Context -Name $this.CAScanProgressSnapshotsContainerName -ErrorAction Stop
            $this.AzSKStorageResourceBaselineContainer = $containerObject;
        }
        catch
        {
            try
            {
                New-AzureStorageContainer -Context $this.AzSKStorageAccount.Context -Name $this.CAScanProgressSnapshotsContainerName -ErrorAction SilentlyContinue
                $containerObject = Get-AzureStorageContainer -Context $this.AzSKStorageAccount.Context -Name $this.CAScanProgressSnapshotsContainerName -ErrorAction SilentlyContinue
                $this.AzSKStorageResourceBaselineContainer = $containerObject;
            }
            catch
            {
                #Do nothing
            }
        }
    }

    hidden [void] GetAzSKStorageAccount()
    {
        if($null -eq $this.AzSKResourceGroup)
        {
            $this.GetAzSKRG();
        }
        if($null -ne $this.AzSKResourceGroup)
        {
            $StorageAccount = Get-AzureRmStorageAccount -ResourceGroupName $this.AzSKResourceGroup.ResourceGroupName | Where-Object {$_.StorageAccountName -like 'azsk*'} -ErrorAction SilentlyContinue
            #if no storage account found then it assumes that there is no control state feature is not used and if there are more than one storage account found it assumes the same
            $this.AzSKStorageAccount = $StorageAccount;
        }
    }

    hidden [PSObject] GetAzSKRG()
    {
        $azSKConfigData = [ConfigurationManager]::GetAzSKConfigData()
        $resourceGroup = Get-AzureRmResourceGroup -Name $azSKConfigData.AzSKRGName -ErrorAction SilentlyContinue
        $this.AzSKResourceGroup = $resourceGroup
        return $resourceGroup;
    }


    [void] UpdateResourceStatus([string] $resourceId, [ScanState] $state)
    {
        $resourceValues = @();
        $this.GetBaselineControlObject();
        if($this.IsListAvailableAndActive())
        {
            #$idHash = [Helpers]::ComputeHash($resourceId)
            $resourceValue = $this.BaselineControlObj.ResourceMapTable | Where-Object { $_.Id -eq $resourceId};
            if($null -ne $resourceValue)
            {
                $resourceValue.ModifiedDate = [DateTime]::UtcNow;
                $resourceValue.State = $state;
                #$this.BaselineControlObj.ResourceMapTable[$idHash] = $resourceValue;
            }
            else
            {
                $resourceValue = [BaselineResource]@{
                    Id = $resourceId;
                    State = $state;
                    CreatedDate = [DateTime]::UtcNow;
                    ModifiedDate = [DateTime]::UtcNow;
                }
                $this.BaselineControlObj.ResourceMapTable +=$resourceValue;
            }

            $this.PersistStorageBlob();
        }
    }

    [void] RemoveControlBaseline()
    {
        if($null -ne $this.BaselineControlObj)
        {
            $AzSKTemp = [Constants]::AzSKAppFolderPath + "\TempState";
            if(-not (Test-Path "$AzSKTemp\ControlsBaseline"))
            {
                mkdir -Path "$AzSKTemp\ControlsBaseline" -ErrorAction Stop | Out-Null
            }
            $masterFilePath = "$AzSKTemp\ControlsBaseline\$($this.BaselineResourceBlobName)"
            $controlStateBlob = Get-AzureStorageBlob -Container $this.CAScanProgressSnapshotsContainerName -Context $this.AzSKStorageAccount.Context -Blob "$($this.BaselineResourceBlobName)" -ErrorAction SilentlyContinue

            if($null -ne $controlStateBlob)
            {
                Get-AzureStorageBlobContent -CloudBlob $controlStateBlob.ICloudBlob -Context $this.AzSKStorageAccount.Context -Destination $masterFilePath -Force                
                $baselineResources  = Get-ChildItem -Path $masterFilePath -Force | Get-Content | ConvertFrom-Json
                if($baselineResources -ne $null -and ($baselineResources.ResourceMapTable | Measure-Object).Count -gt 0 -and ($baselineResources.ResourceMapTable | Where-Object { $_.State -ne "COMP" } | Measure-Object).Count -eq 0)
                {
                    $this.ArchiveBlob("_End_");
                    Remove-AzureStorageBlob -CloudBlob $controlStateBlob.ICloudBlob -Force -Context $this.AzSKStorageAccount.Context 
                }
                
            }            
            $this.BaselineControlObj = $null
        }
    }

    [void] CreateResourceMasterList([PSObject] $resourceIds)
    {

        if(($resourceIds | Measure-Object).Count -gt 0)
        {
            $resourceIdMap = @();
            $resourceIds | ForEach-Object {
                $resourceId = $_;
                #$hashId = [Helpers]::ComputeHash($resourceId);
                $resourceValue = [BaselineResource]@{
                    Id = $resourceId;
                    State = [ScanState]::INIT
                    CreatedDate = [DateTime]::UtcNow;
                    ModifiedDate = [DateTime]::UtcNow;
                }
                #$resourceIdMap.Add($hashId,$resourceValue);
                $resourceIdMap +=$resourceValue
            }
            $masterControlBlob = [BaselineResourceMap]@{
                Id = [DateTime]::UtcNow.ToString("yyyyMMdd_HHmmss");
                CreatedDate = [DateTime]::UtcNow;
                ResourceMapTable = $resourceIdMap;
            }
            $this.BaselineControlObj = $masterControlBlob;
            $this.PersistStorageBlob();
            $this.ActiveStatus = [ActiveStatus]::Yes;
        }
    }

    [void] PersistStorageBlob()
    {
        $this.GetBaselineControlObject();
        if($null -ne $this.BaselineControlObj)
        {
            $AzSKTemp = [Constants]::AzSKAppFolderPath + "\TempState";
            if(-not (Test-Path "$AzSKTemp\ControlsBaseline"))
            {
                mkdir -Path "$AzSKTemp\ControlsBaseline" -ErrorAction Stop | Out-Null
            }
            $masterFilePath = "$AzSKTemp\ControlsBaseline\$($this.BaselineResourceBlobName)"

            [Helpers]::ConvertToJsonCustom($this.BaselineControlObj) | Out-File $masterFilePath -Force
            Set-AzureStorageBlobContent -File $masterFilePath -Container $this.CAScanProgressSnapshotsContainerName -BlobType Block -Context $this.AzSKStorageAccount.Context -Force
        }
    }

    hidden [void] ArchiveBlob()
    {
        $this.ArchiveBlob("_");
    }

    hidden [void] ArchiveBlob([string] $token)
    {
        try
        {
            $AzSKTemp = [Constants]::AzSKAppFolderPath + "\TempState";
            
            if(-not (Test-Path "$AzSKTemp\ControlsBaseline"))
            {
                mkdir -Path "$AzSKTemp\ControlsBaseline" -ErrorAction Stop | Out-Null
            }
            $archiveName =  $this.CAScanProgressSnapshotsContainerName + $token +  (Get-Date).ToUniversalTime().ToString("yyyyMMddHHmmss") + ".json";
            $masterFilePath = "$AzSKTemp\ControlsBaseline\$archiveName"
            $controlStateBlob = Get-AzureStorageBlob -Container $this.CAScanProgressSnapshotsContainerName -Context $this.AzSKStorageAccount.Context -Blob "$($this.BaselineResourceBlobName)" -ErrorAction SilentlyContinue
            if($null -ne $controlStateBlob)
            {
                Get-AzureStorageBlobContent -CloudBlob $controlStateBlob.ICloudBlob -Context $this.AzSKStorageAccount.Context -Destination $masterFilePath -Force            
                Set-AzureStorageBlobContent -File $masterFilePath -Container $this.CAScanProgressSnapshotsContainerName -Blob "Archive/$archiveName" -BlobType Block -Context $this.AzSKStorageAccount.Context -Force
            }
        }
        catch
        {
            #eat exception as archive should not impact actual flow
        }
    }

    hidden [void] GetBaselineControlObject()
    {
        if($null -eq $this.BaselineControlObj)
        {
            # region Migration
            # AzSK TBR
            #[MigrationHelper]::TryMigration($this.SubscriptionContext, $this.InvocationContext);
            #endregion

            $AzSKTemp = [Constants]::AzSKAppFolderPath + "\TempState";
            if(-not (Test-Path "$AzSKTemp\ControlsBaseline"))
            {
                mkdir -Path "$AzSKTemp\ControlsBaseline" -ErrorAction Stop | Out-Null
            }
            $masterFilePath = "$AzSKTemp\ControlsBaseline\$($this.BaselineResourceBlobName)"
            $controlStateBlob = Get-AzureStorageBlob -Container $this.CAScanProgressSnapshotsContainerName -Context $this.AzSKStorageAccount.Context -Blob "$($this.BaselineResourceBlobName)" -ErrorAction SilentlyContinue

            if($null -ne $controlStateBlob)
            {
                Get-AzureStorageBlobContent -CloudBlob $controlStateBlob.ICloudBlob -Context $this.AzSKStorageAccount.Context -Destination $masterFilePath -Force
                $this.BaselineControlObj = Get-ChildItem -Path $masterFilePath -Force | Get-Content | ConvertFrom-Json
            }
        }
    }

    [ActiveStatus] IsMasterListActive()
    {
        if($null -eq $this.AzSKStorageAccount -or $null -eq $this.AzSKStorageResourceBaselineContainer  )
        {
         $this.GetAzSKControlBaselineContainer();
        }
        if($null -ne $this.ControlSettings.BaselineControls)
        {
            $this.GetBaselineControlObject();
            $expiryInDays = [Int32]::Parse($this.ControlSettings.BaselineControls.ExpiryInDays);
            if($null -eq $this.BaselineControlObj)
            {
                return $this.ActiveStatus = [ActiveStatus]::No;
            }
            if($this.BaselineControlObj.CreatedDate.AddDays($expiryInDays) -lt [DateTime]::UtcNow)
            {
                $this.RemoveControlBaseline();
                return $this.ActiveStatus = [ActiveStatus]::No;

            }
            return $this.ActiveStatus = [ActiveStatus]::Yes

        }
        else
        {
            return $this.ActiveStatus = [ActiveStatus]::No;

        }
    }

    [PSObject] GetResourceStatus([string] $resourceId)
    {
        $resourceValues = @();
        $this.GetBaselineControlObject();
        if($this.IsListAvailableAndActive())
        {
            $idHash = [Helpers]::ComputeHash($resourceId)
            $resourceValue = $this.BaselineControlObj.ResourceMapTable[$idHash];
            $resourceValues += $resourceValue;
            return $resourceValues;
        }
        return $null;
    }

    [PSObject] GetNonScannedResources()
    {
        $nonScannedResources = @();
        $this.GetBaselineControlObject();
        if($this.IsListAvailableAndActive())
        {
            $nonScannedResources +=[BaselineResource[]] $this.BaselineControlObj.ResourceMapTable | Where-Object {$_.State -eq [ScanState]::INIT}
            return $nonScannedResources;
        }
        return $null;
    }

    [PSObject] GetAllListedResources()
    {
        $nonScannedResources = @();
        $this.ArchiveBlob()
        $this.GetBaselineControlObject();
        if($this.IsListAvailableAndActive())
        {
            $nonScannedResources += $this.BaselineControlObj.ResourceMapTable
            return $nonScannedResources;
        }
        return $null;
    }

    [Bool] IsListAvailableAndActive()
    {
        if($null -ne $this.BaselineControlObj -and $this.ActiveStatus -eq [ActiveStatus]::Yes -and $null -ne $this.BaselineControlObj.ResourceMapTable)
        {
            return $true
        }
        else
        {
            return $false
        }
    }

    [PSObject] GetBaselineControlDetails()
    {
        return  $this.ControlSettings.BaselineControls
    }
}