AzStackHciExternalActiveDirectory/AzStackHci.ExternalActiveDirectory.Tests.psm1
Import-LocalizedData -BindingVariable lcAdTxt -FileName AzStackHci.ExternalActiveDirectory.Strings.psd1 class HealthModel { # Attributes for Azure Monitor schema [string]$Name #Name of the individual test/rule/alert that was executed. Unique, not exposed to the customer. [string]$Title #User-facing name; one or more sentences indicating the direct issue. [string]$Severity #Severity of the result (Critical, Warning, Informational, Hidden) – this answers how important the result is. Critical is the only update-blocking severity. [string]$Description #Detailed overview of the issue and what impact the issue has on the stamp. [psobject]$Tags #Key-value pairs that allow grouping/filtering individual tests. For example, "Group": "ReadinessChecks", "UpdateType": "ClusterAware" [string]$Status #The status of the check running (i.e. Failed, Succeeded, In Progress) – this answers whether the check ran, and passed or failed. [string]$Remediation #Set of steps that can be taken to resolve the issue found. [string]$TargetResourceID #The unique identifier for the affected resource (such as a node or drive). [string]$TargetResourceName #The name of the affected resource. [string]$TargetResourceType #The type of resource being referred to (well-known set of nouns in infrastructure, aligning with Monitoring). [datetime]$Timestamp #The Time in which the HealthCheck was called. [psobject[]]$AdditionalData #Property bag of key value pairs for additional information. [string]$HealthCheckSource #The name of the services called for the HealthCheck (I.E. Test-AzureStack, Test-Cluster). } class OrganizationalUnitTestResult : HealthModel {} class ExternalADTest { [string]$TestName [scriptblock]$ExecutionBlock } $ExternalAdTestInitializors = @( ) $ExternalAdTests = @( <# Can't execute this test during deployment as Get-KdsRootKey will try to access the DVM KDS and come up with an empty value (New-Object -Type ExternalADTest -Property @{ TestName = "KdsRootKeyExists" ExecutionBlock = { Param ([hashtable]$testContext) $dcName = $null $KdsRootKey = $null $accessDenied = $false try { # Must use the server name and credentials that are passed in if they exist $getDomainControllerParams = @{} if ($testContext["AdServer"] -and $testContext["AdCredentials"]) { $getDomainControllerParams += @{Server = $testContext["AdServer"]} $getDomainControllerParams += @{Credential = $testContext["AdCredentials"]} } else { $getDomainControllerParams += @{DomainName = $testContext["DomainFQDN"]} $getDomainControllerParams += @{MinimumDirectoryServiceVersion = "Windows2012"} $getDomainControllerParams += @{NextClosestSite = $true} $getDomainControllerParams += @{Discover = $true} } $adDomainController = Get-ADDomainController @getDomainControllerParams $dcName = "$($adDomainController.Name).$($adDomainController.Domain)" # This cmdlet doesn't take a server name or credentials, so it may fail when not run from a domain-joined machine $KdsRootKey = $null try { $KdsRootKey = Get-KdsRootKey } catch { $accessDenied = $true } if($KdsRootKey) { # make sure it is effective at least 10 hours ago if(((Get-Date) - $KdsRootKey.EffectiveTime).TotalHours -lt 10) { $KdsRootKey = $null } } } catch {} $rootKeyStatus = if ($dcName -and $KdsRootKey) { 'Succeeded' } else { 'Failed' } if ($accessDenied) { $rootKeyStatus = 'Skipped' } return New-Object PSObject -Property @{ Resource = "KdsRootKey" Status = $rootKeyStatus TimeStamp = [datetime]::UtcNow Source = $ENV:COMPUTERNAME Detail = $testContext["LcAdTxt"].KdsRootKeyMissingRemediation } } }), #> (New-Object -Type ExternalADTest -Property @{ TestName = "RequiredOrgUnitsExist" ExecutionBlock = { Param ([hashtable]$testContext) $serverParams = @{} if ($testContext["AdServer"]) { $serverParams += @{Server = $testContext["AdServer"]} } if ($testContext["AdCredentials"]) { $serverParams += @{Credential = $testContext["AdCredentials"]} } $requiredOUs = @($testContext["ADOUPath"], $testContext["ComputersADOUPath"], $testContext["UsersADOUPath"]) Log-Info -Message (" Checking for the existance of OUs: {0}" -f ($requiredOUs -join ", ")) -Type Info -Function "RequiredOrgUnitsExist" $results = $requiredOUs | ForEach-Object { $resultingOU = $null try { $resultingOU = Get-ADOrganizationalUnit -Identity $_ -ErrorAction SilentlyContinue @serverParams } catch { } return New-Object PSObject -Property @{ Resource = $_ Status = if ($resultingOU) { 'Succeeded' } else { 'Failed' } TimeStamp = [datetime]::UtcNow Source = $ENV:COMPUTERNAME Detail = ($testContext["LcAdTxt"].MissingOURemediation -f $_) } } return $results } }), (New-Object -Type ExternalADTest -Property @{ TestName = "PhysicalMachineObjectsExist" ExecutionBlock = { Param ([hashtable]$testContext) $serverParams = @{} if ($testContext["AdServer"]) { $serverParams += @{Server = $testContext["AdServer"]} } if ($testContext["AdCredentials"]) { $serverParams += @{Credential = $testContext["AdCredentials"]} } $computerAdOuPath = $testContext["ComputersADOUPath"] $domainFQDN = $testContext["DomainFQDN"] $physicalHostsSetting = @($testContext["PhysicalMachineNames"] | Where-Object { -not [string]::IsNullOrEmpty($_) }) Log-Info -Message (" Validating settings for physical hosts: {0}" -f ($physicalHostsSetting -join ", ")) -Type Info -Function "PhysicalMachineObjectsExist" try { $allComputerObjects = Get-ADComputer -Filter "*" @serverParams } catch { Log-Info -Message (" Failed to find any computer objects in ActiveDirectory. Inner exception: {0}" -f $_) -Type Error -Function "PhysicalMachineObjectsExist" $allComputerObjects = @() } $foundPhysicalHosts = @($allComputerObjects | Where-Object {$_.Name -in $physicalHostsSetting}) $missingPhysicalHostEntries = @($physicalHostsSetting | Where-Object {$_ -notin $allComputerObjects.Name}) Log-Info -Message (" Found {0} entries in AD : {1}" -f $foundPhysicalHosts.Count,($foundPhysicalHosts.Name -join ", ")) -Type Info -Function "PhysicalMachineObjectsExist" $physicalHostsWithBadDnsName = $($foundPhysicalHosts | Where-Object { $_.DNSHostName -ne "$($_.Name).$domainFQDN" }) $physicalHostsWithBadSAMAcct = $($foundPhysicalHosts | Where-Object { $_.SAMAccountName -ne "$($_.Name)$" }) Log-Info -Message (" Found {0} entries with invalid DNS names: {1}" -f $physicalHostsWithBadDnsName.Count,(($physicalHostsWithBadDnsName | Select-Object -Property DNSHostName) -join ", ")) -Type Info -Function "PhysicalMachineObjectsExist" Log-Info -Message (" Found {0} entries with invalid SAM account names: {1}" -f $physicalHostsWithBadSAMAcct.Count,(($physicalHostsWithBadSAMAcct | Select-Object -Property SAMAccountName) -join ", ")) -Type Info -Function "PhysicalMachineObjectsExist" $results = @() $hasComputerEntries = ($foundPhysicalHosts -and $foundPhysicalHosts.Count -eq $physicalHostsSetting.Count) $allEntriesHaveCorrectDnsNames = (-not $physicalHostsWithBadDnsName -or $physicalHostsWithBadDnsName.Count -eq 0) $allEntriesHaveCorrectSamAcct = (-not $physicalHostsWithBadSAMAcct -or $physicalHostsWithBadSAMAcct.Count -eq 0) $results += New-Object PSObject -Property @{ Resource = "PhysicalHostAdComputerEntries" Status = if ($hasComputerEntries) { 'Succeeded' } else { 'Failed' } TimeStamp = [datetime]::UtcNow Source = $ENV:COMPUTERNAME Detail = ($testContext["LcAdTxt"].HostsMissingRemediation -f ($missingPhysicalHostEntries -join ", "),$computerAdOuPath) } $results += New-Object PSObject -Property @{ Resource = "PhysicalHostAdComputerDnsNames" Status = if ($allEntriesHaveCorrectDnsNames) { 'Succeeded' } else { 'Failed' } TimeStamp = [datetime]::UtcNow Source = $ENV:COMPUTERNAME Detail = ($testContext["LcAdTxt"].HostsWithIncorrectDnsNameRemediation -f ($physicalHostsWithBadDnsName.Name -join ", ")) } $results += New-Object PSObject -Property @{ Resource = "PhysicalHostAdComputerSamAccounts" Status = if ($allEntriesHaveCorrectSamAcct) { 'Succeeded' } else { 'Failed' } TimeStamp = [datetime]::UtcNow Source = $ENV:COMPUTERNAME Detail = ($testContext["LcAdTxt"].HostsWithIncorrectSamAcctRemediation -f ($physicalHostsWithBadDnsName.Name -join ", ")) } return $results } }), (New-Object -Type ExternalADTest -Property @{ TestName = "ClusterExistsWithRequiredAcl" ExecutionBlock = { Param ([hashtable]$testContext) $serverParams = @{} if ($TestContext["AdServer"]) { $serverParams += @{Server = $TestContext["AdServer"]} } if ($TestContext["AdCredentials"]) { $serverParams += @{Credential = $TestContext["AdCredentials"]} } $createChildAce = $null $readPropertyAce = $null $computersAdOuPath = $testContext["ComputersADOUPath"] $clusterName = $testContext["ClusterName"] Log-Info -Message (" Searching for '{0}' entry in OU '{1}'" -f $clusterName,$computersAdOuPath) -Type Info -Function "ClusterExistsWithRequiredAcl" try { $clusterComputerEntry = Get-ADComputer -SearchBase $computersAdOuPath -Filter "Name -eq '$clusterName'" @serverParams } catch { Log-Info -Message (" Failed to find '{0}' entry in OU '{1}'. Inner exception: {2}" -f $clusterName,$computersAdOuPath,$_) -Type Error -Function "ClusterExistsWithRequiredAcl" $clusterComputerEntry = $null } if ($clusterComputerEntry) { $clusterSID = $clusterComputerEntry.SID Log-Info -Message (" Found entry, SID: {0}" -f $clusterSID) -Type Info -Function "ClusterExistsWithRequiredAcl" # The AD module SHOULD install a drive that we can use to get ACLs. However, sometimes it isn't properly registered # especially if we just installed it. So verify that it's usable $adDriveName = "AD" $tempDriveName = "hciad" $adDriveObject = $null try { $adProvider = Get-PSProvider -PSProvider ActiveDirectory if ($adProvider -and $adProvider.Drives.Count -gt 0) { $adDriveObject = $adProvider.Drives | Where-Object {$_.Name -eq $adDriveName -or $_.Name -eq $tempDriveName} } } catch { Log-Info -Message (" Error while trying to access active directory PS drive. Will fall back to creating a new PS drive. Inner exception: {0}" -f $_) -Type Warning -Function "ClusterExistsWithRequiredAcl" } if (-not $adDriveObject) { try { # Add a new drive $adDriveObject = New-PSDrive -Name $tempDriveName -PSProvider ActiveDirectory -Root '' @serverParams } catch { Log-Info -Message (" Error while trying to create active directory PS drive. Inner exception: {0}" -f $_) -Type Error -Function "ClusterExistsWithRequiredAcl" } } $accessRules = @() if ($adDriveObject) { $adDriveName = $adDriveObject.Name try { $ouPath = ("{0}:\{1}" -f $adDriveName,$computersAdOuPath) $ouAcl = Get-Acl $ouPath } catch { Log-Info -Message (" Can't get acls from {0}. Inner exception: {1}" -f $ouPath,$_) -Type Error -Function "ClusterExistsWithRequiredAcl" } finally { # best effort cleanup if we had added the temp drive try { if ($adDriveName -eq $tempDriveName) { $adDriveObject | Remove-PSDrive } } catch {} } try { # must specify the type to retrieve -- need to get something comparable to the clusterSID if ($ouAcl) { $accessRules = $ouAcl.GetAccessRules($true, $true, $clusterSID.GetType()) } } catch { Log-Info -Message (" Error while trying to get access rules for OU. Inner exception: {0}" -f $_) -Type Error -Function "ClusterExistsWithRequiredAcl" } } # Check that the CreateChild ACE has been added $createChildAce = $accessRules | Where-Object { ` $_.IdentityReference -eq $clusterSID -and ` $_.ActiveDirectoryRights -bor [System.DirectoryServices.ActiveDirectoryRights]::CreateChild -and ` $_.AccessControlType -eq [System.Security.AccessControl.AccessControlType]::Allow -and ` $_.ObjectType -eq ([System.Guid]::New('bf967a86-0de6-11d0-a285-00aa003049e2')) -and $_.InheritanceType -eq [System.DirectoryServices.ActiveDirectorySecurityInheritance]::All } $readPropertyAce = $accessRules | Where-Object { ` $_.IdentityReference -eq $clusterSID -and ` $_.ActiveDirectoryRights -bor [System.DirectoryServices.ActiveDirectoryRights]::ReadProperty -and ` $_.AccessControlType -eq [System.Security.AccessControl.AccessControlType]::Allow -and ` $_.ObjectType -eq [System.Guid]::Empty -and $_.InheritanceType -eq [System.DirectoryServices.ActiveDirectorySecurityInheritance]::All } Log-Info -Message (" Found CreateChild ACE: {0}" -f ([bool]$createChildAce)) -Type Info -Function "ClusterExistsWithRequiredAcl" Log-Info -Message (" Found ReadProperty ACE: {0}" -f ([bool]$readPropertyAce)) -Type Info -Function "ClusterExistsWithRequiredAcl" } return New-Object PSObject -Property @{ Resource = "ClusterAcls" Status = if ($createChildAce -and $readPropertyAce) { 'Succeeded' } else { 'Failed' } TimeStamp = [datetime]::UtcNow Source = $ENV:COMPUTERNAME Detail = ($testContext["LcAdTxt"].ClusterAclsMissingRemediation -f $computersAdOuPath) } } }), (New-Object -Type ExternalADTest -Property @{ TestName = "SecurityGroupsExist" ExecutionBlock = { Param ([hashtable]$testContext) $serverParams = @{} if ($TestContext["AdServer"]) { $serverParams += @{Server = $TestContext["AdServer"]} } if ($TestContext["AdCredentials"]) { $serverParams += @{Credential = $TestContext["AdCredentials"]} } $securityGroups = @( "$($testContext["NamingPrefix"])-Sto-SG", "$($testContext["NamingPrefix"])-FsAcl-InfraSG", "$($testContext["NamingPrefix"])-EceSG", "$($testContext["NamingPrefix"])-BM-ECESG", "$($testContext["NamingPrefix"])-HA-R-SrvSG", "$($testContext["NamingPrefix"])-Hc-Rs-SrvSG", "$($testContext["NamingPrefix"])-IH-HsSG", "$($testContext["NamingPrefix"])-OpsAdmin", "$($testContext["NamingPrefix"])-SB-Jea-MG-VmSG", "$($testContext["NamingPrefix"])-IH-MsSG", "$($testContext["NamingPrefix"])-DLSG" ) $usersOuPath = $testContext["UsersADOUPath"] $missingSecurityGroups = @() # Look up all the required security groups and identify any that are missing foreach ($securityGroup in $securityGroups) { try { $adGroup = Get-AdGroup -SearchBase $usersOuPath -Filter { Name -eq $securityGroup } @serverParams } catch { $adGroup = $null } if (-not $adGroup) { $missingSecurityGroups += $securityGroup } } $physicalHosts = @() # get the list of physical hosts foreach ($host in $testContext["PhysicalMachineNames"]) { $physicalHosts += @{Name=$host; Object=(Get-ADComputer -Identity $host @serverParams); MissingSGs=@()} } # Now check that the physical machines have been added to the required SGs $physicalMachineSecurityGroups = @("$($testContext["NamingPrefix"])-Sto-SG") foreach ($physicalHost in $physicalHosts) { foreach ($physicalMachineSecurityGroup in $physicalMachineSecurityGroups) { $isMember = $false try { $groupObject = Get-ADGroup -SearchBase $usersOuPath -Filter {Name -eq $physicalMachineSecurityGroup} @serverParams if ($groupObject) { $isMember = Get-ADGroupMember -Identity $groupObject @serverParams | Where-Object {$_.SID -eq $($physicalHost.Object.SID)} } } catch { Log-Info -Message (" Failed to check whether host '{0}' is a member of group '{1}'. Inner exception: {2}" -f $physicalHost.Name,$physicalMachineSecurityGroup,$_) -Type Warning -Function "ClusterExistsWithRequiredAcl" } if (-not $isMember) { $physicalHost.MissingSGs += $physicalMachineSecurityGroup } } } $results = @() $results += New-Object PSObject -Property @{ Resource = "SecurityGroups" Status = if ($missingSecurityGroups.Count -eq 0) { 'Succeeded' } else { 'Failed' } TimeStamp = [datetime]::UtcNow Source = $ENV:COMPUTERNAME Detail = ($testContext["LcAdTxt"].SecurityGroupsMissingRemediation -f ($missingSecurityGroups -join ', ')) } foreach ($physicalHost in $physicalHosts) { $missingSecurityGroupMemberships = $physicalHost.MissingSGs $results += New-Object PSObject -Property @{ Resource = "SecurityGroupMembership_$($physicalHost.Name)" Status = if ($missingSecurityGroupMemberships.Count -eq 0) { 'Succeeded' } else { 'Failed' } TimeStamp = [datetime]::UtcNow Source = $ENV:COMPUTERNAME Detail = ($testContext["LcAdTxt"].HostSecurityGroupsMissingRemediation -f $physicalHost.Name,($missingSecurityGroupMemberships -join ', ')) } } return $results } }), (New-Object -Type ExternalADTest -Property @{ TestName = "gMSAsExist" ExecutionBlock = { Param ([hashtable]$testContext) $usersOuPath = $testContext["UsersADOUPath"] $serverParams = @{} if ($TestContext["AdServer"]) { $serverParams += @{Server = $TestContext["AdServer"]} } if ($TestContext["AdCredentials"]) { $serverParams += @{Credential = $TestContext["AdCredentials"]} } $gmsaAccounts = @( [pscustomobject]@{ GmsaName="$($testContext["NamingPrefix"])-BM-ECE"; PrincipalsAllowedToRetrieveManagedPassword= @("$($testContext["NamingPrefix"])-Sto-SG"); ServicePrincipalName=@("$($testContext["NamingPrefix"])/ae3299a9-3e87-4186-bd99-c43c9ae6a571"); MemberOf=@("$($testContext["NamingPrefix"])-EceSG", "$($testContext["NamingPrefix"])-BM-ECESG", "$($testContext["NamingPrefix"])-FsAcl-InfraSG")}, [pscustomobject]@{ GmsaName="$($testContext["NamingPrefix"])-BM-ALM"; PrincipalsAllowedToRetrieveManagedPassword= @("$($testContext["NamingPrefix"])-Sto-SG"); ServicePrincipalName=@(); MemberOf=@("$($testContext["NamingPrefix"])-FsAcl-InfraSG")}, [pscustomobject]@{ GmsaName="$($testContext["NamingPrefix"])-BM-FCA"; PrincipalsAllowedToRetrieveManagedPassword= @("$($testContext["NamingPrefix"])-Sto-SG"); ServicePrincipalName=@(); MemberOf=@("$($testContext["NamingPrefix"])-FsAcl-InfraSG")}, [pscustomobject]@{ GmsaName="$($testContext["NamingPrefix"])-BM-FRA"; PrincipalsAllowedToRetrieveManagedPassword= @("$($testContext["NamingPrefix"])-Sto-SG"); ServicePrincipalName=@(); MemberOf=@("$($testContext["NamingPrefix"])-FsAcl-InfraSG")}, [pscustomobject]@{ GmsaName="$($testContext["NamingPrefix"])-BM-TCA"; PrincipalsAllowedToRetrieveManagedPassword= @("$($testContext["NamingPrefix"])-Sto-SG"); ServicePrincipalName=@(); MemberOf=@("$($testContext["NamingPrefix"])-FsAcl-InfraSG")}, [pscustomobject]@{ GmsaName="$($testContext["NamingPrefix"])-BM-HA"; PrincipalsAllowedToRetrieveManagedPassword= @("$($testContext["NamingPrefix"])-Sto-SG"); ServicePrincipalName=@("$($testContext["NamingPrefix"])/PhysicalNode/1b4dde6b-7ea8-407a-8c9e-f86e8b97fd1c"); MemberOf=@("$($testContext["NamingPrefix"])-FsAcl-InfraSG", "$($testContext["NamingPrefix"])-HA-R-SrvSG")}, [pscustomobject]@{ GmsaName="$($testContext["NamingPrefix"])-SB-LC"; PrincipalsAllowedToRetrieveManagedPassword= @("$($testContext["NamingPrefix"])-Sto-SG"); ServicePrincipalName=@("$($testContext["NamingPrefix"])/754dbc04-8f91-4cb6-a10f-899dac573fa0"); MemberOf=@("$($testContext["NamingPrefix"])-FsAcl-InfraSG", "$($testContext["NamingPrefix"])-Sto-SG" ) }, [pscustomobject]@{ GmsaName="$($testContext["NamingPrefix"])-SB-Jea"; PrincipalsAllowedToRetrieveManagedPassword= @("$($testContext["NamingPrefix"])-Sto-SG"); ServicePrincipalName=@(); MemberOf=@("$($testContext["NamingPrefix"])-FsAcl-InfraSG", "$($testContext["NamingPrefix"])-Sto-SG" ) }, [pscustomobject]@{ GmsaName="$($testContext["NamingPrefix"])-SB-MG"; PrincipalsAllowedToRetrieveManagedPassword= @("$($testContext["NamingPrefix"])-Sto-SG", "$($testContext["NamingPrefix"])-SB-Jea-MG-VmSG"); ServicePrincipalName=@("$($testContext["NamingPrefix"])/ea126685-c89e-4294-959f-bba6bf75b4aa"); MemberOf=@("$($testContext["NamingPrefix"])-FsAcl-InfraSG", "$($testContext["NamingPrefix"])-SB-Jea-MG-VmSG", "$($testContext["NamingPrefix"])-Sto-SG" ) }, [pscustomobject]@{ GmsaName="$($testContext["NamingPrefix"])-SBJeaM"; PrincipalsAllowedToRetrieveManagedPassword= @("$($testContext["NamingPrefix"])-Sto-SG"); ServicePrincipalName=@(); MemberOf=@("$($testContext["NamingPrefix"])-FsAcl-InfraSG", "$($testContext["NamingPrefix"])-Sto-SG" ) }, [pscustomobject]@{ GmsaName="$($testContext["NamingPrefix"])-EceSA"; PrincipalsAllowedToRetrieveManagedPassword= @("$($testContext["NamingPrefix"])-Sto-SG"); ServicePrincipalName=@("$($testContext["NamingPrefix"])/4dde37cc-6ee0-4d75-9444-7061e156507f"); MemberOf=@("$($testContext["NamingPrefix"])-FsAcl-InfraSG", "$($testContext["NamingPrefix"])-EceSG", "$($testContext["NamingPrefix"])-BM-EceSG")}, [pscustomobject]@{ GmsaName="$($testContext["NamingPrefix"])-Urp-SA"; PrincipalsAllowedToRetrieveManagedPassword= @("$($testContext["NamingPrefix"])-Sto-SG"); ServicePrincipalName=@("$($testContext["NamingPrefix"])/110bac92-1879-47ae-9611-e40f8abf4fc0"); MemberOf=@("$($testContext["NamingPrefix"])-FsAcl-InfraSG", "$($testContext["NamingPrefix"])-BM-ECESG", "$($testContext["NamingPrefix"])-EceSG", "$($testContext["NamingPrefix"])-DLSG")}, [pscustomobject]@{ GmsaName="$($testContext["NamingPrefix"])-DL-SA"; PrincipalsAllowedToRetrieveManagedPassword= @("$($testContext["NamingPrefix"])-Sto-SG"); ServicePrincipalName=@("$($testContext["NamingPrefix"])/365645b4-f9a5-4a7d-8669-c08a1c41d66b"); MemberOf=@("$($testContext["NamingPrefix"])-FsAcl-InfraSG", "$($testContext["NamingPrefix"])-BM-ECESG", "$($testContext["NamingPrefix"])-EceSG", "$($testContext["NamingPrefix"])-DLSG")}, [pscustomobject]@{ GmsaName="$($testContext["NamingPrefix"])-BM-MSA"; PrincipalsAllowedToRetrieveManagedPassword= @("$($testContext["NamingPrefix"])-Sto-SG"); ServicePrincipalName=@("$($testContext["NamingPrefix"])/PhysicalNode/d8c180f6-7290-458e-90f0-96894f45e981"); MemberOf=@("$($testContext["NamingPrefix"])-FsAcl-InfraSG", "$($testContext["NamingPrefix"])-IH-MsSG", "$($testContext["NamingPrefix"])-HA-R-SrvSG")} ) $missingGmsaAccounts = @() foreach ($gmsaAccount in $gmsaAccounts) { $accountMissing = $true try { $gmsaName = $gmsaAccount.GmsaName $adGmsaAccount = Get-ADServiceAccount -SearchBase $usersOuPath -Filter {Name -eq $gmsaName} @serverParams if ($adGmsaAccount) { # TODO, identify SPNs and make sure they match # TODO, identify PrincipasAllowedToRetrieveManagedPassword and check $isMember = $true try { foreach ($memberOfGroup in $gmsaAccount.MemberOf) { try { $groupObject = Get-ADGroup -SearchBase $usersOuPath -Filter {Name -eq $memberOfGroup} @serverParams } catch {} if ($groupObject) { try { $isMemberOfThisGroup = Get-ADGroupMember -Identity $groupObject @serverParams | Where-Object {$_.SID -eq $($adGmsaAccount.SID)} } catch { $isMemberOfThisGroup = $false } if (-not $isMemberOfThisGroup) { Log-Info -Message (" Missing GMSA account ({0}) membership: {1}" -f $gmsaName,$memberOfGroup) -Type Warning -Function "gMSAsExist" $isMember = $false } } else { Log-Info -Message (" Missing SG '{0}' expected for to contain GMSA account '{1}'" -f $memberOfGroup,$gmsaName) -Type Warning -Function "gMSAsExist" $isMember = $false } } } catch {} $accountMissing = -not $isMember } } catch {} if ($accountMissing) { $missingGmsaAccounts += $gmsaAccount.GmsaName } } return New-Object PSObject -Property @{ Resource = "GmsaAccounts" Status = if ($missingGmsaAccounts.Count -eq 0) { 'Succeeded' } else { 'Failed' } TimeStamp = [datetime]::UtcNow Source = $ENV:COMPUTERNAME Detail = ($testContext["LcAdTxt"].GmsaAccountsMissingRemediation -f ($missingGmsaAccounts -join ', ')) } } }), (New-Object -Type ExternalADTest -Property @{ TestName = "GroupMembershipsExist" ExecutionBlock = { Param ([hashtable]$testContext) $usersOuPath = $testContext["UsersADOUPath"] $serverParams = @{} if ($TestContext["AdServer"]) { $serverParams += @{Server = $TestContext["AdServer"]} } if ($TestContext["AdCredentials"]) { $serverParams += @{Credential = $TestContext["AdCredentials"]} } $SecurityGroupMemberships = @( [pscustomobject]@{ Name="$($testContext["NamingPrefix"])-HA-R-SrvSG"; MemberOf=@("$($testContext["NamingPrefix"])-Hc-Rs-SrvSG", "$($testContext["NamingPrefix"])-IH-HsSG", "$($testContext["NamingPrefix"])-IH-MsSG", "$($testContext["NamingPrefix"])-FsAcl-InfraSG"); MissingMemberships=@()} ) $results = @() foreach ($securityGroupMembership in $SecurityGroupMemberships) { $sgName = $securityGroupMembership.Name $sgMemberList = $securityGroupMembership.MemberOf $groupObject = $null try { $groupObject = Get-ADGroup -SearchBase $usersOuPath -Filter {Name -eq $sgName} @serverParams } catch { Log-Info -Message (" Failed to get AD security group '{0}'. Inner exception: {1}" -f $sgName,$_) -Type Error -Function "GroupMembershipsExist" } if ($groupObject) { foreach ($securityGroupName in $sgMemberList) { $isMember = $false try { $parentGroupObject = Get-ADGroup -SearchBase $usersOuPath -Filter {Name -eq $securityGroupName} @serverParams if ($groupObject) { $isMember = Get-ADGroupMember -Identity $parentGroupObject @serverParams | Where-Object {$_.SID -eq $($groupObject.SID)} } } catch { Log-Info -Message (" Failed to determine whether security group '{0}' includes security group '{1}'. Inner exception: {1}" -f $securityGroupName,$sgName,$_) -Type Error -Function "GroupMembershipsExist" } if (-not $isMember) { Log-Info -Message (" FAILED: Security group '{0}' DOES NOT include security group '{1}'." -f $securityGroupName,$sgName) -Type Warning -Function "GroupMembershipsExist" $securityGroupMembership.MissingMemberships += $securityGroupName } } } else { $securityGroupMembership.MissingMemberships = $sgMemberList } $results += New-Object PSObject -Property @{ Resource = "NestedSecurityGroups_$sgName" Status = if ($securityGroupMembership.MissingMemberships.Count -eq 0) { 'Succeeded' } else { 'Failed' } TimeStamp = [datetime]::UtcNow Source = $ENV:COMPUTERNAME Detail = ($testContext["LcAdTxt"].NestedSecurityGroupsMissingRemediation -f $sgName,($securityGroupMembership.MissingMemberships -join ', ')) } } return $results } }), (New-Object -Type ExternalADTest -Property @{ TestName = "GpoInheritanceIsBlocked" ExecutionBlock = { Param ([hashtable]$testContext) $serverParams = @{} if ($TestContext["AdServer"]) { $serverParams += @{Server = $TestContext["AdServer"]} } $ouList = @($testContext["ADOUPath"],$testContext["ComputersADOUPath"],$testContext["UsersADOUPath"]) $ousWithoutGpoInheritanceBlocked = @() $accessWasDenied = $false try { foreach ($ouItem in $ouList) { try { $gpInheritance = Get-GPInheritance -Target $ouItem @serverParams } catch { if ($_.Exception -is [System.DirectoryServices.ActiveDirectory.ActiveDirectoryOperationException]) { throw } } if ((-not $gpInheritance) -or (-not $gpInheritance.GpoInheritanceBlocked)) { $ousWithoutGpoInheritanceBlocked += $ouItem } } } catch [System.DirectoryServices.ActiveDirectory.ActiveDirectoryOperationException] { $accessWasDenied = $true } $statusValue = 'Succeeded' if ($ousWithoutGpoInheritanceBlocked.Count -ne 0) { $statusValue = 'Failed' } if ($accessWasDenied) { $statusValue = 'Skipped' } return New-Object PSObject -Property @{ Resource = "OuGpoInheritance" Status = $statusValue TimeStamp = [datetime]::UtcNow Source = $ENV:COMPUTERNAME Detail = $testContext["LcAdTxt"].OuInheritanceBlockedMissingRemediation } } }), (New-Object -Type ExternalADTest -Property @{ TestName = "ExecutingAsDeploymentUser" ExecutionBlock = { <# During deployment, the environment checker itself runs as a local admin account, but we need to make sure that the AD credentials that are passed in meet all the same criteria as if they were created as part of the AD pre creation tool script. As a result, the user specified with these credentials needs to have: * All access to the deployment OU in AD * Membership to a set of SGs as mentioned in the array below #> Param ([hashtable]$testContext) # Values retrieved from the test context $adOuPath = $testContext["ADOUPath"] $namingPrefix = $testContext["NamingPrefix"] $usersOuPath = $testContext["UsersADOUPath"] [pscredential]$credentials = $testContext["AdCredentials"] $credentialName = $null if ($credentials) { # Get the user SID so we can find it in the ACL $credentialParts = $credentials.UserName.Split("\\") $credentialName = $credentialParts[$credentialParts.Length-1] } else { $credentialName = $env:USERNAME } $serverParams = @{} if ($TestContext["AdServer"]) { $serverParams += @{Server = $TestContext["AdServer"]} } if ($TestContext["AdCredentials"]) { $serverParams += @{Credential = $TestContext["AdCredentials"]} } # Defined set of SGs that need to have the AD user as a member. This needs to be kept in sync with # the list at the bottom of AsHciADArtifactsPreCreationTool.psm1 :: New-AsHciSecurityGroup $requiredSgMemberships = @( "$($namingPrefix)-OpsAdmin", "$($namingPrefix)-EceSG", "$($namingPrefix)-BM-ECESG", "$($namingPrefix)-FsAcl-InfraSG", "$($namingPrefix)-DLSG", "Domain Users" ) $deprecatedSgMemberships = @( "$($namingPrefix)-FsAcl-AcsSG" ) $userSID = $null try { $userSecurityIdentifier = Get-ADUser -Filter {Name -eq $credentialName} -SearchBase $adOuPath @serverParams if ($userSecurityIdentifier) { $userSID = [System.Security.Principal.SecurityIdentifier] $userSecurityIdentifier.SID } } catch { Log-Info -Message (" Failed to get user '{0}' in Active Directory. Inner exception: {1}" -f $credentialName,$_) -Type Error -Function "ExecutingAsDeploymentUser" } if (-not $userSID) { return New-Object PSObject -Property @{ Resource = "ExecutingAsDeploymentUser" Status = "Failed" TimeStamp = [datetime]::UtcNow Source = $ENV:COMPUTERNAME Detail = ($testContext["LcAdTxt"].ADUserNotFound -f $credentials.UserName,$adOuPath) } } else { Log-Info -Message (" Found user '{0}' in Active Directory" -f $credentialName) -Type Info -Function "ExecutingAsDeploymentUser" # Test whether the AdCredentials user has all access rights to the OU $userHasOuPermissions = $false try { # The AD module SHOULD install a drive that we can use to get ACLs. However, sometimes it isn't properly registered # especially if we just installed it. So verify that it's usable $adDriveName = "AD" $tempDriveName = "hciad" $adDriveObject = $null try { $adProvider = Get-PSProvider -PSProvider ActiveDirectory if ($adProvider -and $adProvider.Drives.Count -gt 0) { $adDriveObject = $adProvider.Drives | Where-Object {$_.Name -eq $adDriveName -or $_.Name -eq $tempDriveName} } } catch { Log-Info -Message (" Error while trying to access active directory PS drive. Will fall back to creating a new PS drive. Inner exception: {0}" -f $_) -Type Warning -Function "ExecutingAsDeploymentUser" } if (-not $adDriveObject) { try { # Add a new drive $adDriveObject = New-PSDrive -Name $tempDriveName -PSProvider ActiveDirectory -Root '' @serverParams } catch { Log-Info -Message (" Error while trying to create active directory PS drive. Inner exception: {0}" -f $_) -Type Error -Function "ExecutingAsDeploymentUser" } } $ouAcl = $null if ($adDriveObject) { $adDriveName = $adDriveObject.Name try { $ouPath = ("{0}:\{1}" -f $adDriveName,$adOuPath) $ouAcl = Get-Acl $ouPath } catch { Log-Info -Message (" Can't get acls from {0}. Inner exception: {1}" -f $ouPath,$_) -Type Error -Function "ExecutingAsDeploymentUser" } finally { # best effort cleanup if we had added the temp drive try { if ($adDriveName -eq $tempDriveName) { $adDriveObject | Remove-PSDrive } } catch {} } } if ($ouAcl) { try { # must specify the type to retrieve -- need to get something comparable to the userSID $accessRules = $ouAcl.GetAccessRules($true, $true, $userSID.GetType()) } catch { Log-Info -Message (" Error while trying to get access rules for OU. Inner exception: {0}" -f $_) -Type Error -Function "ClusterExistsWithRequiredAcl" } # Check that the GenericAll ACE has been added $genericAllAce = $accessRules | Where-Object { ` $_.IdentityReference -eq $userSID -and ` $_.ActiveDirectoryRights -bor [System.DirectoryServices.ActiveDirectoryRights]::GenericAll -and ` $_.AccessControlType -eq [System.Security.AccessControl.AccessControlType]::Allow -and ` $_.InheritanceType -eq [System.DirectoryServices.ActiveDirectorySecurityInheritance]::All } if ($genericAllAce) { $userHasOuPermissions = $true } else { Log-Info -Message (" Found ACLs for AD OU ({0}), but user ({1})'s SID ({2}) not granted GenericAll access" -f $ouPath,$credentialName,$userSID) -Type Warning -Function "ExecutingAsDeploymentUser" } } } catch { Log-Info -Message (" FAILED to look up ACL for AD OU ({0}) and search for GenericAll ACE for user ({1}). Inner exception: {2}" -f $ouPath,$credentialName,$_) -Type Error -Function "ExecutingAsDeploymentUser" } $missingSgMemberships = @() foreach ($requiredSgName in $requiredSgMemberships) { try { $groupObject = Get-ADGroup -SearchBase $usersOuPath -Filter {Name -eq $requiredSgName} @serverParams } catch { Log-Info -Message (" FAILED to look up required SG ({0}). Inner exception: {1}" -f $requiredSgName,$_) -Type Error -Function "ExecutingAsDeploymentUser" } # If the group doesn't exist we report in a different test if ($groupObject) { try { $adGroupMemberEntry = Get-ADGroupMember -Identity $groupObject | Where-Object {$_.SID -eq $userSID} } catch { Log-Info -Message (" FAILED to look up user SID in required SG membership ({0}). Inner exception: {1}" -f $requiredSgName,$_) -Type Error -Function "ExecutingAsDeploymentUser" } if (-not $adGroupMemberEntry) { $missingSgMemberships += $requiredSgName Log-Info -Message (" User {0} not a member of the required security group: {1}" -f $credentialName,$requiredSgName) -Type Warning -Function "ExecutingAsDeploymentUser" } } } # Find all the AD groups, and search for any that contain the deployment user (especially domain admin!) $extraGroups = @() $failureReasons = @() try { $allGroups = Get-ADGroup -Filter '*' @serverParams } catch { $failureReasons += ($testContext["LcAdTxt"].ActiveDirectoryError -f "Get-ADGroup",$_) Log-Info -Message (" FAILED to get all AD security groups. Inner exception: {0}" -f $_) -Type Error -Function "ExecutingAsDeploymentUser" $allGroups = @() } foreach ($singleGroupObject in $allGroups) { if (-not ($requiredSgMemberships -contains $singleGroupObject.Name) -and -not ($deprecatedSgMemberships -contains $singleGroupObject.Name)) { try { $foundAdUser = Get-ADGroupMember -Identity $singleGroupObject | Where-Object {$_.SID -eq $userSID} } catch { # This can actually fail for a number of reasons that should not block deployment, including one-way trust issues, etc. # For now, simply ignore any group that we enumerated with Get-ADGroup but can't find membership info on. $foundAdUser = $null } if ($foundAdUser) { $extraneousGroupName = $singleGroupObject.DistinguishedName $extraGroups += $extraneousGroupName Log-Info -Message (" User {0} should not be a member of additional security group: {1}" -f $credentialName,$extraneousGroupName) -Type Warning -Function "ExecutingAsDeploymentUser" } } } # Summarize detail based on what failed if (-not $userHasOuPermissions) { $failureReasons += ($testContext["LcAdTxt"].CurrentUserMissingOUAccess -f $adOuPath) } if ($missingSgMemberships.Count -gt 0) { $missingGroupMembershipList = $missingSgMemberships -join ", " $failureReasons += ($testContext["LcAdTxt"].CurrentUserMissingSGMembership -f $missingGroupMembershipList) } if ($extraGroups.Count -gt 0) { $extraneousGroupMembershipList = $extraGroups -join ", " $failureReasons += ($testContext["LcAdTxt"].CurrentUserHasExcessSGMemberships -f $extraneousGroupMembershipList) } if ($failureReasons.Count -gt 0) { $statusValue = 'Failed' $allFailureReasons = $failureReasons -join "; " $detail = ($testContext["LcAdTxt"].CurrentUserFailureSummary -f $credentials.UserName,$allFailureReasons) } else { $statusValue = 'Succeeded' $detail = "" } return New-Object PSObject -Property @{ Resource = "ExecutingAsDeploymentUser" Status = $statusValue TimeStamp = [datetime]::UtcNow Source = $ENV:COMPUTERNAME Detail = $detail } } } }) ) function Test-OrganizationalUnitOnSession { [CmdletBinding()] param ( [Parameter(Mandatory=$true)] [string] $ADOUPath, [Parameter(Mandatory=$true)] [string] $DomainFQDN, [Parameter(Mandatory=$true)] [string] $NamingPrefix, [Parameter(Mandatory=$true)] [string] $ClusterName, [Parameter(Mandatory)] [array] $PhysicalMachineNames, [Parameter(Mandatory=$false)] [System.Management.Automation.Runspaces.PSSession] $Session, [Parameter(Mandatory=$false)] [string] $ActiveDirectoryServer, [Parameter(Mandatory=$false)] [pscredential] $ActiveDirectoryCredentials ) $testContext = @{ ADOUPath = $ADOUPath ComputersADOUPath = "OU=Computers,$ADOUPath" UsersADOUPath = "OU=Users,$ADOUPath" DomainFQDN = $DomainFQDN NamingPrefix = $NamingPrefix ClusterName = $ClusterName LcAdTxt = $lcAdTxt AdServer = $ActiveDirectoryServer AdCredentials = $ActiveDirectoryCredentials AdCredentialsUserName = if ($ActiveDirectoryCredentials) { $ActiveDirectoryCredentials.UserName } else { "" } PhysicalMachineNames = $PhysicalMachineNames } $computerName = if ($Session) { $Session.ComputerName } else { $ENV:COMPUTERNAME } Log-Info -Message "Executing test on $computerName" -Type Info # Reuse the parameters for Invoke-Command so that we only have to set up context and session data once $invokeParams = @{ ScriptBlock = $null ArgumentList = $testContext } if ($Session) { $invokeParams += @{Session = $Session} } # If provided, verify the AD server and credentials are reachable if ($ActiveDirectoryServer -or $ActiveDirectoryCredentials) { $params = @{} if ($ActiveDirectoryServer) { $params["Server"] = $ActiveDirectoryServer } if ($ActiveDirectoryCredentials) { $params["Credential"] = $ActiveDirectoryCredentials } try { Get-ADDomain @params } catch { if (-not $ActiveDirectoryServer) { $ActiveDirectoryServer = "default" } $userName = "default" if ($ActiveDirectoryCredentials) { $userName = $ActiveDirectoryCredentials.UserName } throw ("Unable to contact AD server {0} using {1} credentials. Internal exception: {2}" -f $ActiveDirectoryServer,$userName,$_) } } # Initialize the array of detailed results $detailedResults = @() # Test preparation -- fill in more of the test context that needs to be executed remotely $ExternalAdTestInitializors | ForEach-Object { $invokeParams.ScriptBlock = $_.ExecutionBlock $testName = $_.TestName Log-Info -Message "Executing test initializer $testName" -Type Info try { $results = Invoke-Command @invokeParams if ($results) { $testContext += $results } } catch { throw ("Unable to execute test {0} on {1}. Inner exception: {2}" -f $testName,$computerName,$_) } } Log-Info -Message "Executing tests with parameters: " -Type Info foreach ($key in $testContext.Keys) { if ($key -ne "LcAdTxt") { Log-Info -Message " $key : $($testContext[$key])" -Type Info } } # Update InvokeParams with the full context $invokeParams.ArgumentList = $testContext # For each test, call the test execution block and append the results $ExternalAdTests | ForEach-Object { # override ScriptBlock with the particular test execution block $invokeParams.ScriptBlock = $_.ExecutionBlock $testName = $_.TestName Log-Info -Message "Executing test $testName" -Type Info try { $results = Invoke-Command @invokeParams Log-Info -Message ("Test $testName completed with: {0}" -f $results) -Type Info $detailedResults += $results } catch { Log-Info -Message ("Test $testName FAILED. Inner exception: {0}" -f $_) -Type Info } } return $detailedResults } function Test-OrganizationalUnit { [CmdletBinding()] param ( [Parameter(Mandatory=$true)] [string] $ADOUPath, [Parameter(Mandatory=$true)] [string] $DomainFQDN, [Parameter(Mandatory=$true)] [string] $NamingPrefix, [Parameter(Mandatory=$true)] [string] $ClusterName, [Parameter(Mandatory=$true)] [array] $PhysicalMachineNames, [Parameter(Mandatory=$false)] [System.Management.Automation.Runspaces.PSSession] $PsSession, [Parameter(Mandatory=$false)] [string] $ActiveDirectoryServer = $null, [Parameter(Mandatory=$false)] [pscredential] $ActiveDirectoryCredentials = $null ) $ErrorActionPreference = [System.Management.Automation.ActionPreference]::Stop Log-Info -Message "Executing Test-OrganizationalUnit" $fullTestResults = Test-OrganizationalUnitOnSession -ADOUPath $ADOUPath -DomainFQDN $DomainFQDN -NamingPrefix $NamingPrefix -ClusterName $ClusterName -Session $PsSession -ActiveDirectoryServer $ActiveDirectoryServer -ActiveDirectoryCredentials $ActiveDirectoryCredentials -PhysicalMachineNames $PhysicalMachineNames # Build the results $now = [datetime]::UtcNow $TargetComputerName = if ($PsSession.PSComputerName) { $PsSession.PSComputerName } else { $ENV:COMPUTERNAME } $aggregateStatus = if ($fullTestResults.Status -notcontains 'Failed') { 'Succeeded' } else { 'Failed' } $remediationValues = $fullTestResults | Where-Object -Property Status -NE 'Succeeded' | Select-Object $Remediation $remediationValues = $remediationValues -join "`r`n" if (-not $remediationValues) { $remediationValues = '' } $testOuResult = New-Object -Type OrganizationalUnitTestResult -Property @{ Name = 'AzStackHci_ExternalActiveDirectory_Test_OrganizationalUnit' Title = 'Test AD Organizational Unit' Severity = 'Critical' Description = 'Tests that the specified organizational unit exists and contains the proper sub-OUs' Tags = $null Remediation = 'https://learn.microsoft.com/en-us/azure-stack/hci/deploy/deployment-tool-active-directory' TargetResourceID = "Test_AD_OU_$TargetComputerName" TargetResourceName = "Test_AD_OU_$TargetComputerName" TargetResourceType = 'ActiveDirectory' Timestamp = $now Status = $aggregateStatus AdditionalData = $fullTestResults HealthCheckSource = $ENV:EnvChkrId } return $testOuResult } # SIG # Begin signature block # MIIoLQYJKoZIhvcNAQcCoIIoHjCCKBoCAQExDzANBglghkgBZQMEAgEFADB5Bgor # BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG # KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCC4WcvgbV6kiakE # lS32FsyWb0ckzYoB4MlbKKghhM1iZaCCDXYwggX0MIID3KADAgECAhMzAAADTrU8 # esGEb+srAAAAAANOMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMRMwEQYD # VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy # b3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25p # bmcgUENBIDIwMTEwHhcNMjMwMzE2MTg0MzI5WhcNMjQwMzE0MTg0MzI5WjB0MQsw # CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u # ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMR4wHAYDVQQDExVNaWNy # b3NvZnQgQ29ycG9yYXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB # AQDdCKiNI6IBFWuvJUmf6WdOJqZmIwYs5G7AJD5UbcL6tsC+EBPDbr36pFGo1bsU # p53nRyFYnncoMg8FK0d8jLlw0lgexDDr7gicf2zOBFWqfv/nSLwzJFNP5W03DF/1 # 1oZ12rSFqGlm+O46cRjTDFBpMRCZZGddZlRBjivby0eI1VgTD1TvAdfBYQe82fhm # WQkYR/lWmAK+vW/1+bO7jHaxXTNCxLIBW07F8PBjUcwFxxyfbe2mHB4h1L4U0Ofa # +HX/aREQ7SqYZz59sXM2ySOfvYyIjnqSO80NGBaz5DvzIG88J0+BNhOu2jl6Dfcq # jYQs1H/PMSQIK6E7lXDXSpXzAgMBAAGjggFzMIIBbzAfBgNVHSUEGDAWBgorBgEE # AYI3TAgBBggrBgEFBQcDAzAdBgNVHQ4EFgQUnMc7Zn/ukKBsBiWkwdNfsN5pdwAw # RQYDVR0RBD4wPKQ6MDgxHjAcBgNVBAsTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEW # MBQGA1UEBRMNMjMwMDEyKzUwMDUxNjAfBgNVHSMEGDAWgBRIbmTlUAXTgqoXNzci # tW2oynUClTBUBgNVHR8ETTBLMEmgR6BFhkNodHRwOi8vd3d3Lm1pY3Jvc29mdC5j # b20vcGtpb3BzL2NybC9NaWNDb2RTaWdQQ0EyMDExXzIwMTEtMDctMDguY3JsMGEG # CCsGAQUFBwEBBFUwUzBRBggrBgEFBQcwAoZFaHR0cDovL3d3dy5taWNyb3NvZnQu # Y29tL3BraW9wcy9jZXJ0cy9NaWNDb2RTaWdQQ0EyMDExXzIwMTEtMDctMDguY3J0 # MAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcNAQELBQADggIBAD21v9pHoLdBSNlFAjmk # mx4XxOZAPsVxxXbDyQv1+kGDe9XpgBnT1lXnx7JDpFMKBwAyIwdInmvhK9pGBa31 # TyeL3p7R2s0L8SABPPRJHAEk4NHpBXxHjm4TKjezAbSqqbgsy10Y7KApy+9UrKa2 # kGmsuASsk95PVm5vem7OmTs42vm0BJUU+JPQLg8Y/sdj3TtSfLYYZAaJwTAIgi7d # hzn5hatLo7Dhz+4T+MrFd+6LUa2U3zr97QwzDthx+RP9/RZnur4inzSQsG5DCVIM # pA1l2NWEA3KAca0tI2l6hQNYsaKL1kefdfHCrPxEry8onJjyGGv9YKoLv6AOO7Oh # JEmbQlz/xksYG2N/JSOJ+QqYpGTEuYFYVWain7He6jgb41JbpOGKDdE/b+V2q/gX # UgFe2gdwTpCDsvh8SMRoq1/BNXcr7iTAU38Vgr83iVtPYmFhZOVM0ULp/kKTVoir # IpP2KCxT4OekOctt8grYnhJ16QMjmMv5o53hjNFXOxigkQWYzUO+6w50g0FAeFa8 # 5ugCCB6lXEk21FFB1FdIHpjSQf+LP/W2OV/HfhC3uTPgKbRtXo83TZYEudooyZ/A # Vu08sibZ3MkGOJORLERNwKm2G7oqdOv4Qj8Z0JrGgMzj46NFKAxkLSpE5oHQYP1H # tPx1lPfD7iNSbJsP6LiUHXH1MIIHejCCBWKgAwIBAgIKYQ6Q0gAAAAAAAzANBgkq # hkiG9w0BAQsFADCBiDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24x # EDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlv # bjEyMDAGA1UEAxMpTWljcm9zb2Z0IFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5 # IDIwMTEwHhcNMTEwNzA4MjA1OTA5WhcNMjYwNzA4MjEwOTA5WjB+MQswCQYDVQQG # EwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwG # A1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSgwJgYDVQQDEx9NaWNyb3NvZnQg # Q29kZSBTaWduaW5nIFBDQSAyMDExMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIIC # CgKCAgEAq/D6chAcLq3YbqqCEE00uvK2WCGfQhsqa+laUKq4BjgaBEm6f8MMHt03 # a8YS2AvwOMKZBrDIOdUBFDFC04kNeWSHfpRgJGyvnkmc6Whe0t+bU7IKLMOv2akr # rnoJr9eWWcpgGgXpZnboMlImEi/nqwhQz7NEt13YxC4Ddato88tt8zpcoRb0Rrrg # OGSsbmQ1eKagYw8t00CT+OPeBw3VXHmlSSnnDb6gE3e+lD3v++MrWhAfTVYoonpy # 4BI6t0le2O3tQ5GD2Xuye4Yb2T6xjF3oiU+EGvKhL1nkkDstrjNYxbc+/jLTswM9 # sbKvkjh+0p2ALPVOVpEhNSXDOW5kf1O6nA+tGSOEy/S6A4aN91/w0FK/jJSHvMAh # dCVfGCi2zCcoOCWYOUo2z3yxkq4cI6epZuxhH2rhKEmdX4jiJV3TIUs+UsS1Vz8k # A/DRelsv1SPjcF0PUUZ3s/gA4bysAoJf28AVs70b1FVL5zmhD+kjSbwYuER8ReTB # w3J64HLnJN+/RpnF78IcV9uDjexNSTCnq47f7Fufr/zdsGbiwZeBe+3W7UvnSSmn # Eyimp31ngOaKYnhfsi+E11ecXL93KCjx7W3DKI8sj0A3T8HhhUSJxAlMxdSlQy90 # lfdu+HggWCwTXWCVmj5PM4TasIgX3p5O9JawvEagbJjS4NaIjAsCAwEAAaOCAe0w # ggHpMBAGCSsGAQQBgjcVAQQDAgEAMB0GA1UdDgQWBBRIbmTlUAXTgqoXNzcitW2o # ynUClTAZBgkrBgEEAYI3FAIEDB4KAFMAdQBiAEMAQTALBgNVHQ8EBAMCAYYwDwYD # VR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBRyLToCMZBDuRQFTuHqp8cx0SOJNDBa # BgNVHR8EUzBRME+gTaBLhklodHRwOi8vY3JsLm1pY3Jvc29mdC5jb20vcGtpL2Ny # bC9wcm9kdWN0cy9NaWNSb29DZXJBdXQyMDExXzIwMTFfMDNfMjIuY3JsMF4GCCsG # AQUFBwEBBFIwUDBOBggrBgEFBQcwAoZCaHR0cDovL3d3dy5taWNyb3NvZnQuY29t # L3BraS9jZXJ0cy9NaWNSb29DZXJBdXQyMDExXzIwMTFfMDNfMjIuY3J0MIGfBgNV # HSAEgZcwgZQwgZEGCSsGAQQBgjcuAzCBgzA/BggrBgEFBQcCARYzaHR0cDovL3d3 # dy5taWNyb3NvZnQuY29tL3BraW9wcy9kb2NzL3ByaW1hcnljcHMuaHRtMEAGCCsG # AQUFBwICMDQeMiAdAEwAZQBnAGEAbABfAHAAbwBsAGkAYwB5AF8AcwB0AGEAdABl # AG0AZQBuAHQALiAdMA0GCSqGSIb3DQEBCwUAA4ICAQBn8oalmOBUeRou09h0ZyKb # C5YR4WOSmUKWfdJ5DJDBZV8uLD74w3LRbYP+vj/oCso7v0epo/Np22O/IjWll11l # hJB9i0ZQVdgMknzSGksc8zxCi1LQsP1r4z4HLimb5j0bpdS1HXeUOeLpZMlEPXh6 # I/MTfaaQdION9MsmAkYqwooQu6SpBQyb7Wj6aC6VoCo/KmtYSWMfCWluWpiW5IP0 # wI/zRive/DvQvTXvbiWu5a8n7dDd8w6vmSiXmE0OPQvyCInWH8MyGOLwxS3OW560 # STkKxgrCxq2u5bLZ2xWIUUVYODJxJxp/sfQn+N4sOiBpmLJZiWhub6e3dMNABQam # ASooPoI/E01mC8CzTfXhj38cbxV9Rad25UAqZaPDXVJihsMdYzaXht/a8/jyFqGa # J+HNpZfQ7l1jQeNbB5yHPgZ3BtEGsXUfFL5hYbXw3MYbBL7fQccOKO7eZS/sl/ah # XJbYANahRr1Z85elCUtIEJmAH9AAKcWxm6U/RXceNcbSoqKfenoi+kiVH6v7RyOA # 9Z74v2u3S5fi63V4GuzqN5l5GEv/1rMjaHXmr/r8i+sLgOppO6/8MO0ETI7f33Vt # Y5E90Z1WTk+/gFcioXgRMiF670EKsT/7qMykXcGhiJtXcVZOSEXAQsmbdlsKgEhr # /Xmfwb1tbWrJUnMTDXpQzTGCGg0wghoJAgEBMIGVMH4xCzAJBgNVBAYTAlVTMRMw # EQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVN # aWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNp # Z25pbmcgUENBIDIwMTECEzMAAANOtTx6wYRv6ysAAAAAA04wDQYJYIZIAWUDBAIB # BQCgga4wGQYJKoZIhvcNAQkDMQwGCisGAQQBgjcCAQQwHAYKKwYBBAGCNwIBCzEO # MAwGCisGAQQBgjcCARUwLwYJKoZIhvcNAQkEMSIEIApXxOiu1/lFDJr3V8HRVJdq # 1Xm01CviEfVXg19HUuyCMEIGCisGAQQBgjcCAQwxNDAyoBSAEgBNAGkAYwByAG8A # cwBvAGYAdKEagBhodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20wDQYJKoZIhvcNAQEB # BQAEggEAqROXeN/XoKCEbLUkIINZtExEl953AqsQseQzANBsXY07Cy56HNTH8wBn # feM0P05kqirmQP9WJD+UOcMqn7M8Jy9dKBUbnM3xsEKbCdfq347Jepmsl12EeIN/ # x+3hNsiKS9hOkkl5IK1sJ4zGH3h1xjK1q+Eih2bG9mOd/4xurr1iakm6jCO8+LoH # 97TwpiXsz6a9xJoQJkaUTnPwwP+kDy8bzpr4hM3I/k/aFiStwGni2k08pR8suBwB # aeSm+Q8mYHb0p7AZhWjPk9QzKwx/pPnYHVKG3/qQr63Vb9sRwdHjZcuMgNE79IyP # RqLNI47GDSwNVMaHXK85QmVHFrbS+aGCF5cwgheTBgorBgEEAYI3AwMBMYIXgzCC # F38GCSqGSIb3DQEHAqCCF3AwghdsAgEDMQ8wDQYJYIZIAWUDBAIBBQAwggFSBgsq # hkiG9w0BCRABBKCCAUEEggE9MIIBOQIBAQYKKwYBBAGEWQoDATAxMA0GCWCGSAFl # AwQCAQUABCDiIfj57/hu1si3CVWvWFBeUmgNqGzByJDFEb18pCfdcAIGZQPkWMPD # GBMyMDIzMDkyMjA4MzEwNS4yNTNaMASAAgH0oIHRpIHOMIHLMQswCQYDVQQGEwJV # UzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UE # ChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSUwIwYDVQQLExxNaWNyb3NvZnQgQW1l # cmljYSBPcGVyYXRpb25zMScwJQYDVQQLEx5uU2hpZWxkIFRTUyBFU046OTIwMC0w # NUUwLUQ5NDcxJTAjBgNVBAMTHE1pY3Jvc29mdCBUaW1lLVN0YW1wIFNlcnZpY2Wg # ghHtMIIHIDCCBQigAwIBAgITMwAAAc9SNr5xS81IygABAAABzzANBgkqhkiG9w0B # AQsFADB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UE # BxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYD # VQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMDAeFw0yMzA1MjUxOTEy # MTFaFw0yNDAyMDExOTEyMTFaMIHLMQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2Fz # aGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENv # cnBvcmF0aW9uMSUwIwYDVQQLExxNaWNyb3NvZnQgQW1lcmljYSBPcGVyYXRpb25z # MScwJQYDVQQLEx5uU2hpZWxkIFRTUyBFU046OTIwMC0wNUUwLUQ5NDcxJTAjBgNV # BAMTHE1pY3Jvc29mdCBUaW1lLVN0YW1wIFNlcnZpY2UwggIiMA0GCSqGSIb3DQEB # AQUAA4ICDwAwggIKAoICAQC4Pct+15TYyrUje553lzBQodgmd5Bz7WuH8SdHpAoW # z+01TrHExBSuaMKnxvVMsyYtas5h6aopUGAS5WKVLZAvUtH62TKmAE0JK+i1hafi # CSXLZPcRexxeRkOqeZefLBzXp0nudMOXUUab333Ss8LkoK4l3LYxm1Ebsr3b2OTo # 2ebsAoNJ4kSxmVuPM7C+RDhGtVKR/EmHsQ9GcwGmluu54bqiVFd0oAFBbw4txTU1 # mruIGWP/i+sgiNqvdV/wah/QcrKiGlpWiOr9a5aGrJaPSQD2xgEDdPbrSflYxsRM # dZCJI8vzvOv6BluPcPPGGVLEaU7OszdYjK5f4Z5Su/lPK1eST5PC4RFsVcOiS4L0 # sI4IFZywIdDJHoKgdqWRp6Q5vEDk8kvZz6HWFnYLOlHuqMEYvQLr6OgooYU9z0A5 # cMLHEIHYV1xiaBzx2ERiRY9MUPWohh+TpZWEUZlUm/q9anXVRN0ujejm6OsUVFDs # sIMszRNCqEotJGwtHHm5xrCKuJkFr8GfwNelFl+XDoHXrQYL9zY7Np+frsTXQpKR # NnmI1ashcn5EC+wxUt/EZIskWzewEft0/+/0g3+8YtMkUdaQE5+8e7C8UMiXOHkM # K25jNNQqLCedlJwFIf9ir9SpMc72NR+1j6Uebiz/ZPV74do3jdVvq7DiPFlTb92U # KwIDAQABo4IBSTCCAUUwHQYDVR0OBBYEFDaeKPtp0eTSVdG+gZc5BDkabTg4MB8G # A1UdIwQYMBaAFJ+nFV0AXmJdg/Tl0mWnG1M1GelyMF8GA1UdHwRYMFYwVKBSoFCG # Tmh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2lvcHMvY3JsL01pY3Jvc29mdCUy # MFRpbWUtU3RhbXAlMjBQQ0ElMjAyMDEwKDEpLmNybDBsBggrBgEFBQcBAQRgMF4w # XAYIKwYBBQUHMAKGUGh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2lvcHMvY2Vy # dHMvTWljcm9zb2Z0JTIwVGltZS1TdGFtcCUyMFBDQSUyMDIwMTAoMSkuY3J0MAwG # A1UdEwEB/wQCMAAwFgYDVR0lAQH/BAwwCgYIKwYBBQUHAwgwDgYDVR0PAQH/BAQD # AgeAMA0GCSqGSIb3DQEBCwUAA4ICAQBQgm4pnA0xkd/9uKXJMzdMYyxUfUm/ZusU # Ba32MEZXQuMGp20pSuX2VW9/tpTMo5bkaJdBVoUyd2DbDsNb1kjr/36ntT0jvL3A # oWStAFhZBypmpPbx+BPK49ZlejlM4d5epX668tRRGfFip9Til9yKRfXBrXnM/q64 # IinN7zXEQ3FFQhdJMzt8ibXClO7eFA+1HiwZPWysYWPb/ZOFobPEMvXie+GmEbTK # bhE5tze6RrA9aejjP+v1ouFoD5bMj5Qg+wfZXqe+hfYKpMd8QOnQyez+Nlj1ityn # OZWfwHVR7dVwV0yLSlPT+yHIO8g+3fWiAwpoO17bDcntSZ7YOBljXrIgad4W4gX+ # 4tp1eBsc6XWIITPBNzxQDZZRxD4rXzOB6XRlEVJdYZQ8gbXOirg/dNvS2GxcR50Q # dOXDAumdEHaGNHb6y2InJadCPp2iT5QLC4MnzR+YZno1b8mWpCdOdRs9g21QbbrI # 06iLk9KD61nx7K5ReSucuS5Z9nbkIBaLUxDesFhr1wmd1ynf0HQ51Swryh7YI7TX # T0jr81mbvvI9xtoqjFvIhNBsICdCfTR91ylJTH8WtUlpDhEgSqWt3gzNLPTSvXAx # XTpIM583sZdd+/2YGADMeWmt8PuMce6GsIcLCOF2NiYZ10SXHZS5HRrLrChuzedD # RisWpIu5uTCCB3EwggVZoAMCAQICEzMAAAAVxedrngKbSZkAAAAAABUwDQYJKoZI # hvcNAQELBQAwgYgxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAw # DgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24x # MjAwBgNVBAMTKU1pY3Jvc29mdCBSb290IENlcnRpZmljYXRlIEF1dGhvcml0eSAy # MDEwMB4XDTIxMDkzMDE4MjIyNVoXDTMwMDkzMDE4MzIyNVowfDELMAkGA1UEBhMC # VVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNV # BAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRp # bWUtU3RhbXAgUENBIDIwMTAwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoIC # AQDk4aZM57RyIQt5osvXJHm9DtWC0/3unAcH0qlsTnXIyjVX9gF/bErg4r25Phdg # M/9cT8dm95VTcVrifkpa/rg2Z4VGIwy1jRPPdzLAEBjoYH1qUoNEt6aORmsHFPPF # dvWGUNzBRMhxXFExN6AKOG6N7dcP2CZTfDlhAnrEqv1yaa8dq6z2Nr41JmTamDu6 # GnszrYBbfowQHJ1S/rboYiXcag/PXfT+jlPP1uyFVk3v3byNpOORj7I5LFGc6XBp # Dco2LXCOMcg1KL3jtIckw+DJj361VI/c+gVVmG1oO5pGve2krnopN6zL64NF50Zu # yjLVwIYwXE8s4mKyzbnijYjklqwBSru+cakXW2dg3viSkR4dPf0gz3N9QZpGdc3E # XzTdEonW/aUgfX782Z5F37ZyL9t9X4C626p+Nuw2TPYrbqgSUei/BQOj0XOmTTd0 # lBw0gg/wEPK3Rxjtp+iZfD9M269ewvPV2HM9Q07BMzlMjgK8QmguEOqEUUbi0b1q # GFphAXPKZ6Je1yh2AuIzGHLXpyDwwvoSCtdjbwzJNmSLW6CmgyFdXzB0kZSU2LlQ # +QuJYfM2BjUYhEfb3BvR/bLUHMVr9lxSUV0S2yW6r1AFemzFER1y7435UsSFF5PA # PBXbGjfHCBUYP3irRbb1Hode2o+eFnJpxq57t7c+auIurQIDAQABo4IB3TCCAdkw # EgYJKwYBBAGCNxUBBAUCAwEAATAjBgkrBgEEAYI3FQIEFgQUKqdS/mTEmr6CkTxG # NSnPEP8vBO4wHQYDVR0OBBYEFJ+nFV0AXmJdg/Tl0mWnG1M1GelyMFwGA1UdIARV # MFMwUQYMKwYBBAGCN0yDfQEBMEEwPwYIKwYBBQUHAgEWM2h0dHA6Ly93d3cubWlj # cm9zb2Z0LmNvbS9wa2lvcHMvRG9jcy9SZXBvc2l0b3J5Lmh0bTATBgNVHSUEDDAK # BggrBgEFBQcDCDAZBgkrBgEEAYI3FAIEDB4KAFMAdQBiAEMAQTALBgNVHQ8EBAMC # AYYwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBTV9lbLj+iiXGJo0T2UkFvX # zpoYxDBWBgNVHR8ETzBNMEugSaBHhkVodHRwOi8vY3JsLm1pY3Jvc29mdC5jb20v # cGtpL2NybC9wcm9kdWN0cy9NaWNSb29DZXJBdXRfMjAxMC0wNi0yMy5jcmwwWgYI # KwYBBQUHAQEETjBMMEoGCCsGAQUFBzAChj5odHRwOi8vd3d3Lm1pY3Jvc29mdC5j # b20vcGtpL2NlcnRzL01pY1Jvb0NlckF1dF8yMDEwLTA2LTIzLmNydDANBgkqhkiG # 9w0BAQsFAAOCAgEAnVV9/Cqt4SwfZwExJFvhnnJL/Klv6lwUtj5OR2R4sQaTlz0x # M7U518JxNj/aZGx80HU5bbsPMeTCj/ts0aGUGCLu6WZnOlNN3Zi6th542DYunKmC # VgADsAW+iehp4LoJ7nvfam++Kctu2D9IdQHZGN5tggz1bSNU5HhTdSRXud2f8449 # xvNo32X2pFaq95W2KFUn0CS9QKC/GbYSEhFdPSfgQJY4rPf5KYnDvBewVIVCs/wM # nosZiefwC2qBwoEZQhlSdYo2wh3DYXMuLGt7bj8sCXgU6ZGyqVvfSaN0DLzskYDS # PeZKPmY7T7uG+jIa2Zb0j/aRAfbOxnT99kxybxCrdTDFNLB62FD+CljdQDzHVG2d # Y3RILLFORy3BFARxv2T5JL5zbcqOCb2zAVdJVGTZc9d/HltEAY5aGZFrDZ+kKNxn # GSgkujhLmm77IVRrakURR6nxt67I6IleT53S0Ex2tVdUCbFpAUR+fKFhbHP+Crvs # QWY9af3LwUFJfn6Tvsv4O+S3Fb+0zj6lMVGEvL8CwYKiexcdFYmNcP7ntdAoGokL # jzbaukz5m/8K6TT4JDVnK+ANuOaMmdbhIurwJ0I9JZTmdHRbatGePu1+oDEzfbzL # 6Xu/OHBE0ZDxyKs6ijoIYn/ZcGNTTY3ugm2lBRDBcQZqELQdVTNYs6FwZvKhggNQ # MIICOAIBATCB+aGB0aSBzjCByzELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hp # bmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jw # b3JhdGlvbjElMCMGA1UECxMcTWljcm9zb2Z0IEFtZXJpY2EgT3BlcmF0aW9uczEn # MCUGA1UECxMeblNoaWVsZCBUU1MgRVNOOjkyMDAtMDVFMC1EOTQ3MSUwIwYDVQQD # ExxNaWNyb3NvZnQgVGltZS1TdGFtcCBTZXJ2aWNloiMKAQEwBwYFKw4DAhoDFQDq # 8xzVXwLguauAQj1rrJ4/TyEMm6CBgzCBgKR+MHwxCzAJBgNVBAYTAlVTMRMwEQYD # VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy # b3NvZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1w # IFBDQSAyMDEwMA0GCSqGSIb3DQEBCwUAAgUA6LedETAiGA8yMDIzMDkyMjA0NTY0 # OVoYDzIwMjMwOTIzMDQ1NjQ5WjB3MD0GCisGAQQBhFkKBAExLzAtMAoCBQDot50R # AgEAMAoCAQACAgwuAgH/MAcCAQACAhQxMAoCBQDouO6RAgEAMDYGCisGAQQBhFkK # BAIxKDAmMAwGCisGAQQBhFkKAwKgCjAIAgEAAgMHoSChCjAIAgEAAgMBhqAwDQYJ # KoZIhvcNAQELBQADggEBAE40Ypp1XzCgGb7WdgwwKXpy4leusnN9apOq56K4r2tM # f4ameojoBYpxwIUwEvP7wDaLBM7DGn0VeN3hrY3y5ZuFdbp+B1b1iks+U4PFg5IS # uaOmJzpMBp7lzMiGnrMnDZV77p/Bcx5Y1QzM0fQjydi/SvGb0Reicz+cPZx0H+2C # li0jzwHwXXu57PmhpwCwm1lWyYHC2hqFjY6ENU3h1ArB2zO2D2GzPhLJDP3MGkNA # sJGZ35rWz1TQNWSmSeRebdEU9HxYenYWab1xNY7weggwmWI+wRVjQ8/D/FH4uk6o # nJ5E+crkJCCm0nTWc5kyXRUY0jke4kK49YIeAzm3bGoxggQNMIIECQIBATCBkzB8 # MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVk # bW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQDEx1N # aWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMAITMwAAAc9SNr5xS81IygABAAAB # zzANBglghkgBZQMEAgEFAKCCAUowGgYJKoZIhvcNAQkDMQ0GCyqGSIb3DQEJEAEE # MC8GCSqGSIb3DQEJBDEiBCAUcEoQ6AXCIc2Eobdi+zbjmcA0by0rWrpmGg4AXuP/ # VTCB+gYLKoZIhvcNAQkQAi8xgeowgecwgeQwgb0EILPpsLqeNS4NuYXE2VJlMuvQ # eWVA80ZDFhpOPjSzhPa/MIGYMIGApH4wfDELMAkGA1UEBhMCVVMxEzARBgNVBAgT # Cldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29m # dCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENB # IDIwMTACEzMAAAHPUja+cUvNSMoAAQAAAc8wIgQgwYErpH3GWsKS0Phe/FGbIvgq # zcTrp0DwEm5R13B+KIMwDQYJKoZIhvcNAQELBQAEggIAP91a2MmHlc+OM4iq4i2q # k9WUf2BNQZoXmf+Ej6h59/pMkK8lozVbNSpwZpTR2DYyEstvbxzODPSEeisHG47X # 0MT1jPINuNNgLwj91DK5aHHrzbsd0DrRFW+DLVrCJijQTtV3FmAPGEiXZr1R3/LE # zsSQPkAHMOMD28Gs9uMLBlOdWEjinF4iFKHyO6RR1a4gOJw07GOdpvPB4IG0TBgc # hpNbgaqrgd/hyk1Tv7vChIP2Edtv7lHuJK6JU13WMMeV6V/0IHSmSf8tp5QQm081 # m1Zh1VxTXUhGXNMSaSBgHI8mFj67jW7RjpV4eILcECdDCPjZAwJg7IKAaSZfoAMd # 1gwO6wljzBCYl/8in3DZsDqWRh/xYQAY+dkYg7+GUiM/pRj9PyZICThok7I9f/1p # Q1VFrP5zAHO4NHpprXOls+9vLUAJRwWBiRChu/zeEwe1TvJLqnaIdKj2euaANr7S # gb2RqrG1xS5v66lTHSIoqzms6tEyZW5Fmpm8DC31aB5X5KOA7++rn0SIEcMUoszv # zUNwrNPHOn4KmAce+XmEhFDvVyIwchpgB8AGZmVN6vSvpqCd8pgkBwvkLT7zyXnb # YKiwmTFtdOyxvMWe97KMoga3G5kDAamkzuhi5WBKtZl9CSUoyNOy/Cns1p21zBxe # g7C+behWMwGjxCiD3vQD6Tw= # SIG # End signature block |