Framework/Core/SVT/SVTControlAttestation.ps1
using namespace System.Management.Automation Set-StrictMode -Version Latest class SVTControlAttestation { [SVTEventContext[]] $ControlResults = $null hidden [bool] $dirtyCommitState = $false; hidden [bool] $abortProcess = $false; hidden [ControlStateExtension] $controlStateExtension = $null; hidden [AttestControls] $AttestControlsChoice; hidden [bool] $bulkAttestMode = $false; [AttestationOptions] $attestOptions; hidden [PSObject] $ControlSettings ; hidden [SubscriptionContext] $SubscriptionContext; hidden [InvocationInfo] $InvocationContext; hidden [Object] $repoProject = @{}; SVTControlAttestation([SVTEventContext[]] $ctrlResults, [AttestationOptions] $attestationOptions, [SubscriptionContext] $subscriptionContext, [InvocationInfo] $invocationContext) { $this.SubscriptionContext = $subscriptionContext; $this.InvocationContext = $invocationContext; $this.ControlResults = $ctrlResults; $this.AttestControlsChoice = $attestationOptions.AttestControls; $this.attestOptions = $attestationOptions; $this.controlStateExtension = [ControlStateExtension]::new($this.SubscriptionContext, $this.InvocationContext) $this.controlStateExtension.UniqueRunId = $(Get-Date -format "yyyyMMdd_HHmmss"); $this.controlStateExtension.Initialize($true) $this.ControlSettings=$ControlSettingsJson = [ConfigurationManager]::LoadServerConfigFile("ControlSettings.json"); $this.repoProject.projectsWithRepo = @(); $this.repoProject.projectsWithoutRepo = @(); } [AttestationStatus] GetAttestationValue([string] $AttestationCode) { switch($AttestationCode.ToUpper()) { "1" { return [AttestationStatus]::NotAnIssue;} "2" { return [AttestationStatus]::WillNotFix;} "3" { return [AttestationStatus]::WillFixLater;} "4" { return [AttestationStatus]::ApprovedException;} "5" { return [AttestationStatus]::NotApplicable;} "6" { return [AttestationStatus]::StateConfirmed;} "9" { $this.abortProcess = $true; return [AttestationStatus]::None; } Default { return [AttestationStatus]::None;} } return [AttestationStatus]::None } [ControlState] ComputeEffectiveControlState([ControlState] $controlState, [string] $ControlSeverity, [bool] $isSubscriptionControl, [SVTEventContext] $controlItem, [ControlResult] $controlResult) { Write-Host "$([Constants]::SingleDashLine)" -ForegroundColor Cyan Write-Host "ControlId : $($controlState.ControlId)`nControlSeverity : $ControlSeverity`nDescription : $($controlItem.ControlItem.Description)`nCurrentControlStatus : $($controlState.ActualVerificationResult)`n" if(-not $controlResult.CurrentSessionContext.Permissions.HasRequiredAccess) { Write-Host "Skipping attestation process for this control. You do not have required permissions to evaluate this control. `nNote: If your permissions were elevated recently, please run the 'Disconnect-AzAccount' command to clear the Azure cache and try again." -ForegroundColor Yellow return $controlState; } if(-not $this.isControlAttestable($controlItem, $controlResult)) { Write-Host "This control cannot be attested by policy. Please follow the steps in 'Recommendation' for the control in order to fix the control and minimize exposure to attacks." -ForegroundColor Yellow return $controlState; } $userChoice = "" $isPrevAttested = $false; if($controlResult.AttestationStatus -ne [AttestationStatus]::None) { $isPrevAttested = $true; } $tempCurrentStateObject = $null; if($null -ne $controlResult.StateManagement -and $null -ne $controlResult.StateManagement.CurrentStateData) { $tempCurrentStateObject = $controlResult.StateManagement.CurrentStateData; } #display the current state only if the state object is not empty if($null -ne $tempCurrentStateObject -and $null -ne $tempCurrentStateObject.DataObject) { #Current state object was converted to b64 in SetStateData. We need to decode it back to print it in plaintext in PS console. Write-Host "Configuration data to be attested:" -ForegroundColor Cyan $decodedDataObj = [System.Text.Encoding]::Unicode.GetString([System.Convert]::FromBase64String($tempCurrentStateObject.DataObject)) | ConvertFrom-Json Write-Host "$([JsonHelper]::ConvertToPson($decodedDataObj))" } if($isPrevAttested -and ($this.AttestControlsChoice -eq [AttestControls]::All -or $this.AttestControlsChoice -eq [AttestControls]::AlreadyAttested)) { #Compute the effective attestation status for support backward compatibility $tempAttestationStatus = $controlState.AttestationStatus while($userChoice -ne '0' -and $userChoice -ne '1' -and $userChoice -ne '2' -and $userChoice -ne '9' ) { Write-Host "Existing attestation details:" -ForegroundColor Cyan Write-Host "Attestation Status: $tempAttestationStatus`nVerificationResult: $($controlState.EffectiveVerificationResult)`nAttested By : $($controlState.State.AttestedBy)`nJustification : $($controlState.State.Justification)`n" Write-Host "Please select an action from below: `n[0]: Skip`n[1]: Attest`n[2]: Clear Attestation" -ForegroundColor Cyan $userChoice = Read-Host "User Choice" if(-not [string]::IsNullOrWhiteSpace($userChoice)) { $userChoice = $userChoice.Trim(); } } } else { while($userChoice -ne '0' -and $userChoice -ne '1' -and $userChoice -ne '9' ) { Write-Host "Please select an action from below: `n[0]: Skip`n[1]: Attest" -ForegroundColor Cyan $userChoice = Read-Host "User Choice" if(-not [string]::IsNullOrWhiteSpace($userChoice)) { $userChoice = $userChoice.Trim(); } } } $Justification="" $Attestationstate="" $message = "" [PSObject] $ValidAttestationStatesHashTable = $this.ComputeEligibleAttestationStates($controlItem, $controlResult); [String[]]$ValidAttestationKey = @(0) #Sort attestation status based on key value if($null -ne $ValidAttestationStatesHashTable) { $ValidAttestationStatesHashTable | ForEach-Object { $message += "`n[{0}]: {1}" -f $_.Value,$_.Name; $ValidAttestationKey += $_.Value } } switch ($userChoice.ToUpper()){ "0" #None { } "1" #Attest { $attestationState = "" while($attestationState -notin [String[]]($ValidAttestationKey) -and $attestationState -ne '9' ) { Write-Host "`nPlease select an attestation status from below: `n[0]: Skip$message" -ForegroundColor Cyan $attestationState = Read-Host "User Choice" $attestationState = $attestationState.Trim(); } $attestValue = $this.GetAttestationValue($attestationState); if($attestValue -ne [AttestationStatus]::None) { $controlState.AttestationStatus = $attestValue; } elseif($this.abortProcess) { return $null; } elseif($attestValue -eq [AttestationStatus]::None) { return $controlState; } #In case when the user selects ApprovedException as the reason for attesting, #they'll be prompted to provide the number of days till that approval expires. $exceptionApprovalExpiryDate = "" if($controlState.AttestationStatus -eq [AttestationStatus]::ApprovedException) { $expiryPeriod = $this.ControlSettings.DefaultAttestationPeriodForExemptControl if([string]::IsNullOrWhiteSpace($this.attestOptions.ApprovedExceptionExpiryDate)) { $exceptionApprovalExpiryDate = ([DateTime]::UtcNow).AddDays($expiryPeriod) } else{ try { $maxAllowedExceptionApprovalExpiryDate = ([DateTime]::UtcNow).AddDays($expiryPeriod) [datetime]$proposedExceptionApprovalExpiryDate = $this.attestOptions.ApprovedExceptionExpiryDate if($proposedExceptionApprovalExpiryDate -le [DateTime]::UtcNow) { Write-Host "ExpiryDate should be greater than current date. To attest control using 'ApprovedException' status use '-ApprovedExceptionExpiryDate' parameter to specify the expiry date. Please provide this param in the command with mm/dd/yy date format. For example: -ApprovedExceptionExpiryDate '11/25/20'" -ForegroundColor Yellow; break; } elseif($proposedExceptionApprovalExpiryDate -gt $maxAllowedExceptionApprovalExpiryDate) { Write-Host "`nNote: The exception approval expiry will be set to $($expiryPeriod) days from today.`n" -ForegroundColor Yellow $exceptionApprovalExpiryDate = $maxAllowedExceptionApprovalExpiryDate } else { $exceptionApprovalExpiryDate = $proposedExceptionApprovalExpiryDate } } catch { Write-Host "`nThe date needs to be in mm/dd/yy format. For example: 11/25/20." -ForegroundColor Red throw $_.Exception } } } if($controlState.AttestationStatus -ne [AttestationStatus]::None) { $Justification = "" while([string]::IsNullOrWhiteSpace($Justification)) { $Justification = Read-Host "Justification" try { $SanitizedJustification = [System.Text.UTF8Encoding]::ASCII.GetString([System.Text.UTF8Encoding]::ASCII.GetBytes($Justification)); $Justification = $SanitizedJustification; } catch { # If the justification text is empty then prompting message again to provide justification text. } if([string]::IsNullOrWhiteSpace($Justification)) { Write-Host "`nEmpty space or blank justification is not allowed." } } $this.dirtyCommitState = $true } $controlState.EffectiveVerificationResult = [Helpers]::EvaluateVerificationResult($controlState.ActualVerificationResult,$controlState.AttestationStatus); $controlState.State = $tempCurrentStateObject if($null -eq $controlState.State) { $controlState.State = [StateData]::new(); } $controlState.State.AttestedBy = [ContextHelper]::GetCurrentSessionUser(); $controlState.State.AttestedDate = [DateTime]::UtcNow; $controlState.State.Justification = $Justification #In case of control exemption, calculating the exception approval(attestation) expiry date beforehand, #based on the days entered by the user (default 6 months) if($controlState.AttestationStatus -eq [AttestationStatus]::ApprovedException) { $controlState.State.ApprovedExceptionID = $this.attestOptions.ApprovedExceptionID $controlState.State.ExpiryDate = $exceptionApprovalExpiryDate.ToString("MM/dd/yyyy"); } break; } "2" #Clear Attestation { $this.dirtyCommitState = $true #Clears the control state. This overrides the previous attested controlstate. $controlState.State = $null; $controlState.EffectiveVerificationResult = $controlState.ActualVerificationResult $controlState.AttestationStatus = [AttestationStatus]::None } "9" #Abort { $this.abortProcess = $true; return $null; } Default { } } return $controlState; } [ControlState] ComputeEffectiveControlStateInBulkMode([ControlState] $controlState, [string] $ControlSeverity, [bool] $isSubscriptionControl, [SVTEventContext] $controlItem, [ControlResult] $controlResult) { Write-Host "$([Constants]::SingleDashLine)" -ForegroundColor Cyan Write-Host "ControlId : $($controlState.ControlId)`nControlSeverity : $ControlSeverity`nDescription : $($controlItem.ControlItem.Description)`nCurrentControlStatus : $($controlState.ActualVerificationResult)`n" if(-not $controlResult.CurrentSessionContext.Permissions.HasRequiredAccess) { Write-Host "Skipping attestation process for this control. You do not have required permissions to evaluate this control. `nNote: If your permissions were elevated recently, please run the 'Disconnect-AzAccount' command to clear the Azure cache and try again." -ForegroundColor Yellow return $controlState; } $userChoice = "" if($null -ne $this.attestOptions -and $this.attestOptions.IsBulkClearModeOn) { if($controlState.AttestationStatus -ne [AttestationStatus]::None) { $this.dirtyCommitState = $true #Compute the effective attestation status for support backward compatibility $tempAttestationStatus = $controlState.AttestationStatus Write-Host "Existing attestation details:" -ForegroundColor Cyan Write-Host "Attestation Status: $tempAttestationStatus`nVerificationResult: $($controlState.EffectiveVerificationResult)`nAttested By : $($controlState.State.AttestedBy)`nJustification : $($controlState.State.Justification)`n" } #Clears the control state. This overrides the previous attested controlstate. $controlState.State = $null; $controlState.EffectiveVerificationResult = $controlState.ActualVerificationResult $controlState.AttestationStatus = [AttestationStatus]::None return $controlState; } $ValidAttestationStatesHashTable = $this.ComputeEligibleAttestationStates($controlItem, $controlResult); #Checking if control is attestable if($this.isControlAttestable($controlItem, $controlResult)) { # Checking if the attestation state provided in command parameter is valid for the control if( $this.attestOptions.AttestationStatus -in $ValidAttestationStatesHashTable.Name) { $controlState.AttestationStatus = $this.attestOptions.AttestationStatus; $controlState.EffectiveVerificationResult = [Helpers]::EvaluateVerificationResult($controlState.ActualVerificationResult,$controlState.AttestationStatus); #In case when the user selects ApprovedException as the reason for attesting, #they'll be prompted to provide the number of days till that approval expires. $exceptionApprovalExpiryDate = "" if($controlState.AttestationStatus -eq "ApprovedException") { $expiryPeriod = $this.ControlSettings.DefaultAttestationPeriodForExemptControl if([string]::IsNullOrWhiteSpace($this.attestOptions.ApprovedExceptionExpiryDate)) { $exceptionApprovalExpiryDate = ([DateTime]::UtcNow).AddDays($expiryPeriod) } else{ try { $maxAllowedExceptionApprovalExpiryDate = ([DateTime]::UtcNow).AddDays($expiryPeriod) [datetime]$proposedExceptionApprovalExpiryDate = $this.attestOptions.ApprovedExceptionExpiryDate #([DateTime]::UtcNow).AddDays($numberOfDays) if($proposedExceptionApprovalExpiryDate -le [DateTime]::UtcNow) { Write-Host "ExpiryDate should be greater than current date. To attest control using 'ApprovedException' status use '-ApprovedExceptionExpiryDate' parameter to specify the expiry date. Please provide this param in the command with mm/dd/yy date format. For example: -ApprovedExceptionExpiryDate '11/25/20'" -ForegroundColor Yellow; break; } elseif($proposedExceptionApprovalExpiryDate -gt $maxAllowedExceptionApprovalExpiryDate) { Write-Host "`nNote: The exception approval expiry will be set to $($expiryPeriod) days from today.`n" -ForegroundColor Yellow $exceptionApprovalExpiryDate = $maxAllowedExceptionApprovalExpiryDate } else { $exceptionApprovalExpiryDate = $proposedExceptionApprovalExpiryDate } } catch { Write-Host "`nThe date needs to be in mm/dd/yy format. For example: 11/25/20." -ForegroundColor Red throw $_.Exception } } } if($null -ne $controlResult.StateManagement -and $null -ne $controlResult.StateManagement.CurrentStateData) { $controlState.State = $controlResult.StateManagement.CurrentStateData; } if($null -eq $controlState.State) { $controlState.State = [StateData]::new(); } $this.dirtyCommitState = $true $controlState.State.AttestedBy = [ContextHelper]::GetCurrentSessionUser(); $controlState.State.AttestedDate = [DateTime]::UtcNow; $controlState.State.Justification = $this.attestOptions.JustificationText #In case of control exemption, calculating the exception approval(attestation) expiry date beforehand, #based on the days entered by the user (default 6 months) if($controlState.AttestationStatus -eq [AttestationStatus]::ApprovedException) { $controlState.State.ApprovedExceptionID = $this.attestOptions.ApprovedExceptionID $controlState.State.ExpiryDate = $exceptionApprovalExpiryDate.ToString("MM/dd/yyyy"); } } #if attestation state provided in command parameter is not valid for the control then print warning else { $outvalidSet=$ValidAttestationStatesHashTable.Name -join "," ; Write-Host "The chosen attestation state is not applicable to this control. Valid attestation choices are: $outvalidSet" -ForegroundColor Yellow; return $controlState ; } } #If control is not attestable then print warning else { Write-Host "This control cannot be attested by policy. Please follow the steps in 'Recommendation' for the control in order to fix the control and minimize exposure to attacks." -ForegroundColor Yellow; } return $controlState; } [void] StartControlAttestation() { #Set flag to to run rescan $Global:AttestationValue = $false try { #user provided justification text would be available only in bulk attestation mode. if($null -ne $this.attestOptions -and (-not [string]::IsNullOrWhiteSpace($this.attestOptions.JustificationText) -or $this.attestOptions.IsBulkClearModeOn)) { $this.bulkAttestMode = $true; Write-Host "$([Constants]::SingleDashLine)" -ForegroundColor Yellow } else { Write-Host ("$([Constants]::SingleDashLine)`nNote: Enter 9 during any stage to exit the attestation workflow. This will abort attestation process for the current resource and remaining resources.`n$([Constants]::SingleDashLine)") -ForegroundColor Yellow } if($null -eq $this.ControlResults) { Write-Host "No control results found." -ForegroundColor Yellow } if ($this.attestOptions.AttestationStatus -eq "ApprovedException" -and [string]::IsNullOrWhiteSpace($this.attestOptions.ApprovedExceptionID)) { Write-Host "Exception id is mandatory for approved exception." -ForegroundColor Cyan $exceptionId = Read-Host "Please enter the approved exception id" if ([string]::IsNullOrWhiteSpace($exceptionId)) { Write-Host "Exception id is mandatory for approved exception." -ForegroundColor Red break; } $this.attestOptions.ApprovedExceptionID = $exceptionId } $this.abortProcess = $false; #filtering the controls - Removing all the passed controls #Step1 Group By IDs #added below where condition to filter only for org and project. so only org and projec controll go into attestation $filteredControlResults = @() $allowedResourcesToAttest = @() if($null -ne $this.ControlSettings.AllowAttestationResourceType) { $allowedResourcesToAttest = $this.ControlSettings.AllowAttestationResourceType $attNonEnabledResource = $this.ControlResults | Where {$_.FeatureName -notin $allowedResourcesToAttest } } $filteredControlResults += ($this.ControlResults | Where {$_.FeatureName -in $allowedResourcesToAttest }) | Group-Object { $_.GetUniqueId() } if((($filteredControlResults | Measure-Object).Count -eq 1 -and ($filteredControlResults[0].Group | Measure-Object).Count -gt 0 -and $null -ne $filteredControlResults[0].Group[0].ResourceContext) ` -or ($filteredControlResults | Measure-Object).Count -gt 1) { Write-Host "No. of candidate resources for the attestation: $($filteredControlResults.Count)" -ForegroundColor Cyan if ($this.InvocationContext) { if ($this.InvocationContext.BoundParameters["AttestationHostProjectName"]) { if($this.controlStateExtension.GetControlStatePermission("Organization", "")) { $this.controlStateExtension.SetProjectInExtForOrg() } else { Write-Host "Error: Could not configure host project for organization controls attestation.`nThis may be because you may not have correct privilege (requires 'Project Collection Administrator')." -ForegroundColor Red } } } } #show warning if the keys count is greater than certain number. $counter = 0 #start iterating resource after resource foreach($resource in $filteredControlResults) { $isAttestationRepoPresent = $this.ValidateAttestationRepo($resource); if($isAttestationRepoPresent) { $resourceValueKey = $resource.Name $this.dirtyCommitState = $false; $resourceValue = $resource.Group; $isSubscriptionScan = $false; $counter = $counter + 1 if(($resourceValue | Measure-Object).Count -gt 0) { $SubscriptionId = $resourceValue[0].SubscriptionContext.SubscriptionId if($null -ne $resourceValue[0].ResourceContext) { $ResourceId = $resourceValue[0].ResourceContext.ResourceId Write-Host $([String]::Format([Constants]::ModuleAttestStartHeading, $resourceValue[0].FeatureName, $resourceValue[0].ResourceContext.ResourceGroupName, $resourceValue[0].ResourceContext.ResourceName, $counter, $filteredControlResults.Count)) -ForegroundColor Cyan } else { $isSubscriptionScan = $true; Write-Host $([String]::Format([Constants]::ModuleAttestStartHeadingSub, $resourceValue[0].FeatureName, $resourceValue[0].SubscriptionContext.SubscriptionName, $resourceValue[0].SubscriptionContext.SubscriptionId)) -ForegroundColor Cyan } if(($resourceValue[0].FeatureName -eq "Organization" -or $resourceValue[0].FeatureName -eq "Project") -and !$this.controlStateExtension.GetControlStatePermission($resourceValue[0].FeatureName, $resourceValue[0].ResourceContext.ResourceName) ) { Write-Host "Error: Attestation denied.`nThis may be because you are attempting to attest controls for areas you do not have RBAC permission to." -ForegroundColor Red continue } if($resourceValue[0].FeatureName -eq "Organization" -and !$this.controlStateExtension.GetProject()) { Write-Host "`nNo project defined to store attestation details for organization-specific controls." -ForegroundColor Red Write-Host "Use the '-AttestationHostProjectName' parameter with this command to configure the project that will host attestation details for organization level controls.`nRun 'Get-Help -Name Get-AzSKADOSecurityStatus -Full' for more info." -ForegroundColor Yellow continue } [ControlState[]] $resourceControlStates = @() $count = 0; [SVTEventContext[]] $filteredControlItems = @() $resourceValue | ForEach-Object { $controlItem = $_; $matchedControlItem = $false; if(($controlItem.ControlResults | Measure-Object).Count -gt 0) { [ControlResult[]] $matchedControlResults = @(); $controlItem.ControlResults | ForEach-Object { $controlResult = $_ if($controlResult.ActualVerificationResult -ne [VerificationResult]::Passed -and $controlResult.ActualVerificationResult -ne [VerificationResult]::Error) { if($this.AttestControlsChoice -eq [AttestControls]::All) { $matchedControlItem = $true; $matchedControlResults += $controlResult; $count++; } elseif($this.AttestControlsChoice -eq [AttestControls]::AlreadyAttested -and $controlResult.AttestationStatus -ne [AttestationStatus]::None) { $matchedControlItem = $true; $matchedControlResults += $controlResult; $count++; } elseif($this.AttestControlsChoice -eq [AttestControls]::NotAttested -and $controlResult.AttestationStatus -eq [AttestationStatus]::None) { $matchedControlItem = $true; $matchedControlResults += $controlResult; $count++; } } } } if($matchedControlItem) { $controlItem.ControlResults = $matchedControlResults; $filteredControlItems += $controlItem; } } #Added below variable to supply in setcontrol to send in controlstateextension to verify resourcetype $FeatureName = ""; $resourceName = ""; $resourceGroupName = ""; if($count -gt 0) { Write-Host "No. of controls that need to be attested: $count" -ForegroundColor Cyan foreach( $controlItem in $filteredControlItems) { $FeatureName = $controlItem.FeatureName $resourceName = $controlItem.ResourceContext.ResourceName $resourceGroupName = $controlItem.ResourceContext.ResourceGroupName $controlId = $controlItem.ControlItem.ControlID $controlSeverity = $controlItem.ControlItem.ControlSeverity $controlResult = $null; $controlStatus = ""; $isPrevAttested = $false; if(($controlItem.ControlResults | Measure-Object).Count -gt 0) { foreach( $controlResult in $controlItem.ControlResults) { $controlStatus = $controlResult.ActualVerificationResult; [ControlState] $controlState = [ControlState]::new($controlId,$controlItem.ControlItem.Id,$controlResult.ChildResourceName,$controlStatus,"1.0"); if($null -ne $controlResult.StateManagement -and $null -ne $controlResult.StateManagement.AttestedStateData) { $controlState.State = $controlResult.StateManagement.AttestedStateData } $controlState.AttestationStatus = $controlResult.AttestationStatus $controlState.EffectiveVerificationResult = $controlResult.VerificationResult #ADOTodo: This seems to be unused...also, we should look into if 'tolower()' should be done in general for rsrcIds. $controlState.HashId = [ControlStateExtension]::ComputeHashX($resourceValueKey.ToLower()); $controlState.ResourceId = $resourceValueKey; if($this.bulkAttestMode) { $controlState = $this.ComputeEffectiveControlStateInBulkMode($controlState, $controlSeverity, $isSubscriptionScan, $controlItem, $controlResult) } else { $controlState = $this.ComputeEffectiveControlState($controlState, $controlSeverity, $isSubscriptionScan, $controlItem, $controlResult) } $resourceControlStates +=$controlState; if($this.abortProcess) { Write-Host "Aborted the attestation workflow." -ForegroundColor Yellow return; } } } Write-Host $([Constants]::SingleDashLine) -ForegroundColor Cyan } } else { Write-Host "No attestable controls found.`n$([Constants]::SingleDashLine)" -ForegroundColor Yellow } #remove the entries which doesn't have any state #$resourceControlStates = $resourceControlStates | Where-Object {$_.State} #persist the value back to state if($this.dirtyCommitState) { if(($resourceControlStates | Measure-Object).Count -gt 0) { #Set flag to to run rescan $Global:AttestationValue = $true Write-Host "Attestation summary for this resource:" -ForegroundColor Cyan $output = @() $resourceControlStates | ForEach-Object { $out = "" | Select-Object ControlId, EvaluatedResult, EffectiveResult, AttestationChoice $out.ControlId = $_.ControlId $out.EvaluatedResult = $_.ActualVerificationResult $out.EffectiveResult = $_.EffectiveVerificationResult $out.AttestationChoice = $_.AttestationStatus.ToString() $output += $out } Write-Host ($output | Format-Table ControlId, EvaluatedResult, EffectiveResult, AttestationChoice | Out-String) -ForegroundColor Cyan } Write-Host "Committing the attestation details for this resource..." -ForegroundColor Cyan $this.controlStateExtension.SetControlState($resourceValueKey, $resourceControlStates, $false, $FeatureName, $resourceName, $resourceGroupName) Write-Host "Commit succeeded." -ForegroundColor Cyan } if($null -ne $resourceValue[0].ResourceContext) { $ResourceId = $resourceValue[0].ResourceContext.ResourceId Write-Host $([String]::Format([Constants]::CompletedAttestAnalysis, $resourceValue[0].FeatureName, $resourceValue[0].ResourceContext.ResourceGroupName, $resourceValue[0].ResourceContext.ResourceName)) -ForegroundColor Cyan } else { $isSubscriptionScan = $true; Write-Host $([String]::Format([Constants]::CompletedAttestAnalysisSub, $resourceValue[0].FeatureName, $resourceValue[0].SubscriptionContext.SubscriptionName, $resourceValue[0].SubscriptionContext.SubscriptionId)) -ForegroundColor Cyan } } } else { continue; } } } finally { $folderPath = Join-Path $([Constants]::AzSKAppFolderPath) "Temp" | Join-Path -ChildPath $($this.controlStateExtension.UniqueRunId) [Helpers]::CleanupLocalFolder($folderPath); } } [bool] ValidateAttestationRepo([Object] $resource) { if($resource.Group[0].ResourceContext.ResourceTypeName -eq 'Organization') { $projectName = $this.controlStateExtension.GetProject(); } elseif($resource.Group[0].ResourceContext.ResourceTypeName -eq 'Project') { $projectName = $resource.Group[0].ResourceContext.ResourceName; } else { $projectName = $resource.Group[0].ResourceContext.ResourceGroupName; } if($projectName -in $this.repoProject.projectsWithRepo) { return $true; } elseif($projectName -in $this.repoProject.projectsWithoutRepo) { return $false; } elseif(-not [string]::IsNullOrEmpty($projectName)) { $attestationRepo = [Constants]::AttestationRepo; #Get attesttion repo name from controlsetting file if AttestationRepo varibale value is not empty. if ([Helpers]::CheckMember($this.ControlSettings,"AttestationRepo")) { $attestationRepo = $this.ControlSettings.AttestationRepo; } $rmContext = [ContextHelper]::GetCurrentContext(); $user = ""; $base64AuthInfo = [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes(("{0}:{1}" -f $user,$rmContext.AccessToken))) $uri = "https://dev.azure.com/{0}/{1}/_apis/git/repositories/{2}/refs?api-version=5.0" -f $this.SubscriptionContext.subscriptionid, $projectName, $attestationRepo try { $webRequest = Invoke-RestMethod -Uri $uri -Method Get -ContentType "application/json" -Headers @{Authorization=("Basic {0}" -f $base64AuthInfo)} if($null -ne $webRequest) { $this.repoProject.projectsWithRepo += $projectName return $true; } else { Write-Host $([Constants]::SingleDashLine) -ForegroundColor Red Write-Host "`nAttestation repository was not found in [$projectName] project" -ForegroundColor Red Write-Host "See more at https://aka.ms/adoscanner/attestation `n" -ForegroundColor Yellow Write-Host $([Constants]::SingleDashLine) -ForegroundColor Red $this.repoProject.projectsWithoutRepo += $projectName return $false; } } catch { Write-Host $([Constants]::SingleDashLine) -ForegroundColor Red Write-Host "`nAttestation repository was not found in [$projectName] project" -ForegroundColor Red Write-Host "See more at https://aka.ms/adoscanner/attestation `n" -ForegroundColor Yellow Write-Host $([Constants]::SingleDashLine) -ForegroundColor Red $this.repoProject.projectsWithoutRepo += $projectName return $false; } } elseif($this.controlStateExtension.PrintParamPolicyProjErr -eq $true ){ Write-Host $([Constants]::SingleDashLine) -ForegroundColor Red Write-Host -ForegroundColor Red "Could not fetch attestation-project-name. `nYou can: `n`r(a) Run Set-AzSKADOMonitoringSetting -PolicyProject '<PolicyProjectName>' or `n`r(b) Use '-PolicyProject' parameter to specify the host project containing attestation details of organization controls. `n`r(c) Run Set-AzSKPolicySettings -EnableOrgControlAttestation `$true" Write-Host $([Constants]::SingleDashLine) -ForegroundColor Red return $false; } else{ return $false; } } [bool] isControlAttestable([SVTEventContext] $controlItem, [ControlResult] $controlResult) { # If None is found in array along with other attestation status, 'None' will get precedence. if(($controlItem.ControlItem.ValidAttestationStates | Measure-Object).Count -gt 0 -and ($controlItem.ControlItem.ValidAttestationStates | Where-Object { $_.Trim() -eq [AttestationStatus]::None } | Measure-Object).Count -gt 0) { return $false } else { return $true } } [PSObject] ComputeEligibleAttestationStates([SVTEventContext] $controlItem, [ControlResult] $controlResult) { [System.Collections.ArrayList] $ValidAttestationStates = $null #Default attestation state if($null -ne $this.ControlSettings.DefaultValidAttestationStates){ $ValidAttestationStates = $this.ControlSettings.DefaultValidAttestationStates | Select-Object -Unique } #Additional attestation state if($null -ne $controlItem.ControlItem.ValidAttestationStates) { $ValidAttestationStates += $controlItem.ControlItem.ValidAttestationStates | Select-Object -Unique } $ValidAttestationStates = $ValidAttestationStates.Trim() | Select-Object -Unique #if control not in grace, disable WillFixLater option if(-not $controlResult.IsControlInGrace) { if(($ValidAttestationStates | Where-Object { $_ -eq [AttestationStatus]::WillFixLater} | Measure-Object).Count -gt 0) { $ValidAttestationStates.Remove("WillFixLater") } } $ValidAttestationStatesHashTable = [Constants]::AttestationStatusHashMap.GetEnumerator() | Where-Object { $_.Name -in $ValidAttestationStates } | Sort-Object value if($this.attestOptions.IsExemptModeOn) { $ValidAttestationStatesHashTable += [Constants]::AttestationStatusHashMap.GetEnumerator() | Where-Object { $_.Name -eq [AttestationStatus]::ApprovedException } } return $ValidAttestationStatesHashTable; } } # SIG # Begin signature block # MIIjmAYJKoZIhvcNAQcCoIIjiTCCI4UCAQExDzANBglghkgBZQMEAgEFADB5Bgor # BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG # KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCCM7Db+s8tTP9Nb # 6wE23XiGirP8o3yfuqBmyN/K0jfReqCCDYUwggYDMIID66ADAgECAhMzAAABiK9S # 1rmSbej5AAAAAAGIMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMRMwEQYD # VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy # b3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25p # bmcgUENBIDIwMTEwHhcNMjAwMzA0MTgzOTQ4WhcNMjEwMzAzMTgzOTQ4WjB0MQsw # CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u # ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMR4wHAYDVQQDExVNaWNy # b3NvZnQgQ29ycG9yYXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB # AQCSCNryE+Cewy2m4t/a74wZ7C9YTwv1PyC4BvM/kSWPNs8n0RTe+FvYfU+E9uf0 # t7nYlAzHjK+plif2BhD+NgdhIUQ8sVwWO39tjvQRHjP2//vSvIfmmkRoML1Ihnjs # 9kQiZQzYRDYYRp9xSQYmRwQjk5hl8/U7RgOiQDitVHaU7BT1MI92lfZRuIIDDYBd # vXtbclYJMVOwqZtv0O9zQCret6R+fRSGaDNfEEpcILL+D7RV3M4uaJE4Ta6KAOdv # V+MVaJp1YXFTZPKtpjHO6d9pHQPZiG7NdC6QbnRGmsa48uNQrb6AfmLKDI1Lp31W # MogTaX5tZf+CZT9PSuvjOCLNAgMBAAGjggGCMIIBfjAfBgNVHSUEGDAWBgorBgEE # AYI3TAgBBggrBgEFBQcDAzAdBgNVHQ4EFgQUj9RJL9zNrPcL10RZdMQIXZN7MG8w # VAYDVR0RBE0wS6RJMEcxLTArBgNVBAsTJE1pY3Jvc29mdCBJcmVsYW5kIE9wZXJh # dGlvbnMgTGltaXRlZDEWMBQGA1UEBRMNMjMwMDEyKzQ1ODM4NjAfBgNVHSMEGDAW # gBRIbmTlUAXTgqoXNzcitW2oynUClTBUBgNVHR8ETTBLMEmgR6BFhkNodHRwOi8v # d3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2NybC9NaWNDb2RTaWdQQ0EyMDExXzIw # MTEtMDctMDguY3JsMGEGCCsGAQUFBwEBBFUwUzBRBggrBgEFBQcwAoZFaHR0cDov # L3d3dy5taWNyb3NvZnQuY29tL3BraW9wcy9jZXJ0cy9NaWNDb2RTaWdQQ0EyMDEx # XzIwMTEtMDctMDguY3J0MAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcNAQELBQADggIB # ACnXo8hjp7FeT+H6iQlV3CcGnkSbFvIpKYafgzYCFo3UHY1VHYJVb5jHEO8oG26Q # qBELmak6MTI+ra3WKMTGhE1sEIlowTcp4IAs8a5wpCh6Vf4Z/bAtIppP3p3gXk2X # 8UXTc+WxjQYsDkFiSzo/OBa5hkdW1g4EpO43l9mjToBdqEPtIXsZ7Hi1/6y4gK0P # mMiwG8LMpSn0n/oSHGjrUNBgHJPxgs63Slf58QGBznuXiRaXmfTUDdrvhRocdxIM # i8nXQwWACMiQzJSRzBP5S2wUq7nMAqjaTbeXhJqD2SFVHdUYlKruvtPSwbnqSRWT # GI8s4FEXt+TL3w5JnwVZmZkUFoioQDMMjFyaKurdJ6pnzbr1h6QW0R97fWc8xEIz # LIOiU2rjwWAtlQqFO8KNiykjYGyEf5LyAJKAO+rJd9fsYR+VBauIEQoYmjnUbTXM # SY2Lf5KMluWlDOGVh8q6XjmBccpaT+8tCfxpaVYPi1ncnwTwaPQvVq8RjWDRB7Pa # 8ruHgj2HJFi69+hcq7mWx5nTUtzzFa7RSZfE5a1a5AuBmGNRr7f8cNfa01+tiWjV # Kk1a+gJUBSP0sIxecFbVSXTZ7bqeal45XSDIisZBkWb+83TbXdTGMDSUFKTAdtC+ # r35GfsN8QVy59Hb5ZYzAXczhgRmk7NyE6jD0Ym5TKiW5MIIHejCCBWKgAwIBAgIK # YQ6Q0gAAAAAAAzANBgkqhkiG9w0BAQsFADCBiDELMAkGA1UEBhMCVVMxEzARBgNV # BAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jv # c29mdCBDb3Jwb3JhdGlvbjEyMDAGA1UEAxMpTWljcm9zb2Z0IFJvb3QgQ2VydGlm # aWNhdGUgQXV0aG9yaXR5IDIwMTEwHhcNMTEwNzA4MjA1OTA5WhcNMjYwNzA4MjEw # OTA5WjB+MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UE # BxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSgwJgYD # VQQDEx9NaWNyb3NvZnQgQ29kZSBTaWduaW5nIFBDQSAyMDExMIICIjANBgkqhkiG # 9w0BAQEFAAOCAg8AMIICCgKCAgEAq/D6chAcLq3YbqqCEE00uvK2WCGfQhsqa+la # UKq4BjgaBEm6f8MMHt03a8YS2AvwOMKZBrDIOdUBFDFC04kNeWSHfpRgJGyvnkmc # 6Whe0t+bU7IKLMOv2akrrnoJr9eWWcpgGgXpZnboMlImEi/nqwhQz7NEt13YxC4D # dato88tt8zpcoRb0RrrgOGSsbmQ1eKagYw8t00CT+OPeBw3VXHmlSSnnDb6gE3e+ # lD3v++MrWhAfTVYoonpy4BI6t0le2O3tQ5GD2Xuye4Yb2T6xjF3oiU+EGvKhL1nk # kDstrjNYxbc+/jLTswM9sbKvkjh+0p2ALPVOVpEhNSXDOW5kf1O6nA+tGSOEy/S6 # A4aN91/w0FK/jJSHvMAhdCVfGCi2zCcoOCWYOUo2z3yxkq4cI6epZuxhH2rhKEmd # X4jiJV3TIUs+UsS1Vz8kA/DRelsv1SPjcF0PUUZ3s/gA4bysAoJf28AVs70b1FVL # 5zmhD+kjSbwYuER8ReTBw3J64HLnJN+/RpnF78IcV9uDjexNSTCnq47f7Fufr/zd # sGbiwZeBe+3W7UvnSSmnEyimp31ngOaKYnhfsi+E11ecXL93KCjx7W3DKI8sj0A3 # T8HhhUSJxAlMxdSlQy90lfdu+HggWCwTXWCVmj5PM4TasIgX3p5O9JawvEagbJjS # 4NaIjAsCAwEAAaOCAe0wggHpMBAGCSsGAQQBgjcVAQQDAgEAMB0GA1UdDgQWBBRI # bmTlUAXTgqoXNzcitW2oynUClTAZBgkrBgEEAYI3FAIEDB4KAFMAdQBiAEMAQTAL # BgNVHQ8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBRyLToCMZBD # uRQFTuHqp8cx0SOJNDBaBgNVHR8EUzBRME+gTaBLhklodHRwOi8vY3JsLm1pY3Jv # c29mdC5jb20vcGtpL2NybC9wcm9kdWN0cy9NaWNSb29DZXJBdXQyMDExXzIwMTFf # MDNfMjIuY3JsMF4GCCsGAQUFBwEBBFIwUDBOBggrBgEFBQcwAoZCaHR0cDovL3d3 # dy5taWNyb3NvZnQuY29tL3BraS9jZXJ0cy9NaWNSb29DZXJBdXQyMDExXzIwMTFf # MDNfMjIuY3J0MIGfBgNVHSAEgZcwgZQwgZEGCSsGAQQBgjcuAzCBgzA/BggrBgEF # BQcCARYzaHR0cDovL3d3dy5taWNyb3NvZnQuY29tL3BraW9wcy9kb2NzL3ByaW1h # cnljcHMuaHRtMEAGCCsGAQUFBwICMDQeMiAdAEwAZQBnAGEAbABfAHAAbwBsAGkA # YwB5AF8AcwB0AGEAdABlAG0AZQBuAHQALiAdMA0GCSqGSIb3DQEBCwUAA4ICAQBn # 8oalmOBUeRou09h0ZyKbC5YR4WOSmUKWfdJ5DJDBZV8uLD74w3LRbYP+vj/oCso7 # v0epo/Np22O/IjWll11lhJB9i0ZQVdgMknzSGksc8zxCi1LQsP1r4z4HLimb5j0b # pdS1HXeUOeLpZMlEPXh6I/MTfaaQdION9MsmAkYqwooQu6SpBQyb7Wj6aC6VoCo/ # KmtYSWMfCWluWpiW5IP0wI/zRive/DvQvTXvbiWu5a8n7dDd8w6vmSiXmE0OPQvy # CInWH8MyGOLwxS3OW560STkKxgrCxq2u5bLZ2xWIUUVYODJxJxp/sfQn+N4sOiBp # mLJZiWhub6e3dMNABQamASooPoI/E01mC8CzTfXhj38cbxV9Rad25UAqZaPDXVJi # hsMdYzaXht/a8/jyFqGaJ+HNpZfQ7l1jQeNbB5yHPgZ3BtEGsXUfFL5hYbXw3MYb # BL7fQccOKO7eZS/sl/ahXJbYANahRr1Z85elCUtIEJmAH9AAKcWxm6U/RXceNcbS # oqKfenoi+kiVH6v7RyOA9Z74v2u3S5fi63V4GuzqN5l5GEv/1rMjaHXmr/r8i+sL # gOppO6/8MO0ETI7f33VtY5E90Z1WTk+/gFcioXgRMiF670EKsT/7qMykXcGhiJtX # cVZOSEXAQsmbdlsKgEhr/Xmfwb1tbWrJUnMTDXpQzTGCFWkwghVlAgEBMIGVMH4x # CzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRt # b25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01p # Y3Jvc29mdCBDb2RlIFNpZ25pbmcgUENBIDIwMTECEzMAAAGIr1LWuZJt6PkAAAAA # AYgwDQYJYIZIAWUDBAIBBQCggbAwGQYJKoZIhvcNAQkDMQwGCisGAQQBgjcCAQQw # HAYKKwYBBAGCNwIBCzEOMAwGCisGAQQBgjcCARUwLwYJKoZIhvcNAQkEMSIEIJ95 # 9RCWlDKksuZfgq97vmpIFcg9pQ0y4jwiNcdJ9ccwMEQGCisGAQQBgjcCAQwxNjA0 # oBSAEgBNAGkAYwByAG8AcwBvAGYAdKEcgBpodHRwczovL3d3dy5taWNyb3NvZnQu # Y29tIDANBgkqhkiG9w0BAQEFAASCAQBaC5r9iuGZSWwlJJiMyTPep98F/LxCFZ/p # WkEHHnJbMwzVslLK7bjbMAkXjt2G/O8EioaW3EL9FTXAyF/PH0CURoJEHZ+X1PvY # Pjfgh2kLnKxQLpkvvH3xyOm6WdhMG5iQDcExekKqmkXYjvGPa78BKcdP8Th2Czvz # /V4adZX6p7IEQU9ZYxUPz27V6I5T/COUsDTFUaaUOiMbW5pXV3tjug2fTPfg+RWH # ONT7DwlvlBYcEBWsW/6efLZTD2VHZ1ApRpKMMcqXnNNfdHqFIQATFGWpykeOxNxf # AY3ihk4vpLzW1fAI2ObDlAM+EB+zVl+XMUL4N2zoUQ4TW2P9itE0oYIS8TCCEu0G # CisGAQQBgjcDAwExghLdMIIS2QYJKoZIhvcNAQcCoIISyjCCEsYCAQMxDzANBglg # hkgBZQMEAgEFADCCAVUGCyqGSIb3DQEJEAEEoIIBRASCAUAwggE8AgEBBgorBgEE # AYRZCgMBMDEwDQYJYIZIAWUDBAIBBQAEIDVoG0EM5h6Qr4vbOdBkybOtiiyRGfl+ # 2hJKnDLtzm0vAgZf24yJwWEYEzIwMjEwMTE1MDUzODI2LjkzOFowBIACAfSggdSk # gdEwgc4xCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQH # EwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKTAnBgNV # BAsTIE1pY3Jvc29mdCBPcGVyYXRpb25zIFB1ZXJ0byBSaWNvMSYwJAYDVQQLEx1U # aGFsZXMgVFNTIEVTTjpDNEJELUUzN0YtNUZGQzElMCMGA1UEAxMcTWljcm9zb2Z0 # IFRpbWUtU3RhbXAgU2VydmljZaCCDkQwggT1MIID3aADAgECAhMzAAABIziw5K3Y # WpCdAAAAAAEjMA0GCSqGSIb3DQEBCwUAMHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQI # EwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3Nv # ZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBD # QSAyMDEwMB4XDTE5MTIxOTAxMTQ1NloXDTIxMDMxNzAxMTQ1Nlowgc4xCzAJBgNV # BAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4w # HAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKTAnBgNVBAsTIE1pY3Jvc29m # dCBPcGVyYXRpb25zIFB1ZXJ0byBSaWNvMSYwJAYDVQQLEx1UaGFsZXMgVFNTIEVT # TjpDNEJELUUzN0YtNUZGQzElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAg # U2VydmljZTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAJ280MmwZKXc # AS7x2ZvA4TOOCzw+63Xs9ULGWtdZDN3Vl+aGQEsMwErIkgzQi0fTO0sOD9N3hO1H # aTWHoS80N/Qb6oLR2WCZkv/VM4WFnThOv3yA5zSt+vuKNwrjEHFC0jlMDCJqaU7S # t6WJbl/kAP5sgM0qtpEEQhxtVaf8IoV5lq8vgMJNr30O4rqLYEi/YZWQZYwQHAiV # MunCYpJiccnNONRRdg2D3Tyu22eEJwPQP6DkeEioMy9ehMmBrkjADVOgQV+T4mir # +hAJeINysps6wgRO5qjuV5+jvczNQa1Wm7jxsqBv04GClIp5NHvrXQmZ9mpZdj3r # jxFuZbKjd3ECAwEAAaOCARswggEXMB0GA1UdDgQWBBSB1GwUgNONG/kRwrBo3hkV # +AyeHjAfBgNVHSMEGDAWgBTVYzpcijGQ80N7fEYbxTNoWoVtVTBWBgNVHR8ETzBN # MEugSaBHhkVodHRwOi8vY3JsLm1pY3Jvc29mdC5jb20vcGtpL2NybC9wcm9kdWN0 # cy9NaWNUaW1TdGFQQ0FfMjAxMC0wNy0wMS5jcmwwWgYIKwYBBQUHAQEETjBMMEoG # CCsGAQUFBzAChj5odHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpL2NlcnRzL01p # Y1RpbVN0YVBDQV8yMDEwLTA3LTAxLmNydDAMBgNVHRMBAf8EAjAAMBMGA1UdJQQM # MAoGCCsGAQUFBwMIMA0GCSqGSIb3DQEBCwUAA4IBAQBb5QG3qlfoW6r1V4lT+kO8 # LUzF32S3fUfgn/S1QQBPzORs/6ujj09ytHWNDfOewwkSya1f8S9e+BbnXknH5l3R # 6nS2BkRTANtTmXxvMLTCyveYe/JQIfos+Z3iJ0b1qHDSnEj6Qmdf1MymrPAk5jxh # xhiiXlwILUjvH56y7rLHxK0wnsH12EO9MnkaSNXJNCmSmfgUEkDNzu53C39l6XNR # APauz2/WslIUZcX3NDCMgv5hZi2nhd99HxyaJJscn1f8hZXA++f1HNbq8bdkh3OY # gRnNr7QdnO+Guvtu3dyGqYdQMMGPnAt4L7Ew9ykjy0Uoz64/r0SbQIRYty5eM9M3 # MIIGcTCCBFmgAwIBAgIKYQmBKgAAAAAAAjANBgkqhkiG9w0BAQsFADCBiDELMAkG # A1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQx # HjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEyMDAGA1UEAxMpTWljcm9z # b2Z0IFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IDIwMTAwHhcNMTAwNzAxMjEz # NjU1WhcNMjUwNzAxMjE0NjU1WjB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2Fz # aGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENv # cnBvcmF0aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAx # MDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKkdDbx3EYo6IOz8E5f1 # +n9plGt0VBDVpQoAgoX77XxoSyxfxcPlYcJ2tz5mK1vwFVMnBDEfQRsalR3OCROO # fGEwWbEwRA/xYIiEVEMM1024OAizQt2TrNZzMFcmgqNFDdDq9UeBzb8kYDJYYEby # WEeGMoQedGFnkV+BVLHPk0ySwcSmXdFhE24oxhr5hoC732H8RsEnHSRnEnIaIYqv # S2SJUGKxXf13Hz3wV3WsvYpCTUBR0Q+cBj5nf/VmwAOWRH7v0Ev9buWayrGo8noq # CjHw2k4GkbaICDXoeByw6ZnNPOcvRLqn9NxkvaQBwSAJk3jN/LzAyURdXhacAQVP # Ik0CAwEAAaOCAeYwggHiMBAGCSsGAQQBgjcVAQQDAgEAMB0GA1UdDgQWBBTVYzpc # ijGQ80N7fEYbxTNoWoVtVTAZBgkrBgEEAYI3FAIEDB4KAFMAdQBiAEMAQTALBgNV # HQ8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBTV9lbLj+iiXGJo # 0T2UkFvXzpoYxDBWBgNVHR8ETzBNMEugSaBHhkVodHRwOi8vY3JsLm1pY3Jvc29m # dC5jb20vcGtpL2NybC9wcm9kdWN0cy9NaWNSb29DZXJBdXRfMjAxMC0wNi0yMy5j # cmwwWgYIKwYBBQUHAQEETjBMMEoGCCsGAQUFBzAChj5odHRwOi8vd3d3Lm1pY3Jv # c29mdC5jb20vcGtpL2NlcnRzL01pY1Jvb0NlckF1dF8yMDEwLTA2LTIzLmNydDCB # oAYDVR0gAQH/BIGVMIGSMIGPBgkrBgEEAYI3LgMwgYEwPQYIKwYBBQUHAgEWMWh0 # dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9QS0kvZG9jcy9DUFMvZGVmYXVsdC5odG0w # QAYIKwYBBQUHAgIwNB4yIB0ATABlAGcAYQBsAF8AUABvAGwAaQBjAHkAXwBTAHQA # YQB0AGUAbQBlAG4AdAAuIB0wDQYJKoZIhvcNAQELBQADggIBAAfmiFEN4sbgmD+B # cQM9naOhIW+z66bM9TG+zwXiqf76V20ZMLPCxWbJat/15/B4vceoniXj+bzta1RX # CCtRgkQS+7lTjMz0YBKKdsxAQEGb3FwX/1z5Xhc1mCRWS3TvQhDIr79/xn/yN31a # PxzymXlKkVIArzgPF/UveYFl2am1a+THzvbKegBvSzBEJCI8z+0DpZaPWSm8tv0E # 4XCfMkon/VWvL/625Y4zu2JfmttXQOnxzplmkIz/amJ/3cVKC5Em4jnsGUpxY517 # IW3DnKOiPPp/fZZqkHimbdLhnPkd/DjYlPTGpQqWhqS9nhquBEKDuLWAmyI4ILUl # 5WTs9/S/fmNZJQ96LjlXdqJxqgaKD4kWumGnEcua2A5HmoDF0M2n0O99g/DhO3EJ # 3110mCIIYdqwUB5vvfHhAN/nMQekkzr3ZUd46PioSKv33nJ+YWtvd6mBy6cJrDm7 # 7MbL2IK0cs0d9LiFAR6A+xuJKlQ5slvayA1VmXqHczsI5pgt6o3gMy4SKfXAL1Qn # IffIrE7aKLixqduWsqdCosnPGUFN4Ib5KpqjEWYw07t0MkvfY3v1mYovG8chr1m1 # rtxEPJdQcdeh0sVV42neV8HR3jDA/czmTfsNv11P6Z0eGTgvvM9YBS7vDaBQNdrv # CScc1bN+NR4Iuto229Nfj950iEkSoYIC0jCCAjsCAQEwgfyhgdSkgdEwgc4xCzAJ # BgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25k # MR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKTAnBgNVBAsTIE1pY3Jv # c29mdCBPcGVyYXRpb25zIFB1ZXJ0byBSaWNvMSYwJAYDVQQLEx1UaGFsZXMgVFNT # IEVTTjpDNEJELUUzN0YtNUZGQzElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUtU3Rh # bXAgU2VydmljZaIjCgEBMAcGBSsOAwIaAxUAuhdmjeDinhfC7gw1KBCeM/v7V4Gg # gYMwgYCkfjB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4G # A1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYw # JAYDVQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMDANBgkqhkiG9w0B # AQUFAAIFAOOrnMcwIhgPMjAyMTAxMTUwODQ3MDNaGA8yMDIxMDExNjA4NDcwM1ow # dzA9BgorBgEEAYRZCgQBMS8wLTAKAgUA46ucxwIBADAKAgEAAgIeyQIB/zAHAgEA # AgIR3DAKAgUA46zuRwIBADA2BgorBgEEAYRZCgQCMSgwJjAMBgorBgEEAYRZCgMC # oAowCAIBAAIDB6EgoQowCAIBAAIDAYagMA0GCSqGSIb3DQEBBQUAA4GBAKoX8GrF # HW5qXEmz6iMrQWobz8iwSdr1MrH7fhxxbhmm5B3fvrw3morWvdsv3+r8d7o8lBrg # JbnsBt06IfkkLb4ppIx627l02ID1oBBavf37hZB6xTxlT1UylHqoSOotAcsSCF3J # m2G3Pyib3Sf1FU/4yWFbqQOnsP9pUIVuLHJgMYIDDTCCAwkCAQEwgZMwfDELMAkG # A1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQx # HjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9z # b2Z0IFRpbWUtU3RhbXAgUENBIDIwMTACEzMAAAEjOLDkrdhakJ0AAAAAASMwDQYJ # YIZIAWUDBAIBBQCgggFKMBoGCSqGSIb3DQEJAzENBgsqhkiG9w0BCRABBDAvBgkq # hkiG9w0BCQQxIgQgkwSMlS3aIaBeYWdotTs7JJfXHU7O/vWl1SPkCihFjLwwgfoG # CyqGSIb3DQEJEAIvMYHqMIHnMIHkMIG9BCARmjODP/tEzMQNo6OsxKQADL8pwKJk # M5YnXKrq+xWBszCBmDCBgKR+MHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNo # aW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29y # cG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEw # AhMzAAABIziw5K3YWpCdAAAAAAEjMCIEIFbliRvV05Sx3LEco4lfSxArEMwVzCRU # uGXYUNZ2bES4MA0GCSqGSIb3DQEBCwUABIIBAH2SiikceeZ6IUNc72X1i1cAlLO+ # 5LOgGq2rEqtPINg4RzkzLb1g9zPS59JVfyES3YHtE9aUWGmu7FCy3J8931+Kp61K # 5OmBEl2BLDTmJAJo40iNKJ8EblBhEU84UKvd1JGFSRUI0m8CDy25+VeuDdWvLEbL # IXquVU0GbcK/E64zJ84wrQN5u6WyCAEz64/lXez06lKlFTDZ85Ykv1vA5pZeEYp0 # 0xSV01/eiXY+9ZMOJj6mAoIzh5pAI2xzJ2EBH5WmGJqGaa7NVopTt2pC+eTmgAxL # PuyOoXzqOGywaSJEvaZYXqakktrX74gIrjJSWqmHwtHzQjFW0hNNPeMGqNc= # SIG # End signature block |