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 } } } # SIG # Begin signature block # MIIkBwYJKoZIhvcNAQcCoIIj+DCCI/QCAQExDzANBglghkgBZQMEAgEFADB5Bgor # BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG # KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCC/QWM9uZYm8hUz # TCxAdawFs90fY6RzNWZIw00K/XQkhKCCDZMwggYRMIID+aADAgECAhMzAAAAjoeR # pFcaX8o+AAAAAACOMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMRMwEQYD # VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy # b3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25p # bmcgUENBIDIwMTEwHhcNMTYxMTE3MjIwOTIxWhcNMTgwMjE3MjIwOTIxWjCBgzEL # MAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1v # bmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjENMAsGA1UECxMETU9Q # UjEeMBwGA1UEAxMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMIIBIjANBgkqhkiG9w0B # AQEFAAOCAQ8AMIIBCgKCAQEA0IfUQit+ndnGetSiw+MVktJTnZUXyVI2+lS/qxCv # 6cnnzCZTw8Jzv23WAOUA3OlqZzQw9hYXtAGllXyLuaQs5os7efYjDHmP81LfQAEc # wsYDnetZz3Pp2HE5m/DOJVkt0slbCu9+1jIOXXQSBOyeBFOmawJn+E1Zi3fgKyHg # 78CkRRLPA3sDxjnD1CLcVVx3Qv+csuVVZ2i6LXZqf2ZTR9VHCsw43o17lxl9gtAm # +KWO5aHwXmQQ5PnrJ8by4AjQDfJnwNjyL/uJ2hX5rg8+AJcH0Qs+cNR3q3J4QZgH # uBfMorFf7L3zUGej15Tw0otVj1OmlZPmsmbPyTdo5GPHzwIDAQABo4IBgDCCAXww # HwYDVR0lBBgwFgYKKwYBBAGCN0wIAQYIKwYBBQUHAwMwHQYDVR0OBBYEFKvI1u2y # FdKqjvHM7Ww490VK0Iq7MFIGA1UdEQRLMEmkRzBFMQ0wCwYDVQQLEwRNT1BSMTQw # MgYDVQQFEysyMzAwMTIrYjA1MGM2ZTctNzY0MS00NDFmLWJjNGEtNDM0ODFlNDE1 # ZDA4MB8GA1UdIwQYMBaAFEhuZOVQBdOCqhc3NyK1bajKdQKVMFQGA1UdHwRNMEsw # SaBHoEWGQ2h0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2lvcHMvY3JsL01pY0Nv # ZFNpZ1BDQTIwMTFfMjAxMS0wNy0wOC5jcmwwYQYIKwYBBQUHAQEEVTBTMFEGCCsG # AQUFBzAChkVodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2NlcnRzL01p # Y0NvZFNpZ1BDQTIwMTFfMjAxMS0wNy0wOC5jcnQwDAYDVR0TAQH/BAIwADANBgkq # hkiG9w0BAQsFAAOCAgEARIkCrGlT88S2u9SMYFPnymyoSWlmvqWaQZk62J3SVwJR # avq/m5bbpiZ9CVbo3O0ldXqlR1KoHksWU/PuD5rDBJUpwYKEpFYx/KCKkZW1v1rO # qQEfZEah5srx13R7v5IIUV58MwJeUTub5dguXwJMCZwaQ9px7eTZ56LadCwXreUM # tRj1VAnUvhxzzSB7pPrI29jbOq76kMWjvZVlrkYtVylY1pLwbNpj8Y8zon44dl7d # 8zXtrJo7YoHQThl8SHywC484zC281TllqZXBA+KSybmr0lcKqtxSCy5WJ6PimJdX # jrypWW4kko6C4glzgtk1g8yff9EEjoi44pqDWLDUmuYx+pRHjn2m4k5589jTajMW # UHDxQruYCen/zJVVWwi/klKoCMTx6PH/QNf5mjad/bqQhdJVPlCtRh/vJQy4njpI # BGPveJiiXQMNAtjcIKvmVrXe7xZmw9dVgh5PgnjJnlQaEGC3F6tAE5GusBnBmjOd # 7jJyzWXMT0aYLQ9RYB58+/7b6Ad5B/ehMzj+CZrbj3u2Or2FhrjMvH0BMLd7Hald # G73MTRf3bkcz1UDfasouUbi1uc/DBNM75ePpEIzrp7repC4zaikvFErqHsEiODUF # he/CBAANa8HYlhRIFa9+UrC4YMRStUqCt4UqAEkqJoMnWkHevdVmSbwLnHhwCbww # ggd6MIIFYqADAgECAgphDpDSAAAAAAADMA0GCSqGSIb3DQEBCwUAMIGIMQswCQYD # VQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEe # MBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMTIwMAYDVQQDEylNaWNyb3Nv # ZnQgUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkgMjAxMTAeFw0xMTA3MDgyMDU5 # MDlaFw0yNjA3MDgyMTA5MDlaMH4xCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNo # aW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29y # cG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25pbmcgUENBIDIw # MTEwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCr8PpyEBwurdhuqoIQ # TTS68rZYIZ9CGypr6VpQqrgGOBoESbp/wwwe3TdrxhLYC/A4wpkGsMg51QEUMULT # iQ15ZId+lGAkbK+eSZzpaF7S35tTsgosw6/ZqSuuegmv15ZZymAaBelmdugyUiYS # L+erCFDPs0S3XdjELgN1q2jzy23zOlyhFvRGuuA4ZKxuZDV4pqBjDy3TQJP4494H # DdVceaVJKecNvqATd76UPe/74ytaEB9NViiienLgEjq3SV7Y7e1DkYPZe7J7hhvZ # PrGMXeiJT4Qa8qEvWeSQOy2uM1jFtz7+MtOzAz2xsq+SOH7SnYAs9U5WkSE1JcM5 # bmR/U7qcD60ZI4TL9LoDho33X/DQUr+MlIe8wCF0JV8YKLbMJyg4JZg5SjbPfLGS # rhwjp6lm7GEfauEoSZ1fiOIlXdMhSz5SxLVXPyQD8NF6Wy/VI+NwXQ9RRnez+ADh # vKwCgl/bwBWzvRvUVUvnOaEP6SNJvBi4RHxF5MHDcnrgcuck379GmcXvwhxX24ON # 7E1JMKerjt/sW5+v/N2wZuLBl4F77dbtS+dJKacTKKanfWeA5opieF+yL4TXV5xc # v3coKPHtbcMojyyPQDdPweGFRInECUzF1KVDL3SV9274eCBYLBNdYJWaPk8zhNqw # iBfenk70lrC8RqBsmNLg1oiMCwIDAQABo4IB7TCCAekwEAYJKwYBBAGCNxUBBAMC # AQAwHQYDVR0OBBYEFEhuZOVQBdOCqhc3NyK1bajKdQKVMBkGCSsGAQQBgjcUAgQM # HgoAUwB1AGIAQwBBMAsGA1UdDwQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB8GA1Ud # IwQYMBaAFHItOgIxkEO5FAVO4eqnxzHRI4k0MFoGA1UdHwRTMFEwT6BNoEuGSWh0 # dHA6Ly9jcmwubWljcm9zb2Z0LmNvbS9wa2kvY3JsL3Byb2R1Y3RzL01pY1Jvb0Nl # ckF1dDIwMTFfMjAxMV8wM18yMi5jcmwwXgYIKwYBBQUHAQEEUjBQME4GCCsGAQUF # BzAChkJodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpL2NlcnRzL01pY1Jvb0Nl # ckF1dDIwMTFfMjAxMV8wM18yMi5jcnQwgZ8GA1UdIASBlzCBlDCBkQYJKwYBBAGC # Ny4DMIGDMD8GCCsGAQUFBwIBFjNodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtp # b3BzL2RvY3MvcHJpbWFyeWNwcy5odG0wQAYIKwYBBQUHAgIwNB4yIB0ATABlAGcA # YQBsAF8AcABvAGwAaQBjAHkAXwBzAHQAYQB0AGUAbQBlAG4AdAAuIB0wDQYJKoZI # hvcNAQELBQADggIBAGfyhqWY4FR5Gi7T2HRnIpsLlhHhY5KZQpZ90nkMkMFlXy4s # PvjDctFtg/6+P+gKyju/R6mj82nbY78iNaWXXWWEkH2LRlBV2AySfNIaSxzzPEKL # UtCw/WvjPgcuKZvmPRul1LUdd5Q54ulkyUQ9eHoj8xN9ppB0g430yyYCRirCihC7 # pKkFDJvtaPpoLpWgKj8qa1hJYx8JaW5amJbkg/TAj/NGK978O9C9Ne9uJa7lryft # 0N3zDq+ZKJeYTQ49C/IIidYfwzIY4vDFLc5bnrRJOQrGCsLGra7lstnbFYhRRVg4 # MnEnGn+x9Cf43iw6IGmYslmJaG5vp7d0w0AFBqYBKig+gj8TTWYLwLNN9eGPfxxv # FX1Fp3blQCplo8NdUmKGwx1jNpeG39rz+PIWoZon4c2ll9DuXWNB41sHnIc+BncG # 0QaxdR8UvmFhtfDcxhsEvt9Bxw4o7t5lL+yX9qFcltgA1qFGvVnzl6UJS0gQmYAf # 0AApxbGbpT9Fdx41xtKiop96eiL6SJUfq/tHI4D1nvi/a7dLl+LrdXga7Oo3mXkY # S//WsyNodeav+vyL6wuA6mk7r/ww7QRMjt/fdW1jkT3RnVZOT7+AVyKheBEyIXrv # QQqxP/uozKRdwaGIm1dxVk5IRcBCyZt2WwqASGv9eZ/BvW1taslScxMNelDNMYIV # yjCCFcYCAQEwgZUwfjELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24x # EDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlv # bjEoMCYGA1UEAxMfTWljcm9zb2Z0IENvZGUgU2lnbmluZyBQQ0EgMjAxMQITMwAA # AI6HkaRXGl/KPgAAAAAAjjANBglghkgBZQMEAgEFAKCBtjAZBgkqhkiG9w0BCQMx # DAYKKwYBBAGCNwIBBDAcBgorBgEEAYI3AgELMQ4wDAYKKwYBBAGCNwIBFTAvBgkq # hkiG9w0BCQQxIgQgA3UKB1Y7p7Pj4QAwiKQCPBgCGT6kPbvhoS0Mkoljb1YwSgYK # KwYBBAGCNwIBDDE8MDqgGIAWAEEAegBTAEQASwAgADIALgA1AC4AMaEegBxodHRw # czovL2FrYS5tcy9henNka29zc2RvY3MgMA0GCSqGSIb3DQEBAQUABIIBAJH9eErg # 4gOoq16covhMlV2REWzVmwKfxLek73bwiarpsAXewyLDvbK7zqwvo2rDa8ptLGbk # ZGuE0UDu2mT/mEZqvVX6rEKu/wrGXYVwdvG6L2t8g5LBBV9VTpF358b3pUO/cj1E # idgq/88SQiyd1Y2UiykNwvxth8VCFOhvSVqKhp2AVGO6Rghs0hiXUQ1UmLR0q3C8 # zo5bJIw/6alZadXneCR5FE4VXq/0XjUyiSfMa8fEczomciVhHfbX9hSyHuGc7AKS # MidRfOFuKBVqTffu4TCrCxRS71p+qm15n4tmpd17A2PTjItoyOQM8DqS0IGjLUDi # tEBaN7Dq3pL0MZChghNMMIITSAYKKwYBBAGCNwMDATGCEzgwghM0BgkqhkiG9w0B # BwKgghMlMIITIQIBAzEPMA0GCWCGSAFlAwQCAQUAMIIBPQYLKoZIhvcNAQkQAQSg # ggEsBIIBKDCCASQCAQEGCisGAQQBhFkKAwEwMTANBglghkgBZQMEAgEFAAQgaErW # 8pNVJuoz/IWmxCFrhaZGitg9NPbyJZ+pvH8Ec8ACBlmSFyGhABgTMjAxNzA5MDUw # NzAzNDguNjYzWjAHAgEBgAIB9KCBuaSBtjCBszELMAkGA1UEBhMCVVMxEzARBgNV # BAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jv # c29mdCBDb3Jwb3JhdGlvbjENMAsGA1UECxMETU9QUjEnMCUGA1UECxMebkNpcGhl # ciBEU0UgRVNOOkIxQjctRjY3Ri1GRUMyMSUwIwYDVQQDExxNaWNyb3NvZnQgVGlt # ZS1TdGFtcCBTZXJ2aWNloIIOzzCCBnEwggRZoAMCAQICCmEJgSoAAAAAAAIwDQYJ # KoZIhvcNAQELBQAwgYgxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9u # MRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRp # b24xMjAwBgNVBAMTKU1pY3Jvc29mdCBSb290IENlcnRpZmljYXRlIEF1dGhvcml0 # eSAyMDEwMB4XDTEwMDcwMTIxMzY1NVoXDTI1MDcwMTIxNDY1NVowfDELMAkGA1UE # BhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAc # BgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0 # IFRpbWUtU3RhbXAgUENBIDIwMTAwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK # AoIBAQCpHQ28dxGKOiDs/BOX9fp/aZRrdFQQ1aUKAIKF++18aEssX8XD5WHCdrc+ # Zitb8BVTJwQxH0EbGpUdzgkTjnxhMFmxMEQP8WCIhFRDDNdNuDgIs0Ldk6zWczBX # JoKjRQ3Q6vVHgc2/JGAyWGBG8lhHhjKEHnRhZ5FfgVSxz5NMksHEpl3RYRNuKMYa # +YaAu99h/EbBJx0kZxJyGiGKr0tkiVBisV39dx898Fd1rL2KQk1AUdEPnAY+Z3/1 # ZsADlkR+79BL/W7lmsqxqPJ6Kgox8NpOBpG2iAg16HgcsOmZzTznL0S6p/TcZL2k # AcEgCZN4zfy8wMlEXV4WnAEFTyJNAgMBAAGjggHmMIIB4jAQBgkrBgEEAYI3FQEE # AwIBADAdBgNVHQ4EFgQU1WM6XIoxkPNDe3xGG8UzaFqFbVUwGQYJKwYBBAGCNxQC # BAweCgBTAHUAYgBDAEEwCwYDVR0PBAQDAgGGMA8GA1UdEwEB/wQFMAMBAf8wHwYD # VR0jBBgwFoAU1fZWy4/oolxiaNE9lJBb186aGMQwVgYDVR0fBE8wTTBLoEmgR4ZF # aHR0cDovL2NybC5taWNyb3NvZnQuY29tL3BraS9jcmwvcHJvZHVjdHMvTWljUm9v # Q2VyQXV0XzIwMTAtMDYtMjMuY3JsMFoGCCsGAQUFBwEBBE4wTDBKBggrBgEFBQcw # AoY+aHR0cDovL3d3dy5taWNyb3NvZnQuY29tL3BraS9jZXJ0cy9NaWNSb29DZXJB # dXRfMjAxMC0wNi0yMy5jcnQwgaAGA1UdIAEB/wSBlTCBkjCBjwYJKwYBBAGCNy4D # MIGBMD0GCCsGAQUFBwIBFjFodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vUEtJL2Rv # Y3MvQ1BTL2RlZmF1bHQuaHRtMEAGCCsGAQUFBwICMDQeMiAdAEwAZQBnAGEAbABf # AFAAbwBsAGkAYwB5AF8AUwB0AGEAdABlAG0AZQBuAHQALiAdMA0GCSqGSIb3DQEB # CwUAA4ICAQAH5ohRDeLG4Jg/gXEDPZ2joSFvs+umzPUxvs8F4qn++ldtGTCzwsVm # yWrf9efweL3HqJ4l4/m87WtUVwgrUYJEEvu5U4zM9GASinbMQEBBm9xcF/9c+V4X # NZgkVkt070IQyK+/f8Z/8jd9Wj8c8pl5SpFSAK84Dxf1L3mBZdmptWvkx872ynoA # b0swRCQiPM/tA6WWj1kpvLb9BOFwnzJKJ/1Vry/+tuWOM7tiX5rbV0Dp8c6ZZpCM # /2pif93FSguRJuI57BlKcWOdeyFtw5yjojz6f32WapB4pm3S4Zz5Hfw42JT0xqUK # loakvZ4argRCg7i1gJsiOCC1JeVk7Pf0v35jWSUPei45V3aicaoGig+JFrphpxHL # mtgOR5qAxdDNp9DvfYPw4TtxCd9ddJgiCGHasFAeb73x4QDf5zEHpJM692VHeOj4 # qEir995yfmFrb3epgcunCaw5u+zGy9iCtHLNHfS4hQEegPsbiSpUObJb2sgNVZl6 # h3M7COaYLeqN4DMuEin1wC9UJyH3yKxO2ii4sanblrKnQqLJzxlBTeCG+SqaoxFm # MNO7dDJL32N79ZmKLxvHIa9Zta7cRDyXUHHXodLFVeNp3lfB0d4wwP3M5k37Db9d # T+mdHhk4L7zPWAUu7w2gUDXa7wknHNWzfjUeCLraNtvTX4/edIhJEjCCBNowggPC # oAMCAQICEzMAAACxcRN533X2NcgAAAAAALEwDQYJKoZIhvcNAQELBQAwfDELMAkG # A1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQx # HjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9z # b2Z0IFRpbWUtU3RhbXAgUENBIDIwMTAwHhcNMTYwOTA3MTc1NjU3WhcNMTgwOTA3 # MTc1NjU3WjCBszELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAO # BgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEN # MAsGA1UECxMETU9QUjEnMCUGA1UECxMebkNpcGhlciBEU0UgRVNOOkIxQjctRjY3 # Ri1GRUMyMSUwIwYDVQQDExxNaWNyb3NvZnQgVGltZS1TdGFtcCBTZXJ2aWNlMIIB # IjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqqQklG1Y1lu8ob0P7deumuRn # 4JvRi2GErmK94vgbnWPmd0j/9arA7539HD1dpG1uhYbmnAxc+qsuvMM0fpEvttTK # 4lZSU7ss5rJfWmbFn/J8kSGI8K9iBaB6hQkJuIX4si9ppNr9R3oZI3HbJ/yRkKUP # k4hozpY6CkehRc0/Zfu6tQiyqI7mClXYZTXjw+rLsh3/gdBvYDd38zFBllaf+3ui # mKQgUTXGjbKfqZZk3tEU3ibWVPUxAmmxlG3sWTlXmU31fCw/6TVzGg251lq+Q46O # jbeH9vB2TOcqEso4Nai3J1CdMAYUdlelVVtgQdIx/c+5Hvrw0Y6W7uGBAWnW5wID # AQABo4IBGzCCARcwHQYDVR0OBBYEFE5XPfeLLhRLV7L8Il7Tz7cnRBA7MB8GA1Ud # IwQYMBaAFNVjOlyKMZDzQ3t8RhvFM2hahW1VMFYGA1UdHwRPME0wS6BJoEeGRWh0 # dHA6Ly9jcmwubWljcm9zb2Z0LmNvbS9wa2kvY3JsL3Byb2R1Y3RzL01pY1RpbVN0 # YVBDQV8yMDEwLTA3LTAxLmNybDBaBggrBgEFBQcBAQROMEwwSgYIKwYBBQUHMAKG # Pmh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2kvY2VydHMvTWljVGltU3RhUENB # XzIwMTAtMDctMDEuY3J0MAwGA1UdEwEB/wQCMAAwEwYDVR0lBAwwCgYIKwYBBQUH # AwgwDQYJKoZIhvcNAQELBQADggEBAHPujfu0W8PBTpjfYaPrAKIBLKcljT4+YnWb # bgGvmXU8OvIUDBkkv8gNGGHRO5DSySaCARIzgn2yIheAqh6GwM2yKrfb4eVCYPe1 # CTlCseS5TOv+Tn/95mXj+NxTqvuNmrhgCVr0CQ7b3xoKcwDcQbg7TmerDgbIv2k7 # cEqbYbU/B3MtSX8Zjjf0ZngdKoX0JYkAEDbZchOrRiUtDJItegPKZPf6CjeHYjrm # KwvTOVCzv3lW0uyh1yb/ODeRH+VqENSHCboFiEiq9KpKMOpek1VvQhmI2KbTlRvK # 869gj1NwuUHH8c3WXu4A0X1+CBmU8t0gvd/fFlQvw04veKWh986hggN4MIICYAIB # ATCB46GBuaSBtjCBszELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24x # EDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlv # bjENMAsGA1UECxMETU9QUjEnMCUGA1UECxMebkNpcGhlciBEU0UgRVNOOkIxQjct # RjY3Ri1GRUMyMSUwIwYDVQQDExxNaWNyb3NvZnQgVGltZS1TdGFtcCBTZXJ2aWNl # oiUKAQEwCQYFKw4DAhoFAAMVADq635MoZeR60+ej9uKnRG5YqlPSoIHCMIG/pIG8 # MIG5MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMH # UmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMQ0wCwYDVQQL # EwRNT1BSMScwJQYDVQQLEx5uQ2lwaGVyIE5UUyBFU046NERFOS0wQzVFLTNFMDkx # KzApBgNVBAMTIk1pY3Jvc29mdCBUaW1lIFNvdXJjZSBNYXN0ZXIgQ2xvY2swDQYJ # KoZIhvcNAQEFBQACBQDdWK0XMCIYDzIwMTcwOTA1MDQ1NjU1WhgPMjAxNzA5MDYw # NDU2NTVaMHYwPAYKKwYBBAGEWQoEATEuMCwwCgIFAN1YrRcCAQAwCQIBAAIBIAIB # /zAHAgEAAgIY4zAKAgUA3Vn+lwIBADA2BgorBgEEAYRZCgQCMSgwJjAMBgorBgEE # AYRZCgMBoAowCAIBAAIDFuNgoQowCAIBAAIDB6EgMA0GCSqGSIb3DQEBBQUAA4IB # AQA+ZEd4ooPHiKXaGoIJl7xct64OrgoneiQ1Q/WdfJxEl3cjN5B2vRUuQX+6hq5k # nZBRNSvQhFHIumBL52FqmEl0yu4JrEcavJhNtK+rZYIdiYW/IJN+eYjR7Ca7UiPn # qC0AovLCq7CH9Pa2LZrFMTw3OEfjCa5O4ZZ2eVaBP0Ts30GgU+BVK7RQ1OnnV3rR # L3vwFs9m4RHh92aifGO3xwvmONclucmsupzvizd0YzkG5kFkIj38HaPpRKiR+/z1 # e1s/qQ5W90R5AVKCgBS/r47n+pjTrHtHKX65tYUnd1FCED51VBM/yI372NQ/ts1n # cTv5dpINp/KxiiIUcI+/YNj1MYIC9TCCAvECAQEwgZMwfDELMAkGA1UEBhMCVVMx # EzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoT # FU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUt # U3RhbXAgUENBIDIwMTACEzMAAACxcRN533X2NcgAAAAAALEwDQYJYIZIAWUDBAIB # BQCgggEyMBoGCSqGSIb3DQEJAzENBgsqhkiG9w0BCRABBDAvBgkqhkiG9w0BCQQx # IgQgsp1Q5hpVVGx2gtn5+uz5mBgoq2IEAdZstZIBl4avjdwwgeIGCyqGSIb3DQEJ # EAIMMYHSMIHPMIHMMIGxBBQ6ut+TKGXketPno/bip0RuWKpT0jCBmDCBgKR+MHwx # CzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRt # b25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMTHU1p # Y3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEwAhMzAAAAsXETed919jXIAAAAAACx # MBYEFF50WhcZSLtM19KqNcuqdWzh7UwDMA0GCSqGSIb3DQEBCwUABIIBADDRH8FK # 1ibjLEByrfsZBai8LhpeU+HMPyiIaS46QfvQFxtLnIhWr/1C3OQGhcf+k2bHSM8Y # wD5tF1lAAw9hwWVRtOp8XF9numxXkrNA9iLM6xkWB1kuJ1LsGDb+2IejLtvJcc18 # 8G4gZyC0KMELG4X8vMKbXCSeIPOPc13BmSwbpeV+QKlWZDBfTlaRJ8c468lTjmiT # b0a+3bsbVmZbNC9JQ/Nn8i13KOFiWxCbM0AUKkgdt4QaQHozLKlT1V/ZC+PEyKCk # S1wyINQVLtyxisDMCv/QwRV8lFTn3xKGytWHIp72yH4YbQFwTeefB+AXKrnuStHR # DhOR1vf+bTA+gpo= # SIG # End signature block |