Engines/Bicep/Invoke-AvmBicepCheckPolicy.ps1
|
function Invoke-AvmBicepCheckPolicy { <# .SYNOPSIS Evaluate the Bicep module's selected tests with PSRule for Azure. .DESCRIPTION Uses the package's PSRule options and rule/suppression directory to evaluate defaults and waf-aligned e2e tests with the required Reliability and AVM WAF Security baselines and the advisory Default and Security baselines. Files are tokenized in a temporary tree; original module files are never changed. Missing inputs, tools, baselines, or uninspectable results fail the check. The PSRule and PSRule.Rules.Azure versions pinned in avm.pins.jsonc are exact-version dependencies resolved through the shared tool cache. Missing modules are acquired before evaluation. Tokens are read from VALIDATE_SUBSCRIPTION_IDS or its TEST_SUBSCRIPTION_IDS alias (first entry matching the exact root reservation map), with VALIDATE_SUBSCRIPTION_ID as a General-only fallback, VALIDATE_TENANT_ID, VALIDATE_MANAGEMENT_GROUP_ID (or ARM_MGMTGROUP_ID), TOKEN_NAMEPREFIX, and localToken_* environment variables. .PARAMETER Context Bicep module context produced by Get-AvmModuleContext. .PARAMETER AllowPathFallback Permit a PATH-resolved Bicep CLI matching the pinned version. .PARAMETER Example Exact module-relative paths from the discovered policy tests. .PARAMETER Baseline Exact names from the four supported required and advisory baselines. .OUTPUTS pscustomobject with Engine, Tool, ToolPath, ToolSource, Status, RequiredBaselines, AdvisoryBaselines, TestsSelected, BaselinesExecuted, Evaluations, and Issues. #> [CmdletBinding(SupportsShouldProcess)] [OutputType([pscustomobject])] param( [Parameter(Mandatory)] $Context, [switch] $AllowPathFallback, [ValidateNotNullOrEmpty()] [string[]] $Example, [ValidateNotNullOrEmpty()] [string[]] $Baseline ) Set-StrictMode -Version 3.0 $ErrorActionPreference = 'Stop' if ($Context.Ecosystem -ne 'bicep') { throw [System.ArgumentException]::new( "Invoke-AvmBicepCheckPolicy requires a bicep context (got Ecosystem='$($Context.Ecosystem)').") } $required = @('Azure.Pillar.Reliability', 'CB.AVM.WAF.Security') $advisory = @('Azure.Default', 'Azure.Pillar.Security') if ($PSBoundParameters.ContainsKey('Baseline')) { foreach ($name in $Baseline) { if (($required + $advisory) -cnotcontains $name) { throw [AvmConfigurationException]::new( '-Baseline must select exact supported Bicep policy baseline names.') } } $required = @($required | Where-Object { $Baseline -ccontains $_ }) $advisory = @($advisory | Where-Object { $Baseline -ccontains $_ }) } $baselines = @( foreach ($name in $required) { [pscustomobject]@{ Name = $name; Required = $true } } foreach ($name in $advisory) { [pscustomobject]@{ Name = $name; Required = $false } } ) $issues = [System.Collections.Generic.List[object]]::new() $evaluations = [System.Collections.Generic.List[object]]::new() $tests = [System.Collections.Generic.List[object]]::new() $testModulePaths = @{} $seen = [System.Collections.Generic.HashSet[string]]::new( $(if ($IsWindows) { [System.StringComparer]::OrdinalIgnoreCase } else { [System.StringComparer]::Ordinal })) # Only exceptions of the listed types carry author-facing messages; any # other failure is reported with the fixed fallback message. $addFailure = { param( [System.Management.Automation.ErrorRecord] $ErrorRecord, [string] $Path, [string] $Code, [type[]] $KnownType, [string] $Fallback, [string] $Baseline = '' ) $exception = $ErrorRecord.Exception $message = $Fallback foreach ($type in $KnownType) { if ($exception -is $type) { $message = $exception.Message; break } } $issues.Add((New-AvmBicepPolicyIssue -Root $Context.Root -Path $Path ` -Code $Code -Message $message -Baseline $Baseline)) } $repositoryRoot = $null $modules = @() try { $modules = @(Get-AvmMetadataScope -Context $Context -IncludeModuleDirectories) } catch { & $addFailure $_ $Context.Root 'avm.bicep.psrule-input' ([System.ArgumentException]) ` "PSRule could not enumerate Bicep scopes ($($_.Exception.GetType().Name))." } foreach ($module in $modules) { $scope = Get-AvmBicepConventionScope -Path $module.Path if ($null -eq $scope) { $issues.Add((New-AvmBicepPolicyIssue -Root $Context.Root -Path $module.Path ` -Code 'avm.bicep.psrule-input' ` -Message 'PSRule requires modules under avm/res, avm/ptn, or avm/utl.')) continue } if ($null -ne $repositoryRoot -and $scope.RepositoryRoot -cne $repositoryRoot) { $issues.Add((New-AvmBicepPolicyIssue -Root $Context.Root -Path $module.Path ` -Code 'avm.bicep.psrule-input' ` -Message 'PSRule module scopes must share one repository root.')) continue } $repositoryRoot = $scope.RepositoryRoot try { foreach ($file in @(Get-AvmBicepPolicyTestFile -ModulePath $scope.Path ` -IncludeAllExamples:($PSBoundParameters.ContainsKey('Example')))) { if ($seen.Add($file.FullName)) { $tests.Add($file) $testModulePaths[$file.FullName] = $scope.ModuleRelativePath } } } catch { & $addFailure $_ $module.Path 'avm.bicep.psrule-input' ([AvmConfigurationException]) ` "PSRule could not select test sources ($($_.Exception.GetType().Name))." } } if ($PSBoundParameters.ContainsKey('Example')) { $selected = [System.Collections.Generic.HashSet[string]]::new([System.StringComparer]::Ordinal) foreach ($path in $Example) { $relative = $path.Replace('\', '/') if ([string]::IsNullOrWhiteSpace($relative) -or [System.IO.Path]::IsPathRooted($relative) -or $relative -match '[:\x00-\x1f*?\[\]]' -or $relative.Split('/') -contains '..') { throw [AvmConfigurationException]::new( '-Example must contain literal module-relative policy test paths without parent segments.') } $matched = @($tests | Where-Object { [System.IO.Path]::GetRelativePath($Context.Root, $_.FullName).Replace('\', '/') -ceq $relative }) if ($matched.Count -ne 1) { throw [AvmConfigurationException]::new( "Policy example '$relative' did not match exactly one discovered e2e main.test.bicep file.") } $null = $selected.Add($matched[0].FullName) } $tests = [System.Collections.Generic.List[object]]@($tests | Where-Object { $selected.Contains($_.FullName) }) } if ($tests.Count -eq 0) { $issues.Add((New-AvmBicepPolicyIssue -Root $Context.Root -Path $Context.Root ` -Code 'avm.bicep.psrule-input-missing' ` -Message 'No defaults or waf-aligned main.test.bicep sources were selected for PSRule.')) } $tool = $null $compiler = $null $configuration = $null $verified = @{} if ($issues.Count -eq 0) { try { $tool = Import-AvmBicepPolicyModule -ModuleRoot $Context.Root } catch { & $addFailure $_ $Context.Root 'avm.bicep.psrule-module' ([AvmConfigurationException]) ` 'Required PSRule module versions could not be loaded.' } } if ($issues.Count -eq 0) { try { $configuration = Get-AvmBicepPolicyConfiguration } catch { & $addFailure $_ $Context.Root 'avm.bicep.psrule-config' ([AvmConfigurationException]) ` 'PSRule configuration could not be loaded; inspect ps-rule.yaml and the installed modules.' } } if ($issues.Count -eq 0) { foreach ($baselineDefinition in $baselines) { try { $verified[$baselineDefinition.Name] = Get-AvmBicepPolicyBaseline ` -Configuration $configuration -Name $baselineDefinition.Name } catch { & $addFailure $_ $Context.Root 'avm.bicep.psrule-baseline' ([AvmConfigurationException]) ` "PSRule baseline '$($baselineDefinition.Name)' could not be inspected." $baselineDefinition.Name } } } if ($issues.Count -eq 0 -and -not $PSCmdlet.ShouldProcess($Context.Root, 'Stage Bicep tests for PSRule evaluation')) { $issues.Add((New-AvmBicepPolicyIssue -Root $Context.Root -Path $Context.Root ` -Code 'avm.bicep.psrule-not-run' ` -Message 'PSRule sources were not staged; no baseline ran.')) } if ($issues.Count -eq 0) { try { $compiler = Resolve-AvmTool -Name 'bicep' -ModuleRoot $Context.Root -AllowPathFallback:$AllowPathFallback } catch { $knownCompilerFailure = [type[]]@([AvmToolException], [AvmConfigurationException]) & $addFailure $_ $Context.Root 'avm.bicep.psrule-compiler' $knownCompilerFailure ` 'The pinned Bicep compiler could not be resolved.' } } if ($issues.Count -eq 0) { $previousBicep = [Environment]::GetEnvironmentVariable('PSRULE_AZURE_BICEP_PATH') try { $env:PSRULE_AZURE_BICEP_PATH = $compiler.Path foreach ($test in @($tests | Sort-Object -Culture 'en-US' -Property FullName)) { try { $tokens = Get-AvmBicepPolicyToken -RepositoryRoot $repositoryRoot ` -ModulePath $testModulePaths[$test.FullName] $files = @(Get-AvmBicepPolicySource -Path $test.FullName ` -RepositoryRoot $repositoryRoot -Tokens $tokens) } catch { & $addFailure $_ $test.FullName 'avm.bicep.psrule-source' ([AvmConfigurationException]) ` "PSRule could not read a selected test or local reference ($($_.Exception.GetType().Name))." continue } $stage = $null try { $stage = [System.IO.Directory]::CreateTempSubdirectory('avm-psrule-') Copy-AvmBicepPolicySource -Files $files -Destination $stage.FullName $relative = [System.IO.Path]::GetRelativePath( $repositoryRoot, $test.FullName) $stagePath = Join-Path $stage.FullName $relative if (-not [System.IO.File]::Exists($stagePath)) { throw [AvmConfigurationException]::new( 'The selected PSRule input was not staged.') } foreach ($baselineDefinition in $baselines) { try { $records = @(Invoke-AvmBicepPolicyBaseline ` -StageRoot $stage.FullName -InputPath $relative ` -OptionPath $configuration.OptionPath ` -RulePath $configuration.RulePath ` -Baseline $baselineDefinition.Name) $result = ConvertFrom-AvmBicepPolicyResult -Records $records ` -Baseline $verified[$baselineDefinition.Name] ` -StagePath $stagePath ` -SourcePath $test.FullName -ModuleRoot $Context.Root foreach ($issue in $result.Issues) { $issues.Add($issue) } if (-not $result.Valid) { continue } $evaluations.Add([pscustomobject]@{ File = [System.IO.Path]::GetRelativePath( $Context.Root, $test.FullName).Replace('\', '/') Baseline = $baselineDefinition.Name Required = $baselineDefinition.Required Records = $records.Count ExpandedRecords = $result.ExpandedRecords ProcessedRules = $result.ProcessedRules FailedRules = $result.Failures.Count }) foreach ($failure in $result.Failures) { $severity = if ($baselineDefinition.Required) { 'error' } else { 'warning' } $message = "PSRule baseline '$($baselineDefinition.Name)' failed rule '$($failure.RuleName)' for target type '$($failure.TargetType)'." $issues.Add((New-AvmBicepPolicyIssue -Root $Context.Root ` -Path $test.FullName -Baseline $baselineDefinition.Name ` -RuleName $failure.RuleName -TargetType $failure.TargetType ` -Severity $severity -Code 'avm.bicep.psrule-rule' ` -Message $message)) } } catch { $message = "PSRule baseline '$($baselineDefinition.Name)' could not evaluate this tokenized Bicep test ($($_.Exception.GetType().Name)); verify the source and configuration." $issues.Add((New-AvmBicepPolicyIssue -Root $Context.Root ` -Path $test.FullName -Baseline $baselineDefinition.Name ` -Code 'avm.bicep.psrule-evaluation' ` -Message $message)) } } } catch { $issues.Add((New-AvmBicepPolicyIssue -Root $Context.Root -Path $test.FullName ` -Code 'avm.bicep.psrule-staging' ` -Message 'PSRule could not stage the tokenized Bicep test and its local references.')) } finally { if ($null -ne $stage) { try { [System.IO.Directory]::Delete($stage.FullName, $true) } catch { $issues.Add((New-AvmBicepPolicyIssue -Root $Context.Root ` -Path $test.FullName -Code 'avm.bicep.psrule-cleanup' ` -Message ("PSRule temporary files could not be removed from '{0}'; remove them before retrying." -f $stage.FullName))) } } } } } finally { if ($null -eq $previousBicep) { [Environment]::SetEnvironmentVariable('PSRULE_AZURE_BICEP_PATH', $null) } else { $env:PSRULE_AZURE_BICEP_PATH = $previousBicep } } } return [pscustomobject][ordered]@{ Engine = 'bicep' Tool = if ($null -ne $tool) { $tool.Name } else { 'PSRule.Rules.Azure (not run)' } ToolPath = if ($null -ne $tool) { $tool.Path } else { $null } ToolSource = if ($evaluations.Count -gt 0) { 'powershell-module' } else { 'not-run' } Status = if (@($issues | Where-Object { $_.Severity -eq 'error' }).Count -gt 0) { 'fail' } else { 'pass' } RequiredBaselines = $required AdvisoryBaselines = $advisory TestsSelected = $tests.Count BaselinesExecuted = $evaluations.Count Evaluations = $evaluations.ToArray() Issues = $issues.ToArray() } } # SIG # Begin signature block # MIInRAYJKoZIhvcNAQcCoIInNTCCJzECAQExDzANBglghkgBZQMEAgEFADB5Bgor # BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG # KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCBcCqKegAugZ630 # zfqRvS/aRBYQI5pZwW+ERKUxykqhhKCCDLowggX1MIID3aADAgECAhMzAAACHU0Z # yE7XD1dIAAAAAAIdMA0GCSqGSIb3DQEBCwUAMFcxCzAJBgNVBAYTAlVTMR4wHAYD # VQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBD # b2RlIFNpZ25pbmcgUENBIDIwMjQwHhcNMjYwNDE2MTg1OTQzWhcNMjcwNDE1MTg1 # OTQzWjB0MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UE # BxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMR4wHAYD # VQQDExVNaWNyb3NvZnQgQ29ycG9yYXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IB # DwAwggEKAoIBAQDQvewXxx9gZZFC6Ys1WBay8BJ8kGA4JQnH5CMafqOASlTpK9H8 # o5ZXTXt0caVQTNMUPt445wXYD+dFtaKWTwDn1I52oUSrC9vJin1Gsqt+zyKJL5Dg # 3eQXbQNR61DmMy20GLTIO3SFed9Rfi/ophgCLGFLDR3r0KvHjwMb/jYWS0celV/4 # Lz27LfAekm8v9E5IXaeiXbAUYZKK090n4CVl3JBtbN+9DtI9SNu/yjvozW52/u7R # X/Ttpa/KDlpuokZ+Zcbvmtd9ur9gFLvZzh41o9MsE/clQtdaFWGvuo6Jua/ntpgk # ey3E5/vBFe+MJPG6phdnuo6r57ZudCudiI1bAgMBAAGjggGbMIIBlzAOBgNVHQ8B # Af8EBAMCB4AwHwYDVR0lBBgwFgYKKwYBBAGCN0wIAQYIKwYBBQUHAwMwHQYDVR0O # BBYEFH6QuMwqcPG0hQlQ6c5jCtTTLrVeMEUGA1UdEQQ+MDykOjA4MR4wHAYDVQQL # ExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xFjAUBgNVBAUTDTIzMDAxMis1MDc1NTkw # HwYDVR0jBBgwFoAUf1k/VCHarU/vBeXmo9ctBpQSCDEwYAYDVR0fBFkwVzBVoFOg # UYZPaHR0cDovL3d3dy5taWNyb3NvZnQuY29tL3BraW9wcy9jcmwvTWljcm9zb2Z0 # JTIwQ29kZSUyMFNpZ25pbmclMjBQQ0ElMjAyMDI0LmNybDBtBggrBgEFBQcBAQRh # MF8wXQYIKwYBBQUHMAKGUWh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2lvcHMv # Y2VydHMvTWljcm9zb2Z0JTIwQ29kZSUyMFNpZ25pbmclMjBQQ0ElMjAyMDI0LmNy # dDAMBgNVHRMBAf8EAjAAMA0GCSqGSIb3DQEBCwUAA4ICAQBKTbYOjzwTG/DXGaz9 # s6+fQeaTtDcFmMY+5UyVFCyj7Pv+5i37qfX8lSL/tBIfYQfWsMuBQlfZurJD6r4H # VJ2CeH+1fgiq8dcHdVKoZ3Sa2qXoX3cq9iS8cVb06B7+5/XJ7I0OxHH9fDsvJ3T3 # w5V/ZtAIFmLrl+P0CtG+92uzRsn0nTbdFjOkLMLWPLAU3THohKRlSEMgFJpPkm5n # 5UAZ35xX6FWCrDLsSKb555bTifwa8mJBwdlof0bmfYidH+dxZ1FdDxvLnNl9zeKs # A4kejaaIqqIPguhwAti5Ql7BlTNoJNwxCvBmqW2MQLnCkYN/VVUsR3V2x/rcTNzo # Bf/Z/SpROvdaA2ZOOd1uioXJt3tdLQ7vHpqpib0KfWr/FWXW10q38VxfCnRQBqzb # SuztR7nEMuzX7Ck+B/XaPDXd1qh72+QYyB0Z2VzWmO9zsnb9Uq/dwu8LGeQqnyu6 # 7SDGACvnXii2fb9+US492VTnXSnFKyqwgzUyFMtZK1/sHYTv6bG4TtQUygQxTN+Z # V+aJIlKO2MqZ7bKrAnOzS9m6NgoTdWOq11bTOZwKlIEV/EhV9SWkDmdpR/hPPT2v # 6TEj4F8PT/zHjRezIU5c/DGlt/VhY/pK0XkJtEyMmmS1BMtjU/rqBZVMIm3dnxQs # /TBByr+Cf8Z1r7aifQVQ+WSqzjCCBr0wggSloAMCAQICEzMAAAA5O7Y3Gb8GHWcA # AAAAADkwDQYJKoZIhvcNAQEMBQAwgYgxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpX # YXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQg # Q29ycG9yYXRpb24xMjAwBgNVBAMTKU1pY3Jvc29mdCBSb290IENlcnRpZmljYXRl # IEF1dGhvcml0eSAyMDExMB4XDTI0MDgwODIwNTQxOFoXDTM2MDMyMjIyMTMwNFow # VzELMAkGA1UEBhMCVVMxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEo # MCYGA1UEAxMfTWljcm9zb2Z0IENvZGUgU2lnbmluZyBQQ0EgMjAyNDCCAiIwDQYJ # KoZIhvcNAQEBBQADggIPADCCAgoCggIBANgBnB7jOMeqlRYHNa265v4IY9fH8TKh # emHfPINe1gpLaV3dhg324WwH06LcHbpnsBukCDNitryo0dtS/EW6I/yEL/bLSY8h # KpbfQuWusBPr9qazYcDxCW/qnjb5JsI1s8bNOg3bVATvQVL4tcf03aTycsz8QeCd # M0l/yHRObJ9QqazM1r6VPEOJ7LL+uEEb73w6QCuhs89a1uv1zerOYMnsneRRwCbp # yW11IcggU0cRKDDq1pjVJzIbIF6+oiXXbReOsgeI8zu1FyQfK0fVkaya8SmVHQ/t # Of23mZ4W9k0Ri22QW9p3UgSC5OUDktKxxcCmGL6tXLfOGSWHIIV4YrTJTT6PNty5 # REojHJuZHArkF9VnHTERWoTjAzfI3kP+5b4alUdhgAZ7ttOu1bVnXfHaqPYl2rPs # 20ji03LOVWsh/radgE17es5hL+t6lV0eVHrVhsssROWJuz2MXMCt7iw7lFPG9LXK # Gjsmonn2gotGdHIuEg5JnJMJVmixd5LRlkmgYRZKzhxSCwyoGIq0PhaA7Y+VPct5 # pCHkijcIIDm0nlkK+0KyepolcqGm0T/GYQRMhHJlGOOmVQop36wUVUYklUy++vDW # eEgEo4s7hxN6mIbf2MSIQ/iIfMZgJxC69oukMUXCrOC3SkE/xIkgpfl22MM1itkZ # 35nNXkMolU1lAgMBAAGjggFOMIIBSjAOBgNVHQ8BAf8EBAMCAYYwEAYJKwYBBAGC # NxUBBAMCAQAwHQYDVR0OBBYEFH9ZP1Qh2q1P7wXl5qPXLQaUEggxMBkGCSsGAQQB # gjcUAgQMHgoAUwB1AGIAQwBBMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAU # ci06AjGQQ7kUBU7h6qfHMdEjiTQwWgYDVR0fBFMwUTBPoE2gS4ZJaHR0cDovL2Ny # bC5taWNyb3NvZnQuY29tL3BraS9jcmwvcHJvZHVjdHMvTWljUm9vQ2VyQXV0MjAx # MV8yMDExXzAzXzIyLmNybDBeBggrBgEFBQcBAQRSMFAwTgYIKwYBBQUHMAKGQmh0 # dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2kvY2VydHMvTWljUm9vQ2VyQXV0MjAx # MV8yMDExXzAzXzIyLmNydDANBgkqhkiG9w0BAQwFAAOCAgEAFJQfOChP7onn6fLI # MKrSlN1WYKwDFgAddymOUO3FrM8d7B/W/iQ6DxXsDn7D5W4wMwYeLystcEqfkjz4 # NURRgazyMu5yRzQh4LqjA4tStTcJh1opExo7nn5PuPBYnbu0+THSuVHTe0VTTPVh # ily/piFrDo3axQ9P4C+Ol5yet+2gTfekICS5xS+cYfSIvgn0JksVBVMYVI5QFu/q # hnLhsEFEUzG8fvv0hjgkO+lkpV9ty6GkN4vdnd7ya6Q6aR9y34aiM1qmxaxBi6OU # nyNl6fkuun/diTFnYDLTppOkr/mg5WSfCiDVMNCxtj4wPKC5OmHm1DQIt/MNokbb # H3UGsFP1QbzsLocuSqLCvH09Io3fDPTmscR9Y75G4qX7RTX8AdBPo0I6OEojf39z # uFZt0qOHm65YWQE69cZM2ueE1MB05dNNgHK9gTE7zKvK/fg8B2qjW88MT/WF5V5u # vZGtqa9FSL2RazArA+rDPuf6JGYz4HpgMZHB4S6szWSKYBv0VisCzfxgeU+dquXW # 9bd0auYlOB58DPcOYKdc3Se94g+xL4pcEhbB54JOgAkwYTu/9dLeH2pDqeJZAABV # DWRQCaXfO5LgyKwKCLYXpigrZYCjUSBcr+Ve8PFWMhVTQl0v4q8J/AUmQN5W4n10 # 1cY2L4A7GTQG1h32HHAvfQESWP0xghngMIIZ3AIBATBuMFcxCzAJBgNVBAYTAlVT # MR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jv # c29mdCBDb2RlIFNpZ25pbmcgUENBIDIwMjQCEzMAAAIdTRnITtcPV0gAAAAAAh0w # DQYJYIZIAWUDBAIBBQCggZAwGQYJKoZIhvcNAQkDMQwGCisGAQQBgjcCAQQwLwYJ # KoZIhvcNAQkEMSIEIGQPP4UtbXuMm/3vUEvyWut/iWsRjzN78YU1iqG8/kQTMEIG # CisGAQQBgjcCAQwxNDAyoBSAEgBNAGkAYwByAG8AcwBvAGYAdKEagBhodHRwOi8v # d3d3Lm1pY3Jvc29mdC5jb20wDQYJKoZIhvcNAQEBBQAEggEAuuL60r52mhmz36+T # /Gpo5uhpEh+hiqyNvBOSVVQrzop34ugbAVxT/g7xT0IHdVGMWshme09fZo+DuBaD # 5yTT25PYpNe5r5x0DQP7a7+aSvFI6NAIkBKpKpLyIGhdRW1HY7C2aappBNuBi+ta # DplLmLrJ+NyclNghNh12X4FF9y0Ev1/wr8twM5OAgNJfLNox3aPBNm5zw+XoKs/k # Vps8mbhmIH2JPzJJvEUaYNgFw1GivdKaCtJTQZHkIxOcRWJzxwLQ6t4lOgKlNS6Q # wFuu/aQFGBESF4xqsBjEaLpBJKcU94rr+enc7HUnCdACSZz8rHnOMDCQyw5xh0Ts # SRWu6qGCF7AwghesBgorBgEEAYI3AwMBMYIXnDCCF5gGCSqGSIb3DQEHAqCCF4kw # gheFAgEDMQ8wDQYJYIZIAWUDBAIBBQAwggFaBgsqhkiG9w0BCRABBKCCAUkEggFF # MIIBQQIBAQYKKwYBBAGEWQoDATAxMA0GCWCGSAFlAwQCAQUABCDJiWlPua/yfYX9 # aW4k0OKaLLa+w4pfOJE5A3y/vmr+sQIGaq0ro1WIGBMyMDI2MTAyMTA4NTExOC4w # MjJaMASAAgH0oIHZpIHWMIHTMQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGlu # Z3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBv # cmF0aW9uMS0wKwYDVQQLEyRNaWNyb3NvZnQgSXJlbGFuZCBPcGVyYXRpb25zIExp # bWl0ZWQxJzAlBgNVBAsTHm5TaGllbGQgVFNTIEVTTjo2QjA1LTA1RTAtRDk0NzEl # MCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAgU2VydmljZaCCEf4wggcoMIIF # EKADAgECAhMzAAACEUUYOZtDz/xsAAEAAAIRMA0GCSqGSIb3DQEBCwUAMHwxCzAJ # BgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25k # MR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jv # c29mdCBUaW1lLVN0YW1wIFBDQSAyMDEwMB4XDTI1MDgxNDE4NDgxM1oXDTI2MTEx # MzE4NDgxM1owgdMxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAw # DgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24x # LTArBgNVBAsTJE1pY3Jvc29mdCBJcmVsYW5kIE9wZXJhdGlvbnMgTGltaXRlZDEn # MCUGA1UECxMeblNoaWVsZCBUU1MgRVNOOjZCMDUtMDVFMC1EOTQ3MSUwIwYDVQQD # ExxNaWNyb3NvZnQgVGltZS1TdGFtcCBTZXJ2aWNlMIICIjANBgkqhkiG9w0BAQEF # AAOCAg8AMIICCgKCAgEAz7m7MxAdL5Vayrk7jsMo3GnhN85ktHCZEvEcj4BIccHK # d/NKC7uPvpX5dhO63W6VM5iCxklG8qQeVVrPaKvj8dYYJC7DNt4NN3XlVdC/vove # JuPPhTJ/u7X+pYmV2qehTVPOOB1/hpmt51SzgxZczMdnFl+X2e1PgutSA5CAh9/X # z5NW0CxnYVz8g0Vpxg+Bq32amktRXr8m3BSEgUs8jgWRPVzPHEczpbhloGGEfHaR # OmHhVKIqN+JhMweEjU2NXM2W6hm32j/QH/I/KWqNNfYchHaG0xJljVTYoUKPpcQD # uhH9dQKEgvGxj2U5/3Fq1em4dO6Ih04m6R+ttxr6Y8oRJH9ZhZ3sciFBIvZh7E2Y # FXOjP4MGybSylQTPDEFAtHHgpkskeEUhsPDR9VvWWhekhQx3qXaAKh+AkLmz/hpE # 3e0y+RIKO2AREjULJAKgf+R9QnNvqMeMkz9PGrjsijqWGzB2k2JNyaUYKlbmQweO # absCioiY2fJbimjVyFAGk5AeYddUFxvJGgRVCH7BeBPKAq7MMOmSCTOMZ0Sw6zyN # x4Uhh5Y0uJ0ZOoTKnB3KfdN/ba/eKHFeEhi3WqAfzTxiy0rMvhsfsXZK7zoclqaR # vVl8Q48J174+eyriypY9HhU+ohgiYi4uQGDDVdTDeKDtoC/hD2Cn+ARzwE1rFfEC # AwEAAaOCAUkwggFFMB0GA1UdDgQWBBRifUUDwOnqIcvfb53+yV0EZn7OcDAfBgNV # HSMEGDAWgBSfpxVdAF5iXYP05dJlpxtTNRnpcjBfBgNVHR8EWDBWMFSgUqBQhk5o # dHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2NybC9NaWNyb3NvZnQlMjBU # aW1lLVN0YW1wJTIwUENBJTIwMjAxMCgxKS5jcmwwbAYIKwYBBQUHAQEEYDBeMFwG # CCsGAQUFBzAChlBodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2NlcnRz # L01pY3Jvc29mdCUyMFRpbWUtU3RhbXAlMjBQQ0ElMjAyMDEwKDEpLmNydDAMBgNV # HRMBAf8EAjAAMBYGA1UdJQEB/wQMMAoGCCsGAQUFBwMIMA4GA1UdDwEB/wQEAwIH # gDANBgkqhkiG9w0BAQsFAAOCAgEApEKdnMeIIUiU6PatZ/qbrwiDzYUMKRczC4Bp # /XY1S9NmHI+2c3dcpwH2SOmDfdvIIqt7mRrgvBPYOvJ9CtZS5eeIrsObC0b0ggKT # v2wrTgWG+qktqNFEhQeipdURNLN68uHAm5edwBytd1kwy5r6B93klxDsldOmVWtw # /ngj7knN09muCmwr17JnsMFcoIN/H59s+1RYN7Vid4+7nj8FcvYy9rbZOMndBzsT # iosF1M+aMIJX2k3EVFVsuDL7/R5ppI9Tg7eWQOWKMZHPdsA3ZqWzDuhJqTzoFSQS # hnZenC+xq/z9BhHPFFbUtfjAoG6EDPjSQJYXmogja8OEa19xwnh3wVufeP+ck+/0 # gxNi7g+kO6WaOm052F4siD8xi6Uv75L7798lHvPThcxHHsgXqMY592d1wUof3tL/ # eDaQ0UhnYCU8yGkU2XJnctONnBKAvURAvf2qiIWDj4Lpcm0zA7VuofuJR1Tpuyc5 # p1ja52bNZBBVqAOwyDhAmqWsJXAjYXnssC/fJkee314Fh+GIyMgvAPRScgqRZqV1 # 6dTBYvoe+w1n/wWs/ySTUsxDw4T/AITcu5PAsLnCVpArDrFLRTFyut+eHUoG6UYZ # fj8/RsuQ42INse1pb/cPm7G2lcLJtkIKT80xvB1LiaNvPTBVEcmNSvFUM0xrXZXc # YcxVXiYwggdxMIIFWaADAgECAhMzAAAAFcXna54Cm0mZAAAAAAAVMA0GCSqGSIb3 # DQEBCwUAMIGIMQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4G # A1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMTIw # MAYDVQQDEylNaWNyb3NvZnQgUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkgMjAx # MDAeFw0yMTA5MzAxODIyMjVaFw0zMDA5MzAxODMyMjVaMHwxCzAJBgNVBAYTAlVT # MRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQK # ExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1l # LVN0YW1wIFBDQSAyMDEwMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA # 5OGmTOe0ciELeaLL1yR5vQ7VgtP97pwHB9KpbE51yMo1V/YBf2xK4OK9uT4XYDP/ # XE/HZveVU3Fa4n5KWv64NmeFRiMMtY0Tz3cywBAY6GB9alKDRLemjkZrBxTzxXb1 # hlDcwUTIcVxRMTegCjhuje3XD9gmU3w5YQJ6xKr9cmmvHaus9ja+NSZk2pg7uhp7 # M62AW36MEBydUv626GIl3GoPz130/o5Tz9bshVZN7928jaTjkY+yOSxRnOlwaQ3K # Ni1wjjHINSi947SHJMPgyY9+tVSP3PoFVZhtaDuaRr3tpK56KTesy+uDRedGbsoy # 1cCGMFxPLOJiss254o2I5JasAUq7vnGpF1tnYN74kpEeHT39IM9zfUGaRnXNxF80 # 3RKJ1v2lIH1+/NmeRd+2ci/bfV+AutuqfjbsNkz2K26oElHovwUDo9Fzpk03dJQc # NIIP8BDyt0cY7afomXw/TNuvXsLz1dhzPUNOwTM5TI4CvEJoLhDqhFFG4tG9ahha # YQFzymeiXtcodgLiMxhy16cg8ML6EgrXY28MyTZki1ugpoMhXV8wdJGUlNi5UPkL # iWHzNgY1GIRH29wb0f2y1BzFa/ZcUlFdEtsluq9QBXpsxREdcu+N+VLEhReTwDwV # 2xo3xwgVGD94q0W29R6HXtqPnhZyacaue7e3PmriLq0CAwEAAaOCAd0wggHZMBIG # CSsGAQQBgjcVAQQFAgMBAAEwIwYJKwYBBAGCNxUCBBYEFCqnUv5kxJq+gpE8RjUp # zxD/LwTuMB0GA1UdDgQWBBSfpxVdAF5iXYP05dJlpxtTNRnpcjBcBgNVHSAEVTBT # MFEGDCsGAQQBgjdMg30BATBBMD8GCCsGAQUFBwIBFjNodHRwOi8vd3d3Lm1pY3Jv # c29mdC5jb20vcGtpb3BzL0RvY3MvUmVwb3NpdG9yeS5odG0wEwYDVR0lBAwwCgYI # KwYBBQUHAwgwGQYJKwYBBAGCNxQCBAweCgBTAHUAYgBDAEEwCwYDVR0PBAQDAgGG # MA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAU1fZWy4/oolxiaNE9lJBb186a # GMQwVgYDVR0fBE8wTTBLoEmgR4ZFaHR0cDovL2NybC5taWNyb3NvZnQuY29tL3Br # aS9jcmwvcHJvZHVjdHMvTWljUm9vQ2VyQXV0XzIwMTAtMDYtMjMuY3JsMFoGCCsG # AQUFBwEBBE4wTDBKBggrBgEFBQcwAoY+aHR0cDovL3d3dy5taWNyb3NvZnQuY29t # L3BraS9jZXJ0cy9NaWNSb29DZXJBdXRfMjAxMC0wNi0yMy5jcnQwDQYJKoZIhvcN # AQELBQADggIBAJ1VffwqreEsH2cBMSRb4Z5yS/ypb+pcFLY+TkdkeLEGk5c9MTO1 # OdfCcTY/2mRsfNB1OW27DzHkwo/7bNGhlBgi7ulmZzpTTd2YurYeeNg2LpypglYA # A7AFvonoaeC6Ce5732pvvinLbtg/SHUB2RjebYIM9W0jVOR4U3UkV7ndn/OOPcbz # aN9l9qRWqveVtihVJ9AkvUCgvxm2EhIRXT0n4ECWOKz3+SmJw7wXsFSFQrP8DJ6L # GYnn8AtqgcKBGUIZUnWKNsIdw2FzLixre24/LAl4FOmRsqlb30mjdAy87JGA0j3m # Sj5mO0+7hvoyGtmW9I/2kQH2zsZ0/fZMcm8Qq3UwxTSwethQ/gpY3UA8x1RtnWN0 # SCyxTkctwRQEcb9k+SS+c23Kjgm9swFXSVRk2XPXfx5bRAGOWhmRaw2fpCjcZxko # JLo4S5pu+yFUa2pFEUep8beuyOiJXk+d0tBMdrVXVAmxaQFEfnyhYWxz/gq77EFm # PWn9y8FBSX5+k77L+DvktxW/tM4+pTFRhLy/AsGConsXHRWJjXD+57XQKBqJC482 # 2rpM+Zv/Cuk0+CQ1ZyvgDbjmjJnW4SLq8CdCPSWU5nR0W2rRnj7tfqAxM328y+l7 # vzhwRNGQ8cirOoo6CGJ/2XBjU02N7oJtpQUQwXEGahC0HVUzWLOhcGbyoYIDWTCC # AkECAQEwggEBoYHZpIHWMIHTMQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGlu # Z3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBv # cmF0aW9uMS0wKwYDVQQLEyRNaWNyb3NvZnQgSXJlbGFuZCBPcGVyYXRpb25zIExp # bWl0ZWQxJzAlBgNVBAsTHm5TaGllbGQgVFNTIEVTTjo2QjA1LTA1RTAtRDk0NzEl # MCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAgU2VydmljZaIjCgEBMAcGBSsO # AwIaAxUAKyp8q2VdgAq1VGkzd7PZwV6zNc2ggYMwgYCkfjB8MQswCQYDVQQGEwJV # UzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UE # ChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQgVGlt # ZS1TdGFtcCBQQ0EgMjAxMDANBgkqhkiG9w0BAQsFAAIFAO6Cfx0wIhgPMjAyNjEw # MjEwMDAwMjlaGA8yMDI2MTAyMjAwMDAyOVowdzA9BgorBgEEAYRZCgQBMS8wLTAK # AgUA7oJ/HQIBADAKAgEAAgIHbgIB/zAHAgEAAgITIjAKAgUA7oPQnQIBADA2Bgor # BgEEAYRZCgQCMSgwJjAMBgorBgEEAYRZCgMCoAowCAIBAAIDB6EgoQowCAIBAAID # AYagMA0GCSqGSIb3DQEBCwUAA4IBAQBLyZ7f2qMdvPDEkz7WqusUrWJ8a4Q/g07j # pktWJ52RT2qpLqR2L+gTlhTp22oNGWZ5xMVpnzCRKFCs2v2ZysM9N0sK0Dk+Mcvp # MQtcIOpFMzoqjGAiwIMM7mm7GgbRYvQugBXh2VYvfOwipldVPwXrSRq1gJRE/M8g # NiH8ldPyphtv12PyVkgsMAytMLlN8BfJQ1F7V2XG5ZxruN0EVSgMm9DFBA3D0PYJ # 5LLOFyR34zKs4BGAcelRaT9tD/3/0FEZBkd3iabvBUKpbtRx09IsYGFuaFK9N/EE # WwKZZwcDqkqBo4ph28Kqt8KngF0S6bPZBrl18O6WbgaMWYfIsaeAMYIEDTCCBAkC # AQEwgZMwfDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNV # BAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQG # A1UEAxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENBIDIwMTACEzMAAAIRRRg5m0PP # /GwAAQAAAhEwDQYJYIZIAWUDBAIBBQCgggFKMBoGCSqGSIb3DQEJAzENBgsqhkiG # 9w0BCRABBDAvBgkqhkiG9w0BCQQxIgQg3QwHAFOOgrnkbsNrISguumdxnHeYYfoo # CAFg8ScCAvIwgfoGCyqGSIb3DQEJEAIvMYHqMIHnMIHkMIG9BCAsrTOpmu+HTq1a # XFwvlhjF8p2nUCNNCEX/OWLHNDMmtzCBmDCBgKR+MHwxCzAJBgNVBAYTAlVTMRMw # EQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVN # aWNyb3NvZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0 # YW1wIFBDQSAyMDEwAhMzAAACEUUYOZtDz/xsAAEAAAIRMCIEILn5PGJimpfsiLD9 # GWrfswn2JQvRMRZEyAPrCXK2tH3pMA0GCSqGSIb3DQEBCwUABIICAFxls9O4WO/g # iWL6KU9mwkoEvi1+ZNvqBBG0vwwQuSG4bw6lNj70oZm0KknkITEItD9CW/KNcU47 # Rohxg4bGvLfo7CcBirhiV3uZ99EO9A5CEJqFJm6RRCffCKYC1gJGNx24TEfi6KLT # 2xfFN0gRs/LYdf3GieFdm3Vfqj+WaIhS89UwBgLZWy0X9J6TUz5WtHQ+sjwUNUD0 # amhoY0CHoiXgkCQQHtxa5XWjzwz/iZEdNW2laNfWiaP1LoNXwzASsXvtK1/ku6ek # HmgMtQx8tyb5HMfqMUD0A7MOFkuo7swD6LHRceYGE7EHnw69tUJbxLqoHFgKRoAw # RDk+m3kPdMtL3ygbhTl4izUthyzz7PvYQ9MtKJgHH3j9+kUlsb9Lb883COGDYhnv # BI9cTOdshAzPrO1DeHKL+QLwGxgspmIly333AwDecvStdGdWY8Ya5ZXPJlRJsj8f # ZrwVda0krKQxUW94CTRb03XAr+Lq7Uzqr3xLnbaStC9ZJ4utu5yDRhTMGy6zB1YE # qmf1WMH4A4HmFEloPhW26YnvjxR54z7A4cTao+oS6ZUzJkQttq5w3uAh6K8hGTGu # 1SMSKa+7J2+r6VStDJ1u6bXFgkTwh+svnBSXSL05gOlQlc4P9B/+ZUHdRTUKuyAU # aS3go0BZJXKFyxJVGcaZc9ylN3VlUc4O # SIG # End signature block |