ArcHci.psm1
######################################################################################### # # Copyright (c) Microsoft Corporation. All rights reserved. # # ARC appliance self-service VM Day 0/2 Operations # ######################################################################################### #requires -runasadministrator using module moc # Imports Common.psm1 from Moc module # this also imports $global:defaultworkingDir Import-Module ((Get-Module "MOC" -ListAvailable | Sort-Object Version -Descending)[0].ModuleBase + "\common.psm1") $moduleName = "ArcHci" $global:ArcHciModule = $moduleName $defaultTokenExpiryDays = 60 $global:kvaCtlFullPath = "C:\Program Files\AksHci\kvactl.exe" $global:sshPrivateKeyFile = "C:\ProgramData\kva\.ssh\logkey" New-ModuleEventLog -moduleName $script:moduleName function Reset-ArcHciConfigurationKey { <# .DESCRIPTION Resets the ARC HCI module configuration key #> $global:configurationKeys[$global:ArcHciModule] = "HKLM:SOFTWARE\Microsoft\${global:ArcHciModule}PS"; } if (!$global:config) { $global:config = @{} } function Initialize-ArcHciConfiguration { <# .DESCRIPTION Initialize ARC HCI Configuration #> Reset-ArcHciConfigurationKey if (-Not $global:config.ContainsKey($global:ArcHciModule)) { $global:config += @{ $global:ArcHciModule = @{ "workingDir" = $global:defaultworkingDir }; } } if (-Not (Test-Configuration -moduleName $global:ArcHciModule)) { Set-ConfigurationValue -name "workingDir" -value $global:defaultworkingDir -module $global:ArcHciModule Save-ConfigurationDirectory -moduleName $global:ArcHciModule -WorkingDir $global:defaultworkingDir Save-Configuration -moduleName $global:ArcHciModule } } Initialize-ArcHciConfiguration function Uninitialize-ArcHciConfiguration { <# .DESCRIPTION Uninitializes ARC HCI Configuration Wipes off any existing cached configuration #> if ($global:config.ContainsKey($global:ArcHciModule)) { $global:config.Remove($global:ArcHciModule) } } function Get-ArcHciConfig { <# .DESCRIPTION Loads and returns the current ARC HCI configuration. #> Import-ArcHciConfig return $global:config[$global:ArcHciModule] } function Get-ArcHciConfigValue { <# .DESCRIPTION Reads a configuration value from the registry .PARAMETER name Name of the configuration value #> param ( [String] $name ) return Get-ConfigurationValue -name $name -module $global:ArcHciModule } function Import-ArcHciConfig { <# .DESCRIPTION Loads a configuration from persisted storage. If no configuration is present then a default configuration can be optionally generated and persisted. #> Reset-ArcHciConfigurationKey if (Test-Configuration -moduleName $global:ArcHciModule) { Import-Configuration -moduleName $global:ArcHciModule } else { throw "ARCHCI doesn't currently have a config file. Please make sure to reload all powershell windows before continuing" } } function Set-ArcHciConfigValue { <# .DESCRIPTION Persists a configuration value to the registry .PARAMETER name Name of the configuration value .PARAMETER value Value to be persisted #> param ( [String] $name, [Object] $value ) Reset-ArcHciConfigurationKey Set-ConfigurationValue -name $name -value $value -module $global:ArcHciModule } function Set-ArcHciConfig { <# .DESCRIPTION Configures ARCHCI by persisting the specified parameters to the registry. Any parameter which is not explictly provided by the user will be defaulted. .PARAMETER workingDir Path to the working directory #> [CmdletBinding()] param ( [String] $workingDir = $global:defaultworkingDir ) Reset-ArcHciConfigurationKey Set-ConfigurationValue -name "workingDir" -value $workingDir -module $global:ArcHciModule Save-ConfigurationDirectory -moduleName $global:ArcHciModule -WorkingDir $workingDir Save-Configuration -moduleName $global:ArcHciModule } function New-MocNetworkSetting { <# .DESCRIPTION Create network settings to be used for the Arc Hci deployment. .PARAMETER name The name of the vnet .PARAMETER vswitchName The name of the vswitch .PARAMETER MacPoolName The name of the mac pool .PARAMETER vlanID The VLAN ID for the vnet .PARAMETER ipaddressprefix The address prefix to use for static IP assignment .PARAMETER gateway The gateway to use when using static IP .PARAMETER dnsservers The dnsservers to use when using static IP .PARAMETER vipPoolStart Beginning of the IP address pool .PARAMETER vipPoolEnd End of the IP address pool .PARAMETER k8snodeippoolstart The starting ip address to use for VM's in the cluster. .PARAMETER k8snodeippoolend The ending ip address to use for VM's in the cluster. .OUTPUTS VirtualNetwork object .EXAMPLE New-MocNetworkSetting -name "External" -vipPoolStart "172.16.0.240" -vipPoolEnd "172.16.0.250" #> param ( [Parameter(Mandatory=$true)] [string] $name, [Parameter(Mandatory=$true)] [string] $vswitchName, [Parameter(Mandatory=$false)] [String] $MacPoolName = $global:cloudMacPool, [Parameter(Mandatory=$false)] [int] $vlanID = $global:defaultVlanID, [Parameter(Mandatory=$false)] [String] $ipaddressprefix, [Parameter(Mandatory=$false)] [String] $gateway, [Parameter(Mandatory=$false)] [String[]] $dnsservers, [Parameter(Mandatory=$true)] [String] $vipPoolStart, [Parameter(Mandatory=$true)] [String] $vipPoolEnd, [Parameter(Mandatory=$false)] [String] $k8snodeippoolstart, [Parameter(Mandatory=$false)] [String] $k8snodeippoolend ) return New-VirtualNetwork -name $name -vswitchName $vswitchName -MacPoolName $MacPoolName -vlanID $vlanID -ipaddressprefix $ipaddressprefix -gateway $gateway -dnsservers $dnsservers -vippoolstart $vippoolstart -vippoolend $vippoolend -k8snodeippoolstart $k8snodeippoolstart -k8snodeippoolend $k8snodeippoolend } function New-ArcHciMocTokenFile { <# .DESCRIPTION Creates a new MOC Admin Identity for the Appliance and writes it to the given file path. .PARAMETER arcTokenFilePath Optional parameter. Path to the file where the arc token would be output. #> Param ( [Parameter(Mandatory=$false)] [String] $arcTokenFilePath = ($(Get-Location).Path + "\kvatoken.tok") ) $clusterName = "Appliance" try { Remove-MocIdentity -name $clusterName } catch { } $base64Identity = New-MocAdminIdentity -name $clusterName -validityDays $defaultTokenExpiryDays $utf8String = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($base64Identity)) $Utf8NoBomEncoding = New-Object System.Text.UTF8Encoding $False [System.IO.File]::WriteAllLines($arcTokenFilePath, $utf8String, $Utf8NoBomEncoding) Write-Output "HCI login file successfully generated in '$arcTokenFilePath'" } function New-ArcHciIdentityFiles { <# .DESCRIPTION Creates the Arc HCI token files .PARAMETER workDirectory Optional parameter. Path to the work directory (optional parameter. Defaults to the local directory.) Please set this parameter to a shared storage location if you are running in an HCI cluster. .PARAMETER azstackhciImage Optional parameter. Name of the azstackhci-operator image to use in place of the default image. .PARAMETER azstackhciVersion Optional parameter. Version of the azstackhci-operator image to use in place of the default image. .PARAMETER mocImage Optional parameter. Name of the moc-operator image to use in place of the default image. .PARAMETER mocVersion Optional parameter. Version of the moc-operator image to use in place of the default image. .PARAMETER mocConfigFilePath Optional parameter. Path to the hci-config.json file. .OUTPUTS N/A .EXAMPLE New-ArcHciIdentityFiles #> Param ( [Parameter(Mandatory=$false)] [String] $workDirectory = $(Get-ArcHciConfigValue -name "workingDir"), [Parameter(Mandatory=$false)] [String] $azstackhciImage, [Parameter(Mandatory=$false)] [String] $azstackhciVersion, [Parameter(Mandatory=$false)] [String] $mocImage, [Parameter(Mandatory=$false)] [String] $mocVersion, [Parameter(Mandatory=$false)] [String] $mocConfigFilePath = ($workDirectory + "\hci-config.json") ) $mocConfig = Get-MocConfig $mocoperator = "moc-operator" try { Remove-MocIdentity -name $mocoperator } catch { } $mocOperatorIdentity = New-MocIdentity -name $mocoperator -validityDays $defaultTokenExpiryDays -fqdn $mocConfig.cloudFqdn -location $mocConfig.cloudLocation -port $mocConfig.cloudAgentPort -authport $mocConfig.cloudAgentAuthorizerPort -encode New-MocRoleAssignmentWhenAvailable -identityName $mocoperator -roleName "Contributor" -location $mocConfig.cloudLocation | Out-Null $configData = @{} $configData["secret.loginString"] = $mocOperatorIdentity $configData["secret.cloudFQDN"] = $mocConfig.cloudFqdn if ($azstackhciImage) { $configData["azstackhciOperator.image"] = $azstackhciImage } if ($azstackhciVersion) { $configData["azstackhciOperator.version"] = $azstackhciVersion } if ($mocImage) { $configData["mocOperator.image"] = $mocImage } if ($mocVersion) { $configData["mocOperator.version"] = $mocVersion } $jsonData = convertto-json $configData Remove-Item -Path $mocConfigFilePath -Force -ErrorAction Ignore Set-Content -Path $mocConfigFilePath -Value $jsonData -ErrorVariable err Write-Output "MOC config file successfully generated in '$mocConfigFilePath'" Write-Output "Cloud agent service FQDN/IP: '$($mocConfig.cloudFqdn)'" } function New-ArcHciConfigFiles { <# .DESCRIPTION Creates the Arc HCI config files .PARAMETER subscriptionID The Azure subscription GUID .PARAMETER location Azure location (optional parameter. defaults to "eastus") .PARAMETER resourceGroup Name of the Azure resource group in which the Arc HCI appliance will be created .PARAMETER resourceName Name of the Azure resource .PARAMETER controlPlaneIP IP Address to be used for the Arc Appliance control plane .PARAMETER azstackhciImage Optional parameter. Name of the azstackhci-operator image to use in place of the default image. .PARAMETER azstackhciVersion Optional parameter. Version of the azstackhci-operator image to use in place of the default image. .PARAMETER mocImage Optional parameter. Name of the moc-operator image to use in place of the default image. .PARAMETER mocVersion Optional parameter. Version of the moc-operator image to use in place of the default image. .PARAMETER cloudFqdn Optional parameter. Fully qualified domain name (FQDN) or IP address of the cloud agent service. .PARAMETER vnetName Optional parameter. Name of the virtual network the ARC resource bridge will connect to. The vnet will be automatically created if it doesn't exist. NOTE: this name must be all lower characters. .PARAMETER vswitchName Optional parameter. Name of the virtual switch the ARC resource bridge will connect to. On HCI, this virtual switch must be an external virtual switch. .PARAMETER vippoolstart The starting ip address to use for the vip pool. The vip pool addresses will be used by the k8s API server and k8s services .PARAMETER vippoolend The ending ip address to use for the vip pool. The vip pool addresses will be used by the k8s API server and k8s services .PARAMETER ipaddressprefix The address prefix to use for static IP assignment .PARAMETER gateway The gateway to use when using static IP .PARAMETER dnsservers The dnsservers to use when using static IP .PARAMETER k8snodeippoolstart The starting ip address to use for VM's in the cluster. .PARAMETER k8snodeippoolend The ending ip address to use for VM's in the cluster. .PARAMETER vlanID The VLAN ID for the vnet .PARAMETER proxyServerHTTP http urls for proxy .PARAMETER proxyServerHTTPS https urls for proxy .PARAMETER proxyServerNoProxy urls which can bypass proxy .PARAMETER certificateFilePath Name of the cert File Path for proxy .PARAMETER proxyServerUsername Username for proxy authentication .PARAMETER proxyServerPassword Password for proxy authentication .PARAMETER workDirectory Path to the work directory (optional parameter. Defaults to the local directory.) Please set this parameter to a shared storage location if you are running in an HCI cluster. .OUTPUTS N/A .EXAMPLE New-ArcHciConfigFiles -subscriptionID "12345678-4567-1234-8888-c64fc26bd67e" -location "eastus" -resourceGroup "MyResourceGroup" -resourceName "MyAppliance" #Example if the proxy params are provided New-ArcHciConfigFiles -subscriptionID "12345678-4567-1234-8888-c64fc26bd67e" -location "eastus" -resourceGroup "MyResourceGroup" -resourceName "MyAppliance" -proxyServerHTTP "http://myproxy:8080" -proxyServerHTTPS "https://myproxy:8080" -proxyServerNoProxy "localhost,127.0.0.1" -certificateFilePath "C:\Proxy.cert -PlaceholderUsername "PlaceholderUsername" -PlaceholderPassword "PlaceholderPassword"" #> Param ( [Parameter(Mandatory=$true)] [String] $subscriptionID, [Parameter(Mandatory=$false)] [String] $location = "eastus", [Parameter(Mandatory=$true)] [String] $resourceGroup, [Parameter(Mandatory=$true)] [String] $resourceName, [Parameter(Mandatory=$false)] [String] $controlPlaneIP, [Parameter(Mandatory=$false)] [String] $azstackhciImage, [Parameter(Mandatory=$false)] [String] $azstackhciVersion, [Parameter(Mandatory=$false)] [String] $mocImage, [Parameter(Mandatory=$false)] [String] $mocVersion, [Parameter(Mandatory=$false)] [String] $cloudFqdn, [Parameter(Mandatory=$false)] [String] $vnetName, [Parameter(Mandatory=$false)] [String] $vswitchName, [Parameter(Mandatory=$false)] [String] $vippoolstart, [Parameter(Mandatory=$false)] [String] $vippoolend, [Parameter(Mandatory=$false)] [String] $ipaddressprefix, [Parameter(Mandatory=$false)] [String] $gateway, [Parameter(Mandatory=$false)] [String[]] $dnsservers, [Parameter(Mandatory=$false)] [String] $k8snodeippoolstart, [Parameter(Mandatory=$false)] [String] $k8snodeippoolend, [Parameter(Mandatory=$false)] [String] $vlanID, [Parameter(Mandatory=$false)] [String] $proxyServerHTTP, [Parameter(Mandatory=$false)] [String] $proxyServerHTTPS, [Parameter(Mandatory=$false)] [String] $proxyServerNoProxy, [Parameter(Mandatory=$false)] [String] $certificateFilePath, [Parameter(Mandatory=$false)] [String] $proxyServerUsername, [Parameter(Mandatory=$false)] [String] $proxyServerPassword, [Parameter(Mandatory=$false)] [String] $workDirectory = $global:defaultworkingDir ) Test-HCIMachineRequirements New-ArcHciConfigFilesInternal -subscriptionID $subscriptionID -location $location -resourceGroup $resourceGroup -resourceName $resourceName ` -azstackhciImage $azstackhciImage -azstackhciVersion $azstackhciVersion -mocImage $mocImage -mocVersion $mocVersion -controlPlaneIP $controlPlaneIP -workDirectory $workDirectory ` -cloudFqdn $cloudFqdn -vnetName $vnetName -vswitchName $vswitchName -vippoolstart $vippoolstart -vippoolend $vippoolend -gateway $gateway -dnsservers $dnsservers -ipaddressprefix $ipaddressprefix -k8snodeippoolstart $k8snodeippoolstart -k8snodeippoolend $k8snodeippoolend -vlanID $vlanID ` -proxyServerHTTP $proxyServerHTTP -proxyServerHTTPS $proxyServerHTTPS -proxyServerNoProxy $proxyServerNoProxy -certificateFilePath $certificateFilePath -proxyServerUsername $proxyServerUsername -proxyServerPassword $proxyServerPassword } function New-ArcHciConfigFilesInternal { <# .DESCRIPTION Creates the Arc HCI config files Documentation: See 'New-ArcHciAksConfigFiles' and 'New-ArcHciIdentityFiles' #> Param ( [Parameter(Mandatory=$true)] [String] $subscriptionID, [Parameter(Mandatory=$false)] [String] $location = "eastus", [Parameter(Mandatory=$true)] [String] $resourceGroup, [Parameter(Mandatory=$true)] [String] $resourceName, [Parameter(Mandatory=$false)] [String] $controlPlaneIP, [Parameter(Mandatory=$false)] [String] $azstackhciImage, [Parameter(Mandatory=$false)] [String] $azstackhciVersion, [Parameter(Mandatory=$false)] [String] $mocImage, [Parameter(Mandatory=$false)] [String] $mocVersion, [Parameter(Mandatory=$false)] [String] $cloudFqdn, [Parameter(Mandatory=$false)] [String] $vnetName, [Parameter(Mandatory=$false)] [String] $vswitchName, [Parameter(Mandatory=$false)] [String] $vippoolstart, [Parameter(Mandatory=$false)] [String] $vippoolend, [Parameter(Mandatory=$false)] [String] $ipaddressprefix, [Parameter(Mandatory=$false)] [String] $gateway, [Parameter(Mandatory=$false)] [String[]] $dnsservers, [Parameter(Mandatory=$false)] [String] $k8snodeippoolstart, [Parameter(Mandatory=$false)] [String] $k8snodeippoolend, [Parameter(Mandatory=$false)] [String] $vlanID, [Parameter(Mandatory=$false)] [String] $proxyServerHTTP, [Parameter(Mandatory=$false)] [String] $proxyServerHTTPS, [Parameter(Mandatory=$false)] [String] $proxyServerNoProxy, [Parameter(Mandatory=$false)] [String] $certificateFilePath, [Parameter(Mandatory=$false)] [String] $proxyServerUsername, [Parameter(Mandatory=$false)] [String] $proxyServerPassword, [Parameter(Mandatory=$false)] [String] $workDirectory = $global:defaultworkingDir ) New-ArcHciIdentityFiles -azstackhciImage $azstackhciImage -azstackhciVersion $azstackhciVersion -mocImage $mocImage -mocVersion $mocVersion -workDirectory $workDirectory New-ArcHciAksConfigFiles -subscriptionID $subscriptionID -location $location -resourceGroup $resourceGroup -resourceName $resourceName -workDirectory $workDirectory -controlPlaneIP $controlPlaneIP ` -cloudFqdn $cloudFqdn -vnetName $vnetName -vswitchName $vswitchName -vippoolstart $vippoolstart -vippoolend $vippoolend -gateway $gateway -dnsservers $dnsservers -ipaddressprefix $ipaddressprefix -k8snodeippoolstart $k8snodeippoolstart -k8snodeippoolend $k8snodeippoolend -vlanID $vlanID ` -proxyServerHTTP $proxyServerHTTP -proxyServerHTTPS $proxyServerHTTPS -proxyServerNoProxy $proxyServerNoProxy -certificateFilePath $certificateFilePath -proxyServerUsername $proxyServerUsername -proxyServerPassword $proxyServerPassword } function New-ArcHciAksConfigFiles { <# .DESCRIPTION Creates the Arc Appliance config files .PARAMETER subscriptionID The Azure subscription GUID .PARAMETER location Azure location (optional parameter. defaults to "eastus") .PARAMETER resourceGroup Name of the Azure resource group in which the Arc HCI appliance will be created .PARAMETER resourceName Name of the Azure resource .PARAMETER controlPlaneIP IP Address to be used for the Arc Appliance control plane .PARAMETER cloudFqdn Optional parameter. Fully qualified domain name (FQDN) or IP address of the cloud agent service. .PARAMETER vnetName Optional parameter. Name of the virtual network the ARC resource bridge will connect to. The vnet will be automatically created if it doesn't exist. NOTE: this name must be all lower characters. .PARAMETER vswitchName Optional parameter. Name of the virtual switch the ARC resource bridge will connect to. On HCI, this virtual switch must be an external virtual switch. .PARAMETER vippoolstart The starting ip address to use for the vip pool. The vip pool addresses will be used by the k8s API server and k8s services .PARAMETER vippoolend The ending ip address to use for the vip pool. The vip pool addresses will be used by the k8s API server and k8s services .PARAMETER ipaddressprefix The address prefix to use for static IP assignment .PARAMETER gateway The gateway to use when using static IP .PARAMETER dnsservers The dnsservers to use when using static IP .PARAMETER k8snodeippoolstart The starting ip address to use for VM's in the cluster. .PARAMETER k8snodeippoolend The ending ip address to use for VM's in the cluster. .PARAMETER vlanID The VLAN ID for the vnet .PARAMETER workDirectory Path to the work directory (optional parameter. Defaults to the local directory.) Please set this parameter to a shared storage location if you are running in an HCI cluster. .PARAMETER proxyServerHTTP http urls for proxy server .PARAMETER proxyServerHTTPS https urls for proxy server .PARAMETER proxyServerNoProxy urls which can bypass proxy server .PARAMETER certificateFilePath Name of the cert File Path for proxy .PARAMETER proxyServerUsername Username for proxy authentication .PARAMETER proxyServerPassword Password for proxy authentication .OUTPUTS N/A .EXAMPLE New-ArcHciAksConfigFiles -subscriptionID "12345678-4567-1234-8888-c64fc26bd67e" -location "eastus" -resourceGroup "MyResourceGroup" -resourceName "MyAppliance" #> Param ( [Parameter(Mandatory=$true)] [String] $subscriptionID, [Parameter(Mandatory=$false)] [String] $location, [Parameter(Mandatory=$true)] [String] $resourceGroup, [Parameter(Mandatory=$true)] [String] $resourceName, [Parameter(Mandatory=$false)] [String] $controlPlaneIP, [Parameter(Mandatory=$false)] [String] $cloudFqdn, [Parameter(Mandatory=$false)] [String] $vnetName, [Parameter(Mandatory=$false)] [String] $vswitchName, [Parameter(Mandatory=$false)] [String] $vippoolstart, [Parameter(Mandatory=$false)] [String] $vippoolend, [Parameter(Mandatory=$false)] [String] $ipaddressprefix, [Parameter(Mandatory=$false)] [String] $gateway, [Parameter(Mandatory=$false)] [String[]] $dnsservers, [Parameter(Mandatory=$false)] [String] $k8snodeippoolstart, [Parameter(Mandatory=$false)] [String] $k8snodeippoolend, [Parameter(Mandatory=$false)] [String] $vlanID, [Parameter(Mandatory=$false)] [String] $proxyServerHTTP, [Parameter(Mandatory=$false)] [String] $proxyServerHTTPS, [Parameter(Mandatory=$false)] [String] $proxyServerNoProxy, [Parameter(Mandatory=$false)] [String] $certificateFilePath, [Parameter(Mandatory=$false)] [String] $proxyServerUsername, [Parameter(Mandatory=$false)] [String] $proxyServerPassword, [Parameter(Mandatory=$false)] [String] $workDirectory = $global:defaultworkingDir ) Set-ArcHciConfig -workingDir $workDirectory Set-ArcHciConfigValue -name "configFileLocation" -Value $workDirectory $arcTokenFilePath = Join-Path -Path $workDirectory -ChildPath "kvatoken.tok" Set-ArcHciConfigValue -name "kvaTokenLocation" -Value $arcTokenFilePath New-ArcHciMocTokenFile -arcTokenFilePath $arcTokenFilePath Write-Output "Generating ARC HCI configuration files..." $mocConfig = Get-MocConfig if([string]::IsNullOrEmpty($controlPlaneIP)) { $controlPlaneIP = $mocConfig.vnetvippoolend } $kubectlLoc = $mocConfig.installationPackageDir $oldPath = $($Env:PATH) if (-Not $oldPath.Split(';').Contains($kubectlLoc)) { $Env:PATH="$($Env:PATH);$kubectlLoc" } $kubeFolder = Join-Path -Path $Env:USERPROFILE -ChildPath ".kube" New-Item -ItemType Directory $kubeFolder -Force | Out-Null if([string]::IsNullOrEmpty($cloudFqdn)) { $cloudFqdn = $mocConfig.cloudFqdn } if([string]::IsNullOrEmpty($vnetName)) { $vnetName = $mocConfig.vnetName } if([string]::IsNullOrEmpty($vswitchName)) { $vswitchName = $mocConfig.vswitchName } if([string]::IsNullOrEmpty($vippoolstart) -or [string]::IsNullOrEmpty($vippoolend)) { $vippoolstart = $mocConfig.vnetvippoolstart $vippoolend = $mocConfig.vnetvippoolend } if([string]::IsNullOrEmpty($ipaddressprefix)) { $ipaddressprefix = $mocConfig.ipaddressprefix } if($dnsservers -eq $null -or $dnsservers.count -eq 0) { $dnsservers = $mocConfig.dnsservers.Split(",") } if([string]::IsNullOrEmpty($gateway)) { $gateway = $mocConfig.gateway } if([string]::IsNullOrEmpty($k8snodeippoolstart) -or [string]::IsNullOrEmpty($k8snodeippoolend)) { $k8snodeippoolstart = $mocConfig.k8snodeippoolstart $k8snodeippoolend = $mocConfig.k8snodeippoolend } if([string]::IsNullOrEmpty($vlanID)) { $vlanID = $mocConfig.vlanid } Set-ArcHciConfigValue -name "controlPlaneIP" -Value $controlPlaneIP Set-ArcHciConfigValue -name "cloudFqdn" -Value $cloudFqdn Set-ArcHciConfigValue -name "vswitchName" -Value $vswitchName Set-ArcHciConfigValue -name "vippoolstart" -Value $vippoolstart Set-ArcHciConfigValue -name "vippoolend" -Value $vippoolend Set-ArcHciConfigValue -name "ipaddressprefix" -Value $ipaddressprefix Set-ArcHciConfigValue -name "dnsservers" -Value $dnsservers Set-ArcHciConfigValue -name "gateway" -Value $gateway Set-ArcHciConfigValue -name "k8snodeippoolstart" -Value $k8snodeippoolstart Set-ArcHciConfigValue -name "k8snodeippoolend" -Value $k8snodeippoolend Set-ArcHciConfigValue -name "vlanID" -Value $vlanID $dnsserver = "" if(-Not [string]::IsNullOrEmpty($ipaddressprefix)) { foreach ($dns in $dnsservers) { $dns = $dns.Trim(" ") if(-Not [string]::IsNullOrEmpty($dns)) { $dnsserver += "`n - " + $dns } } } $yaml = @" # hci-appliance.yaml [config file] # Absolute path to vCenter/HCI/other Fabric/infra specific credentials and configuration details infrastructureConfigPath: "$($workDirectory.Replace("\","\\"))\\hci-infra.yaml" # Specify admin cluster settings applianceClusterConfig: # Used to connect to the Kubernetes API controlPlaneEndpoint: "$controlPlaneIP" "@ # if proxy params are provided publish networking block in appliance yaml if ((-Not [string]::IsNullOrEmpty($proxyServerHTTP)) -or (-Not [string]::IsNullOrEmpty($proxyServerHTTPS))) { $yaml += @" `n networking: proxy: certificateFilePath: "$($certificateFilePath.Replace("\","\\"))" "@ Set-ArcHciConfigValue -name "certificateFilePath" -Value $certificateFilePath #Appliance require authentication present within the url itself. #For example: http: "http://username:password@contoso.com:3128" if (($proxyServerUsername) -and ($proxyServerPassword)) { $proxyString += ($proxyServerUsername + ":" + $proxyServerPassword + "@") $httpProxyString = "http://" + $proxyString + $proxyServerHTTP.Substring(7) $httpsProxyString = "https://" + $proxyString + $proxyServerHTTPS.Substring(8) $yaml += @" `n http: "$httpProxyString" https: "$httpsProxyString" "@ Set-ArcHciConfigValue -name "proxyServerHTTP" -Value $httpProxyString Set-ArcHciConfigValue -name "proxyServerHTTPS" -Value $httpsProxyString } else{ $yaml += @" `n http: "$proxyServerHTTP" https: "$proxyServerHTTPS" "@ #persist values in registry Set-ArcHciConfigValue -name "proxyServerHTTP" - Value $proxyServerHTTP Set-ArcHciConfigValue -name "proxyServerHTTPS" -Value $proxyServerHTTPS } $yaml += @" `n noproxy: "$proxyServerNoProxy" "@ Set-ArcHciConfigValue -name "proxyServerNoProxy" -Value $proxyServerNoProxy } $yaml += @" `n # Relative or absolute path to Arc Appliance ARM resource definition to be created in Azure applianceResourceFilePath: "$($workDirectory.Replace("\","\\"))\\hci-resource.yaml" "@ $yamlFile = "$($workDirectory)\hci-appliance.yaml" Remove-Item -Path $yamlFile -Force -ErrorAction Ignore Set-Content -NoNewline -Path $yamlFile -Value $yaml -ErrorVariable err $yaml = @" # hci-infra.yaml [as referenced in file above] # infra file azurestackhciprovider: # Cloud agent configuration cloudagent: # Address of machine on which HCI Environment is deployed address: "$cloudFqdn" # port and authenticationport port: 55000 authenticationport: 65000 # Relative path to the cloudconfig file to access HCI Environment loginconfigfile: "$($arcTokenFilePath.Replace("\","\\"))" location: MocLocation group: management storagecontainer: MocStorageContainer virtualnetwork: name: "$vnetname" vswitchname: "$vswitchName" "@ if (-Not [string]::IsNullOrEmpty($vlanID)) { $yaml += @" `n vlanid: $($vlanID) "@ } if (-Not [string]::IsNullOrEmpty($vippoolstart)) { $yaml += @" `n vippoolstart: $vippoolstart vippoolend: $vippoolend "@ } if (-Not [string]::IsNullOrEmpty($ipaddressprefix)) { $yaml += @" `n ipaddressprefix: $ipaddressprefix gateway: $gateway dnsservers: $dnsserver k8snodeippoolstart: $k8snodeippoolstart k8snodeippoolend: $k8snodeippoolend "@ } $yaml += @" `n appliancevm: vmsize: Standard_A4_v2 download: workingdirectory: "$($workDirectory.Replace("\","\\"))" "@ $yamlFile = "$($workDirectory)\hci-infra.yaml" Remove-Item -Path $yamlFile -Force -ErrorAction Ignore Set-Content -Path $yamlFile -Value $yaml -ErrorVariable err $yaml = @" # hci-resource.yaml [as reference in hci-appliance.yaml above] # resource file resource: # Resource group must exist resource_group: $resourceGroup # Resource name name: $resourceName # Location location: $location # Subscription should match CLI context subscription: $subscriptionID "@ $yamlFile = "$($workDirectory)\hci-resource.yaml" Remove-Item -Path $yamlFile -Force -ErrorAction Ignore Set-Content -Path $yamlFile -Value $yaml -ErrorVariable err Write-Output "Config file successfully generated in '$workDirectory'" } function Remove-ArcHciIdentityFiles { <# .DESCRIPTION Removes the Arc HCI token files .PARAMETER workDirectory Path to the work directory (optional parameter. Defaults to the local directory.) Please set this parameter to a shared storage location if you are running in an HCI cluster. .OUTPUTS N/A .EXAMPLE Remove-ArcHciIdentityFiles #> Param ( [Parameter(Mandatory=$false)] [String] $workDirectory = $(Get-ArcHciConfigValue -name "workingDir") ) # Remove Identities Remove-Item -Path "$($workDirectory)\hci-config.json" -Force -ErrorAction Ignore $mocoperator = "moc-operator" Remove-MocIdentity -name $mocoperator } function Remove-ArcHciAksConfigFiles { <# .DESCRIPTION Removes the Arc Appliance config files .PARAMETER workDirectory Path to the work directory (optional parameter. Defaults to the local directory.) Please set this parameter to a shared storage location if you are running in an HCI cluster. .OUTPUTS N/A .EXAMPLE Remove-ArcHciAksConfigFiles #> Param ( [Parameter(Mandatory=$false)] [String] $workDirectory = $(Get-ArcHciConfigValue -name "workingDir") ) # Remove Identities Remove-Item -Path "$($workDirectory)\kvatoken.tok" -Force -ErrorAction Ignore $clusterName = "Appliance" Remove-MocIdentity -name $clusterName # Remove the appliance config files Remove-Item -Path "$($workDirectory)\hci-appliance.yaml" -Force -ErrorAction Ignore Remove-Item -Path "$($workDirectory)\hci-infra.yaml" -Force -ErrorAction Ignore Remove-Item -Path "$($workDirectory)\hci-resource.yaml" -Force -ErrorAction Ignore $kubeFolder = "$($Env:USERPROFILE)\.kube" Remove-Item -Path "$kubeFolder\config" -Force -ErrorAction Ignore } function Remove-ArcHciConfigFiles { <# .DESCRIPTION Removes the Arc HCI config files .PARAMETER workDirectory Path to the work directory (optional parameter. Defaults to the local directory.) Please set this parameter to a shared storage location if you are running in an HCI cluster. .OUTPUTS N/A .EXAMPLE Remove-ArcHciConfigFiles #> Param ( [Parameter(Mandatory=$false)] [String] $workDirectory = $(Get-ArcHciConfigValue -name "workingDir") ) Remove-ArcHciIdentityFiles -workDirectory $workDirectory Remove-ArcHciAksConfigFiles -workDirectory $workDirectory } function Get-ArcHciFirstControlPlaneNodeIp { <# .DESCRIPTION Finds the ARC resource bridge control plane VM and retrieves its non-control plane IPv4. #> # Assumption: we assume that the ARC control plane VM is the VM that contains "control-plane" in the name and is located in the **management** group Get-MocConfig > $null # Workaround for Get-MocVirtualMachine initialization. Call into Get-MocConfig first to initialize MOC context. $mocVm = (Get-MocVirtualMachine -group management | where { $_.name -match ".*control-plane.*" }) if ($mocVm -ne $null) { $controlPlaneIp = ([ipaddress](Get-ArcHciConfig).controlPlaneIP) $hypervVm = Get-VM -Name "$($mocVm.name)*" -ComputerName $mocVm.virtualmachineproperties.host.id $vmIpAddresses = ([ipaddress[]]$hypervVm.NetworkAdapters.IPAddresses) | where { $_.AddressFamily -eq "InterNetwork" -And $_ -ne $controlPlaneIp} if ($vmIpAddresses -ne $null) { return $vmIpAddresses[0] } } return $null } function Get-ArcHciLogs { <# .DESCRIPTION Collects all the logs from the deployment and compresses them. .PARAMETER workDirectory Path to the work directory (optional parameter. Defaults to the local directory.) Please set this parameter to a shared storage location if you are running in an HCI cluster. .PARAMETER activity Activity name to use when updating progress .PARAMETER kubeconfig Path to the appliance kubeconfig .PARAMETER ip IP address of the ARC appliance VM or kubernetes api server .PARAMETER logDir Path to the directory to store the logs .PARAMETER kvaTokenPath Path to the KVA token (which was generated during the installation of the ARC resource bridge) #> param ( [Parameter(Mandatory=$false)] [String] $workDirectory = $(Get-ArcHciConfigValue -name "workingDir"), [Parameter()] [String]$activity = $MyInvocation.MyCommand.Name, [Parameter(Mandatory=$false)] [String]$kubeconfig = "$($Env:USERPROFILE)\.kube\config", [Parameter(Mandatory=$false)] [String]$logDir = [System.Io.Path]::GetTempPath(), [Parameter(Mandatory=$false)] [ipaddress]$ip = $(Get-ArcHciFirstControlPlaneNodeIp), [Parameter(Mandatory=$false)] [String] $kvaTokenPath = (Get-ArcHciConfigValue -name "kvaTokenLocation") ) $arcHciLogDir = $(Join-Path $logDir "archcilogs") Remove-Item $arcHciLogDir -Force -Recurse -Confirm:$false -ErrorAction SilentlyContinue > $null New-Item -ItemType Directory -Path $arcHciLogDir -Force > $null New-Item -ItemType Directory -Path "$($arcHciLogDir)\config\" -Force > $null $mocConfig = Get-MocConfig -ErrorAction SilentlyContinue Get-ArcHciConfig -ErrorAction SilentlyContinue | Format-Table -AutoSize > "$($arcHciLogDir)\config\archciconfig.txt" $mocConfig | Format-Table -AutoSize > "$($arcHciLogDir)\config\mocconfig.txt" Copy-Item -Path "$($workDirectory)\hci-appliance.yaml" -Destination "$($arcHciLogDir)\config\" -ErrorAction Ignore Copy-Item -Path "$($workDirectory)\hci-infra.yaml" -Destination "$($arcHciLogDir)\config\" -ErrorAction Ignore Copy-Item -Path "$($workDirectory)\hci-resource.yaml" -Destination "$($arcHciLogDir)\config\" -ErrorAction Ignore Get-MocLogs -path $arcHciLogDir -activity $activity -AgentLogs -EventLogs Get-DownloadSdkLogs -Path $arcHciLogDir Write-StatusWithProgress -activity $activity -moduleName $script:moduleName -status $("Collecting ARC appliance Logs...") # WORKAROUND: Pipe the cloud FQDN and kva token path to workaround the "az arcappliance logs" interractive prompt # TODO: Remove this hack once arcappliance logs has a way to pass these through parameters instead of interractively asking for these. if ($ip) { "$($mocConfig.cloudFqdn)`n$($kvaTokenPath)`n" | az.cmd arcappliance logs hci --kubeconfig $kubeconfig --ip $ip --out-dir $arcHciLogDir > "$($arcHciLogDir)\arcappliancelogsout.txt" } else { "$($mocConfig.cloudFqdn)`n$($kvaTokenPath)`n" | az.cmd arcappliance logs hci --kubeconfig $kubeconfig --out-dir $arcHciLogDir > "$($arcHciLogDir)\arcappliancelogsout.txt" } az.cmd version > "$($arcHciLogDir)\cliversions.txt" Write-StatusWithProgress -activity $activity -moduleName $script:moduleName -status $("Compressing Logs...") $zipName = Join-Path $logDir "archcilogs.zip" Remove-Item $zipName -Force -Recurse -Confirm:$false -ErrorAction SilentlyContinue Compress-Directory -ZipFilename $zipName -SourceDir $arcHciLogDir Remove-Item $arcHciLogDir -Force -Recurse -Confirm:$false -ErrorAction SilentlyContinue Write-Output "Logs were successfully saved to ""$zipName""" return $zipName } function New-KvaVirtualNetwork { <# .DESCRIPTION Creates a new virtual network usig kvactl .PARAMETER name The name of the vnet .PARAMETER vswitchName The name of the vswitch .PARAMETER vippoolstart The starting ip address to use for the vip pool. The vip pool addresses will be used by the k8s API server and k8s services .PARAMETER vippoolend The ending ip address to use for the vip pool. The vip pool addresses will be used by the k8s API server and k8s services .PARAMETER ipaddressprefix The address prefix to use for static IP assignment .PARAMETER gateway The gateway to use when using static IP .PARAMETER dnsservers The dnsservers to use when using static IP .PARAMETER k8snodeippoolstart The starting ip address to use for VM's in the cluster. .PARAMETER k8snodeippoolend The ending ip address to use for VM's in the cluster. .PARAMETER vlanID The VLAN ID for the vnet .PARAMETER kubeconfig Path to the appliance kubeconfig #> param ( [Parameter(Mandatory=$true, ParameterSetName="DHCP")] [Parameter(Mandatory=$true, ParameterSetName="Static")] [ValidateNotNullOrEmpty()] [String] $name, [Parameter(Mandatory=$true, ParameterSetName="DHCP")] [Parameter(Mandatory=$true, ParameterSetName="Static")] [ValidateNotNullOrEmpty()] [String] $vswitchName, [Parameter(Mandatory=$true, ParameterSetName="DHCP")] [Parameter(Mandatory=$true, ParameterSetName="Static")] [ValidateNotNullOrEmpty()] [String] $vippoolstart, [Parameter(Mandatory=$true, ParameterSetName="DHCP")] [Parameter(Mandatory=$true, ParameterSetName="Static")] [ValidateNotNullOrEmpty()] [String] $vippoolend, [Parameter(Mandatory=$true, ParameterSetName="Static")] [ValidateNotNullOrEmpty()] [String] $ipaddressprefix, [Parameter(Mandatory=$true, ParameterSetName="Static")] [ValidateNotNullOrEmpty()] [String] $gateway, [Parameter(Mandatory=$true, ParameterSetName="Static")] [ValidateNotNullOrEmpty()] [String] $dnsservers, [Parameter(Mandatory=$true, ParameterSetName="Static")] [ValidateNotNullOrEmpty()] [String] $k8snodeippoolstart, [Parameter(Mandatory=$true, ParameterSetName="Static")] [ValidateNotNullOrEmpty()] [String] $k8snodeippoolend, [Parameter(Mandatory=$false, ParameterSetName="Static")] [ValidateNotNullOrEmpty()] [String] $vlanID, [Parameter(Mandatory=$true, ParameterSetName="DHCP")] [Parameter(Mandatory=$true, ParameterSetName="Static")] [ValidateNotNullOrEmpty()] [String] $kubeconfig ) $dnsserversYaml = "" if(-Not [string]::IsNullOrEmpty($ipaddressprefix)) { foreach ($dns in $dnsservers.Split(",")) { $dns = $dns.Trim(" ") if(-Not [string]::IsNullOrEmpty($dns)) { $dnsserversYaml += "`n - " + $dns } } } # Create the network config file $akshcinetworkyaml = @" apiVersion: msft.microsoft/v1 kind: AksHciNetwork metadata: labels: msft.microsoft/capi-name: capi-for-moc name: $name spec: group: target-group location: MocLocation name: $name type: Transparent vippoolstart: $vippoolstart vippoolend: $vippoolend vswitchname: $vswitchName ipaddressprefix: $ipaddressprefix gateway: $gateway dnsservers: $dnsserversYaml k8snodeippoolstart: $k8snodeippoolstart k8snodeippoolend: $k8snodeippoolend "@ if (-Not [string]::IsNullOrEmpty($vlanID)) { $akshcinetworkyaml += @" `n vlanid: $vlanID "@ } $yamlFile = Join-Path $(Get-Location).Path "vnet-$name.yaml" Set-Content -Path $yamlFile -Value $akshcinetworkyaml -ErrorVariable err # Create the kva virtual network $config = Get-MocConfig $kubectlPath = Join-Path $config.installationPackageDir "kubectl.exe" try { # Create the kva virtual network Invoke-Expression "$kubectlPath apply -f '$yamlFile' --kubeconfig '$kubeconfig'" # Get the list of kva virtual networks Invoke-Expression "$kubectlPath get akshcinetworks -A --kubeconfig '$kubeconfig'" } catch { Write-Output "Error while creating KVA virtual network" } } function Get-TargetClusterAdminCredentials { <# .DESCRIPTION Gets the kubeconfig of target cluster .PARAMETER clusterName The name of the target cluster .PARAMETER outfile Path to the file in which target cluster kubeconfig will be stored .PARAMETER kubeconfig Path to the appliance kubeconfig #> param ( [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] [String] $clusterName, [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] [String] $outfile, [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] [String] $kubeconfig ) $config = Get-MocConfig $kubectlPath = Join-Path $config.installationPackageDir "kubectl.exe" try { # Get the target cluster kubeconfig $secretName = $clusterName.ToLower() + '-kubeconfig' $encodedSecret = Invoke-Expression "$kubectlPath get secrets '$secretName' -o jsonpath='{.data.value}' --kubeconfig '$kubeconfig'" $targetKubeconfig = [System.Text.Encoding]::ASCII.GetString([System.Convert]::FromBase64String($encodedSecret)) Set-Content -Path $outfile -Value $targetKubeconfig } catch { Write-Output "Error while retrieving the target cluster kubeconfig" } } function Invoke-CommandLine { <# .DESCRIPTION Executes a command and optionally ignores errors. .PARAMETER command Comamnd to execute. .PARAMETER arguments Arguments to pass to the command. .PARAMETER showOutput Optionally, show live output from the executing command. #> param ( [String]$command, [String]$arguments, [Switch]$showOutput ) if ($showOutput.IsPresent) { $result = (& $command $arguments.Split(" ") | Out-Default) 2>&1 } else { $result = (& $command $arguments.Split(" ") 2>&1) } $out = $result | Where-Object {$_.gettype().Name -ine "ErrorRecord"} # On a non-zero exit code, this may contain the error #$outString = ($out | Out-String).ToLowerInvariant() if ($LASTEXITCODE) { $err = $result | Where-Object {$_.gettype().Name -eq "ErrorRecord"} $errMessage = "$command $arguments (Error: $LASTEXITCODE [$err])" throw $errMessage } return $out } function Repair-ArcHciApplianceCerts { <# .DESCRIPTION Attempts to repair failed TLS to cloudagent .PARAMETER sshPrivateKeyFile SSH private key file .PARAMETER force Force repair(without checks) .PARAMETER kubeconfig Path to the kubeconfig file .PARAMETER group Appliance group .PARAMETER clusterName Name of the appliance cluster .PARAMETER arcTokenFilePath File path to the ARC indentity token .PARAMETER kvactlPath Location of the kvactl.exe tool (which may need to be installed separately) #> [CmdletBinding(DefaultParameterSetName = 'Default')] param ( [Parameter()] [String] $sshPrivateKeyFile = $global:sshPrivateKeyFile, [Parameter()] [Switch] $force, [Parameter()] [string] $kubeconfig, [Parameter()] [string] $group, [Parameter()] [string] $clusterName, [Parameter(Mandatory=$false)] [String] $arcTokenFilePath, [Parameter(Mandatory=$false)] [String] $kvactlpath = $global:kvaCtlFullPath ) if (-Not (Test-Path $kvactlpath)) { throw "Please make sure you install KvaCtl.exe in '$kvactlpath' before running repair" } $kvaIdentity = Invoke-MocIdentityRotate -name $clusterName -encode $utf8String = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($kvaIdentity)) $Utf8NoBomEncoding = New-Object System.Text.UTF8Encoding $False [System.IO.File]::WriteAllLines($arcTokenFilePath, $utf8String, $Utf8NoBomEncoding) Invoke-CommandLine -command $kvactlpath -arguments $("repair --kubeconfig ""$kubeconfig"" --sshprivatekey ""$sshPrivateKeyFile"" --tags ""Group=$group"" --force=$force --verbose") -showOutput } function Repair-MocOperatorToken { <# .DESCRIPTION Refresh the moc-operator token #> Repair-Moc $mocConfig = Get-MocConfig $mocoperator = "moc-operator" try { Remove-MocIdentity -name $mocOperator } catch { } $mocOperatorIdentity = New-MocIdentity -name $mocoperator -validityDays $defaultTokenExpiryDays -fqdn $mocConfig.cloudFqdn -location $mocConfig.cloudLocation -port $mocConfig.cloudAgentPort -authport $mocConfig.cloudAgentAuthorizerPort -encode New-MocRoleAssignmentWhenAvailable -identityName $mocoperator -roleName "Contributor" -location $mocConfig.cloudLocation | Out-Null $loginString = [Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes($mocOperatorIdentity)) kubectl.exe patch secret controllerconfig -p "{\""data\"":{\""LoginString\"":\""$loginString\""}}" -n moc-operator-system kubectl.exe delete pods -n moc-operator-system -l control-plane=controller-manager } function Repair-ArcHciVmInfra { <# .DESCRIPTION Rotates the security certificates and tokens in MOC and ARC resource bridge .PARAMETER workDirectory TBD #> param ( [Parameter(Mandatory=$false)] [String] $workDirectory = $(Get-ArcHciConfigValue -name "workingDir"), [Parameter()] [String] $sshPrivateKeyFile = $global:sshPrivateKeyFile, [Parameter()] [Switch] $force, [Parameter()] [string] $kubeconfig, [Parameter()] [string] $group, [Parameter()] [string] $clusterName, [Parameter(Mandatory=$false)] [String] $arcTokenFilePath, [Parameter(Mandatory=$false)] [String] $kvactlpath = $global:kvaCtlFullPath ) if (-Not (Test-Path $kvactlpath)) { throw "Please make sure you install KvaCtl.exe in '$kvactlpath' before running repair" } Repair-MocOperatorToken Repair-ArcHciApplianceCerts -sshprivatekey $sshPrivateKeyFile -force:$force.IsPresent -kubeconfig $($env:USERPROFILE + "\.kube\config") -group "management" -clusterName "Appliance" -arcTokenFilePath $($workDirectory + "\kvatoken.tok") -kvactlpath $kvactlpath } function Test-HCIMachineRequirements { <# .DESCRIPTION Checks 1. The substrate is Azure Stack HCI 2. HCI node is registered #> # Check if the machine is Azure Stack HCI # The ps module "AzureStackHCI" is available only on the azstackhci machines if($null -eq (Get-Module -ListAvailable -Name "AzureStackHCI" -ErrorAction:Ignore)) { throw "The machine is not Azure Stack HCI. Installation is not supported in other machines." } Import-Module -Name "AzureStackHCI" $hciStatus = Get-AzureStackHCI # Check if the HCI machine is registered; if not, throw error if ($hciStatus.RegistrationStatus -ine "Registered") { throw "The HCI machine ($env:computername) is not registered. Please register your HCI cluster." } } # SIG # Begin signature block # MIInrQYJKoZIhvcNAQcCoIInnjCCJ5oCAQExDzANBglghkgBZQMEAgEFADB5Bgor # BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG # KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCCOt8OkEszN9RBd # iN3r5PcSycXVAlLhUMKNJWlK0vJfj6CCDYEwggX/MIID56ADAgECAhMzAAACzI61 # lqa90clOAAAAAALMMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMRMwEQYD # VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy # b3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25p # bmcgUENBIDIwMTEwHhcNMjIwNTEyMjA0NjAxWhcNMjMwNTExMjA0NjAxWjB0MQsw # CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u # ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMR4wHAYDVQQDExVNaWNy # b3NvZnQgQ29ycG9yYXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB # AQCiTbHs68bADvNud97NzcdP0zh0mRr4VpDv68KobjQFybVAuVgiINf9aG2zQtWK # No6+2X2Ix65KGcBXuZyEi0oBUAAGnIe5O5q/Y0Ij0WwDyMWaVad2Te4r1Eic3HWH # UfiiNjF0ETHKg3qa7DCyUqwsR9q5SaXuHlYCwM+m59Nl3jKnYnKLLfzhl13wImV9 # DF8N76ANkRyK6BYoc9I6hHF2MCTQYWbQ4fXgzKhgzj4zeabWgfu+ZJCiFLkogvc0 # RVb0x3DtyxMbl/3e45Eu+sn/x6EVwbJZVvtQYcmdGF1yAYht+JnNmWwAxL8MgHMz # xEcoY1Q1JtstiY3+u3ulGMvhAgMBAAGjggF+MIIBejAfBgNVHSUEGDAWBgorBgEE # AYI3TAgBBggrBgEFBQcDAzAdBgNVHQ4EFgQUiLhHjTKWzIqVIp+sM2rOHH11rfQw # UAYDVR0RBEkwR6RFMEMxKTAnBgNVBAsTIE1pY3Jvc29mdCBPcGVyYXRpb25zIFB1 # ZXJ0byBSaWNvMRYwFAYDVQQFEw0yMzAwMTIrNDcwNTI5MB8GA1UdIwQYMBaAFEhu # ZOVQBdOCqhc3NyK1bajKdQKVMFQGA1UdHwRNMEswSaBHoEWGQ2h0dHA6Ly93d3cu # bWljcm9zb2Z0LmNvbS9wa2lvcHMvY3JsL01pY0NvZFNpZ1BDQTIwMTFfMjAxMS0w # Ny0wOC5jcmwwYQYIKwYBBQUHAQEEVTBTMFEGCCsGAQUFBzAChkVodHRwOi8vd3d3 # Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2NlcnRzL01pY0NvZFNpZ1BDQTIwMTFfMjAx # MS0wNy0wOC5jcnQwDAYDVR0TAQH/BAIwADANBgkqhkiG9w0BAQsFAAOCAgEAeA8D # sOAHS53MTIHYu8bbXrO6yQtRD6JfyMWeXaLu3Nc8PDnFc1efYq/F3MGx/aiwNbcs # J2MU7BKNWTP5JQVBA2GNIeR3mScXqnOsv1XqXPvZeISDVWLaBQzceItdIwgo6B13 # vxlkkSYMvB0Dr3Yw7/W9U4Wk5K/RDOnIGvmKqKi3AwyxlV1mpefy729FKaWT7edB # d3I4+hldMY8sdfDPjWRtJzjMjXZs41OUOwtHccPazjjC7KndzvZHx/0VWL8n0NT/ # 404vftnXKifMZkS4p2sB3oK+6kCcsyWsgS/3eYGw1Fe4MOnin1RhgrW1rHPODJTG # AUOmW4wc3Q6KKr2zve7sMDZe9tfylonPwhk971rX8qGw6LkrGFv31IJeJSe/aUbG # dUDPkbrABbVvPElgoj5eP3REqx5jdfkQw7tOdWkhn0jDUh2uQen9Atj3RkJyHuR0 # GUsJVMWFJdkIO/gFwzoOGlHNsmxvpANV86/1qgb1oZXdrURpzJp53MsDaBY/pxOc # J0Cvg6uWs3kQWgKk5aBzvsX95BzdItHTpVMtVPW4q41XEvbFmUP1n6oL5rdNdrTM # j/HXMRk1KCksax1Vxo3qv+13cCsZAaQNaIAvt5LvkshZkDZIP//0Hnq7NnWeYR3z # 4oFiw9N2n3bb9baQWuWPswG0Dq9YT9kb+Cs4qIIwggd6MIIFYqADAgECAgphDpDS # AAAAAAADMA0GCSqGSIb3DQEBCwUAMIGIMQswCQYDVQQGEwJVUzETMBEGA1UECBMK # V2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0 # IENvcnBvcmF0aW9uMTIwMAYDVQQDEylNaWNyb3NvZnQgUm9vdCBDZXJ0aWZpY2F0 # ZSBBdXRob3JpdHkgMjAxMTAeFw0xMTA3MDgyMDU5MDlaFw0yNjA3MDgyMTA5MDla # MH4xCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdS # ZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMT # H01pY3Jvc29mdCBDb2RlIFNpZ25pbmcgUENBIDIwMTEwggIiMA0GCSqGSIb3DQEB # AQUAA4ICDwAwggIKAoICAQCr8PpyEBwurdhuqoIQTTS68rZYIZ9CGypr6VpQqrgG # OBoESbp/wwwe3TdrxhLYC/A4wpkGsMg51QEUMULTiQ15ZId+lGAkbK+eSZzpaF7S # 35tTsgosw6/ZqSuuegmv15ZZymAaBelmdugyUiYSL+erCFDPs0S3XdjELgN1q2jz # y23zOlyhFvRGuuA4ZKxuZDV4pqBjDy3TQJP4494HDdVceaVJKecNvqATd76UPe/7 # 4ytaEB9NViiienLgEjq3SV7Y7e1DkYPZe7J7hhvZPrGMXeiJT4Qa8qEvWeSQOy2u # M1jFtz7+MtOzAz2xsq+SOH7SnYAs9U5WkSE1JcM5bmR/U7qcD60ZI4TL9LoDho33 # X/DQUr+MlIe8wCF0JV8YKLbMJyg4JZg5SjbPfLGSrhwjp6lm7GEfauEoSZ1fiOIl # XdMhSz5SxLVXPyQD8NF6Wy/VI+NwXQ9RRnez+ADhvKwCgl/bwBWzvRvUVUvnOaEP # 6SNJvBi4RHxF5MHDcnrgcuck379GmcXvwhxX24ON7E1JMKerjt/sW5+v/N2wZuLB # l4F77dbtS+dJKacTKKanfWeA5opieF+yL4TXV5xcv3coKPHtbcMojyyPQDdPweGF # RInECUzF1KVDL3SV9274eCBYLBNdYJWaPk8zhNqwiBfenk70lrC8RqBsmNLg1oiM # CwIDAQABo4IB7TCCAekwEAYJKwYBBAGCNxUBBAMCAQAwHQYDVR0OBBYEFEhuZOVQ # BdOCqhc3NyK1bajKdQKVMBkGCSsGAQQBgjcUAgQMHgoAUwB1AGIAQwBBMAsGA1Ud # DwQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFHItOgIxkEO5FAVO # 4eqnxzHRI4k0MFoGA1UdHwRTMFEwT6BNoEuGSWh0dHA6Ly9jcmwubWljcm9zb2Z0 # LmNvbS9wa2kvY3JsL3Byb2R1Y3RzL01pY1Jvb0NlckF1dDIwMTFfMjAxMV8wM18y # Mi5jcmwwXgYIKwYBBQUHAQEEUjBQME4GCCsGAQUFBzAChkJodHRwOi8vd3d3Lm1p # Y3Jvc29mdC5jb20vcGtpL2NlcnRzL01pY1Jvb0NlckF1dDIwMTFfMjAxMV8wM18y # Mi5jcnQwgZ8GA1UdIASBlzCBlDCBkQYJKwYBBAGCNy4DMIGDMD8GCCsGAQUFBwIB # FjNodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2RvY3MvcHJpbWFyeWNw # cy5odG0wQAYIKwYBBQUHAgIwNB4yIB0ATABlAGcAYQBsAF8AcABvAGwAaQBjAHkA # XwBzAHQAYQB0AGUAbQBlAG4AdAAuIB0wDQYJKoZIhvcNAQELBQADggIBAGfyhqWY # 4FR5Gi7T2HRnIpsLlhHhY5KZQpZ90nkMkMFlXy4sPvjDctFtg/6+P+gKyju/R6mj # 82nbY78iNaWXXWWEkH2LRlBV2AySfNIaSxzzPEKLUtCw/WvjPgcuKZvmPRul1LUd # d5Q54ulkyUQ9eHoj8xN9ppB0g430yyYCRirCihC7pKkFDJvtaPpoLpWgKj8qa1hJ # Yx8JaW5amJbkg/TAj/NGK978O9C9Ne9uJa7lryft0N3zDq+ZKJeYTQ49C/IIidYf # wzIY4vDFLc5bnrRJOQrGCsLGra7lstnbFYhRRVg4MnEnGn+x9Cf43iw6IGmYslmJ # aG5vp7d0w0AFBqYBKig+gj8TTWYLwLNN9eGPfxxvFX1Fp3blQCplo8NdUmKGwx1j # NpeG39rz+PIWoZon4c2ll9DuXWNB41sHnIc+BncG0QaxdR8UvmFhtfDcxhsEvt9B # xw4o7t5lL+yX9qFcltgA1qFGvVnzl6UJS0gQmYAf0AApxbGbpT9Fdx41xtKiop96 # eiL6SJUfq/tHI4D1nvi/a7dLl+LrdXga7Oo3mXkYS//WsyNodeav+vyL6wuA6mk7 # r/ww7QRMjt/fdW1jkT3RnVZOT7+AVyKheBEyIXrvQQqxP/uozKRdwaGIm1dxVk5I # RcBCyZt2WwqASGv9eZ/BvW1taslScxMNelDNMYIZgjCCGX4CAQEwgZUwfjELMAkG # A1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQx # HjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEoMCYGA1UEAxMfTWljcm9z # b2Z0IENvZGUgU2lnbmluZyBQQ0EgMjAxMQITMwAAAsyOtZamvdHJTgAAAAACzDAN # BglghkgBZQMEAgEFAKCBrjAZBgkqhkiG9w0BCQMxDAYKKwYBBAGCNwIBBDAcBgor # BgEEAYI3AgELMQ4wDAYKKwYBBAGCNwIBFTAvBgkqhkiG9w0BCQQxIgQgt+UxVuxb # dnP4pyajsxeT8QD1ejr2xz0ZOPRnLfsHRa0wQgYKKwYBBAGCNwIBDDE0MDKgFIAS # AE0AaQBjAHIAbwBzAG8AZgB0oRqAGGh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbTAN # BgkqhkiG9w0BAQEFAASCAQBmHudGNLrYyq6JkZPxlhdtNw8LgAMFKE21GI9k/50k # 8JtHAshg7xGWLgXqV91aUHJVFQUQ+XuSu+vCAwuCucqNWPNf9uP+a7BabDydZjdo # 8vFPseDlbsqRNi225OjA89DCWRlMBlf+2/u6oGqlbb2V71ETgSuHnx7t0Dk0R9TS # HrVtjOq/6m6ObV0B/hGxGLBFfWTQpeZCy0BoPnqg7/Pc7nG4vbRcPmjai3TrhjZc # 7C2c7Ju/XPnRxGGaJqCQ5Phog6l+7KkfEC2pUghXxNLTUkLRg5SZ8kZ6sFJneS/8 # xWn+F46plfuHaVvg+uxVkF1w9hYlg9UEp5K+umBLcp2qoYIXDDCCFwgGCisGAQQB # gjcDAwExghb4MIIW9AYJKoZIhvcNAQcCoIIW5TCCFuECAQMxDzANBglghkgBZQME # AgEFADCCAVUGCyqGSIb3DQEJEAEEoIIBRASCAUAwggE8AgEBBgorBgEEAYRZCgMB # MDEwDQYJYIZIAWUDBAIBBQAEIFLEabH5U1d6RJmdiIF6RnLXkP4InIDGbZS6PJa2 # sZWcAgZjKgo54scYEzIwMjIxMDE0MjMyMzU2LjQ5OVowBIACAfSggdSkgdEwgc4x # CzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRt # b25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKTAnBgNVBAsTIE1p # Y3Jvc29mdCBPcGVyYXRpb25zIFB1ZXJ0byBSaWNvMSYwJAYDVQQLEx1UaGFsZXMg # VFNTIEVTTjozMkJELUUzRDUtM0IxRDElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUt # U3RhbXAgU2VydmljZaCCEV8wggcQMIIE+KADAgECAhMzAAABrfzfTVjjXTLpAAEA # AAGtMA0GCSqGSIb3DQEBCwUAMHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNo # aW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29y # cG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEw # MB4XDTIyMDMwMjE4NTEzNloXDTIzMDUxMTE4NTEzNlowgc4xCzAJBgNVBAYTAlVT # MRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQK # ExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKTAnBgNVBAsTIE1pY3Jvc29mdCBPcGVy # YXRpb25zIFB1ZXJ0byBSaWNvMSYwJAYDVQQLEx1UaGFsZXMgVFNTIEVTTjozMkJE # LUUzRDUtM0IxRDElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAgU2Vydmlj # ZTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAOieUyqlTSrVLhvY7TO8 # vgC+T5N/y/MXeR3oNwE0rLI1Eg/gM5g9NhP+KqqJc/7uPL4TsoALb+RVf6roYNll # yQrYmquUjwsq262MD5L9l9rU1plz2tMPehP8addVlNIjYIBh0NC4CyME6txVppQr # 7eFd/bW0X9tnZy1aDW+zoaJB2FY8haokq5cRONEW4uoVsTTXsICkbYOAYffIIGak # MFXVvB30NcsuiDn6uDk83XXTs0tnSr8FxzPoD8SgPPIcWaWPEjCQLr5I0BxfdUli # wNPHIPEglqosrClRjXG7rcZWbWeODgATi0i6DUsv1Wn0LOW4svK4/Wuc/v9dlmuI # ramv9whbgCykUuYZy8MxTzsQqU2Rxcm8h89CXA5jf1k7k3ZiaLUJ003MjtTtNXzl # gb+k1A5eL17G3C4Ejw5AoViM+UBGQvxuTxpFeaGoQFqeOGGtEK0qk0wdUX9p/4Au # 9Xsle5D5fvypBdscXBslUBcT6+CYq0kQ9smsTyhV4DK9wb9Zn7ObEOfT0AQyppI6 # jwzBjHhAGFyrKYjIbglMaEixjRv7XdNic2VuYKyS71A0hs6dbbDx/V7hDbdv2srt # Z2VTO0y2E+4QqMRKtABv4AggjYKz5TYGuQ4VbbPY8fBO9Xqva3Gnx1ZDOQ3nGVFK # HwarGDcNdB3qesvtJbIGJgJjAgMBAAGjggE2MIIBMjAdBgNVHQ4EFgQUfVB0HQS8 # qiFabmqEqOV9LrLGwVkwHwYDVR0jBBgwFoAUn6cVXQBeYl2D9OXSZacbUzUZ6XIw # XwYDVR0fBFgwVjBUoFKgUIZOaHR0cDovL3d3dy5taWNyb3NvZnQuY29tL3BraW9w # cy9jcmwvTWljcm9zb2Z0JTIwVGltZS1TdGFtcCUyMFBDQSUyMDIwMTAoMSkuY3Js # MGwGCCsGAQUFBwEBBGAwXjBcBggrBgEFBQcwAoZQaHR0cDovL3d3dy5taWNyb3Nv # ZnQuY29tL3BraW9wcy9jZXJ0cy9NaWNyb3NvZnQlMjBUaW1lLVN0YW1wJTIwUENB # JTIwMjAxMCgxKS5jcnQwDAYDVR0TAQH/BAIwADATBgNVHSUEDDAKBggrBgEFBQcD # CDANBgkqhkiG9w0BAQsFAAOCAgEAi9AdRbsx/gOSdBXndwRejQuutQqce3k3bgs1 # slPjZSx6FDXp1IZzjOyT1Jo/3eUWDBFJdi+Heu1NoyDdGn9vL6rxly1L68K4MnfL # Bm+ybyjN+xa1eNa4+4cOoOuxE2Kt8jtmZbIhx2jvY7F9qY/lanR5PSbUKyClhNQh # xsnNUp/JSQ+o7nAuQJ+wsCwPCrXYE7C+TvKDja6e6WU0K4RiBXFGU1z6Mt3K9wlM # D/QGU4+/IGZDmE+/Z/k0JfJjZyxCAlcmhe3rgdhDzAsGxJYq4PblGZTBdr8wkQwp # P2jggyMMawMM5DggwvXaDbrqCQ8gksNhCZzTqfS2dbgLF0m7HfwlUMrcnzi/bdTS # RWzIXg5QsH1t5XaaIH+TZ1uZBtwXJ8EOXr6S+2A6q8RQVY10KnBH6YpGE9OhXPfu # Iu882muFEdh4EXbPdARUR1IMSIxg88khSBC/YBwQhCpjTksq5J3Z+jyHWZ4MnXX5 # R42mAR584iRYc7agYvuotDEqcD0U9lIjgW31PqfqZQ1tuYZTiGcKE9QcYGvZFKnV # dkqK8V0M9e+kF5CqDOrMMYRV2+I/FhyQsJHxK/G53D0O5bvdIh2gDnEHRAFihdZj # 29Z7W0paGPotGX0oB5r9wqNjM3rbvuEe6FJ323MPY1x9/N1g126T/SokqADJBTKq # yBYN4zMwggdxMIIFWaADAgECAhMzAAAAFcXna54Cm0mZAAAAAAAVMA0GCSqGSIb3 # DQEBCwUAMIGIMQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4G # A1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMTIw # MAYDVQQDEylNaWNyb3NvZnQgUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkgMjAx # MDAeFw0yMTA5MzAxODIyMjVaFw0zMDA5MzAxODMyMjVaMHwxCzAJBgNVBAYTAlVT # MRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQK # ExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1l # LVN0YW1wIFBDQSAyMDEwMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA # 5OGmTOe0ciELeaLL1yR5vQ7VgtP97pwHB9KpbE51yMo1V/YBf2xK4OK9uT4XYDP/ # XE/HZveVU3Fa4n5KWv64NmeFRiMMtY0Tz3cywBAY6GB9alKDRLemjkZrBxTzxXb1 # hlDcwUTIcVxRMTegCjhuje3XD9gmU3w5YQJ6xKr9cmmvHaus9ja+NSZk2pg7uhp7 # M62AW36MEBydUv626GIl3GoPz130/o5Tz9bshVZN7928jaTjkY+yOSxRnOlwaQ3K # Ni1wjjHINSi947SHJMPgyY9+tVSP3PoFVZhtaDuaRr3tpK56KTesy+uDRedGbsoy # 1cCGMFxPLOJiss254o2I5JasAUq7vnGpF1tnYN74kpEeHT39IM9zfUGaRnXNxF80 # 3RKJ1v2lIH1+/NmeRd+2ci/bfV+AutuqfjbsNkz2K26oElHovwUDo9Fzpk03dJQc # NIIP8BDyt0cY7afomXw/TNuvXsLz1dhzPUNOwTM5TI4CvEJoLhDqhFFG4tG9ahha # YQFzymeiXtcodgLiMxhy16cg8ML6EgrXY28MyTZki1ugpoMhXV8wdJGUlNi5UPkL # iWHzNgY1GIRH29wb0f2y1BzFa/ZcUlFdEtsluq9QBXpsxREdcu+N+VLEhReTwDwV # 2xo3xwgVGD94q0W29R6HXtqPnhZyacaue7e3PmriLq0CAwEAAaOCAd0wggHZMBIG # CSsGAQQBgjcVAQQFAgMBAAEwIwYJKwYBBAGCNxUCBBYEFCqnUv5kxJq+gpE8RjUp # zxD/LwTuMB0GA1UdDgQWBBSfpxVdAF5iXYP05dJlpxtTNRnpcjBcBgNVHSAEVTBT # MFEGDCsGAQQBgjdMg30BATBBMD8GCCsGAQUFBwIBFjNodHRwOi8vd3d3Lm1pY3Jv # c29mdC5jb20vcGtpb3BzL0RvY3MvUmVwb3NpdG9yeS5odG0wEwYDVR0lBAwwCgYI # KwYBBQUHAwgwGQYJKwYBBAGCNxQCBAweCgBTAHUAYgBDAEEwCwYDVR0PBAQDAgGG # MA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAU1fZWy4/oolxiaNE9lJBb186a # GMQwVgYDVR0fBE8wTTBLoEmgR4ZFaHR0cDovL2NybC5taWNyb3NvZnQuY29tL3Br # aS9jcmwvcHJvZHVjdHMvTWljUm9vQ2VyQXV0XzIwMTAtMDYtMjMuY3JsMFoGCCsG # AQUFBwEBBE4wTDBKBggrBgEFBQcwAoY+aHR0cDovL3d3dy5taWNyb3NvZnQuY29t # L3BraS9jZXJ0cy9NaWNSb29DZXJBdXRfMjAxMC0wNi0yMy5jcnQwDQYJKoZIhvcN # AQELBQADggIBAJ1VffwqreEsH2cBMSRb4Z5yS/ypb+pcFLY+TkdkeLEGk5c9MTO1 # OdfCcTY/2mRsfNB1OW27DzHkwo/7bNGhlBgi7ulmZzpTTd2YurYeeNg2LpypglYA # A7AFvonoaeC6Ce5732pvvinLbtg/SHUB2RjebYIM9W0jVOR4U3UkV7ndn/OOPcbz # aN9l9qRWqveVtihVJ9AkvUCgvxm2EhIRXT0n4ECWOKz3+SmJw7wXsFSFQrP8DJ6L # GYnn8AtqgcKBGUIZUnWKNsIdw2FzLixre24/LAl4FOmRsqlb30mjdAy87JGA0j3m # Sj5mO0+7hvoyGtmW9I/2kQH2zsZ0/fZMcm8Qq3UwxTSwethQ/gpY3UA8x1RtnWN0 # SCyxTkctwRQEcb9k+SS+c23Kjgm9swFXSVRk2XPXfx5bRAGOWhmRaw2fpCjcZxko # JLo4S5pu+yFUa2pFEUep8beuyOiJXk+d0tBMdrVXVAmxaQFEfnyhYWxz/gq77EFm # PWn9y8FBSX5+k77L+DvktxW/tM4+pTFRhLy/AsGConsXHRWJjXD+57XQKBqJC482 # 2rpM+Zv/Cuk0+CQ1ZyvgDbjmjJnW4SLq8CdCPSWU5nR0W2rRnj7tfqAxM328y+l7 # vzhwRNGQ8cirOoo6CGJ/2XBjU02N7oJtpQUQwXEGahC0HVUzWLOhcGbyoYIC0jCC # AjsCAQEwgfyhgdSkgdEwgc4xCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5n # dG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9y # YXRpb24xKTAnBgNVBAsTIE1pY3Jvc29mdCBPcGVyYXRpb25zIFB1ZXJ0byBSaWNv # MSYwJAYDVQQLEx1UaGFsZXMgVFNTIEVTTjozMkJELUUzRDUtM0IxRDElMCMGA1UE # AxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAgU2VydmljZaIjCgEBMAcGBSsOAwIaAxUA # QJLRrUVR4ZbBDgWPjuNqVctUzpCggYMwgYCkfjB8MQswCQYDVQQGEwJVUzETMBEG # A1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWlj # cm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQgVGltZS1TdGFt # cCBQQ0EgMjAxMDANBgkqhkiG9w0BAQUFAAIFAOb0K98wIhgPMjAyMjEwMTQyMjQx # MzVaGA8yMDIyMTAxNTIyNDEzNVowdzA9BgorBgEEAYRZCgQBMS8wLTAKAgUA5vQr # 3wIBADAKAgEAAgIhrQIB/zAHAgEAAgISjjAKAgUA5vV9XwIBADA2BgorBgEEAYRZ # CgQCMSgwJjAMBgorBgEEAYRZCgMCoAowCAIBAAIDB6EgoQowCAIBAAIDAYagMA0G # CSqGSIb3DQEBBQUAA4GBAHU2XYYpN2OBy+Pxfog5MmER6ne6X6EoMXRsEECSyohG # 2i4OoAIpSbhT/S/lpfnsxccFZtC/5qLPaIMLaN7dLu2GT/dFQYET/fk/nC1R+JRR # Q9+b75E31MpZbSF46J+n6NgQunpDweqZrMwlxz+0RN3jeEfr4Zq9r0rwPnsPGG/v # MYIEDTCCBAkCAQEwgZMwfDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0 # b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3Jh # dGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENBIDIwMTACEzMA # AAGt/N9NWONdMukAAQAAAa0wDQYJYIZIAWUDBAIBBQCgggFKMBoGCSqGSIb3DQEJ # AzENBgsqhkiG9w0BCRABBDAvBgkqhkiG9w0BCQQxIgQgjhlihlXhmGokdJjtgmmE # HK+7kLp5FDi22PqWTR7jdCMwgfoGCyqGSIb3DQEJEAIvMYHqMIHnMIHkMIG9BCCf # 6nw9CR5e1+Ottcn1w992Kmn8YMTY/DWPIHeMbMtQgjCBmDCBgKR+MHwxCzAJBgNV # BAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4w # HAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29m # dCBUaW1lLVN0YW1wIFBDQSAyMDEwAhMzAAABrfzfTVjjXTLpAAEAAAGtMCIEIJ7z # 3mLQ+EnUv5qCrcCl+1OQC+M4mLWhEsx6qzrct+d7MA0GCSqGSIb3DQEBCwUABIIC # AJX+l41s2YyuW+MsnLqowUrz6V3Dk4xZrDPzgbBauV1e2vwvtVR1+4wx28frxo9O # S0osMS1c2XLiXLcBTob/J3lQ2LEf3oHSn24/Dwy9+r7jt0G2NKILoP8EkJaa66E1 # DZAy1F2tO0lR5fd4RfYf4KYWNRC8G9vkqfP1DZL7LLR2QL6Y4+2cIVFFZoiMcq0V # nTBK9NiLLGvm4SjYY2zrPensLdAFMc7vNU5LjkFe/tjFFB/e8oms8bYgCpLdhXX0 # MIqPS7R9uQWJIP74WBeN33Xm9ZuYX2w+1OzHOcVUkxpbbzZvQ/+0TV8Xz3s1vFyU # HSa3HZIKpdCFVH6OSiTpfXcHICH5Hl/S7Qtrr50zHuLN5dHk6B9n7A2ROWalYkgs # jH1qJtkCeaH+KreAhemBJIOXDJVLLZjFo8Au90n0d0aCtVOjRUYOoEwIituZEQeU # giDEAh5WfkcktN82yfE4qajop59PYEZlRKcv+RROb6m1rS0NBjhF9V/i8v15o1FQ # 5aHY5Xc4to1/NvR1pJPshPwLdJs3PQY8kJ8EgHZ9QMV6v1xxcBRTcwI7U30i07AG # Y8znw8eT2IY4ArfuNgE4RVEAPvVmR168QQIulVBKHiUFuMgOU2O1Q4NORUQtKaqG # vy49Kopg7MBoot1XdZHstO9iLJc5M7z6bVnmROK8hVqj # SIG # End signature block |