Framework/Core/SVT/Services/LogicApps.ps1
Set-StrictMode -Version Latest class LogicAppControl { [string] $Name = "" [string] $Automated = "" [string] $MethodName = "" [string] $Remarks = "" } class LogicAppApprovedConnector { [string] $connectorName = "" [LogicAppControl[]] $ApplicableControls = @() [LogicAppControl[]] $NotApplicableControls = @() } class LogicAppNotApprovedConnector { [string] $connectorName = "" [string] $Remarks = "" } class LogicAppConnectorsMetadata { [LogicAppApprovedConnector[]] $ApprovedConnectors = @() [LogicAppNotApprovedConnector[]] $notApprovedConnectors = @() } class LogicApps: SVTBase { hidden [PSObject] $ResourceObject; hidden [LogicAppConnectorsMetadata] $LogicAppConnectorsMetadata LogicApps([string] $subscriptionId, [string] $resourceGroupName, [string] $resourceName): Base($subscriptionId, $resourceGroupName, $resourceName) { $this.GetResourceObject(); $this.LogicAppConnectorsMetadata = [LogicAppConnectorsMetadata] ($this.LoadServerConfigFile("LogicApps.Connectors.json")); } LogicApps([string] $subscriptionId, [SVTResource] $svtResource): Base($subscriptionId, $svtResource) { $this.GetResourceObject(); $this.LogicAppConnectorsMetadata = [LogicAppConnectorsMetadata] ($this.LoadServerConfigFile("LogicApps.Connectors.json")); } hidden [PSObject] GetResourceObject() { if (-not $this.ResourceObject) { $this.ResourceObject = Get-AzureRmResource -Name $this.ResourceContext.ResourceName ` -ResourceGroupName $this.ResourceContext.ResourceGroupName -ResourceType $this.ResourceContext.ResourceType if(-not $this.ResourceObject) { throw ("Resource '{0}' not found under Resource Group '{1}'" -f ($this.ResourceContext.ResourceName), ($this.ResourceContext.ResourceGroupName)) } } return $this.ResourceObject; } hidden [ControlResult[]] CheckConnectorsAADAuth([ControlResult] $controlResult) { [ControlResult[]] $controlResultList = @() [PSObject[]] $Connectors = @() if(Get-Member -InputObject $this.ResourceObject.Properties.parameters -Name '$connections' -MemberType Properties) { $apiConnections = $this.ResourceObject.Properties.parameters.'$connections'.value if($null -ne $apiConnections) { $apiConnections | Get-Member -MemberType *Property | ForEach-Object{ try { $apiConId = ($apiConnections.($_.name) | Select-Object connectionId).connectionId $apiConObj = Get-AzureRmResource -ResourceId $apiConId $apiName=$apiConObj.Properties.Api.Name $Connector = New-Object PSObject Add-Member -InputObject $Connector -MemberType NoteProperty -Name ConnectorName -Value $apiName Add-Member -InputObject $Connector -MemberType NoteProperty -Name ConnectorType -Value $apiName Add-Member -InputObject $Connector -MemberType NoteProperty -Name ConnectorObj -Value $null $Connectors+=$Connector } catch { #Consuming the exception intentionally to prevent adding deleted connections } } } } $Definition=$this.ResourceObject.Properties.definition if($null -ne $Definition.Actions -and -not[string]::IsNullOrEmpty($this.ResourceObject.Properties.definition.actions)) { $Definition.Actions | Get-Member -MemberType *Property | ForEach-Object{ $Name=$_.name if($Definition.Actions.$Name.type -ne 'ApiConnection') { $Connector = New-Object PSObject Add-Member -InputObject $Connector -MemberType NoteProperty -Name ConnectorName -Value $Name Add-Member -InputObject $Connector -MemberType NoteProperty -Name ConnectorType -Value $Definition.Actions.$Name.type Add-Member -InputObject $Connector -MemberType NoteProperty -Name ConnectorObj -Value $Definition.Actions.$Name $Connectors+=$Connector } } } if($null -ne $Definition.triggers -and -not[string]::IsNullOrEmpty($this.ResourceObject.Properties.definition.triggers)) { $Definition.Triggers | Get-Member -MemberType *Property | ForEach-Object{ $Name=$_.name if($Definition.Triggers.$Name.type -ne 'ApiConnection') { $Connector = New-Object PSObject Add-Member -InputObject $Connector -MemberType NoteProperty -Name ConnectorName -Value $Name Add-Member -InputObject $Connector -MemberType NoteProperty -Name ConnectorType -Value $Definition.Triggers.$Name.type Add-Member -InputObject $Connector -MemberType NoteProperty -Name ConnectorObj -Value $Definition.Triggers.$Name $Connectors+=$Connector } } } if($Connectors.Count -gt 0) { $Result = $this.GetConnectorsStatus($Connectors , "AAD") if($null -ne $Result) { $controlResultList += $Result } } else { $controlResult.AddMessage([VerificationResult]::Passed,"Logic app workflow is empty. No connectors found.") $controlResultList += $controlResult } return $controlResultList } hidden [ControlResult[]] CheckConnectorsEncryptionInTransit([ControlResult] $controlResult) { [ControlResult[]] $controlResultList = @() [PSObject[]] $Connectors = @() if(Get-Member -InputObject $this.ResourceObject.Properties.parameters -Name '$connections' -MemberType Properties) { $apiConnections = $this.ResourceObject.Properties.parameters.'$connections'.value if($null -ne $apiConnections) { $apiConnections | Get-Member -MemberType *Property | ForEach-Object{ try { $apiConId = ($apiConnections.($_.name) | Select-Object connectionId).connectionId $apiConObj = Get-AzureRmResource -ResourceId $apiConId $apiName=$apiConObj.Properties.Api.Name $Connector = New-Object PSObject Add-Member -InputObject $Connector -MemberType NoteProperty -Name ConnectorName -Value $apiName Add-Member -InputObject $Connector -MemberType NoteProperty -Name ConnectorType -Value $apiName Add-Member -InputObject $Connector -MemberType NoteProperty -Name ConnectorObj -Value $null $Connectors+=$Connector } catch { #Consuming the exception intentionally to prevent adding deleted connections } } } } $Definition=$this.ResourceObject.Properties.definition if($null -ne $Definition.Actions -and -not[string]::IsNullOrEmpty($this.ResourceObject.Properties.definition.actions)) { $Definition.Actions | Get-Member -MemberType *Property | ForEach-Object{ $Name=$_.name if($Definition.Actions.$Name.type -ne 'ApiConnection') { $Connector = New-Object PSObject Add-Member -InputObject $Connector -MemberType NoteProperty -Name ConnectorName -Value $Name Add-Member -InputObject $Connector -MemberType NoteProperty -Name ConnectorType -Value $Definition.Actions.$Name.type Add-Member -InputObject $Connector -MemberType NoteProperty -Name ConnectorObj -Value $Definition.Actions.$Name $Connectors+=$Connector } } } if($null -ne $Definition.triggers -and -not[string]::IsNullOrEmpty($this.ResourceObject.Properties.definition.triggers)) { $Definition.Triggers | Get-Member -MemberType *Property | ForEach-Object{ $Name=$_.name if($Definition.Triggers.$Name.type -ne 'ApiConnection') { $Connector = New-Object PSObject Add-Member -InputObject $Connector -MemberType NoteProperty -Name ConnectorName -Value $Name Add-Member -InputObject $Connector -MemberType NoteProperty -Name ConnectorType -Value $Definition.Triggers.$Name.type Add-Member -InputObject $Connector -MemberType NoteProperty -Name ConnectorObj -Value $Definition.Triggers.$Name $Connectors+=$Connector } } } if($Connectors.Count -gt 0) { $Result = $this.GetConnectorsStatus($Connectors , "EncryptionTransit") if($null -ne $Result) { $controlResultList += $Result } } return $controlResultList } hidden [ControlResult] CheckConnectorsSecretsHandling([ControlResult] $controlResult) { $complianceStatus = [VerificationResult]::Manual $userMsg = [string]::Empty $IsFailed = $false $Definition=$this.ResourceObject.Properties.definition if($null -ne $Definition.Actions -and -not[string]::IsNullOrEmpty($this.ResourceObject.Properties.definition.actions)) { $Definition.Actions | Get-Member -MemberType *Property | ForEach-Object{ $connectorName=$_.name $Connector = $Definition.Actions.$connectorName if($Connector.type -eq 'http') { $complianceStatus = $this.CheckSecretsHandlingForHttp($Connector) if($complianceStatus -eq [VerificationResult]::Failed) { $IsFailed = $true $userMsg += "Connector - " + $connectorName + "`r`nType - " + $Connector.type ` +"`r`nSecret(s) are given as plain text in Code View, must use 'SecureString' parameter" } } } } if($null -ne $Definition.triggers -and -not[string]::IsNullOrEmpty($this.ResourceObject.Properties.definition.triggers)) { $Definition.Triggers | Get-Member -MemberType *Property | ForEach-Object{ $connectorName=$_.name $Connector = $Definition.Triggers.$connectorName if($Connector.type -eq 'http') { $complianceStatus = $this.CheckSecretsHandlingForHttp($Connector) if($complianceStatus -eq [VerificationResult]::Failed) { $IsFailed = $true $userMsg += "Connector - " + $connectorName + "`r`nType - " + $Connector.type ` +"`r`nSecret(s) are given as plain text in Code View, must use 'SecureString' parameter" } } } } #No HTTP connector is present. Display generic message for users. if($userMsg -eq [string]::Empty) { $userMsg = "Please verify manually that Logic App code view doesn't contain any secrets/credentials in plain text" } if($IsFailed) { $complianceStatus = [VerificationResult]::Failed } $controlResult.AddMessage($complianceStatus , $userMsg) return $controlResult } hidden [ControlResult] CheckLogicAppsInSameRG([ControlResult] $controlResult) { $OtherAppsinSameRG= Get-AzureRmResource -ResourceGroupName $this.ResourceContext.ResourceGroupName -ResourceType $this.ResourceContext.ResourceType | Where-Object{$_.ResourceId -ne $this.ResourceObject.ResourceId } if($null -ne $OtherAppsinSameRG) { $controlResult.AddMessage("Below are the Logic Apps present in the same resource group as " + $this.ResourceContext.ResourceName + " - Logic App") $controlResult.AddMessage([VerificationResult]::Verify, "Validate that these Logic Apps trust each other",$OtherAppsinSameRG) $controlResult.SetStateData("Logic Apps present in same resource group", $OtherAppsinSameRG); } else { $controlResult.AddMessage([VerificationResult]::Passed, "No other logic apps found in resource group ["+ $this.ResourceContext.ResourceGroupName +"]") } return $controlResult } hidden [ControlResult] CheckTriggersAccessControl([ControlResult] $controlResult) { $IsFailed = $False $IsAccessConfigSet = ($null -ne (Get-Member -InputObject $this.Resourceobject.Properties -Name accessControl -MemberType Properties)) if($IsAccessConfigSet) { $IsTriggerRestricted = ($null -ne (Get-Member -InputObject $this.Resourceobject.Properties.accessControl -Name triggers -MemberType Properties)) #Check trigger access control if($IsTriggerRestricted) { if($this.ResourceObject.Properties.accessControl.triggers.allowedCallerIpAddresses.Count -eq 0) { #verify scenario $controlResult.AddMessage("Access control for triggers is set to `"Only other Logic Apps`"") } else { $triggerIPRange = $this.ResourceObject.Properties.accessControl.triggers.allowedCallerIpAddresses.addressRange if($triggerIPRange -contains $this.ControlSettings.UniversalIPRange) { #fail if universal IP range found $IsFailed = $True $controlResult.AddMessage("IP range $($this.ControlSettings.UniversalIPRange) must be removed from triggers IP ranges") } else { $controlResult.AddMessage("Please verify below:") } $controlResult.AddMessage("IP ranges for triggers :",$triggerIPRange) $controlResult.SetStateData("IP ranges for triggers", $triggerIPRange); } } else { #fail if no trigger access control found $IsFailed = $True $controlResult.AddMessage("Access control for triggers is not found") } } else { $IsFailed = $True $controlResult.AddMessage("Access control for triggers is not found") } if($IsFailed -eq $True) { $controlResult.VerificationResult = [VerificationResult]::Failed } else { $controlResult.VerificationResult = [VerificationResult]::Verify } return $controlResult } hidden [ControlResult] CheckContentsAccessControl([ControlResult] $controlResult) { $IsFailed = $False $IsAccessConfigSet = ($null -ne (Get-Member -InputObject $this.Resourceobject.Properties -Name accessControl -MemberType Properties)) if($IsAccessConfigSet) { $IsContentRestricted = ($null -ne (Get-Member -InputObject $this.Resourceobject.Properties.accessControl -Name contents -MemberType Properties)) #check content access control if($IsContentRestricted) { $contentIPRange = $this.ResourceObject.Properties.accessControl.contents.allowedCallerIpAddresses.addressRange if($contentIPRange -contains $this.ControlSettings.UniversalIPRange) { #fail if universal IP range assigned $IsFailed = $True $controlResult.AddMessage("IP range $($this.ControlSettings.UniversalIPRange) must be removed from contents IP ranges") } else { $controlResult.AddMessage("Please verify below:") } $controlResult.AddMessage("IP ranges for contents :", $contentIPRange) $controlResult.SetStateData("IP ranges for contents", $contentIPRange); } else { #fail if content access control not found $IsFailed = $True $controlResult.AddMessage("Access control for contents is not found") } } else { $IsFailed = $True $controlResult.AddMessage("Access control for contents is not found") } if($IsFailed -eq $True) { $controlResult.VerificationResult = [VerificationResult]::Failed } else { $controlResult.VerificationResult = [VerificationResult]::Verify } return $controlResult } #internal functions hidden [boolean] CheckSecretParameter([string] $secretString,[PSObject[]] $parametersList) { if(!$secretString.Trim().StartsWith("@parameters(")) { return $false } else { $temp=($secretString.replace(' ','')).split('(')[1] $parametervalue=$temp.split(')')[0].Trim("'") $type=$parametersList.$parametervalue.type if($type -ne "securestring") {return $false} else {return $true} } } hidden [VerificationResult] CheckSecretsHandlingForHttp([PSObject] $Connector) { $complianceStatus = [VerificationResult]::Manual if(Get-Member -inputobject $Connector.inputs -name "authentication" -Membertype Properties) { $authentication = $Connector.inputs.authentication switch($authentication.type) { "ActiveDirectoryOAuth" { $IsValidSecret=$this.CheckSecretParameter($authentication.secret,$this.ResourceObject.Properties.definition.parameters) if($IsValidSecret -ne $true) { $complianceStatus = [VerificationResult]::Failed } } "ClientCertificate" { $IsValidPw=$this.CheckSecretParameter($authentication.Password,$this.ResourceObject.Properties.definition.parameters) if($IsValidPw -ne $true) { $complianceStatus = [VerificationResult]::Failed } } "default" { $complianceStatus = [VerificationResult]::Manual } } } return $complianceStatus } hidden [ControlResult] CheckAadAuthForHttp([string] $remarks , [ControlResult] $childControlResult , [PSObject]$Connector) { $isPassed = $false if(Get-Member -inputobject $Connector.ConnectorObj.inputs -name "authentication" -Membertype Properties) { if($Connector.ConnectorObj.inputs.authentication.type -eq "ActiveDirectoryOAuth") { $isPassed = $true } } if($isPassed) { $childControlResult.AddMessage([VerificationResult]::Passed,"AAD Authentication is used in connector - "+ $Connector.ConnectorName) } else { $childControlResult.AddMessage([VerificationResult]::Failed, "AAD Authentication is not used in connector - "+ $Connector.ConnectorName) } return $childControlResult } hidden [ControlResult] CheckEncryptionTransitForHttp([string] $remarks , [ControlResult] $childControlResult , [PSObject]$Connector) { $isPassed = $true $uriString = $Connector.ConnectorObj.inputs.uri if(([system.Uri]$uriString).Scheme -ne 'https') { $isPassed = $false } if($isPassed) { $childControlResult.AddMessage([VerificationResult]::Passed,"Connector name : " + $Connector.ConnectorName + "`r`nConnector URI : "+ $uriString) } else { $childControlResult.AddMessage([VerificationResult]::Failed, ` "Must use HTTPS URI for below connector`r`n" ` + "Connector name : " + $Connector.ConnectorName + "`r`nConnector URI : "+$uriString) } return $childControlResult } hidden [ControlResult] CheckEncryptionTransitForWebhook([string] $remarks , [ControlResult] $childControlResult , [PSObject]$Connector) { $isPassed = $true $subURI = $Connector.connectorObj.inputs.subscribe.uri $unSubURI = $connector.connectorObj.inputs.unsubscribe.uri if(([system.Uri]$subURI).Scheme -ne 'https' -or ([system.Uri]$unSubURI).Scheme -ne 'https') { $isPassed = $false } if($isPassed) { $childControlResult.AddMessage([VerificationResult]::Passed, "Connector name : " + $Connector.ConnectorName ` + "`r`nWebhook subscribe URI : "+ $subURI` + "`r`nWebhook unsubscribe URI : "+$unSubURI) } else { $childControlResult.AddMessage([VerificationResult]::Failed, "Must use HTTPS URI(s) for below connector`r`n" ` + "Connector name : " + $Connector.ConnectorName ` + "`r`nWebhook subscribe URI : "+ $subURI` + "`r`nWebhook unsubscribe URI : "+$unSubURI) } return $childControlResult } hidden [ControlResult[]] GetConnectorsStatus([PSObject] $Connectors,[string] $controlName) { $controlResultList = @() $Connectors | ForEach-Object{ $Connector = $_ $connectorName=$Connector.ConnectorName $ConnectorObj = $Connector.ConnectorObj $connectorType=$Connector.ConnectorType [ControlResult] $childControlResult = [ControlResult]@{ChildResourceName = $connectorName + " ("+$connectorType+" connector)";} if($connectorName -eq "manual") { $connectorName = $connectorType } #check if this connector belongs to not approved list $notApprovedConnector = $this.LogicAppConnectorsMetadata.NotApprovedConnectors | Where-Object {$_.connectorName -eq $connectorType} if($notApprovedConnector) { $childControlResult.AddMessage([VerificationResult]::Failed, $notApprovedConnector.Remarks) } else { #Check if it belongs to approved connectors $approvedConnector = $this.LogicAppConnectorsMetadata.ApprovedConnectors | Where-Object {$_.connectorName -eq $connectorType} if(($approvedConnector|Measure-Object).Count -gt 0) { #check if control is applicable on this connector $applicableControl = $approvedConnector.ApplicableControls | Where-Object {$_.Name -eq $controlName} $notApplicableControl = $approvedConnector.NotApplicableControls | Where-Object {$_.Name -eq $controlName} if(($applicableControl|Measure-Object).Count -gt 0) { #Get method name $methodName = $applicableControl.MethodName $childControlResult = $this.$methodName($applicableControl.Remarks , $childControlResult, $Connector) } else { $methodName = $notApplicableControl.MethodName if($notApplicableControl.Remarks -eq [string]::Empty) { $notApplicableControl.Remarks = "This control is not applicable on connector type - " + $connectorType } $childControlResult = $this.$methodName($notApplicableControl.Remarks , $childControlResult, $Connector) } } else { $childControlResult.AddMessage([VerificationResult]::Manual, $connectorType+" connector is not evaluated yet") } } $controlResultList += $childControlResult } return $controlResultList } hidden [ControlResult] DefaultPassed([string] $remarks,[ControlResult] $childControlResult, [PSObject] $Connector) { $childControlResult.AddMessage([VerificationResult]::Passed, $remarks) return $childControlResult } hidden [ControlResult] DefaultManual([string] $remarks,[ControlResult] $childControlResult, [PSObject] $Connector) { $childControlResult.AddMessage([VerificationResult]::Manual, $remarks) return $childControlResult } } # SIG # Begin signature block # MIIkAgYJKoZIhvcNAQcCoIIj8zCCI+8CAQExDzANBglghkgBZQMEAgEFADB5Bgor # BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG # KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCC9vR/tsrl2CE5o # 8jLvQLjUypgz8U29LRoK1r9QBhMhWaCCDZMwggYRMIID+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 # xTCCFcECAQEwgZUwfjELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24x # EDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlv # bjEoMCYGA1UEAxMfTWljcm9zb2Z0IENvZGUgU2lnbmluZyBQQ0EgMjAxMQITMwAA # AI6HkaRXGl/KPgAAAAAAjjANBglghkgBZQMEAgEFAKCBsDAZBgkqhkiG9w0BCQMx # DAYKKwYBBAGCNwIBBDAcBgorBgEEAYI3AgELMQ4wDAYKKwYBBAGCNwIBFTAvBgkq # hkiG9w0BCQQxIgQgTJgGfYIlbHw8Adqxfm87EVVuqpVxhqX2fTjaeA70NjIwRAYK # KwYBBAGCNwIBDDE2MDSgEoAQAEEAegBTAEQASwAyADUAMqEegBxodHRwczovL2Fr # YS5tcy9henNka29zc2RvY3MgMA0GCSqGSIb3DQEBAQUABIIBAHNULVTCjDVnA9qN # 20TCs0KejE4VddJdcN9nfQhmvjIa6JTJO+o+oY592LOpFakwYV5Pfdv2ukaZjiiS # JAp8Jq7Dfm6ON3f6KrTDhh3w09rqs/OIDiSwiyOMPPaa7WJEpjb4DWlDGKDOdM7F # aEK+HVIXNRzss1I1YYpwEKxWASTyYhaG8drCWuRfamHrd0MZO35JnAZchPwwzt3K # 8z4i6Lrv6L/X+E/BjGnVxbaxMH9OUzt/IMPlltETj9PV7pd1WFXA9RHrvwGAUKD1 # LMDP3ltw7/cX2ap1aoyT+1yYGigKwc7A4147KGGXu5WAvNw4wacw/8bbH+QFZ6Ws # 1vcbUGehghNNMIITSQYKKwYBBAGCNwMDATGCEzkwghM1BgkqhkiG9w0BBwKgghMm # MIITIgIBAzEPMA0GCWCGSAFlAwQCAQUAMIIBPQYLKoZIhvcNAQkQAQSgggEsBIIB # KDCCASQCAQEGCisGAQQBhFkKAwEwMTANBglghkgBZQMEAgEFAAQgb6U62TEhzTao # 2EDWmbnw+b+jHxmBsIAbsJP3SuA4ssoCBlmtv368dBgTMjAxNzA5MDUwOTM3MTMu # OTk4WjAHAgEBgAIB9KCBuaSBtjCBszELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldh # c2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBD # b3Jwb3JhdGlvbjENMAsGA1UECxMETU9QUjEnMCUGA1UECxMebkNpcGhlciBEU0Ug # RVNOOjk4RkQtQzYxRS1FNjQxMSUwIwYDVQQDExxNaWNyb3NvZnQgVGltZS1TdGFt # cCBTZXJ2aWNloIIO0DCCBnEwggRZoAMCAQICCmEJgSoAAAAAAAIwDQYJKoZIhvcN # AQELBQAwgYgxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYD # VQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xMjAw # BgNVBAMTKU1pY3Jvc29mdCBSb290IENlcnRpZmljYXRlIEF1dGhvcml0eSAyMDEw # MB4XDTEwMDcwMTIxMzY1NVoXDTI1MDcwMTIxNDY1NVowfDELMAkGA1UEBhMCVVMx # EzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoT # FU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUt # U3RhbXAgUENBIDIwMTAwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCp # HQ28dxGKOiDs/BOX9fp/aZRrdFQQ1aUKAIKF++18aEssX8XD5WHCdrc+Zitb8BVT # JwQxH0EbGpUdzgkTjnxhMFmxMEQP8WCIhFRDDNdNuDgIs0Ldk6zWczBXJoKjRQ3Q # 6vVHgc2/JGAyWGBG8lhHhjKEHnRhZ5FfgVSxz5NMksHEpl3RYRNuKMYa+YaAu99h # /EbBJx0kZxJyGiGKr0tkiVBisV39dx898Fd1rL2KQk1AUdEPnAY+Z3/1ZsADlkR+ # 79BL/W7lmsqxqPJ6Kgox8NpOBpG2iAg16HgcsOmZzTznL0S6p/TcZL2kAcEgCZN4 # zfy8wMlEXV4WnAEFTyJNAgMBAAGjggHmMIIB4jAQBgkrBgEEAYI3FQEEAwIBADAd # BgNVHQ4EFgQU1WM6XIoxkPNDe3xGG8UzaFqFbVUwGQYJKwYBBAGCNxQCBAweCgBT # AHUAYgBDAEEwCwYDVR0PBAQDAgGGMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgw # FoAU1fZWy4/oolxiaNE9lJBb186aGMQwVgYDVR0fBE8wTTBLoEmgR4ZFaHR0cDov # L2NybC5taWNyb3NvZnQuY29tL3BraS9jcmwvcHJvZHVjdHMvTWljUm9vQ2VyQXV0 # XzIwMTAtMDYtMjMuY3JsMFoGCCsGAQUFBwEBBE4wTDBKBggrBgEFBQcwAoY+aHR0 # cDovL3d3dy5taWNyb3NvZnQuY29tL3BraS9jZXJ0cy9NaWNSb29DZXJBdXRfMjAx # MC0wNi0yMy5jcnQwgaAGA1UdIAEB/wSBlTCBkjCBjwYJKwYBBAGCNy4DMIGBMD0G # CCsGAQUFBwIBFjFodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vUEtJL2RvY3MvQ1BT # L2RlZmF1bHQuaHRtMEAGCCsGAQUFBwICMDQeMiAdAEwAZQBnAGEAbABfAFAAbwBs # AGkAYwB5AF8AUwB0AGEAdABlAG0AZQBuAHQALiAdMA0GCSqGSIb3DQEBCwUAA4IC # AQAH5ohRDeLG4Jg/gXEDPZ2joSFvs+umzPUxvs8F4qn++ldtGTCzwsVmyWrf9efw # eL3HqJ4l4/m87WtUVwgrUYJEEvu5U4zM9GASinbMQEBBm9xcF/9c+V4XNZgkVkt0 # 70IQyK+/f8Z/8jd9Wj8c8pl5SpFSAK84Dxf1L3mBZdmptWvkx872ynoAb0swRCQi # PM/tA6WWj1kpvLb9BOFwnzJKJ/1Vry/+tuWOM7tiX5rbV0Dp8c6ZZpCM/2pif93F # SguRJuI57BlKcWOdeyFtw5yjojz6f32WapB4pm3S4Zz5Hfw42JT0xqUKloakvZ4a # rgRCg7i1gJsiOCC1JeVk7Pf0v35jWSUPei45V3aicaoGig+JFrphpxHLmtgOR5qA # xdDNp9DvfYPw4TtxCd9ddJgiCGHasFAeb73x4QDf5zEHpJM692VHeOj4qEir995y # fmFrb3epgcunCaw5u+zGy9iCtHLNHfS4hQEegPsbiSpUObJb2sgNVZl6h3M7COaY # LeqN4DMuEin1wC9UJyH3yKxO2ii4sanblrKnQqLJzxlBTeCG+SqaoxFmMNO7dDJL # 32N79ZmKLxvHIa9Zta7cRDyXUHHXodLFVeNp3lfB0d4wwP3M5k37Db9dT+mdHhk4 # L7zPWAUu7w2gUDXa7wknHNWzfjUeCLraNtvTX4/edIhJEjCCBNowggPCoAMCAQIC # EzMAAACdIJxWd1XUKJoAAAAAAJ0wDQYJKoZIhvcNAQELBQAwfDELMAkGA1UEBhMC # VVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNV # BAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRp # bWUtU3RhbXAgUENBIDIwMTAwHhcNMTYwOTA3MTc1NjQxWhcNMTgwOTA3MTc1NjQx # WjCBszELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcT # B1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjENMAsGA1UE # CxMETU9QUjEnMCUGA1UECxMebkNpcGhlciBEU0UgRVNOOjk4RkQtQzYxRS1FNjQx # MSUwIwYDVQQDExxNaWNyb3NvZnQgVGltZS1TdGFtcCBTZXJ2aWNlMIIBIjANBgkq # hkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0kSYnBFaKhouqp9TXW1dvLZZdpHAJlsD # 5shsX6Mq60wARnQ4FL8qeF2wI0zsbmBI7EnkW3WmcP3z1K5Vbo69BB9nPRn9MXKC # lKFzsS688BzU2+8huMaptMbCRgcumcw+IQvDLkjfDGp1xTWO11mcqztIfp6y4PxU # lt4TRzlC0G7WS/2/DKTwC+X66MiIi+6c+3XhxEvoyw5kzlfeYKh6Ss5lHLhlliNi # O38FT1lm3ekN1fh8vsBM3nsKlhvMVTkEbwYIQTi79RnftXoEdwUc4uyMx/Gxml5H # bsyyHqPalniB7vAHmIBRvroKFB5+njpZJKFXcwz+QUROlsJUUQ+pxQIDAQABo4IB # GzCCARcwHQYDVR0OBBYEFLyGCMpbalrK5L3My4K0FUjqh+WhMB8GA1UdIwQYMBaA # FNVjOlyKMZDzQ3t8RhvFM2hahW1VMFYGA1UdHwRPME0wS6BJoEeGRWh0dHA6Ly9j # cmwubWljcm9zb2Z0LmNvbS9wa2kvY3JsL3Byb2R1Y3RzL01pY1RpbVN0YVBDQV8y # MDEwLTA3LTAxLmNybDBaBggrBgEFBQcBAQROMEwwSgYIKwYBBQUHMAKGPmh0dHA6 # Ly93d3cubWljcm9zb2Z0LmNvbS9wa2kvY2VydHMvTWljVGltU3RhUENBXzIwMTAt # MDctMDEuY3J0MAwGA1UdEwEB/wQCMAAwEwYDVR0lBAwwCgYIKwYBBQUHAwgwDQYJ # KoZIhvcNAQELBQADggEBAH/eJCG9We+01otxylmRvi6oRoK7j99kHX3mKgu8KGdL # /vl3v7X0TqT96EoPPmcis1aJbZcIWuwjFPV5KhNXjJIXnQYh6vOo6hs73NuEmkv3 # chX2n48nqP+l4tYgiZVNQKkVYF65lwHXMAv/QmprVtnsWlw2A4DMFi1qwbkzZE/b # Xmt/2G/AroGlOO06zl1yGoxMFctfk4yy3aoALeP9ZCipqb4QHf4V3CePH46kA+qO # N9sEJVMf4TJ69zsikMzcKg3BXoYJ1T5W76sloHrLMkBY9r0JW7bJ/3tHeXSGpYad # 2CINV17hqA3GJk4C9v069gGs95e8uZEOYdud0++mNmmhggN5MIICYQIBATCB46GB # uaSBtjCBszELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNV # BAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjENMAsG # A1UECxMETU9QUjEnMCUGA1UECxMebkNpcGhlciBEU0UgRVNOOjk4RkQtQzYxRS1F # NjQxMSUwIwYDVQQDExxNaWNyb3NvZnQgVGltZS1TdGFtcCBTZXJ2aWNloiUKAQEw # CQYFKw4DAhoFAAMVABgNrLOMaDCz+HQZsnjOgCs1Lwj6oIHCMIG/pIG8MIG5MQsw # CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u # ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMQ0wCwYDVQQLEwRNT1BS # MScwJQYDVQQLEx5uQ2lwaGVyIE5UUyBFU046NERFOS0wQzVFLTNFMDkxKzApBgNV # BAMTIk1pY3Jvc29mdCBUaW1lIFNvdXJjZSBNYXN0ZXIgQ2xvY2swDQYJKoZIhvcN # AQEFBQACBQDdWK08MCIYDzIwMTcwOTA1MDQ1NzMyWhgPMjAxNzA5MDYwNDU3MzJa # MHcwPQYKKwYBBAGEWQoEATEvMC0wCgIFAN1YrTwCAQAwCgIBAAICCx8CAf8wBwIB # AAICGfkwCgIFAN1Z/rwCAQAwNgYKKwYBBAGEWQoEAjEoMCYwDAYKKwYBBAGEWQoD # AaAKMAgCAQACAxbjYKEKMAgCAQACAwehIDANBgkqhkiG9w0BAQUFAAOCAQEAXx7C # hhJLdt3IxXL5XlEtbvtaM3D5dLryCCrpdVxdBa8tiJ9KNN+Obl1U11Ptn8IEf2iv # lYllx4OYSyVJIMJaDDULZ8L6Ha3jFrFY1t14JXF/fXn7uCfB408U5MUTd6kt1UzZ # Vu06E9UBmTIdP92fk9ynKvTcTPdjHm9FMwvSaM17+aYxiWJi5Tx0j6m2JsWF56Jn # pM8kmeurcKe5nv02ZXHKdApMQ3LUkTuXLb3KaqguPGdfcrRrbbOCZgCzZgBtOwo3 # 1XKC9v2PTMJHe9ovRm29n8otiFWSz5m7XJbliLnRUrEMoRDv8hhYfwjt/ySci+xc # kmf+ayYRDN2K/LZ6CzGCAvUwggLxAgEBMIGTMHwxCzAJBgNVBAYTAlVTMRMwEQYD # VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy # b3NvZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1w # IFBDQSAyMDEwAhMzAAAAnSCcVndV1CiaAAAAAACdMA0GCWCGSAFlAwQCAQUAoIIB # MjAaBgkqhkiG9w0BCQMxDQYLKoZIhvcNAQkQAQQwLwYJKoZIhvcNAQkEMSIEIJ1L # khQ8EJL5vGR8jzfrGNPA3xRh3dh0hcJxOTeWRzEVMIHiBgsqhkiG9w0BCRACDDGB # 0jCBzzCBzDCBsQQUGA2ss4xoMLP4dBmyeM6AKzUvCPowgZgwgYCkfjB8MQswCQYD # VQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEe # MBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQDEx1NaWNyb3Nv # ZnQgVGltZS1TdGFtcCBQQ0EgMjAxMAITMwAAAJ0gnFZ3VdQomgAAAAAAnTAWBBSt # 1PgXjDVCOF2A6cFXHHztiVNYzTANBgkqhkiG9w0BAQsFAASCAQDGY2mmJK5jik9s # fdsdezBgy36wge8SwqoVTdoVJ11vLhrvAwSCHTZvHCQSafOHTXSa3yXMUx94LlyW # AYL0f8HhnAkn0liYKnwzBj/0e95pYxdwt1RUqYR/cKorxqiTS6+MEBQII2NZElYQ # dm8Zw/D3/vt3ZYCxLanw4fXakbQ4dkVL2vOVbFt92gODV2llib6uMh2EmFNInPQ+ # B8QHHd3LWPh0n3FgphFKpqyNTvDEt9sj4tg7gu/MhaW6kiXBiS09nu4D7u/RHjYo # mqan8kBGjBTf2ISjQiQLi4jbGr4kyiWa0CWDvQMZNmyofLBN4eGvxkJyPEmk5hS4 # jDopGxHz # SIG # End signature block |