Framework/Core/SVT/Services/ServiceFabric.ps1
Set-StrictMode -Version Latest class ServiceFabric : SVTBase { hidden [PSObject] $ResourceObject; hidden [string] $ClusterTagValue; hidden [PSObject] $ApplicationList; hidden [string] $DefaultTagName = "clusterName" hidden [string] $CertStoreLocation = "CurrentUser" hidden [string] $CertStoreName = "My" ServiceFabric([string] $subscriptionId, [SVTResource] $svtResource): Base($subscriptionId, $svtResource) { $this.GetResourceObject(); } hidden [PSObject] GetResourceObject() { if (-not $this.ResourceObject) { $this.ResourceObject = Get-AzureRmResource -ResourceGroupName $this.ResourceContext.ResourceGroupName -ResourceType $this.ResourceContext.ResourceType -ResourceName $this.ResourceContext.ResourceName -ApiVersion 2016-03-01 $this.ResourceObject.Tags.GetEnumerator() | Where-Object { $_.Name -eq $this.DefaultTagName } | ForEach-Object {$this.ClusterTagValue = $_.Value } ## Commented below two lines of code. This will be covered once we Service Fabric module gets available as part of AzureRM modules set. #$this.CheckClusterAccess(); #$this.ApplicationList = Get-ServiceFabricApplication 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] CheckSecurityMode([ControlResult] $controlResult) { $isCertificateEnabled = [Helpers]::CheckMember($this.ResourceObject.Properties,"certificate" ) #Validate if primary certificate is enabled on cluster. Presence of certificate property value indicates, security mode is turned on. if($isCertificateEnabled) { $controlResult.AddMessage([VerificationResult]::Passed,"Service Fabric cluster is secured with certificate", $this.ResourceObject.Properties.certificate); } else { $controlResult.AddMessage([VerificationResult]::Failed,"Service Fabric cluster is not secured with certificate"); } return $controlResult; } hidden [ControlResult] CheckClusterCertificateSSL([ControlResult] $controlResult) { $managementEndpointUri = $this.ResourceObject.Properties.managementEndpoint $managementEndpointUriScheme = ([System.Uri]$managementEndpointUri).Scheme #Validate if cluster management endpoint url is SSL enabled if($managementEndpointUriScheme -eq "https") { #Hit web request to management endpoint uri and validate certificate trust level $request = [System.Net.HttpWebRequest]::Create($managementEndpointUri) try { $request.GetResponse().Dispose() $controlResult.AddMessage([VerificationResult]::Passed,"Service Fabric cluster is protected with CA signed certificate"); } catch [System.Net.WebException] { #Trust failure indicates self signed certificate or domain mismatch certificate present on endpoint if ($_.Exception.Status -eq [System.Net.WebExceptionStatus]::TrustFailure) { $controlResult.AddMessage([VerificationResult]::Verify,"Validate if self signed certificate is not used for cluster management endpoint protection",$this.ResourceObject.Properties.managementEndpoint); $controlResult.SetStateData("Management endpoint", $this.ResourceObject.Properties.managementEndpoint); } else { throw } } } else { $controlResult.AddMessage([VerificationResult]::Failed,"Service Fabric cluster is not protected by SSL") } return $controlResult; } hidden [ControlResult] CheckAADClientAuthentication([ControlResult] $controlResult) { $isAADEnabled = [Helpers]::CheckMember($this.ResourceObject.Properties,"azureActiveDirectory") #Presence of 'AzureActiveDirectory' indicates, AAD authentication is enabled for client authentication if($isAADEnabled) { $controlResult.AddMessage([VerificationResult]::Passed,"AAD is enabled for client authentication",$this.ResourceObject.Properties.azureActiveDirectory ) } else { $controlResult.AddMessage([VerificationResult]::Failed,"AAD is not enabled for client authentication") } return $controlResult } hidden [ControlResult] CheckClusterProtectionLevel([ControlResult] $controlResult) { $fabricSecuritySettings = $this.ResourceObject.Properties.fabricSettings | where {$_.Name -eq "Security"} #Absence of security settings indicates, secure mode is not enabled on cluster. if($null -ne $fabricSecuritySettings) { $clusterProtectionLevel = $fabricSecuritySettings.parameters | Where-Object { $_.name -eq "ClusterProtectionLevel"} if($clusterProtectionLevel.value -eq "EncryptAndSign") { $controlResult.AddMessage([VerificationResult]::Passed,"Cluster security is ON with 'EncryptAndSign' protection level",$clusterProtectionLevel); } else { $controlResult.AddMessage([VerificationResult]::Failed,"Cluster security is not set with 'EncryptAndSign' protection level. Current protection level is :", $clusterProtectionLevel); $controlResult.SetStateData("Cluster protection level", $clusterProtectionLevel); } } else { $controlResult.AddMessage([VerificationResult]::Failed,"Cluster security is OFF"); } return $controlResult } hidden [ControlResult[]] CheckNSGConfigurations([ControlResult] $controlResult) { [ControlResult[]] $controlResultList = @() $virtualNetworkResources = $this.GetLinkedResources("Microsoft.Network/virtualNetworks") #Iterate through all cluster linked VNet resources $virtualNetworkResources |ForEach-Object{ $virtualNetwork=Get-AzureRmVirtualNetwork -ResourceGroupName $_.ResourceGroupName -Name $_.Name $subnetConfig = Get-AzureRmVirtualNetworkSubnetConfig -VirtualNetwork $virtualNetwork #Iterate through Subnet and validate if NSG is configured or not $subnetConfig | ForEach-Object{ $subnetName =$_.Name [ControlResult] $childControlResult = $this.CreateControlResult($subnetName); $isCompliant = ($_.NetworkSecurityGroup -ne $null) #If NSG is enabled on Subnet display all security rules applied if($isCompliant) { $nsgResource = Get-AzureRmResource -ResourceId $_.NetworkSecurityGroup.Id $nsgResourceDetails = Get-AzureRmNetworkSecurityGroup -ResourceGroupName $nsgResource.ResourceGroupName -Name $nsgResource.Name $childControlResult.AddMessage([VerificationResult]::Verify, "Validate NSG security rules applied on subnet '$subnetName' ", $nsgResourceDetails) $childControlResult.SetStateData("NSG security rules applied on subnet", $nsgResourceDetails); } #If NSG is not enabled on Subnet fail the TCP with Subnet details else { $childControlResult.AddMessage([VerificationResult]::Failed, "NSG is not configured on subnet '$subnetName'",$_) } $controlResultList += $childControlResult } } return $controlResultList } hidden [ControlResult[]] CheckStorageEncryption([ControlResult] $controlResult) { [ControlResult[]] $controlResultList = @() $vmssResources = $this.GetLinkedResources("Microsoft.Compute/virtualMachineScaleSets") #Iterate through cluster linked vmss resources $vmssResources | ForEach-Object{ $vmssResourceId = Get-AzureRmResource -ResourceId $_.ResourceId #Get all storage account details where vmss disk is stored $vmssResourceId.Properties.virtualMachineProfile.storageProfile.osDisk.vhdContainers | ForEach-Object{ $storageName = Convert-String -InputObject $_ -Example "https://accountname.blob.core.windows.net/vhds=accountname" $storageAccount = Get-AzureRmStorageAccount -Name $storageName -ResourceGroupName $this.ResourceContext.ResourceGroupName [ControlResult] $childControlResult = $this.CreateControlResult($storageName); #Validate if storage account storing vmss os disk/Cluster data is encrypted or not if($null -ne $storageAccount.Encryption) { $childControlResult.AddMessage([VerificationResult]::Passed, "Storage encryption is enabled for '$storageName'"); } else { $childControlResult.AddMessage([VerificationResult]::Failed, "Storage encryption is not enabled for '$storageName'"); } $controlResultList += $childControlResult } } return $controlResultList; } hidden [ControlResult[]] CheckVmssDiagnostics([ControlResult] $controlResult) { [ControlResult[]] $controlResultList = @() $vmssResources = $this.GetLinkedResources("Microsoft.Compute/virtualMachineScaleSets") #Iterate through cluster linked vmss resources $vmssResources | ForEach-Object{ $VMScaleSetName = $_.Name [ControlResult] $childControlResult = $this.CreateControlResult($VMScaleSetName); $nodeTypeResource = Get-AzureRmVmss -ResourceGroupName $_.ResourceGroupName -VMScaleSetName $VMScaleSetName $diagnosticsSettings = $nodeTypeResource.VirtualMachineProfile.ExtensionProfile.Extensions | ? { $_.Type -eq "IaaSDiagnostics" -and $_.Publisher -eq "Microsoft.Azure.Diagnostics" } #Validate if diagnostics is enabled on vmss if($null -ne $diagnosticsSettings ) { $childControlResult.AddMessage([VerificationResult]::Passed, "Diagnostics is enabled on Vmss '$VMScaleSetName'",$diagnosticsSettings); } else { $childControlResult.AddMessage([VerificationResult]::Failed, "Diagnostics is disabled on Vmss '$VMScaleSetName'"); } $controlResultList += $childControlResult } return $controlResultList } hidden [ControlResult[]] CheckStatefulServiceReplicaSetSize([ControlResult] $controlResult) { [ControlResult[]] $controlResultList = @() #Iterate through the applications present in cluster if($this.ApplicationList) { $this.ApplicationList | ForEach-Object{ $serviceFabricApplication = $_ Get-ServiceFabricService -ApplicationName $serviceFabricApplication.ApplicationName | ForEach-Object{ $serviceName = $_.ServiceName [ControlResult] $childControlResult = $this.CreateControlResult($serviceName); $serviceDescription = Get-ServiceFabricServiceDescription -ServiceName $_.ServiceName #Filter application with Stateful service type if($serviceDescription.ServiceKind -eq "Stateful") { [ControlResult] $childControlResult = $this.CreateControlResult($serviceName) #Validate minimum replica and target replica size for each service $isCompliant = !($serviceDescription.MinReplicaSetSize -lt 3 -or $serviceDescription.TargetReplicaSetSize -lt 3) if($isCompliant){ $controlStatus = [VerificationResult]::Passed } else{ $controlStatus = [VerificationResult]::Failed } $childControlResult.AddMessage([VerificationResult]::Failed, "Replica set size details for service '$serviceName'",$serviceDescription) $controlResultList += $childControlResult } } } } else { $controlResult.AddMessage([VerificationResult]::Passed,"No stateful service found.") $controlResultList += $controlResult } return $controlResultList } hidden [ControlResult[]] CheckStatelessServiceInstanceCount([ControlResult] $controlResult) { [ControlResult[]] $controlResultList = @() #Iterate through the applications present in cluster if($this.ApplicationList) { $this.ApplicationList | ForEach-Object{ $serviceFabricApplication = $_ Get-ServiceFabricService -ApplicationName $serviceFabricApplication.ApplicationName | ForEach-Object{ $serviceName = $_.ServiceName [ControlResult] $childControlResult = $this.CreateControlResult($serviceName); $serviceDescription = Get-ServiceFabricServiceDescription -ServiceName $serviceName #Filter application with Stateless service type if($serviceDescription.ServiceKind -eq "Stateless") { $instantCount = $serviceDescription.InstanceCount Add-OutputLogEvent -OutputLogFilePath $outputLogFilePath -EventData "Service Fabric service [$serviceName] has instance count : [$instantCount]" #Validate instancecount it -1 (auto) or greater than equal to 3 if($serviceDescription.InstanceCount -eq -1 -and $serviceDescription.InstanceCount -ge 3){$controlStatus = [VerificationResult]::Passed } else{ $controlStatus = [VerificationResult]::Failed } $childControlResult.AddMessage([VerificationResult]::Failed, "Instance count for service '$serviceName'",$serviceDescription) $controlResultList += $childControlResult } } } } else { $controlResult.AddMessage([VerificationResult]::Passed,"No stateless service found.") $controlResultList += $controlResult } return $controlResultList } hidden [ControlResult[]] CheckPublicEndpointSSL([ControlResult] $controlResult) { [ControlResult[]] $controlResultList = @() $loadBalancerBackendPorts = @() $loadBalancerResources = $this.GetLinkedResources("Microsoft.Network/loadBalancers") #Collect all open ports on load balancer $loadBalancerResources | ForEach-Object{ $loadBalancerResource = Get-AzureRmLoadBalancer -Name $_.ResourceName -ResourceGroupName $_.ResourceGroupName $loadBalancingRules = @($loadBalancerResource.FrontendIpConfigurations | ? { $_.PublicIpAddress -ne $null } | % { $_.LoadBalancingRules }) $loadBalancingRules | % { $loadBalancingRuleId = $_.Id; $loadBalancingRule = $loadBalancerResource.LoadBalancingRules | ? { $_.Id -eq $loadBalancingRuleId } | select -First 1 $loadBalancerBackendPorts += $loadBalancingRule.BackendPort; }; } #If no ports open, Pass the TCP if($loadBalancerBackendPorts.Count -eq 0) { $controlResult.AddMessage([VerificationResult]::Passed,"No ports enabled.") $controlResultList += $controlResult } #If Ports are open for public in load balancer, map load balancer ports with application endpoint ports and validate if SSL is enabled. else { $controlResult.AddMessage("List of publicly exposed port",$loadBalancerBackendPorts) if($this.ApplicationList) { $this.ApplicationList | ForEach-Object{ $serviceFabricApplication = $_ Get-ServiceFabricServiceType -ApplicationTypeName $serviceFabricApplication.ApplicationTypeName -ApplicationTypeVersion $serviceFabricApplication.ApplicationTypeVersion | ForEach-Object{ $currentService = $_ $serviceManifest = [xml](Get-ServiceFabricServiceManifest -ApplicationTypeName $serviceFabricApplication.ApplicationTypeName -ApplicationTypeVersion $serviceFabricApplication.ApplicationTypeVersion -ServiceManifestName $_.ServiceManifestName) $serviceManifest.ServiceManifest.Resources.Endpoints.ChildNodes | ForEach-Object{ $endpoint = $_ $serviceTypeName = $currentService.ServiceTypeName [ControlResult] $childControlResult = $this.CreateControlResult($serviceTypeName +"_" + $endpoint.Name); if($endpoint.Port -eq $null) { #Add message $childControlResult.AddMessage([VerificationResult]::Passed) } else { if($loadBalancerBackendPorts.Contains($endpoint.Port) ) { if($endpoint.Protocol -eq "https"){ $controlResult.AddMessage([VerificationResult]::Passed,"Endpoint is protected with SSL") } elseif($endpoint.Protocol -eq "http"){ $controlResult.AddMessage([VerificationResult]::Failed,"Endpoint is not protected with SSL") } else { $controlResult.AddMessage([VerificationResult]::Verify,"Verify if endpoint is protected with SSL",$endpoint) } } else { $controlResult.AddMessage([VerificationResult]::Passed,"Endpoint is not publicly opened") } } $controlResultList += $childControlResult } } } } else { $controlResult.AddMessage([VerificationResult]::Passed,"No service found.") $controlResultList += $controlResult } } return $controlResultList } [void] CheckClusterAccess() { #Function to validate authentication and connect with Service Fabric cluster $sfCluster = $null $uri = ([System.Uri]$this.ResourceObject.Properties.managementEndpoint).Host $primaryNodeType = $this.ResourceObject.Properties.nodeTypes | where { $_.isPrimary -eq $true } $ClusterConnectionUri = $uri +":"+ $primaryNodeType.clientConnectionEndpointPort $this.PublishCustomMessage("Connecting with Service Fabric cluster...") $this.PublishCustomMessage("Validating if Service Fabric is secure...") $isClusterSecure = [Helpers]::CheckMember($this.ResourceObject.Properties,"certificate" ) if($isClusterSecure) { $serviceFabricCertificate = $this.ResourceObject.Properties.certificate $this.PublishCustomMessage("Service Fabric is secure") $CertThumbprint= $this.ResourceObject.Properties.certificate.thumbprint $serviceFabricAAD =$this.ResourceObject.Properties.azureActiveDirectory if($serviceFabricAAD -ne $null) { try { $this.PublishCustomMessage("Connecting Service Fabric using AAD...") $sfCluster = Connect-ServiceFabricCluster -ConnectionEndpoint $ClusterConnectionUri -AzureActiveDirectory -ServerCertThumbprint $CertThumbprint #-SecurityToken " $this.PublishCustomMessage("Connection using AAD is successful.") } catch { throw "You may not have permission to connect with cluster" } } else { $this.PublishCustomMessage("Validating if cluster certificate present on machine...") $IsCertPresent = (Get-ChildItem -Path Cert:\$this.CertStoreLocation\$this.CertStoreName | Where-Object {$_.Thumbprint -eq $CertThumbprint }).Count if($IsCertPresent) { $this.PublishCustomMessage("Connecting Service Fabric using certificate") $sfCluster = Connect-serviceFabricCluster -ConnectionEndpoint $ClusterConnectionUri -KeepAliveIntervalInSec 10 -X509Credential -ServerCertThumbprint $CertThumbprint -FindType FindByThumbprint -FindValue $CertThumbprint -StoreLocation $this.CertStoreLocation -StoreName $this.CertStoreName } else { throw "Can not connect with Service Fabric due to unavailability of cluster certificate in local machine. Validate cluster certificate is present in 'CurrentUser' location." } } } else { "Service Fabric is unsecure" $sfCluster = Connect-serviceFabricCluster -ConnectionEndpoint $ClusterConnectionUri "Service Fabric connection is successful" } } [PSObject] GetLinkedResources([string] $resourceType) { return Find-AzureRmResource -TagName $this.DefaultTagName -TagValue $this.ClusterTagValue | Where-Object { ($_.ResourceType -EQ $resourceType) -and ($_.ResourceGroupName -eq $this.ResourceContext.ResourceGroupName) } } } # SIG # Begin signature block # MIIkAQYJKoZIhvcNAQcCoIIj8jCCI+4CAQExDzANBglghkgBZQMEAgEFADB5Bgor # BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG # KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCAO3RiBy6W1b3oC # +FRFjDLDd+Z5wAf7Oqg3GOnV8yY8WKCCDZMwggYRMIID+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 # xDCCFcACAQEwgZUwfjELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24x # EDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlv # bjEoMCYGA1UEAxMfTWljcm9zb2Z0IENvZGUgU2lnbmluZyBQQ0EgMjAxMQITMwAA # AI6HkaRXGl/KPgAAAAAAjjANBglghkgBZQMEAgEFAKCBsDAZBgkqhkiG9w0BCQMx # DAYKKwYBBAGCNwIBBDAcBgorBgEEAYI3AgELMQ4wDAYKKwYBBAGCNwIBFTAvBgkq # hkiG9w0BCQQxIgQg/VMUM7HH7M3/XUxGeBGj45Dhcdp3I1aK/xzb1ImH9XcwRAYK # KwYBBAGCNwIBDDE2MDSgEoAQAEEAegBTAEQASwAyADUAMqEegBxodHRwczovL2Fr # YS5tcy9henNka29zc2RvY3MgMA0GCSqGSIb3DQEBAQUABIIBAHqVmrCPCGDi8FPU # 2HHarbYTeoW7RNAmIz1g7mH4Om1vgZkD+wKz1Lxqj4mfwt+VuX92VYh3+Zgnd0/1 # EHvjyh2shQ6bua1MtA6cW3TPt+nSjyVuRmeZNkSg95kT9KKd2i6KD/e7MMSvIHZW # qAKD0UiFbGHtcD0CDCRYmoy3X6PGY8+haCRnh6RQnrxj700gvHxPyH//h/DgUubG # /LW6iPG00ihXzyZoMbHDNnBFBMqPlfMpBlUMw3yJ1oGss3GPw/gKB1Z8L4miHcVQ # midKLpRPnM4Z4l//8ZNPtSJcJBkC8hcaZ/4z+sA9nTUw03tGf7+vn3gK+Mtu+jaS # NCapTtuhghNMMIITSAYKKwYBBAGCNwMDATGCEzgwghM0BgkqhkiG9w0BBwKgghMl # MIITIQIBAzEPMA0GCWCGSAFlAwQCAQUAMIIBPQYLKoZIhvcNAQkQAQSgggEsBIIB # KDCCASQCAQEGCisGAQQBhFkKAwEwMTANBglghkgBZQMEAgEFAAQgxDoY7BkZ/g53 # CGnUZz+pJfWA+Efui7bHVve9KAEQLC4CBlmSFyJyhxgTMjAxNzA5MDUwOTM3MTUu # NzIzWjAHAgEBgAIB9KCBuaSBtjCBszELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldh # c2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBD # b3Jwb3JhdGlvbjENMAsGA1UECxMETU9QUjEnMCUGA1UECxMebkNpcGhlciBEU0Ug # RVNOOkIxQjctRjY3Ri1GRUMyMSUwIwYDVQQDExxNaWNyb3NvZnQgVGltZS1TdGFt # cCBTZXJ2aWNloIIOzzCCBnEwggRZoAMCAQICCmEJgSoAAAAAAAIwDQYJKoZIhvcN # 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 # EzMAAACxcRN533X2NcgAAAAAALEwDQYJKoZIhvcNAQELBQAwfDELMAkGA1UEBhMC # VVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNV # BAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRp # bWUtU3RhbXAgUENBIDIwMTAwHhcNMTYwOTA3MTc1NjU3WhcNMTgwOTA3MTc1NjU3 # WjCBszELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcT # B1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjENMAsGA1UE # CxMETU9QUjEnMCUGA1UECxMebkNpcGhlciBEU0UgRVNOOkIxQjctRjY3Ri1GRUMy # MSUwIwYDVQQDExxNaWNyb3NvZnQgVGltZS1TdGFtcCBTZXJ2aWNlMIIBIjANBgkq # hkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqqQklG1Y1lu8ob0P7deumuRn4JvRi2GE # rmK94vgbnWPmd0j/9arA7539HD1dpG1uhYbmnAxc+qsuvMM0fpEvttTK4lZSU7ss # 5rJfWmbFn/J8kSGI8K9iBaB6hQkJuIX4si9ppNr9R3oZI3HbJ/yRkKUPk4hozpY6 # CkehRc0/Zfu6tQiyqI7mClXYZTXjw+rLsh3/gdBvYDd38zFBllaf+3uimKQgUTXG # jbKfqZZk3tEU3ibWVPUxAmmxlG3sWTlXmU31fCw/6TVzGg251lq+Q46OjbeH9vB2 # TOcqEso4Nai3J1CdMAYUdlelVVtgQdIx/c+5Hvrw0Y6W7uGBAWnW5wIDAQABo4IB # GzCCARcwHQYDVR0OBBYEFE5XPfeLLhRLV7L8Il7Tz7cnRBA7MB8GA1UdIwQYMBaA # FNVjOlyKMZDzQ3t8RhvFM2hahW1VMFYGA1UdHwRPME0wS6BJoEeGRWh0dHA6Ly9j # cmwubWljcm9zb2Z0LmNvbS9wa2kvY3JsL3Byb2R1Y3RzL01pY1RpbVN0YVBDQV8y # MDEwLTA3LTAxLmNybDBaBggrBgEFBQcBAQROMEwwSgYIKwYBBQUHMAKGPmh0dHA6 # Ly93d3cubWljcm9zb2Z0LmNvbS9wa2kvY2VydHMvTWljVGltU3RhUENBXzIwMTAt # MDctMDEuY3J0MAwGA1UdEwEB/wQCMAAwEwYDVR0lBAwwCgYIKwYBBQUHAwgwDQYJ # KoZIhvcNAQELBQADggEBAHPujfu0W8PBTpjfYaPrAKIBLKcljT4+YnWbbgGvmXU8 # OvIUDBkkv8gNGGHRO5DSySaCARIzgn2yIheAqh6GwM2yKrfb4eVCYPe1CTlCseS5 # TOv+Tn/95mXj+NxTqvuNmrhgCVr0CQ7b3xoKcwDcQbg7TmerDgbIv2k7cEqbYbU/ # B3MtSX8Zjjf0ZngdKoX0JYkAEDbZchOrRiUtDJItegPKZPf6CjeHYjrmKwvTOVCz # v3lW0uyh1yb/ODeRH+VqENSHCboFiEiq9KpKMOpek1VvQhmI2KbTlRvK869gj1Nw # uUHH8c3WXu4A0X1+CBmU8t0gvd/fFlQvw04veKWh986hggN4MIICYAIBATCB46GB # uaSBtjCBszELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNV # BAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjENMAsG # A1UECxMETU9QUjEnMCUGA1UECxMebkNpcGhlciBEU0UgRVNOOkIxQjctRjY3Ri1G # RUMyMSUwIwYDVQQDExxNaWNyb3NvZnQgVGltZS1TdGFtcCBTZXJ2aWNloiUKAQEw # CQYFKw4DAhoFAAMVADq635MoZeR60+ej9uKnRG5YqlPSoIHCMIG/pIG8MIG5MQsw # CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u # ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMQ0wCwYDVQQLEwRNT1BS # MScwJQYDVQQLEx5uQ2lwaGVyIE5UUyBFU046NERFOS0wQzVFLTNFMDkxKzApBgNV # BAMTIk1pY3Jvc29mdCBUaW1lIFNvdXJjZSBNYXN0ZXIgQ2xvY2swDQYJKoZIhvcN # AQEFBQACBQDdWK0XMCIYDzIwMTcwOTA1MDQ1NjU1WhgPMjAxNzA5MDYwNDU2NTVa # MHYwPAYKKwYBBAGEWQoEATEuMCwwCgIFAN1YrRcCAQAwCQIBAAIBIAIB/zAHAgEA # AgIY4zAKAgUA3Vn+lwIBADA2BgorBgEEAYRZCgQCMSgwJjAMBgorBgEEAYRZCgMB # oAowCAIBAAIDFuNgoQowCAIBAAIDB6EgMA0GCSqGSIb3DQEBBQUAA4IBAQA+ZEd4 # ooPHiKXaGoIJl7xct64OrgoneiQ1Q/WdfJxEl3cjN5B2vRUuQX+6hq5knZBRNSvQ # hFHIumBL52FqmEl0yu4JrEcavJhNtK+rZYIdiYW/IJN+eYjR7Ca7UiPnqC0AovLC # q7CH9Pa2LZrFMTw3OEfjCa5O4ZZ2eVaBP0Ts30GgU+BVK7RQ1OnnV3rRL3vwFs9m # 4RHh92aifGO3xwvmONclucmsupzvizd0YzkG5kFkIj38HaPpRKiR+/z1e1s/qQ5W # 90R5AVKCgBS/r47n+pjTrHtHKX65tYUnd1FCED51VBM/yI372NQ/ts1ncTv5dpIN # p/KxiiIUcI+/YNj1MYIC9TCCAvECAQEwgZMwfDELMAkGA1UEBhMCVVMxEzARBgNV # BAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jv # c29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAg # UENBIDIwMTACEzMAAACxcRN533X2NcgAAAAAALEwDQYJYIZIAWUDBAIBBQCgggEy # MBoGCSqGSIb3DQEJAzENBgsqhkiG9w0BCRABBDAvBgkqhkiG9w0BCQQxIgQgb1zh # cn3HS9yduKkOxtXg+sIcm8oEJTh74vmCkFZNuIowgeIGCyqGSIb3DQEJEAIMMYHS # MIHPMIHMMIGxBBQ6ut+TKGXketPno/bip0RuWKpT0jCBmDCBgKR+MHwxCzAJBgNV # BAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4w # HAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29m # dCBUaW1lLVN0YW1wIFBDQSAyMDEwAhMzAAAAsXETed919jXIAAAAAACxMBYEFF50 # WhcZSLtM19KqNcuqdWzh7UwDMA0GCSqGSIb3DQEBCwUABIIBAAbnInFeVXEH50YW # AfbshM7CjeurDtuUZe6hGS5lPYrpafHCbtByB4gWfeUGdL/6XThOEeeZgqi4jU9H # NLv8cRoSDt/xkyM0zgBnRrVPh+s06uUw4yY0kp47NbC+HX1dCcEdDV7ysCVyzIq/ # QkEEO1/PeSejL4aetHmcvxBhlS2125M3BHw6g2RisrTqXdIafNjjrRexBDfgz6g5 # KnTtJoa1NcSdKhvmm+OT50BaixVp8X3Fk1uzEsAGnM2d+oztLBos0Jm1Bte+ouSx # SZUeTeiGKIPn+SLJ6jXZ5mXAYDV4ezqjPbffOIMablmhjUde7FHKtTkM5MfJDGWa # 4htHgYw= # SIG # End signature block |