Engines/Terraform/Invoke-AvmTerraformCheckPolicy.ps1
|
function Invoke-AvmTerraformCheckPolicy { <# .SYNOPSIS Evaluate Terraform examples against the pinned APRL and AVMSEC policies. .DESCRIPTION Implements the legacy AVM pr-check policy lifecycle without mutating the repository. The module is copied beneath the AVM cache, then every direct examples/* directory is evaluated independently: - examples carrying .e2eignore are skipped - pre.ps1 runs when present; pre.sh is rejected with migration guidance - .env is loaded into Terraform and Conftest subprocesses - terraform init, plan, and show produce a plan-JSON input - APRL and AVMSEC run separately with all namespaces enabled - the pinned AVMSEC exemptions and that example's local exceptions/ directory are included in both evaluations - post.ps1 runs after the example; post.sh is rejected likewise The staging directory is deliberately beneath the policy bundle cache. Conftest 0.68.2 strips the drive letter from --policy paths on Windows; keeping the working tree and bundles on one volume preserves resolution. Conftest output is captured as JSON rather than streamed. The legacy command used --quiet; omitting that switch here has the same user-visible silence while retaining success records needed for the Evaluated count. .PARAMETER Context Terraform module context produced by Get-AvmModuleContext. .PARAMETER AllowPathFallback Allow Terraform and Conftest to resolve from PATH. .OUTPUTS A Terraform engine result with Status, Evaluated, and Issues. #> [CmdletBinding()] [OutputType([pscustomobject])] param( [Parameter(Mandatory)] $Context, [switch] $AllowPathFallback ) Set-StrictMode -Version 3.0 $ErrorActionPreference = 'Stop' if ($Context.Ecosystem -ne 'terraform') { throw [System.ArgumentException]::new( "Invoke-AvmTerraformCheckPolicy requires a terraform context (got Ecosystem='$($Context.Ecosystem)').") } $conftest = Resolve-AvmTool -Name 'conftest' -AllowPathFallback:$AllowPathFallback $terraform = Resolve-AvmTool -Name 'terraform' -AllowPathFallback:$AllowPathFallback $aprlAsset = Resolve-AvmPolicyBundle -Name 'avm-policy-aprl' $avmsecAsset = Resolve-AvmPolicyBundle -Name 'avm-policy-avmsec' $defaultExceptionSource = Join-Path $avmsecAsset.Path 'avm_exceptions.rego.bak' if (-not (Test-Path -LiteralPath $defaultExceptionSource -PathType Leaf)) { throw [AvmConfigurationException]::new( "The pinned AVMSEC bundle does not contain the default exemption policy: '$defaultExceptionSource'.") } $examplesRoot = Join-Path $Context.Root 'examples' $examplePaths = @() if (Test-Path -LiteralPath $examplesRoot -PathType Container) { $examplePaths = @(Get-ChildItem -LiteralPath $examplesRoot -Directory -ErrorAction Stop | ForEach-Object FullName) [System.Array]::Sort($examplePaths, [System.StringComparer]::Ordinal) } $activeExamples = @($examplePaths | Where-Object { -not (Test-Path -LiteralPath (Join-Path $_ '.e2eignore') -PathType Leaf) }) if ($activeExamples.Count -eq 0) { return [pscustomobject][ordered]@{ Engine = 'terraform' Tool = ('{0}/{1}' -f $conftest.Name, $conftest.Version) ToolPath = $conftest.Path ToolSource = $conftest.Source Status = 'skipped' Evaluated = 0 Issues = @() } } $shellHooks = [System.Collections.Generic.List[string]]::new() foreach ($sourceExample in $activeExamples) { $exampleName = Split-Path -Path $sourceExample -Leaf foreach ($hookName in @('pre.sh', 'post.sh')) { if (Test-Path -LiteralPath (Join-Path $sourceExample $hookName) -PathType Leaf) { $shellHooks.Add(('examples/{0}/{1}' -f $exampleName, $hookName)) } } } if ($shellHooks.Count -gt 0) { throw [AvmConfigurationException]::new( ("The terraform policy engine runs PowerShell hooks only. Refactor these shell hooks to '.ps1': {0}" -f ($shellHooks -join ', '))) } $stageParent = Join-Path (Get-AvmFolder -Kind Cache) 'policy-stage' $stageRoot = Join-Path $stageParent ('avm-policy-' + [guid]::NewGuid().ToString('N')) $issues = [System.Collections.Generic.List[object]]::new() $evaluated = 0 try { Copy-AvmTerraformModuleTree -SourceRoot $Context.Root -DestinationRoot $stageRoot $defaultExceptions = Join-Path ` -Path $stageRoot ` -ChildPath 'policy' ` -AdditionalChildPath 'default_exceptions' $null = New-Item -ItemType Directory -Path $defaultExceptions -Force -ErrorAction Stop Copy-Item ` -LiteralPath $defaultExceptionSource ` -Destination (Join-Path $defaultExceptions 'avm_exceptions.rego') ` -Force ` -ErrorAction Stop foreach ($sourceExample in $activeExamples) { $relativeExample = [System.IO.Path]::GetRelativePath($Context.Root, $sourceExample) $stagedExample = Join-Path $stageRoot $relativeExample $exampleName = Split-Path -Path $sourceExample -Leaf $envVars = @{} $primaryError = $null try { Invoke-AvmScriptHook ` -HookPath (Join-Path $stagedExample 'pre.ps1') ` -WorkingDirectory $stagedExample ` -Label ("policy {0} pre.ps1" -f $exampleName) $envVars = ConvertFrom-AvmDotEnv -Path (Join-Path $stagedExample '.env') $initResult = Invoke-AvmProcess ` -FilePath $terraform.Path ` -ArgumentList @('init', '-input=false', '-no-color') ` -WorkingDirectory $stagedExample ` -EnvVars $envVars ` -Label ("terraform init ({0})" -f $exampleName) $null = $initResult $planResult = Invoke-AvmProcess ` -FilePath $terraform.Path ` -ArgumentList @('plan', '-out=tfplan', '-input=false', '-no-color') ` -WorkingDirectory $stagedExample ` -EnvVars $envVars ` -Label ("terraform plan ({0})" -f $exampleName) $null = $planResult $showResult = Invoke-AvmProcess ` -FilePath $terraform.Path ` -ArgumentList @('show', '-json', 'tfplan') ` -WorkingDirectory $stagedExample ` -EnvVars $envVars ` -Label ("terraform show ({0})" -f $exampleName) $planJsonPath = Join-Path $stagedExample 'tfplan.json' [System.IO.File]::WriteAllText( $planJsonPath, [string]$showResult.StdOut, [System.Text.UTF8Encoding]::new($false)) $localExceptions = Join-Path $stagedExample 'exceptions' $policyRuns = @( [pscustomobject]@{ Name = 'APRL'; Path = $aprlAsset.Path } [pscustomobject]@{ Name = 'AVMSEC'; Path = $avmsecAsset.Path } ) foreach ($policyRun in $policyRuns) { $arguments = [System.Collections.Generic.List[string]]::new() $arguments.Add('test') $arguments.Add('--all-namespaces') $arguments.Add('--policy') $arguments.Add([string]$policyRun.Path) $arguments.Add('--policy') $arguments.Add($defaultExceptions) if (Test-Path -LiteralPath $localExceptions -PathType Container) { $arguments.Add('--policy') $arguments.Add($localExceptions) } $arguments.Add('--output') $arguments.Add('json') $arguments.Add('tfplan.json') $policyResult = Invoke-AvmProcess ` -FilePath $conftest.Path ` -ArgumentList $arguments.ToArray() ` -WorkingDirectory $stagedExample ` -EnvVars $envVars ` -Label ("conftest {0} ({1})" -f $policyRun.Name, $exampleName) ` -IgnoreExitCode $parsed = ConvertFrom-AvmPolicyResult ` -Result $policyResult ` -ExamplePath $relativeExample ` -PolicyName $policyRun.Name $evaluated += $parsed.Evaluated foreach ($issue in $parsed.Issues) { $issues.Add($issue) } } } catch { $primaryError = $_ } try { Invoke-AvmScriptHook ` -HookPath (Join-Path $stagedExample 'post.ps1') ` -WorkingDirectory $stagedExample ` -EnvVars $envVars ` -Label ("policy {0} post.ps1" -f $exampleName) } catch { if ($null -eq $primaryError) { throw } Write-AvmLog ` -Level Warning ` -File (Join-Path $relativeExample 'post.ps1') ` -Message ("Policy post hook also failed; preserving the primary error: {0}" -f $_.Exception.Message) } if ($null -ne $primaryError) { throw $primaryError } } } finally { if (Test-Path -LiteralPath $stageRoot) { Remove-Item -LiteralPath $stageRoot -Recurse -Force -ErrorAction SilentlyContinue } } $status = if (@($issues | Where-Object { $_.Severity -eq 'error' }).Count -gt 0) { 'fail' } else { 'pass' } return [pscustomobject][ordered]@{ Engine = 'terraform' Tool = ('{0}/{1}' -f $conftest.Name, $conftest.Version) ToolPath = $conftest.Path ToolSource = $conftest.Source Status = $status Evaluated = $evaluated Issues = $issues.ToArray() } } function ConvertFrom-AvmPolicyResult { [CmdletBinding()] [OutputType([pscustomobject])] param( [Parameter(Mandatory)] $Result, [Parameter(Mandatory)] [string] $ExamplePath, [Parameter(Mandatory)] [string] $PolicyName ) if ($Result.ExitCode -notin @(0, 1)) { $detail = if ([string]::IsNullOrWhiteSpace($Result.StdErr)) { 'conftest produced no diagnostic.' } else { $Result.StdErr.Trim() } throw [AvmProcessException]::new( "conftest $PolicyName exited with code $($Result.ExitCode): $detail") } $payload = if ($Result.StdOut) { $Result.StdOut.Trim() } else { '' } if ($Result.ExitCode -eq 1 -and [string]::IsNullOrWhiteSpace($payload)) { $detail = if ([string]::IsNullOrWhiteSpace($Result.StdErr)) { 'conftest produced no result.' } else { $Result.StdErr.Trim() } throw [AvmProcessException]::new( "conftest $PolicyName exited before evaluating a policy: $detail") } $records = @() if (-not [string]::IsNullOrWhiteSpace($payload)) { try { $records = @($payload | ConvertFrom-Json -ErrorAction Stop) } catch { throw [AvmProcessException]::new( "Could not parse conftest $PolicyName JSON output: $($_.Exception.Message)") } } $issues = [System.Collections.Generic.List[object]]::new() $evaluated = 0 $issueFile = [System.IO.Path]::Combine($ExamplePath, 'tfplan.json').Replace('\', '/') foreach ($record in $records) { if (-not $record) { continue } $namespace = if ($record.PSObject.Properties['namespace']) { [string]$record.namespace } else { $PolicyName.ToLowerInvariant() } if ($record.PSObject.Properties['successes']) { $evaluated += [int]$record.successes } foreach ($bucket in @('failures', 'warnings', 'exceptions')) { if ($record.PSObject.Properties[$bucket] -and $record.$bucket) { $evaluated += @($record.$bucket).Count } } foreach ($bucket in @( [pscustomobject]@{ Name = 'failures'; Severity = 'error' } [pscustomobject]@{ Name = 'warnings'; Severity = 'warning' } )) { if (-not $record.PSObject.Properties[$bucket.Name] -or -not $record.($bucket.Name)) { continue } foreach ($finding in @($record.($bucket.Name))) { $message = if ($finding.PSObject.Properties['msg']) { [string]$finding.msg } else { '' } $issues.Add([pscustomobject][ordered]@{ File = $issueFile Line = 0 Column = 0 Severity = $bucket.Severity Code = $namespace Message = $message }) } } } return [pscustomobject][ordered]@{ Evaluated = $evaluated Issues = $issues.ToArray() } } # SIG # Begin signature block # MIInUwYJKoZIhvcNAQcCoIInRDCCJ0ACAQExDzANBglghkgBZQMEAgEFADB5Bgor # BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG # KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCCAODKLZoY3pVri # hn6toe8Crmvn9CCBPaG7Zgec8bOjraCCDMkwggYEMIID7KADAgECAhMzAAACHPrN # xZvoL37EAAAAAAIcMA0GCSqGSIb3DQEBCwUAMFcxCzAJBgNVBAYTAlVTMR4wHAYD # VQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBD # b2RlIFNpZ25pbmcgUENBIDIwMjQwHhcNMjYwNDE2MTg1OTQxWhcNMjcwNDE1MTg1 # OTQxWjB0MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UE # BxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMR4wHAYD # VQQDExVNaWNyb3NvZnQgQ29ycG9yYXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IB # DwAwggEKAoIBAQDVsZfgOKmM31HPfoWOoNEiw0SlCiIxUMC0I9NMWbucKOw/e9lP # oAoehQVu6SG65V4EPzrYsnBnFPNoi4/HoOdjhz1qkrEt4I6tEcxXU6oOeY9zGveC # /3iBeuhLYxM3M/PkcUoebF+Nednm8OkdSPoDu8imViHPQq/8CQUu0WRR4rE+dMRf # rpVqfmNi2qWCX94T4MsepijGVkwE//tJg0ryAiYdHT34LSnlG/RSBZmQRGWZ5g8j # qnKjRParSqMft1gvjuUTVgtWNZfgcLFSK5Wa0myrq8OPcgTGGsRgun+tnSS+IxDT # xVsAPH1OzvPjwomguByhUe/OcvUN0D5Wmp7xAgMBAAGjggGqMIIBpjAOBgNVHQ8B # Af8EBAMCB4AwHwYDVR0lBBgwFgYKKwYBBAGCN0wIAQYIKwYBBQUHAwMwHQYDVR0O # BBYEFNoH7a2YDjOSwpkp6DHcmUS7J+0yMFQGA1UdEQRNMEukSTBHMS0wKwYDVQQL # EyRNaWNyb3NvZnQgSXJlbGFuZCBPcGVyYXRpb25zIExpbWl0ZWQxFjAUBgNVBAUT # DTIzMDAxMis1MDc1NjkwHwYDVR0jBBgwFoAUf1k/VCHarU/vBeXmo9ctBpQSCDEw # YAYDVR0fBFkwVzBVoFOgUYZPaHR0cDovL3d3dy5taWNyb3NvZnQuY29tL3BraW9w # cy9jcmwvTWljcm9zb2Z0JTIwQ29kZSUyMFNpZ25pbmclMjBQQ0ElMjAyMDI0LmNy # bDBtBggrBgEFBQcBAQRhMF8wXQYIKwYBBQUHMAKGUWh0dHA6Ly93d3cubWljcm9z # b2Z0LmNvbS9wa2lvcHMvY2VydHMvTWljcm9zb2Z0JTIwQ29kZSUyMFNpZ25pbmcl # MjBQQ0ElMjAyMDI0LmNydDAMBgNVHRMBAf8EAjAAMA0GCSqGSIb3DQEBCwUAA4IC # AQAUnEqhaRXe0T3hIJjvdQErEkrA/7bByjn6t5IArODkkRjzkYwtKMc2yYj2quaN # rLutWw2YZcngKPy1b71YyDJQTy4NDRwaSh9Tw5thrk3NmcPrAHia5vtcBJ1CgtKK # 7mQbIcQ22d/N3813ayCDDFewu1+jsZmX+r/aTEqaOM4TVxVtRSkuCy8nAXKuChOK # Li/zA4XuH8iEYqIsj2YoNaeSxVmeGiERXpKdo3dDmYi0kO5w2D8VS4c3+9h6gElY # BaAAg/dYErBg27qT3vv0zRDJhJufvCNylA8S7/+8H5E/PV5cng6na9VV/w9OV3qu # uND6zdGa2EX38Glp50F9AIQk3p2xXmcvorDeM4XJ7UlWYBi6g80J1SSOQnInCYFE # msfUNn3+1AaTJKSJL83quKArTac2pKhu0Yzzzrzo6HrsRiQKzpnRBb1/dMa6P3hz # 75XbMRBctNsFhZC07WCmjExdLg2eHW5uV0TY8D5+6wozJf7vF3+WHkYPO85Z+BC6 # U4FkNbYNycZ9cE4j1tXRdyDCfml6c0HWPHjNVDObrv9lKt3qUqFpX38VCqVCyNOO # 1UcXfQiVjJw32U2WUKZjt/neJKHEBsm9kFsLuWzkQ53+qcaSaytmsCnk2gOglrlD # 5d3kKyvvAw+rzm0lT8K38P6PLxfZQHhu4W8dV7Av8N2ZmDCCBr0wggSloAMCAQIC # EzMAAAA5O7Y3Gb8GHWcAAAAAADkwDQYJKoZIhvcNAQEMBQAwgYgxCzAJBgNVBAYT # AlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYD # VQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xMjAwBgNVBAMTKU1pY3Jvc29mdCBS # b290IENlcnRpZmljYXRlIEF1dGhvcml0eSAyMDExMB4XDTI0MDgwODIwNTQxOFoX # DTM2MDMyMjIyMTMwNFowVzELMAkGA1UEBhMCVVMxHjAcBgNVBAoTFU1pY3Jvc29m # dCBDb3Jwb3JhdGlvbjEoMCYGA1UEAxMfTWljcm9zb2Z0IENvZGUgU2lnbmluZyBQ # Q0EgMjAyNDCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBANgBnB7jOMeq # lRYHNa265v4IY9fH8TKhemHfPINe1gpLaV3dhg324WwH06LcHbpnsBukCDNitryo # 0dtS/EW6I/yEL/bLSY8hKpbfQuWusBPr9qazYcDxCW/qnjb5JsI1s8bNOg3bVATv # QVL4tcf03aTycsz8QeCdM0l/yHRObJ9QqazM1r6VPEOJ7LL+uEEb73w6QCuhs89a # 1uv1zerOYMnsneRRwCbpyW11IcggU0cRKDDq1pjVJzIbIF6+oiXXbReOsgeI8zu1 # FyQfK0fVkaya8SmVHQ/tOf23mZ4W9k0Ri22QW9p3UgSC5OUDktKxxcCmGL6tXLfO # GSWHIIV4YrTJTT6PNty5REojHJuZHArkF9VnHTERWoTjAzfI3kP+5b4alUdhgAZ7 # ttOu1bVnXfHaqPYl2rPs20ji03LOVWsh/radgE17es5hL+t6lV0eVHrVhsssROWJ # uz2MXMCt7iw7lFPG9LXKGjsmonn2gotGdHIuEg5JnJMJVmixd5LRlkmgYRZKzhxS # CwyoGIq0PhaA7Y+VPct5pCHkijcIIDm0nlkK+0KyepolcqGm0T/GYQRMhHJlGOOm # VQop36wUVUYklUy++vDWeEgEo4s7hxN6mIbf2MSIQ/iIfMZgJxC69oukMUXCrOC3 # SkE/xIkgpfl22MM1itkZ35nNXkMolU1lAgMBAAGjggFOMIIBSjAOBgNVHQ8BAf8E # BAMCAYYwEAYJKwYBBAGCNxUBBAMCAQAwHQYDVR0OBBYEFH9ZP1Qh2q1P7wXl5qPX # LQaUEggxMBkGCSsGAQQBgjcUAgQMHgoAUwB1AGIAQwBBMA8GA1UdEwEB/wQFMAMB # Af8wHwYDVR0jBBgwFoAUci06AjGQQ7kUBU7h6qfHMdEjiTQwWgYDVR0fBFMwUTBP # oE2gS4ZJaHR0cDovL2NybC5taWNyb3NvZnQuY29tL3BraS9jcmwvcHJvZHVjdHMv # TWljUm9vQ2VyQXV0MjAxMV8yMDExXzAzXzIyLmNybDBeBggrBgEFBQcBAQRSMFAw # TgYIKwYBBQUHMAKGQmh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2kvY2VydHMv # TWljUm9vQ2VyQXV0MjAxMV8yMDExXzAzXzIyLmNydDANBgkqhkiG9w0BAQwFAAOC # AgEAFJQfOChP7onn6fLIMKrSlN1WYKwDFgAddymOUO3FrM8d7B/W/iQ6DxXsDn7D # 5W4wMwYeLystcEqfkjz4NURRgazyMu5yRzQh4LqjA4tStTcJh1opExo7nn5PuPBY # nbu0+THSuVHTe0VTTPVhily/piFrDo3axQ9P4C+Ol5yet+2gTfekICS5xS+cYfSI # vgn0JksVBVMYVI5QFu/qhnLhsEFEUzG8fvv0hjgkO+lkpV9ty6GkN4vdnd7ya6Q6 # aR9y34aiM1qmxaxBi6OUnyNl6fkuun/diTFnYDLTppOkr/mg5WSfCiDVMNCxtj4w # PKC5OmHm1DQIt/MNokbbH3UGsFP1QbzsLocuSqLCvH09Io3fDPTmscR9Y75G4qX7 # RTX8AdBPo0I6OEojf39zuFZt0qOHm65YWQE69cZM2ueE1MB05dNNgHK9gTE7zKvK # /fg8B2qjW88MT/WF5V5uvZGtqa9FSL2RazArA+rDPuf6JGYz4HpgMZHB4S6szWSK # YBv0VisCzfxgeU+dquXW9bd0auYlOB58DPcOYKdc3Se94g+xL4pcEhbB54JOgAkw # YTu/9dLeH2pDqeJZAABVDWRQCaXfO5LgyKwKCLYXpigrZYCjUSBcr+Ve8PFWMhVT # Ql0v4q8J/AUmQN5W4n101cY2L4A7GTQG1h32HHAvfQESWP0xghngMIIZ3AIBATBu # MFcxCzAJBgNVBAYTAlVTMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24x # KDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25pbmcgUENBIDIwMjQCEzMAAAIc # +s3Fm+gvfsQAAAAAAhwwDQYJYIZIAWUDBAIBBQCggZAwGQYJKoZIhvcNAQkDMQwG # CisGAQQBgjcCAQQwLwYJKoZIhvcNAQkEMSIEIKa9lxPtogxfexymxtg42QxrHfQ7 # b7Q9OMgyG27Kpp7xMEIGCisGAQQBgjcCAQwxNDAyoBSAEgBNAGkAYwByAG8AcwBv # AGYAdKEagBhodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20wDQYJKoZIhvcNAQEBBQAE # ggEADv6R7xGTAow47txHi0RlMMHSekDRDCuEmw0S8DxmRAfjDyW1lnDGCLf7zjoi # kzAYR+cSmausuY11HeOuTyDlem5Iup4mHJ2OcMQUApE+vdYTMp7S6AE8KEjflajC # f6W7xdTlrLaRvXF851vwB+k/NXQlB/EVbkeTgb0dH8RN3XkkdiSjhyuFshBx+6oj # lFMh12jGSjhH/TyyLCwG7psq8htjVXoYCxLgPqDQcN1w+J7FsDeQJWj092M+6fuJ # D8miPHHdbYwwh1tTASy0lDr3tzw6hE4EWE408Yrx8tWgLncIaFQL/+jO5kHKc9CR # 9NiXiTNLVoqB3dTbQIqvKPMZYqGCF7AwghesBgorBgEEAYI3AwMBMYIXnDCCF5gG # CSqGSIb3DQEHAqCCF4kwgheFAgEDMQ8wDQYJYIZIAWUDBAIBBQAwggFaBgsqhkiG # 9w0BCRABBKCCAUkEggFFMIIBQQIBAQYKKwYBBAGEWQoDATAxMA0GCWCGSAFlAwQC # AQUABCAGpixbOudQtD/qSt2y3GXz94lg38OWjqPqUrQy6GWLNQIGanJOR6aXGBMy # MDI2MDgwNzE5MjcwNS43MTZaMASAAgH0oIHZpIHWMIHTMQswCQYDVQQGEwJVUzET # MBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMV # TWljcm9zb2Z0IENvcnBvcmF0aW9uMS0wKwYDVQQLEyRNaWNyb3NvZnQgSXJlbGFu # ZCBPcGVyYXRpb25zIExpbWl0ZWQxJzAlBgNVBAsTHm5TaGllbGQgVFNTIEVTTjo2 # NTFBLTA1RTAtRDk0NzElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAgU2Vy # dmljZaCCEf4wggcoMIIFEKADAgECAhMzAAACFRgD04EHJnxTAAEAAAIVMA0GCSqG # SIb3DQEBCwUAMHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAw # DgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24x # JjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEwMB4XDTI1MDgx # NDE4NDgyMFoXDTI2MTExMzE4NDgyMFowgdMxCzAJBgNVBAYTAlVTMRMwEQYDVQQI # EwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3Nv # ZnQgQ29ycG9yYXRpb24xLTArBgNVBAsTJE1pY3Jvc29mdCBJcmVsYW5kIE9wZXJh # dGlvbnMgTGltaXRlZDEnMCUGA1UECxMeblNoaWVsZCBUU1MgRVNOOjY1MUEtMDVF # MC1EOTQ3MSUwIwYDVQQDExxNaWNyb3NvZnQgVGltZS1TdGFtcCBTZXJ2aWNlMIIC # IjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAw3HV3hVxL0lEYPV03XeNKZ51 # 7VIbgexhlDPdpXwDS0BYtxPwi4XYpZR1ld0u6cr2Xjuugdg50DUx5WHL0QhY2d9v # kJSk02rE/75hcKt91m2Ih287QRxRMmFu3BF6466k8qp5uXtfe6uciq49YaS8p+dz # v3uTarD4hQ8UT7La95pOJiRqxxd0qOGLECvHLEXPXioNSx9pyhzhm6lt7ezLxJeF # VYtxShkavPoZN0dOCiYeh4KgoKoyagzMuSiLCiMUW4Ue4Qsm658FJNGTNh7V5qXY # VA6k5xjw5WeWdKOz0i9A5jBcbY9fVOo/cA8i1bytzcDTxb3nctcly8/OYeNstkab # /Isq3Cxe1vq96fIHE1+ZGmJjka1sodwqPycVp/2tb+BjulPL5D6rgUXTPF84U82R # LKHV57bB8fHRpgnjcWBQuXPgVeSXpERWimt0NF2lCOLzqgrvS/vYqde5Ln9YlKKh # AZ/xDE0TLIIr6+I/2JTtXP34nfjTENVqMBISWcakIxAwGb3RB5yHCxynIFNVLcfK # AsEdC5U2em0fAvmVv0sonqnv17cuaYi2eCLWhoK1Ic85Dw7s/lhcXrBpY4n/Rl5l # 3wHzs4vOIhu87DIy5QUaEupEsyY0NWqgI4BWl6v1wgse+l8DWFeUXofhUuCgVTuT # HN3K8idoMbn8Q3edUIECAwEAAaOCAUkwggFFMB0GA1UdDgQWBBSJIXfxcqAwFqGj # 9jdwQtdSqadj1zAfBgNVHSMEGDAWgBSfpxVdAF5iXYP05dJlpxtTNRnpcjBfBgNV # HR8EWDBWMFSgUqBQhk5odHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2Ny # bC9NaWNyb3NvZnQlMjBUaW1lLVN0YW1wJTIwUENBJTIwMjAxMCgxKS5jcmwwbAYI # KwYBBQUHAQEEYDBeMFwGCCsGAQUFBzAChlBodHRwOi8vd3d3Lm1pY3Jvc29mdC5j # b20vcGtpb3BzL2NlcnRzL01pY3Jvc29mdCUyMFRpbWUtU3RhbXAlMjBQQ0ElMjAy # MDEwKDEpLmNydDAMBgNVHRMBAf8EAjAAMBYGA1UdJQEB/wQMMAoGCCsGAQUFBwMI # MA4GA1UdDwEB/wQEAwIHgDANBgkqhkiG9w0BAQsFAAOCAgEAd42HtV+kGbvxzLBT # C5O7vkCIBPy/BwpjCzeL53hAiEOebp+VdNnwm9GVCfYq3KMfrj4UvKQTUAaS5Zkw # e1gvZ3ljSSnCOyS5OwNu9dpg3ww+QW2eOcSLkyVAWFrLn6Iig3TC/zWMvVhqXtdF # hG2KJ1lSbN222csY3E3/BrGluAlvET9gmxVyyxNy59/7JF5zIGcJibydxs94JL1B # tPgXJOfZzQ+/3iTc6eDtmaWT6DKdnJocp8wkXKWPIsBEfkD6k1Qitwvt0mHrORah # 75SjecOKt4oWayVLkPTho12e0ongEg1cje5fxSZGthrMrWKvI4R7HEC7k8maH9eP # A3ViH0CVSSOefaPTGMzIhHCo5p3jG5SMcyO3eA9uEaYQJITJlLG3BwwGmypY7C/8 # /nj1SOhgx1HgJ0ywOJL9xfP4AOcWmCfbsqgGbCaC7WH5sINdzfMar8V7YNFqkbCG # UKhc8GpIyE+MKnyVn33jsuaGAlNRg7dVRUSoYLJxvUsw9GOwyBpBwbE9sqOLm+Hs # O00oF23PMio7WFXcFTZAjp3ujihBAfLrXICgGOHPdkZ042u1LZqOcnlr3XzvgMe+ # mPPyasW8f0rtzJj3V5E/EKiyQlPxj9Mfq2x9himnlXWGZCVPeEBROrNbDYBfazTy # LNCOTsRtksOSV3FBtPnpQtLN754wggdxMIIFWaADAgECAhMzAAAAFcXna54Cm0mZ # AAAAAAAVMA0GCSqGSIb3DQEBCwUAMIGIMQswCQYDVQQGEwJVUzETMBEGA1UECBMK # V2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0 # IENvcnBvcmF0aW9uMTIwMAYDVQQDEylNaWNyb3NvZnQgUm9vdCBDZXJ0aWZpY2F0 # ZSBBdXRob3JpdHkgMjAxMDAeFw0yMTA5MzAxODIyMjVaFw0zMDA5MzAxODMyMjVa # MHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdS # ZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMT # HU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEwMIICIjANBgkqhkiG9w0BAQEF # AAOCAg8AMIICCgKCAgEA5OGmTOe0ciELeaLL1yR5vQ7VgtP97pwHB9KpbE51yMo1 # V/YBf2xK4OK9uT4XYDP/XE/HZveVU3Fa4n5KWv64NmeFRiMMtY0Tz3cywBAY6GB9 # alKDRLemjkZrBxTzxXb1hlDcwUTIcVxRMTegCjhuje3XD9gmU3w5YQJ6xKr9cmmv # Haus9ja+NSZk2pg7uhp7M62AW36MEBydUv626GIl3GoPz130/o5Tz9bshVZN7928 # jaTjkY+yOSxRnOlwaQ3KNi1wjjHINSi947SHJMPgyY9+tVSP3PoFVZhtaDuaRr3t # pK56KTesy+uDRedGbsoy1cCGMFxPLOJiss254o2I5JasAUq7vnGpF1tnYN74kpEe # HT39IM9zfUGaRnXNxF803RKJ1v2lIH1+/NmeRd+2ci/bfV+AutuqfjbsNkz2K26o # ElHovwUDo9Fzpk03dJQcNIIP8BDyt0cY7afomXw/TNuvXsLz1dhzPUNOwTM5TI4C # vEJoLhDqhFFG4tG9ahhaYQFzymeiXtcodgLiMxhy16cg8ML6EgrXY28MyTZki1ug # poMhXV8wdJGUlNi5UPkLiWHzNgY1GIRH29wb0f2y1BzFa/ZcUlFdEtsluq9QBXps # xREdcu+N+VLEhReTwDwV2xo3xwgVGD94q0W29R6HXtqPnhZyacaue7e3PmriLq0C # AwEAAaOCAd0wggHZMBIGCSsGAQQBgjcVAQQFAgMBAAEwIwYJKwYBBAGCNxUCBBYE # FCqnUv5kxJq+gpE8RjUpzxD/LwTuMB0GA1UdDgQWBBSfpxVdAF5iXYP05dJlpxtT # NRnpcjBcBgNVHSAEVTBTMFEGDCsGAQQBgjdMg30BATBBMD8GCCsGAQUFBwIBFjNo # dHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL0RvY3MvUmVwb3NpdG9yeS5o # dG0wEwYDVR0lBAwwCgYIKwYBBQUHAwgwGQYJKwYBBAGCNxQCBAweCgBTAHUAYgBD # AEEwCwYDVR0PBAQDAgGGMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAU1fZW # y4/oolxiaNE9lJBb186aGMQwVgYDVR0fBE8wTTBLoEmgR4ZFaHR0cDovL2NybC5t # aWNyb3NvZnQuY29tL3BraS9jcmwvcHJvZHVjdHMvTWljUm9vQ2VyQXV0XzIwMTAt # MDYtMjMuY3JsMFoGCCsGAQUFBwEBBE4wTDBKBggrBgEFBQcwAoY+aHR0cDovL3d3 # dy5taWNyb3NvZnQuY29tL3BraS9jZXJ0cy9NaWNSb29DZXJBdXRfMjAxMC0wNi0y # My5jcnQwDQYJKoZIhvcNAQELBQADggIBAJ1VffwqreEsH2cBMSRb4Z5yS/ypb+pc # FLY+TkdkeLEGk5c9MTO1OdfCcTY/2mRsfNB1OW27DzHkwo/7bNGhlBgi7ulmZzpT # Td2YurYeeNg2LpypglYAA7AFvonoaeC6Ce5732pvvinLbtg/SHUB2RjebYIM9W0j # VOR4U3UkV7ndn/OOPcbzaN9l9qRWqveVtihVJ9AkvUCgvxm2EhIRXT0n4ECWOKz3 # +SmJw7wXsFSFQrP8DJ6LGYnn8AtqgcKBGUIZUnWKNsIdw2FzLixre24/LAl4FOmR # sqlb30mjdAy87JGA0j3mSj5mO0+7hvoyGtmW9I/2kQH2zsZ0/fZMcm8Qq3UwxTSw # ethQ/gpY3UA8x1RtnWN0SCyxTkctwRQEcb9k+SS+c23Kjgm9swFXSVRk2XPXfx5b # RAGOWhmRaw2fpCjcZxkoJLo4S5pu+yFUa2pFEUep8beuyOiJXk+d0tBMdrVXVAmx # aQFEfnyhYWxz/gq77EFmPWn9y8FBSX5+k77L+DvktxW/tM4+pTFRhLy/AsGConsX # HRWJjXD+57XQKBqJC4822rpM+Zv/Cuk0+CQ1ZyvgDbjmjJnW4SLq8CdCPSWU5nR0 # W2rRnj7tfqAxM328y+l7vzhwRNGQ8cirOoo6CGJ/2XBjU02N7oJtpQUQwXEGahC0 # HVUzWLOhcGbyoYIDWTCCAkECAQEwggEBoYHZpIHWMIHTMQswCQYDVQQGEwJVUzET # MBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMV # TWljcm9zb2Z0IENvcnBvcmF0aW9uMS0wKwYDVQQLEyRNaWNyb3NvZnQgSXJlbGFu # ZCBPcGVyYXRpb25zIExpbWl0ZWQxJzAlBgNVBAsTHm5TaGllbGQgVFNTIEVTTjo2 # NTFBLTA1RTAtRDk0NzElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAgU2Vy # dmljZaIjCgEBMAcGBSsOAwIaAxUAj6eTejbuYE1Ifjbfrt6tXevCUSCggYMwgYCk # fjB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMH # UmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQD # Ex1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMDANBgkqhkiG9w0BAQsFAAIF # AO4gGDcwIhgPMjAyNjA4MDcwODM5MTlaGA8yMDI2MDgwODA4MzkxOVowdzA9Bgor # BgEEAYRZCgQBMS8wLTAKAgUA7iAYNwIBADAKAgEAAgIjCgIB/zAHAgEAAgIUMTAK # AgUA7iFptwIBADA2BgorBgEEAYRZCgQCMSgwJjAMBgorBgEEAYRZCgMCoAowCAIB # AAIDB6EgoQowCAIBAAIDAYagMA0GCSqGSIb3DQEBCwUAA4IBAQArn/c9z3WaL1z8 # g5in7QKs8M3lXJs3kE3J3Ztu5Sw+5EZEVIuWgQcHDrXEnve//1CS49fV0Qvi4I99 # rGBEH20iH7I0MeaSVLeQPvFYg4GTfKDfnCXazNy9sb7z9GuKTWRdR/kzxYjPNBd8 # S2M/KGpxz5wda4Ao01Wp3ka4PEg8ogMEo99XJK67MtvTzA/4pw8uFsuoJyBZM85t # pDMU3h6M8t/oDxa78Loh81QE6CtnFWFPu9Cbr5fWvjxxhverv/2os8gTidIi0tP7 # iHdM1u95nZjdo56hTmanQlrWlfkMCZu3j/enz0mowp811CZrTid8Mr0qKd9D7n4/ # iyXDix9tMYIEDTCCBAkCAQEwgZMwfDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldh # c2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBD # b3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENBIDIw # MTACEzMAAAIVGAPTgQcmfFMAAQAAAhUwDQYJYIZIAWUDBAIBBQCgggFKMBoGCSqG # SIb3DQEJAzENBgsqhkiG9w0BCRABBDAvBgkqhkiG9w0BCQQxIgQgv060NqFO/hCY # TrE2+yT0dKXnaUleQowFxubJDFWHBQowgfoGCyqGSIb3DQEJEAIvMYHqMIHnMIHk # MIG9BCBwEPR2PDrTFLcrtQsKrUi7oz5JNRCF/KRHMihSNe7sijCBmDCBgKR+MHwx # CzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRt # b25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMTHU1p # Y3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEwAhMzAAACFRgD04EHJnxTAAEAAAIV # MCIEIGQYFn8hsk+Tvqg64Eoetx5faA85sa/Mh8BM4zZ0initMA0GCSqGSIb3DQEB # CwUABIICADNovgK5rLdYdC0UFSQ1CtLchSTsmENsIF+8T4RDhkXuDvRhtOkd5RLV # vTfg7oXeiFIsSK8jRI6DGWIlCFsa1LFDBXDku4HNSQGOABsZii0/PcnssxQuZV/T # rLT2kBM85cpfywomQJhYU3XZraV3AEixcKKWopLlTiSCVHWfmGL3wEaO2BT80d4N # OOxTw/rCpXBPeBAIQs3SBWPHNo3pEj4WPzYYr2N8IuqxvB+WMe7KBGH9UdwNT4lq # QEOFexqOrR2deryYKKEmmc4l9Pv3xZl7GGyQXTGc0GeO9zV4qgWMUQ2jBsrZy4Kn # 62NPt7XuTvljGf9cjz9/8ukSZDpNCLfn1LFTwBlwYOJbmUCoQaNnJFRBxfgOeimL # 7OVKfS6QhCUHtJGio+xdaA0Fg6oJOKsRi0pqE6HOlU9nriKxL05VutFw81wPbDIJ # 5soG6OZb/SHD2nBu4AQOCJU58phtamSiAlXUQwoACP1qrXrsS28peAqoiCOHuY63 # 5urRDgiqJfQI86vlTrKP4k6wj63nfDAKc2cwBTS0p3WWezqArMmj2IMvAbG/F2p1 # R5cVrIW10bP3KxvciIRHzzEKw4kOqbiHlm38gmjQ89iPukl2///yc0+JAtrZXHYo # M+CeY0TH4ke7CTOjO+e34t5i7Iyakfy9BaI7jAswHfP9KbmZsvDS # SIG # End signature block |