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 [OrganizationContext] $OrganizationContext; hidden [InvocationInfo] $InvocationContext; hidden [Object] $repoProject = @{}; SVTControlAttestation([SVTEventContext[]] $ctrlResults, [AttestationOptions] $attestationOptions, [OrganizationContext] $organizationContext, [InvocationInfo] $invocationContext) { $this.OrganizationContext = $organizationContext; $this.InvocationContext = $invocationContext; $this.ControlResults = $ctrlResults; $this.AttestControlsChoice = $attestationOptions.AttestControls; $this.attestOptions = $attestationOptions; $this.controlStateExtension = [ControlStateExtension]::new($this.OrganizationContext, $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] $isOrganizationControl, [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." -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] $isOrganizationControl, [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([Helpers]::CheckMember($this.ControlSettings,"AttestableResourceTypes") -and $null -ne $this.ControlSettings.AttestableResourceTypes) { $allowedResourcesToAttest = $this.ControlSettings.AttestableResourceTypes; } $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; $isOrganizationScan = $false; $counter = $counter + 1 if(($resourceValue | Measure-Object).Count -gt 0) { $OrganizationName = $resourceValue[0].OrganizationContext.OrganizationName 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 { $isOrganizationScan = $true; Write-Host $([String]::Format([Constants]::ModuleAttestStartHeadingSub, $resourceValue[0].FeatureName, $resourceValue[0].OrganizationContext.OrganizationName, $resourceValue[0].OrganizationContext.OrganizationId)) -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, $isOrganizationScan, $controlItem, $controlResult) } else { $controlState = $this.ComputeEffectiveControlState($controlState, $controlSeverity, $isOrganizationScan, $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 { $isOrganizationScan = $true; Write-Host $([String]::Format([Constants]::CompletedAttestAnalysisSub, $resourceValue[0].FeatureName, $resourceValue[0].OrganizationContext.OrganizationName, $resourceValue[0].OrganizationContext.OrganizationId)) -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.OrganizationContext.OrganizationName, $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 # MIIjoQYJKoZIhvcNAQcCoIIjkjCCI44CAQExDzANBglghkgBZQMEAgEFADB5Bgor # BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG # KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCAxwLiiA5421H/l # cpPkIWmydTiqgYKNRrUT4KKzSFOu46CCDYEwggX/MIID56ADAgECAhMzAAABh3IX # chVZQMcJAAAAAAGHMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMRMwEQYD # VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy # b3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25p # bmcgUENBIDIwMTEwHhcNMjAwMzA0MTgzOTQ3WhcNMjEwMzAzMTgzOTQ3WjB0MQsw # CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u # ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMR4wHAYDVQQDExVNaWNy # b3NvZnQgQ29ycG9yYXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB # AQDOt8kLc7P3T7MKIhouYHewMFmnq8Ayu7FOhZCQabVwBp2VS4WyB2Qe4TQBT8aB # znANDEPjHKNdPT8Xz5cNali6XHefS8i/WXtF0vSsP8NEv6mBHuA2p1fw2wB/F0dH # sJ3GfZ5c0sPJjklsiYqPw59xJ54kM91IOgiO2OUzjNAljPibjCWfH7UzQ1TPHc4d # weils8GEIrbBRb7IWwiObL12jWT4Yh71NQgvJ9Fn6+UhD9x2uk3dLj84vwt1NuFQ # itKJxIV0fVsRNR3abQVOLqpDugbr0SzNL6o8xzOHL5OXiGGwg6ekiXA1/2XXY7yV # Fc39tledDtZjSjNbex1zzwSXAgMBAAGjggF+MIIBejAfBgNVHSUEGDAWBgorBgEE # AYI3TAgBBggrBgEFBQcDAzAdBgNVHQ4EFgQUhov4ZyO96axkJdMjpzu2zVXOJcsw # UAYDVR0RBEkwR6RFMEMxKTAnBgNVBAsTIE1pY3Jvc29mdCBPcGVyYXRpb25zIFB1 # ZXJ0byBSaWNvMRYwFAYDVQQFEw0yMzAwMTIrNDU4Mzg1MB8GA1UdIwQYMBaAFEhu # ZOVQBdOCqhc3NyK1bajKdQKVMFQGA1UdHwRNMEswSaBHoEWGQ2h0dHA6Ly93d3cu # bWljcm9zb2Z0LmNvbS9wa2lvcHMvY3JsL01pY0NvZFNpZ1BDQTIwMTFfMjAxMS0w # Ny0wOC5jcmwwYQYIKwYBBQUHAQEEVTBTMFEGCCsGAQUFBzAChkVodHRwOi8vd3d3 # Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2NlcnRzL01pY0NvZFNpZ1BDQTIwMTFfMjAx # MS0wNy0wOC5jcnQwDAYDVR0TAQH/BAIwADANBgkqhkiG9w0BAQsFAAOCAgEAixmy # S6E6vprWD9KFNIB9G5zyMuIjZAOuUJ1EK/Vlg6Fb3ZHXjjUwATKIcXbFuFC6Wr4K # NrU4DY/sBVqmab5AC/je3bpUpjtxpEyqUqtPc30wEg/rO9vmKmqKoLPT37svc2NV # BmGNl+85qO4fV/w7Cx7J0Bbqk19KcRNdjt6eKoTnTPHBHlVHQIHZpMxacbFOAkJr # qAVkYZdz7ikNXTxV+GRb36tC4ByMNxE2DF7vFdvaiZP0CVZ5ByJ2gAhXMdK9+usx # zVk913qKde1OAuWdv+rndqkAIm8fUlRnr4saSCg7cIbUwCCf116wUJ7EuJDg0vHe # yhnCeHnBbyH3RZkHEi2ofmfgnFISJZDdMAeVZGVOh20Jp50XBzqokpPzeZ6zc1/g # yILNyiVgE+RPkjnUQshd1f1PMgn3tns2Cz7bJiVUaqEO3n9qRFgy5JuLae6UweGf # AeOo3dgLZxikKzYs3hDMaEtJq8IP71cX7QXe6lnMmXU/Hdfz2p897Zd+kU+vZvKI # 3cwLfuVQgK2RZ2z+Kc3K3dRPz2rXycK5XCuRZmvGab/WbrZiC7wJQapgBodltMI5 # GMdFrBg9IeF7/rP4EqVQXeKtevTlZXjpuNhhjuR+2DMt/dWufjXpiW91bo3aH6Ea # jOALXmoxgltCp1K7hrS6gmsvj94cLRf50QQ4U8Qwggd6MIIFYqADAgECAgphDpDS # AAAAAAADMA0GCSqGSIb3DQEBCwUAMIGIMQswCQYDVQQGEwJVUzETMBEGA1UECBMK # V2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0 # IENvcnBvcmF0aW9uMTIwMAYDVQQDEylNaWNyb3NvZnQgUm9vdCBDZXJ0aWZpY2F0 # ZSBBdXRob3JpdHkgMjAxMTAeFw0xMTA3MDgyMDU5MDlaFw0yNjA3MDgyMTA5MDla # MH4xCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdS # ZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMT # H01pY3Jvc29mdCBDb2RlIFNpZ25pbmcgUENBIDIwMTEwggIiMA0GCSqGSIb3DQEB # AQUAA4ICDwAwggIKAoICAQCr8PpyEBwurdhuqoIQTTS68rZYIZ9CGypr6VpQqrgG # OBoESbp/wwwe3TdrxhLYC/A4wpkGsMg51QEUMULTiQ15ZId+lGAkbK+eSZzpaF7S # 35tTsgosw6/ZqSuuegmv15ZZymAaBelmdugyUiYSL+erCFDPs0S3XdjELgN1q2jz # y23zOlyhFvRGuuA4ZKxuZDV4pqBjDy3TQJP4494HDdVceaVJKecNvqATd76UPe/7 # 4ytaEB9NViiienLgEjq3SV7Y7e1DkYPZe7J7hhvZPrGMXeiJT4Qa8qEvWeSQOy2u # M1jFtz7+MtOzAz2xsq+SOH7SnYAs9U5WkSE1JcM5bmR/U7qcD60ZI4TL9LoDho33 # X/DQUr+MlIe8wCF0JV8YKLbMJyg4JZg5SjbPfLGSrhwjp6lm7GEfauEoSZ1fiOIl # XdMhSz5SxLVXPyQD8NF6Wy/VI+NwXQ9RRnez+ADhvKwCgl/bwBWzvRvUVUvnOaEP # 6SNJvBi4RHxF5MHDcnrgcuck379GmcXvwhxX24ON7E1JMKerjt/sW5+v/N2wZuLB # l4F77dbtS+dJKacTKKanfWeA5opieF+yL4TXV5xcv3coKPHtbcMojyyPQDdPweGF # RInECUzF1KVDL3SV9274eCBYLBNdYJWaPk8zhNqwiBfenk70lrC8RqBsmNLg1oiM # CwIDAQABo4IB7TCCAekwEAYJKwYBBAGCNxUBBAMCAQAwHQYDVR0OBBYEFEhuZOVQ # BdOCqhc3NyK1bajKdQKVMBkGCSsGAQQBgjcUAgQMHgoAUwB1AGIAQwBBMAsGA1Ud # DwQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFHItOgIxkEO5FAVO # 4eqnxzHRI4k0MFoGA1UdHwRTMFEwT6BNoEuGSWh0dHA6Ly9jcmwubWljcm9zb2Z0 # LmNvbS9wa2kvY3JsL3Byb2R1Y3RzL01pY1Jvb0NlckF1dDIwMTFfMjAxMV8wM18y # Mi5jcmwwXgYIKwYBBQUHAQEEUjBQME4GCCsGAQUFBzAChkJodHRwOi8vd3d3Lm1p # Y3Jvc29mdC5jb20vcGtpL2NlcnRzL01pY1Jvb0NlckF1dDIwMTFfMjAxMV8wM18y # Mi5jcnQwgZ8GA1UdIASBlzCBlDCBkQYJKwYBBAGCNy4DMIGDMD8GCCsGAQUFBwIB # FjNodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2RvY3MvcHJpbWFyeWNw # cy5odG0wQAYIKwYBBQUHAgIwNB4yIB0ATABlAGcAYQBsAF8AcABvAGwAaQBjAHkA # XwBzAHQAYQB0AGUAbQBlAG4AdAAuIB0wDQYJKoZIhvcNAQELBQADggIBAGfyhqWY # 4FR5Gi7T2HRnIpsLlhHhY5KZQpZ90nkMkMFlXy4sPvjDctFtg/6+P+gKyju/R6mj # 82nbY78iNaWXXWWEkH2LRlBV2AySfNIaSxzzPEKLUtCw/WvjPgcuKZvmPRul1LUd # d5Q54ulkyUQ9eHoj8xN9ppB0g430yyYCRirCihC7pKkFDJvtaPpoLpWgKj8qa1hJ # Yx8JaW5amJbkg/TAj/NGK978O9C9Ne9uJa7lryft0N3zDq+ZKJeYTQ49C/IIidYf # wzIY4vDFLc5bnrRJOQrGCsLGra7lstnbFYhRRVg4MnEnGn+x9Cf43iw6IGmYslmJ # aG5vp7d0w0AFBqYBKig+gj8TTWYLwLNN9eGPfxxvFX1Fp3blQCplo8NdUmKGwx1j # NpeG39rz+PIWoZon4c2ll9DuXWNB41sHnIc+BncG0QaxdR8UvmFhtfDcxhsEvt9B # xw4o7t5lL+yX9qFcltgA1qFGvVnzl6UJS0gQmYAf0AApxbGbpT9Fdx41xtKiop96 # eiL6SJUfq/tHI4D1nvi/a7dLl+LrdXga7Oo3mXkYS//WsyNodeav+vyL6wuA6mk7 # r/ww7QRMjt/fdW1jkT3RnVZOT7+AVyKheBEyIXrvQQqxP/uozKRdwaGIm1dxVk5I # RcBCyZt2WwqASGv9eZ/BvW1taslScxMNelDNMYIVdjCCFXICAQEwgZUwfjELMAkG # A1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQx # HjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEoMCYGA1UEAxMfTWljcm9z # b2Z0IENvZGUgU2lnbmluZyBQQ0EgMjAxMQITMwAAAYdyF3IVWUDHCQAAAAABhzAN # BglghkgBZQMEAgEFAKCBsDAZBgkqhkiG9w0BCQMxDAYKKwYBBAGCNwIBBDAcBgor # BgEEAYI3AgELMQ4wDAYKKwYBBAGCNwIBFTAvBgkqhkiG9w0BCQQxIgQg3qGHfwBD # fPTFb+p6y77Rv0p7b7oxRNcvM4QTuMqOfO8wRAYKKwYBBAGCNwIBDDE2MDSgFIAS # AE0AaQBjAHIAbwBzAG8AZgB0oRyAGmh0dHBzOi8vd3d3Lm1pY3Jvc29mdC5jb20g # MA0GCSqGSIb3DQEBAQUABIIBACmN+WkSFcvyX8YXUwCialxNJbd1gHPzUlNnnE4D # WN2PfgpGF5QvUFul/weSbiTXTzCECMV06CntXtZSGtdZRo54Kr+v4tEZixUGR+QJ # t5BWlkQn7piOUUT9z8jtcNlovHRCc4Lal9FtkJI/exRwHxVT06+pNaW797xTvqpN # supqLst515RxvC/Bnhq8Hko4iWaUUNGt3tONHVXkxeua8flxkOftnKy5RIDwp+qo # 9O7prwRT3CDwZsDMo3A5oadjKlr8QxTRDy6a0d1PkEVuyiWaS11NBbJml9prwphb # VpqlXQdugJy5zMHQMlUGx88nrootshAEf8oOztqzdZZA5S+hghL+MIIS+gYKKwYB # BAGCNwMDATGCEuowghLmBgkqhkiG9w0BBwKgghLXMIIS0wIBAzEPMA0GCWCGSAFl # AwQCAQUAMIIBWQYLKoZIhvcNAQkQAQSgggFIBIIBRDCCAUACAQEGCisGAQQBhFkK # AwEwMTANBglghkgBZQMEAgEFAAQgIWV38StO7/bjLoKbGBUlzE1+r0UitNPlXHRS # syCIHkICBmAlqWkzHhgTMjAyMTAyMTUwNDUxMjQuNjUyWjAEgAIB9KCB2KSB1TCB # 0jELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1Jl # ZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEtMCsGA1UECxMk # TWljcm9zb2Z0IElyZWxhbmQgT3BlcmF0aW9ucyBMaW1pdGVkMSYwJAYDVQQLEx1U # aGFsZXMgVFNTIEVTTjpGQzQxLTRCRDQtRDIyMDElMCMGA1UEAxMcTWljcm9zb2Z0 # IFRpbWUtU3RhbXAgU2VydmljZaCCDk0wggT5MIID4aADAgECAhMzAAABQCMZ1l7e # lSQxAAAAAAFAMA0GCSqGSIb3DQEBCwUAMHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQI # EwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3Nv # ZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBD # QSAyMDEwMB4XDTIwMTAxNTE3MjgyNloXDTIyMDExMjE3MjgyNlowgdIxCzAJBgNV # BAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4w # HAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xLTArBgNVBAsTJE1pY3Jvc29m # dCBJcmVsYW5kIE9wZXJhdGlvbnMgTGltaXRlZDEmMCQGA1UECxMdVGhhbGVzIFRT # UyBFU046RkM0MS00QkQ0LUQyMjAxJTAjBgNVBAMTHE1pY3Jvc29mdCBUaW1lLVN0 # YW1wIFNlcnZpY2UwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCufWsz # cerVL03TPxH5gqpm7bnKSTk6VPxOy7C10FbIMJEWgBKT18HqyIKiUWFcGHJ6Phzf # IjA3RTIlYE5MCMe144hiN8KnHnf2tuAEjn8FMe0L6pwFPt+0+SdO1Cfz2U05yk/v # R+5hVkuhCwOcuMbHG1b95V7BHlDQjWZZB8nLnE596WTk5aPPdhXgcq2rIhHMll39 # HNxjzDqqbOhI2xgh2+WJPZ55BlvJhN0lCxGjMgpMwsIlQF9WOjDZ8kwO3MMH1cQ5 # 1+E9bO9Q5p1iCqqHSWyUBHs1X3QUWZmBlYBGsbyPtmdWcLkw5c5L80jnxLjzJyy6 # DSk3Y0YsuTZhaPELAgMBAAGjggEbMIIBFzAdBgNVHQ4EFgQUNUMcLiZ3RiCOjNKq # dWz454QtDmcwHwYDVR0jBBgwFoAU1WM6XIoxkPNDe3xGG8UzaFqFbVUwVgYDVR0f # BE8wTTBLoEmgR4ZFaHR0cDovL2NybC5taWNyb3NvZnQuY29tL3BraS9jcmwvcHJv # ZHVjdHMvTWljVGltU3RhUENBXzIwMTAtMDctMDEuY3JsMFoGCCsGAQUFBwEBBE4w # TDBKBggrBgEFBQcwAoY+aHR0cDovL3d3dy5taWNyb3NvZnQuY29tL3BraS9jZXJ0 # cy9NaWNUaW1TdGFQQ0FfMjAxMC0wNy0wMS5jcnQwDAYDVR0TAQH/BAIwADATBgNV # HSUEDDAKBggrBgEFBQcDCDANBgkqhkiG9w0BAQsFAAOCAQEAYwxSraBC4IL3Cvhi # EhJ8/Khto1hXc6/hjBaxJ8jP+PXFo31O8sAHYHE+LYK1FuBsFR/jyfTvJF5kifC7 # avy/Aug0bZO1jN7LTUNHKOOw2iIcX1S5EsXIpkKGQoLej2vQ7LbHRhiNSkPFUKFn # mrlwB/DzzjA/SJRxicooafx4nSfCmvvOv9OW74c6NcNP0LvnhpLgpQU2bwPuLC69 # ZbNI5WXtcxZ27zYGedOYHuzY5x/cjhp0bN2LFDlnHFrfM4C8rOtX7QdxVAhjdJAn # 0/OMNGXMK+IxOHEDwVQhEvcWdiq9yFaQShnjDxLsWwZY2VctZDt8cxveXiCO54fI # 7inq1TCCBnEwggRZoAMCAQICCmEJgSoAAAAAAAIwDQYJKoZIhvcNAQELBQAwgYgx # CzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRt # b25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xMjAwBgNVBAMTKU1p # Y3Jvc29mdCBSb290IENlcnRpZmljYXRlIEF1dGhvcml0eSAyMDEwMB4XDTEwMDcw # MTIxMzY1NVoXDTI1MDcwMTIxNDY1NVowfDELMAkGA1UEBhMCVVMxEzARBgNVBAgT # Cldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29m # dCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENB # IDIwMTAwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCpHQ28dxGKOiDs # /BOX9fp/aZRrdFQQ1aUKAIKF++18aEssX8XD5WHCdrc+Zitb8BVTJwQxH0EbGpUd # zgkTjnxhMFmxMEQP8WCIhFRDDNdNuDgIs0Ldk6zWczBXJoKjRQ3Q6vVHgc2/JGAy # WGBG8lhHhjKEHnRhZ5FfgVSxz5NMksHEpl3RYRNuKMYa+YaAu99h/EbBJx0kZxJy # GiGKr0tkiVBisV39dx898Fd1rL2KQk1AUdEPnAY+Z3/1ZsADlkR+79BL/W7lmsqx # qPJ6Kgox8NpOBpG2iAg16HgcsOmZzTznL0S6p/TcZL2kAcEgCZN4zfy8wMlEXV4W # nAEFTyJNAgMBAAGjggHmMIIB4jAQBgkrBgEEAYI3FQEEAwIBADAdBgNVHQ4EFgQU # 1WM6XIoxkPNDe3xGG8UzaFqFbVUwGQYJKwYBBAGCNxQCBAweCgBTAHUAYgBDAEEw # CwYDVR0PBAQDAgGGMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAU1fZWy4/o # olxiaNE9lJBb186aGMQwVgYDVR0fBE8wTTBLoEmgR4ZFaHR0cDovL2NybC5taWNy # b3NvZnQuY29tL3BraS9jcmwvcHJvZHVjdHMvTWljUm9vQ2VyQXV0XzIwMTAtMDYt # MjMuY3JsMFoGCCsGAQUFBwEBBE4wTDBKBggrBgEFBQcwAoY+aHR0cDovL3d3dy5t # aWNyb3NvZnQuY29tL3BraS9jZXJ0cy9NaWNSb29DZXJBdXRfMjAxMC0wNi0yMy5j # cnQwgaAGA1UdIAEB/wSBlTCBkjCBjwYJKwYBBAGCNy4DMIGBMD0GCCsGAQUFBwIB # FjFodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vUEtJL2RvY3MvQ1BTL2RlZmF1bHQu # aHRtMEAGCCsGAQUFBwICMDQeMiAdAEwAZQBnAGEAbABfAFAAbwBsAGkAYwB5AF8A # UwB0AGEAdABlAG0AZQBuAHQALiAdMA0GCSqGSIb3DQEBCwUAA4ICAQAH5ohRDeLG # 4Jg/gXEDPZ2joSFvs+umzPUxvs8F4qn++ldtGTCzwsVmyWrf9efweL3HqJ4l4/m8 # 7WtUVwgrUYJEEvu5U4zM9GASinbMQEBBm9xcF/9c+V4XNZgkVkt070IQyK+/f8Z/ # 8jd9Wj8c8pl5SpFSAK84Dxf1L3mBZdmptWvkx872ynoAb0swRCQiPM/tA6WWj1kp # vLb9BOFwnzJKJ/1Vry/+tuWOM7tiX5rbV0Dp8c6ZZpCM/2pif93FSguRJuI57BlK # cWOdeyFtw5yjojz6f32WapB4pm3S4Zz5Hfw42JT0xqUKloakvZ4argRCg7i1gJsi # OCC1JeVk7Pf0v35jWSUPei45V3aicaoGig+JFrphpxHLmtgOR5qAxdDNp9DvfYPw # 4TtxCd9ddJgiCGHasFAeb73x4QDf5zEHpJM692VHeOj4qEir995yfmFrb3epgcun # Caw5u+zGy9iCtHLNHfS4hQEegPsbiSpUObJb2sgNVZl6h3M7COaYLeqN4DMuEin1 # wC9UJyH3yKxO2ii4sanblrKnQqLJzxlBTeCG+SqaoxFmMNO7dDJL32N79ZmKLxvH # Ia9Zta7cRDyXUHHXodLFVeNp3lfB0d4wwP3M5k37Db9dT+mdHhk4L7zPWAUu7w2g # UDXa7wknHNWzfjUeCLraNtvTX4/edIhJEqGCAtcwggJAAgEBMIIBAKGB2KSB1TCB # 0jELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1Jl # ZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEtMCsGA1UECxMk # TWljcm9zb2Z0IElyZWxhbmQgT3BlcmF0aW9ucyBMaW1pdGVkMSYwJAYDVQQLEx1U # aGFsZXMgVFNTIEVTTjpGQzQxLTRCRDQtRDIyMDElMCMGA1UEAxMcTWljcm9zb2Z0 # IFRpbWUtU3RhbXAgU2VydmljZaIjCgEBMAcGBSsOAwIaAxUAQqXmHvITpjsyl+Yy # kRtDOQlyUVOggYMwgYCkfjB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGlu # Z3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBv # cmF0aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMDAN # BgkqhkiG9w0BAQUFAAIFAOPUHAwwIhgPMjAyMTAyMTUwNjAwNDRaGA8yMDIxMDIx # NjA2MDA0NFowdzA9BgorBgEEAYRZCgQBMS8wLTAKAgUA49QcDAIBADAKAgEAAgIl # 1gIB/zAHAgEAAgIRTTAKAgUA49VtjAIBADA2BgorBgEEAYRZCgQCMSgwJjAMBgor # BgEEAYRZCgMCoAowCAIBAAIDB6EgoQowCAIBAAIDAYagMA0GCSqGSIb3DQEBBQUA # A4GBAIDbKVDg4tOREtk+wHaJvFbgLVp52tXWkLz1ze7/HfO5BFa4IBOHrnPcuQyM # H0V4HQMPoK2v3HSxQ0+dFW40Rl1UGL983jqVYwh7Eqg7rN+H3ZncaB+IiTiTI9vo # 2Yfr5b2wHLpA2V5rQjphqBxqNzAuTAO413aksgv1v6nA8zbaMYIDDTCCAwkCAQEw # gZMwfDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcT # B1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UE # AxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENBIDIwMTACEzMAAAFAIxnWXt6VJDEA # AAAAAUAwDQYJYIZIAWUDBAIBBQCgggFKMBoGCSqGSIb3DQEJAzENBgsqhkiG9w0B # CRABBDAvBgkqhkiG9w0BCQQxIgQgulHIPScSi1mEEaD8IPIcSDiY+NzbLQHqBqM7 # KPLql2wwgfoGCyqGSIb3DQEJEAIvMYHqMIHnMIHkMIG9BCAvNrC16szSpFwk7/Ny # 8lPt2j/JynxFmxFJOqq2AgiXgzCBmDCBgKR+MHwxCzAJBgNVBAYTAlVTMRMwEQYD # VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy # b3NvZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1w # IFBDQSAyMDEwAhMzAAABQCMZ1l7elSQxAAAAAAFAMCIEICb9dx8657GfP9wxqCCx # tL8V9+s+JfrRoOh7+rM4HrNTMA0GCSqGSIb3DQEBCwUABIIBADxbnS88/QP0ZXuu # wf5IdNQIROMUjnX9tK7TifanLusk/Iun45+uAN8ah2MqqdLHvA92Cryj1yUt7Jrf # X5iyscgch1XHcG4m2l3UNGh4YcO7KirAAWi7EtkY1S+tCS196zvrdAufQGD3yDui # 2YrEDhd2GNry8Vobi98/ND8R2NktnZRjjZfgAn7oLCSCd4LR49ChLH44ub1HBvlO # B/BBXQoC4BfB2XLtzF0M3CVi/022iAbRktXfcDzW9HaxSUZEaHSJzX0agkNQupgt # 0RYI4VZSFrZotli/ip3LzopiX3hgkE03Y+vCANhCHDnosxw+ierxS7eGH98uu3QH # nML13ew= # SIG # End signature block |