Framework/Managers/ControlStateExtension.ps1
Set-StrictMode -Version Latest class ControlStateExtension { hidden [PSObject] $AzSDKResourceGroup = $null; hidden [PSObject] $AzSDKStorageAccount = $null; hidden [PSObject] $AzSDKStorageContainer = $null; hidden [PSObject] $ControlStateIndexer = $null; hidden [int] $HasControlStateReadPermissions = -1; hidden [int] $HasControlStateWritePermissions = -1; hidden [string] $IndexerBlobName ="Resource.index.json" ControlStateExtension() { } hidden [void] Initialize([bool] $CreateResourcesIfNotExists) { $this.GetAzSDKControlStateContainer($CreateResourcesIfNotExists) } hidden [PSObject] GetAzSDKRG([bool] $createIfNotExists) { $azSDKConfigData = [ConfigurationManager]::GetAzSdkConfigData() $resourceGroup = Get-AzureRmResourceGroup -Name $azSDKConfigData.AzSDKRGName -ErrorAction SilentlyContinue if($createIfNotExists -and ($null -eq $resourceGroup -or ($resourceGroup | Measure-Object).Count -eq 0)) { if([Helpers]::NewAzSDKResourceGroup($azSDKConfigData.AzSDKRGName, [Constants]::AzSDKRGLocation, "")) { $resourceGroup = Get-AzureRmResourceGroup -Name $azSDKConfigData.AzSDKRGName -ErrorAction SilentlyContinue } } $this.AzSDKResourceGroup = $resourceGroup return $resourceGroup; } hidden [void] GetAzSDKStorageAccount($createIfNotExists) { if($null -eq $this.AzSDKResourceGroup) { $this.GetAzSDKRG($createIfNotExists); } if($null -ne $this.AzSDKResourceGroup) { $StorageAccount = Get-AzureRmStorageAccount -ResourceGroupName $this.AzSDKResourceGroup.ResourceGroupName | Where-Object {$_.StorageAccountName -like 'azsdk*'} -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 if($createIfNotExists -and ($null -eq $StorageAccount -or ($StorageAccount | Measure-Object).Count -eq 0)) { $storageAccountName = ("azsdk" + (Get-Date).ToUniversalTime().ToString("yyyyMMddHHmmss")); $storageObject = [Helpers]::NewAzsdkCompliantStorage($storageAccountName, $this.AzSDKResourceGroup.ResourceGroupName, [Constants]::AzSDKRGLocation) if($null -ne $storageObject -and ($storageObject | Measure-Object).Count -gt 0) { $StorageAccount = Get-AzureRmStorageAccount -ResourceGroupName $this.AzSDKResourceGroup.ResourceGroupName | Where-Object {$_.StorageAccountName -like 'azsdk*'} -ErrorAction SilentlyContinue } } $this.AzSDKStorageAccount = $StorageAccount; } } hidden [void] GetAzSDKControlStateContainer([bool] $createIfNotExists) { $ContainerName = "azsdk-controls-state" if($null -eq $this.AzSDKStorageAccount) { $this.GetAzSDKStorageAccount($createIfNotExists) } if($null -eq $this.AzSDKStorageAccount) { #No storage account => no permissions at all $this.HasControlStateReadPermissions = 0 $this.HasControlStateWritePermissions = 0 return; } try { #Able to read the container then read permissions are good $containerObject = Get-AzureStorageContainer -Context $this.AzSDKStorageAccount.Context -Name $ContainerName -ErrorAction Stop $this.AzSDKStorageContainer = $containerObject; $this.HasControlStateReadPermissions = 1 } catch { #Resetting permissions in the case of exception $this.HasControlStateReadPermissions = 0 $this.HasControlStateWritePermissions = 0 try { if($createIfNotExists) { New-AzureStorageContainer -Context $this.AzSDKStorageAccount.Context -Name $ContainerName -ErrorAction SilentlyContinue $this.HasControlStateWritePermissions = 1 } $containerObject = $containerObject = Get-AzureStorageContainer -Context $this.AzSDKStorageAccount.Context -Name $ContainerName -ErrorAction SilentlyContinue $this.AzSDKStorageContainer = $containerObject; $this.HasControlStateReadPermissions = 1 } catch { #Do nothing } } } hidden [bool] ComputeControlStateIndexer() { #check for permission validation if($this.HasControlStateReadPermissions -le 0) { return $false; } #return if you don't have the required state attestation configuration during the runtime evaluation if( $null -eq $this.AzSDKResourceGroup -or $null -eq $this.AzSDKStorageAccount -or $null -eq $this.AzSDKStorageContainer) { return $false; } $StorageAccount = $this.AzSDKStorageAccount; $containerObject = $this.AzSDKStorageContainer; $ContainerName = "" if($null -ne $this.AzSDKStorageContainer) { $ContainerName = $this.AzSDKStorageContainer.Name } $indexerBlob = $null; try { $indexerBlob = Get-AzureStorageBlob -Container $ContainerName -Blob $this.IndexerBlobName -Context $StorageAccount.Context -ErrorAction Stop } catch { #Do Nothing. Below code would create a default indexer. } [ControlStateIndexer[]] $indexerObjects = @(); $this.ControlStateIndexer = $indexerObjects if($null -eq $indexerBlob) { return $true; } $AzSDKTemp = [Constants]::AzSdkAppFolderPath + "\Temp\ServerControlState"; if(-not (Test-Path -Path $AzSDKTemp)) { mkdir -Path $AzSDKTemp -Force } Get-AzureStorageBlobContent -CloudBlob $indexerBlob.ICloudBlob -Context $StorageAccount.Context -Destination $AzSDKTemp -Force $indexerObject = Get-ChildItem -Path "$AzSDKTemp\$($this.IndexerBlobName)" -Force | Get-Content | ConvertFrom-Json $this.ControlStateIndexer += $indexerObject; return $true; } hidden [PSObject] GetControlState([string] $id) { try { [ControlState[]] $controlStates = @(); $retVal = $this.ComputeControlStateIndexer(); if($null -ne $this.ControlStateIndexer -and $retVal) { $indexes = @(); $indexes += $this.ControlStateIndexer $selectedIndex = $indexes | Where-Object { $_.ResourceId -eq $id -and $_.ExpiryTime -gt [DateTime]::UtcNow} if(($selectedIndex | Measure-Object).Count -gt 0) { $controlStateBlobName = $selectedIndex.HashId + ".json" $azSDKConfigData = [ConfigurationManager]::GetAzSdkConfigData() #$azSDKConfigData.$AzSDKRGName #Look of is there is a AzSDK RG and AzSDK Storage account $StorageAccount = $this.AzSDKStorageAccount; $containerObject = $this.AzSDKStorageContainer $ContainerName = "" if($null -ne $this.AzSDKStorageContainer) { $ContainerName = $this.AzSDKStorageContainer.Name } $controlStateBlob = Get-AzureStorageBlob -Container $ContainerName -Blob $controlStateBlobName -Context $StorageAccount.Context -ErrorAction SilentlyContinue if($null -eq $controlStateBlob) { return $controlStates; } $AzSDKTemp = [Constants]::AzSdkAppFolderPath + "\Temp\ServerControlState"; if(-not (Test-Path -Path $AzSDKTemp)) { mkdir -Path $AzSDKTemp -Force } Get-AzureStorageBlobContent -CloudBlob $controlStateBlob.ICloudBlob -Context $StorageAccount.Context -Destination $AzSDKTemp -Force $ControlStatesJson = Get-ChildItem -Path "$AzSDKTemp\$controlStateBlobName" -Force | Get-Content | ConvertFrom-Json if($null -ne $ControlStatesJson) { $ControlStatesJson | ForEach-Object { try { $controlStates += [ControlState] $_; } catch { [EventBase]::PublishGenericException($_); } } } } } return $controlStates; } finally{ $this.CleanTempFolder(); } } hidden [void] SetControlState([string] $id, [ControlState[]] $controlStates, [bool] $Override) { $AzSDKTemp = [Constants]::AzSdkAppFolderPath + "\Temp\ServerControlState"; if(-not (Test-Path "$AzSDKTemp\ControlState")) { mkdir -Path "$AzSDKTemp\ControlState" -ErrorAction Stop | Out-Null } $hash = [Helpers]::ComputeHash($id); $indexerPath = "$AzSDKTemp\ControlState\$($this.IndexerBlobName)" $fileName = "$AzSDKTemp\ControlState\$hash.json" $StorageAccount = $this.AzSDKStorageAccount; $containerObject = $this.AzSDKStorageContainer $ContainerName = "" if($null -ne $this.AzSDKStorageContainer) { $ContainerName = $this.AzSDKStorageContainer.Name } $finalControlStates = $controlStates; if($Override) { # in the case of override, just persist what is evaluated in the current context. No merging with older data $this.UpdateControlIndexer($id, $controlStates); $finalControlStates = $controlStates | Where-Object { $_.State}; } else { #merge with the exiting if found $persistedControlStates = $this.GetPersistedControlStates("$hash.json"); $finalControlStates = $this.MergeControlStates($persistedControlStates, $controlStates); $this.UpdateControlIndexer($id, $finalControlStates); } [Helpers]::ConvertToJsonCustom($finalControlStates) | Out-File $fileName -Force if($null -ne $this.ControlStateIndexer) { [Helpers]::ConvertToJsonCustom($this.ControlStateIndexer) | Out-File $indexerPath -Force $controlStateArray = Get-ChildItem -Path "$AzSDKTemp\ControlState" $controlStateArray | ForEach-Object { Set-AzureStorageBlobContent -File $_.FullName -Container $ContainerName -BlobType Block -Context $StorageAccount.Context -Force } } else { #clean up the container as there is no indexer Get-AzureStorageBlob -Container $ContainerName -Context $StorageAccount.Context | Remove-AzureStorageBlob } } hidden [ControlState[]] GetPersistedControlStates([string] $controlStateBlobName) { $AzSDKTemp = [Constants]::AzSdkAppFolderPath + "\Temp\ServerControlState"; if(-not (Test-Path "$AzSDKTemp\ExistingControlStates")) { mkdir -Path "$AzSDKTemp\ExistingControlStates" -ErrorAction Stop | Out-Null } $StorageAccount = $this.AzSDKStorageAccount; $containerObject = $this.AzSDKStorageContainer $ContainerName = "" if($null -ne $this.AzSDKStorageContainer) { $ContainerName = $this.AzSDKStorageContainer.Name } [ControlState[]] $ControlStatesJson = @() try { $controlStateBlob = Get-AzureStorageBlob -Container $ContainerName -Blob $controlStateBlobName -Context $StorageAccount.Context -ErrorAction SilentlyContinue Get-AzureStorageBlobContent -CloudBlob $controlStateBlob.ICloudBlob -Context $StorageAccount.Context -Destination "$AzSDKTemp\ExistingControlStates" -Force $ControlStatesJson = Get-ChildItem -Path "$AzSDKTemp\ExistingControlStates\$controlStateBlobName" -Force | Get-Content | ConvertFrom-Json } catch { $ControlStatesJson = @() } return $ControlStatesJson } hidden [ControlState[]] MergeControlStates([ControlState[]] $persistedControlStates,[ControlState[]] $controlStates) { [ControlState[]] $computedControlStates = $controlStates; if(($computedControlStates | Measure-Object).Count -le 0) { $computedControlStates = @(); } if(($persistedControlStates | Measure-Object).Count -gt 0) { $persistedControlStates | ForEach-Object { $controlState = $_; if(($computedControlStates | Where-Object { $_.InternalId -eq $controlState.InternalId} | Measure-Object).Count -le 0) { $computedControlStates += $controlState; } } } #remove the control states with null state which would be in the case of clear attestation. $computedControlStates = $computedControlStates | Where-Object { $_.State} return $computedControlStates; } hidden [void] UpdateControlIndexer([string] $id, [ControlState[]] $controlStates) { $this.ControlStateIndexer = $null; $retVal = $this.ComputeControlStateIndexer(); $StorageAccount = $this.AzSDKStorageAccount; $containerObject = $this.AzSDKStorageContainer $ContainerName = "" if($null -ne $this.AzSDKStorageContainer) { $ContainerName = $this.AzSDKStorageContainer.Name } if($retVal) { $tempHash = [Helpers]::ComputeHash($id); $filteredIndexerObject = $this.ControlStateIndexer | Where-Object { $_.HashId -eq $tempHash} if($null -ne $filteredIndexerObject) { if(($controlStates | Measure-Object).Count -le 0) { $this.ControlStateIndexer = $this.ControlStateIndexer | Where-Object { $_.HashId -ne $tempHash} } else { $filteredIndexerObject.ExpiryTime = [DateTime]::UtcNow.AddMonths(3); $filteredIndexerObject.AttestedBy = [Helpers]::GetCurrentSessionUser(); $filteredIndexerObject.AttestedDate = [DateTime]::UtcNow; $filteredIndexerObject.Version = "1.0"; } } else { $currentIndexObject = [ControlStateIndexer]::new(); $currentIndexObject.ResourceId = $id $currentIndexObject.HashId = $tempHash; $currentIndexObject.ExpiryTime = [DateTime]::UtcNow.AddMonths(3); $currentIndexObject.AttestedBy = [Helpers]::GetCurrentSessionUser(); $currentIndexObject.AttestedDate = [DateTime]::UtcNow; $currentIndexObject.Version = "1.0"; $this.ControlStateIndexer += $currentIndexObject; } } } [bool] HasControlStateReadAccessPermissions() { if($this.HasControlStateReadPermissions -le 0) { return $false; } else { return $true; } } [bool] HasControlStateWriteAccessPermissions() { if($this.HasControlStateWritePermissions -le 0) { return $false; } else { return $true; } } hidden [void] CleanTempFolder() { $AzSDKTemp = [Constants]::AzSdkAppFolderPath + "\Temp"; if(Test-Path "$AzSDKTemp") { rmdir -Path $AzSDKTemp -Recurse -Force -ErrorAction Stop | Out-Null } } } |