Scripts/PatchIssuerThumbprints.ps1 (304 lines of code) (raw):

# ------------------------------------------------------------ # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License (MIT). See License.txt in the repo root for license information. # Feedback : anmenard@microsoft.com # ------------------------------------------------------------ <# .SYNOPSIS Script to expand the set of trusted direct issuers of a service fabric cluster .DESCRIPTION Usage Instructions: Refer to https://github.com/Azure/Service-Fabric-Troubleshooting-Guides/blob/master/Security/Fix%20Expired%20Cluster%20Certificate%20-%20Automated%20Script .PARAMETER clusterDataRootPath service fabric data installation path. default d:\svcfab in azure .PARAMETER targetIssuerThumbprints [required][string] a comma-delimited list of the thumbprints to ADD. E,g, abc...xyz,def...uvw .PARAMETER nodeIpArray [string[]] string array of ip addresses of nodes in cluster .PARAMETER cacheCredentials [switch] enable storing credentials in $global:creds variable. to clear, execute: $global:creds=$null .PARAMETER localOnly switch to optionally run script only on local node. use when there are connectivity issues between nodes by rdp'ing to each node and running with this switch. .Parameter hard Switch to change the patch style. Default is a soft patch, which is meant to keep the logical node processes, Fabric, FabricHost from closing. Soft patches do not change the fundamental node definition, so a cluster upgrade must be pushed soon after cluster health is restored. Soft patches may need to be run multiple times as nodes regress to their previous definition. A hard patch will change the fundamental node definition, but a cluster upgrade should still be pushed soon after cluster health is restored. Hard patches guarantee that fabric logical node will close, cluster availability loss is all but guaranteed, if not already being experienced. In general hard patches are much less likely to regress on their own. #> Param( [ValidateNotNullOrEmpty()] [string] $clusterDataRootPath = "D:\SvcFab", [ValidateNotNullOrEmpty()] [string]$targetIssuerThumbprints, [ValidateNotNullOrEmpty()] [string[]]$nodeIpArray = @("10.0.0.4", "10.0.0.5", "10.0.0.6" ), [switch]$cacheCredentials, [switch]$localOnly, [switch]$hard ) $error.Clear() $ErrorActionPreference = 'continue' $startTime = get-date $global:failNodes = @() $global:successNodes = @() $count = 0 $creds = $null # remove if on a vm in the vnet, but one which is not part of the cluster If (!(Test-Path $clusterDataRootPath)) { Write-Host $clusterDataRootPath " not found, exiting." return } $curValue = (get-item wsman:\localhost\Client\TrustedHosts).value $scriptBlock = { param($clusterDataRootPath, $newIssuerThumbprints, $hard) Write-Host "$env:computername : Running on $((Get-WmiObject win32_computersystem).DNSHostName)" -ForegroundColor Green function StopServiceFabricServices { if ($(Get-Process | ? ProcessName -like "*FabricInstaller*" | measure).Count -gt 0) { Write-Warning "$env:computername : Found FabricInstaller running, may cause issues if not stopped, consult manual guide..." Write-Host "$env:computername : Pausing (15s)..." -ForegroundColor Green Start-Sleep -Seconds 15 } $bootstrapAgent = "ServiceFabricNodeBootstrapAgent" $fabricHost = "FabricHostSvc" $bootstrapService = Get-Service -Name $bootstrapAgent if ($bootstrapService.Status -eq "Running") { Stop-Service $bootstrapAgent -ErrorAction SilentlyContinue Write-Host "$env:computername : Stopping $bootstrapAgent service" -ForegroundColor Green } Do { Start-Sleep -Seconds 1 $bootstrapService = Get-Service -Name $bootstrapAgent if(!$bootstrapService) { break } if ($bootstrapService.Status -eq "Stopped") { Write-Host "$env:computername : $bootstrapAgent now stopped" -ForegroundColor Green } else { Write-Host "$env:computername : $bootstrapAgent current status: $($bootstrapService.Status)" -ForegroundColor Green } } While ($bootstrapService.Status -ne "Stopped") $fabricHostService = Get-Service -Name $fabricHost if ($fabricHostService.Status -eq "Running") { Stop-Service $fabricHost -ErrorAction SilentlyContinue Write-Host "$env:computername : Stopping $fabricHost service" -ForegroundColor Green } Do { Start-Sleep -Seconds 1 $fabricHostService = Get-Service -Name $fabricHost if(!$fabricHostService) { break } if ($fabricHostService.Status -eq "Stopped") { Write-Host "$env:computername : $fabricHost now stopped" -ForegroundColor Green } else { Write-Host "$env:computername : $fabricHost current status: $($fabricHostService.Status)" -ForegroundColor Green } } While ($fabricHostService.Status -ne "Stopped") } function StartServiceFabricServices { $bootstrapAgent = "ServiceFabricNodeBootstrapAgent" $fabricHost = "FabricHostSvc" $fabricHostService = Get-Service -Name $fabricHost if ($fabricHostService.Status -eq "Stopped") { Start-Service $fabricHost -ErrorAction SilentlyContinue Write-Host "$env:computername : Starting $fabricHost service" -ForegroundColor Green } Do { Start-Sleep -Seconds 1 $fabricHostService = Get-Service -Name $fabricHost if(!$fabricHostService) { break } if ($fabricHostService.Status -eq "Running") { Write-Host "$env:computername : $fabricHost now running" -ForegroundColor Green } else { Write-Host "$env:computername : $fabricHost current status: $($fabricHostService.Status)" -ForegroundColor Green } } While ($fabricHostService.Status -ne "Running") $bootstrapService = Get-Service -Name $bootstrapAgent if ($bootstrapService.Status -eq "Stopped") { Start-Service $bootstrapAgent -ErrorAction SilentlyContinue Write-Host "$env:computername : Starting $bootstrapAgent service" -ForegroundColor Green } Do { Start-Sleep -Seconds 1 $bootstrapService = Get-Service -Name $bootstrapAgent if(!$bootstrapService) { break } if ($bootstrapService.Status -eq "Running") { Write-Host "$env:computername : $bootstrapAgent now running" -ForegroundColor Green } else { Write-Host "$env:computername : $bootstrapAgent current status: $($bootstrapService.Status)" -ForegroundColor Green } } While ($bootstrapService.Status -ne "Running") } #config files we need #"D:\SvcFab\_sys_0\Fabric\clusterManifest.current.xml" #"D:\SvcFab\_sys_0\Fabric\FabricPackage.current.xml" #"D:\SvcFab\_sys_0\Fabric\Fabric.Data\InfrastructureManifest.xml" #"D:\SvcFab\_sys_0\Fabric\Fabric.Config.1.131523081591497214\Settings.xml" $result = Get-ChildItem -Path $clusterDataRootPath -Filter "Fabric.Data" -Directory -Recurse $hostPath = $result.Parent.Parent.Name Write-Host "---------------------------------------------------------------------------------------------------------" Write-Host "---- Working on ip:" $hostPath Write-Host "---------------------------------------------------------------------------------------------------------" $manifestFile = $clusterDataRootPath + "\" + $hostPath + "\Fabric\ClusterManifest.current.xml" $packagefile = $clusterDataRootPath + "\" + $hostPath + "\Fabric\Fabric.Package.current.xml" $infrastructureManifestFile = $clusterDataRootPath + "\" + $hostPath + "\Fabric\Fabric.Data\InfrastructureManifest.xml" #to get the settings.xml we need to determine the current version #"D:\SvcFab\_sys_0\Fabric\Fabric.Package.current.xml" --> Read to determine verion# <ConfigPackage Name="Fabric.Config" Version="1.131523081591497214" /> $currentPackageXml = [xml](Get-Content $packageFile) $packageName = $currentPackageXml.ServicePackage.DigestedConfigPackage.ConfigPackage | Select-Object -ExpandProperty Name $packageVersion = $currentPackageXml.ServicePackage.DigestedConfigPackage.ConfigPackage | Select-Object -ExpandProperty Version $settingsFile = $clusterDataRootPath + "\" + $hostPath + "\Fabric\" + $packageName + "." + $packageVersion + "\settings.xml" # make a backup folder $backupFolder = $clusterDataRootPath + '\Temp\ManifestBackups' $backupSettingsFolder = ($backupFolder + "\" + $packageName + "." + $packageVersion) New-Item -ItemType Directory -Force -Path $backupFolder | out-null New-Item -ItemType Directory -Force -Path $backupSettingsFolder | out-null #copy current config to the backup folder Copy-Item -Path $settingsFile -Destination ($backupFolder + "\" + $packageName + "." + $packageVersion) -Force -Verbose Copy-Item -Path $packageFile -Destination $backupFolder -Force -Verbose if ($hard) { # if hard patching, backup cluster manifest as well Copy-Item -Path $manifestFile -Destination $backupFolder -Force -Verbose Write-Host "$env:computername : Stopping services " -ForegroundColor Green StopServiceFabricServices } $PatchCertCNEntry = { param ($xmlSection, $commonName, $thumbprints) if(@($xmlSection.Value).Count -eq 1) { $xmlSection.Value = $thumbprints } else { $xmlSection[@($xmlSection.Name).IndexOf($commonname)].Value = $thumbprints } } # settings xml patch $settingsXml = [xml](Get-Content $settingsFile) $fabricnodesection = $settingsXml.Settings.Section.Get($settingsXml.Settings.Section.Name.IndexOf("FabricNode")).Parameter $commonname = $fabricnodesection.Value.get($fabricnodeSection.Name.IndexOf("ClusterX509FindValue")) $adminClientSection = $settingsXml.Settings.Section.Get($settingsXml.Settings.Section.Name.IndexOf("Security/AdminClientX509Names")).Parameter $clientSection = $settingsXml.Settings.Section.Get($settingsXml.Settings.Section.Name.IndexOf("Security/ClientX509Names")).Parameter $clusterSection = $settingsXml.Settings.Section.Get($settingsXml.Settings.Section.Name.IndexOf("Security/ClusterX509Names")).Parameter $serverSection = $settingsXml.Settings.Section.Get($settingsXml.Settings.Section.Name.IndexOf("Security/ServerX509Names")).Parameter $currentIssuerThumbprints = @($clusterSection.Value).Get(@($clusterSection.Name).IndexOf($commonname)) Write-Host ("$env:computername : Current issuer configuration is " + $currentIssuerThumbprints + " Target issuer configuration is " + $newIssuerThumbprints) -ForegroundColor Green invoke-command -ScriptBlock $PatchCertCNEntry -ArgumentList $clusterSection, $commonname, $newIssuerThumbprints invoke-command -ScriptBlock $PatchCertCNEntry -ArgumentList $serverSection, $commonname, $newIssuerThumbprints invoke-command -ScriptBlock $PatchCertCNEntry -ArgumentList $adminClientSection, $commonname, $newIssuerThumbprints invoke-command -ScriptBlock $PatchCertCNEntry -ArgumentList $clientSection, $commonname, $newIssuerThumbprints $settingsXml.Save($settingsFile) # end settings xml patch if ($hard) { # manifest xml patch Set-ItemProperty -Path $manifestFile -Name IsReadOnly -Value $false $manifestXml = [xml](Get-Content $manifestFile) $clusterSection = $manifestXml.ClusterManifest.FabricSettings.Section.Get($manifestXml.ClusterManifest.FabricSettings.Section.Name.IndexOf("Security/ClusterX509Names")).Parameter $serverSection = $manifestXml.ClusterManifest.FabricSettings.Section.Get($manifestXml.ClusterManifest.FabricSettings.Section.Name.IndexOf("Security/ServerX509Names")).Parameter $adminClientSection = $manifestXml.ClusterManifest.FabricSettings.Section.Get($manifestXml.ClusterManifest.FabricSettings.Section.Name.IndexOf("Security/AdminClientX509Names")).Parameter $clientSection = $manifestXml.ClusterManifest.FabricSettings.Section.Get($manifestXml.ClusterManifest.FabricSettings.Section.Name.IndexOf("Security/ClientX509Names")).Parameter invoke-command -ScriptBlock $PatchCertCNEntry -ArgumentList $clusterSection, $commonname, $newIssuerThumbprints invoke-command -ScriptBlock $PatchCertCNEntry -ArgumentList $serverSection, $commonname, $newIssuerThumbprints invoke-command -ScriptBlock $PatchCertCNEntry -ArgumentList $adminClientSection, $commonname, $newIssuerThumbprints invoke-command -ScriptBlock $PatchCertCNEntry -ArgumentList $clientSection, $commonname, $newIssuerThumbprints $manifestXml.Save($manifestFile) # end manifest xml patch #update the node configuration $logRoot = $clusterDataRootPath + "\Log" Write-Host "$env:computername : Updating Node configuration" -ForegroundColor Green New-ServiceFabricNodeConfiguration -FabricDataRoot $clusterDataRootPath -FabricLogRoot $logRoot -ClusterManifestPath $manifestFile -InfrastructureManifestPath $infrastructureManifestFile Write-Host "$env:computername : Updating Node configuration complete" -ForegroundColor Green #restart these services Write-Host "$env:computername : Starting services " -ForegroundColor Green StartServiceFabricServices } else { # we append a benign space to the end of fabric package file, this triggers watchers on this file Add-Content -Path $packagefile -Value ' ' } Write-Host "$env:computername : Finished patch" -ForegroundColor Green } if ($localOnly) { write-host "executing on local node only" invoke-command -ScriptBlock $scriptBlock -ArgumentList $clusterDataRootPath, $targetIssuerThumbprints, $hard return } if (!$global:creds) { Write-Host "Enter your RDP Credentials" #Get the RDP User Name and Password $creds = Get-Credential if ($cacheCredentials) { $global:creds = $creds } } else{ $creds = $global:creds } ForEach ($nodeIpAddress in $nodeIpArray) { $count++ #Verifying whether corresponding VM is up and running if (Test-Connection -ComputerName $nodeIpAddress -Quiet) { $activity = "total minutes: $(((get-date) - $startTime).TotalMinutes.tostring("0.0")). connecting to: $nodeIpAddress ($count of $($nodeIpArray.Count))" $status = "success: $($global:successNodes | sort -Unique) fail: $($global:failNodes | sort -Unique)" Write-Progress -Activity $activity ` -Status $status ` -PercentComplete (($count / $nodeIpArray.Count) * 100) write-host "$env:computername : updating trustedhosts list" -foregroundcolor green set-item wsman:\localhost\Client\TrustedHosts -value $nodeIpAddress -Force Start-Sleep(1) $error.clear() Invoke-Command -Authentication Negotiate -ComputerName $nodeIpAddress { Set-NetFirewallRule -DisplayGroup 'File and Printer Sharing' -Enabled True -PassThru | Select-Object DisplayName, Enabled } -Credential ($creds) if ($error) { $global:failNodes += $nodeIpAddress continue } $error.clear() Invoke-Command -Authentication Negotiate -Computername $nodeIpAddress -Scriptblock $scriptBlock ` -ArgumentList $clusterDataRootPath, $targetIssuerThumbprints, $hard if ($error) { $global:failNodes += $nodeIpAddress } else { $global:successNodes += $nodeIpAddress } } else { Write-Warning "$env:computername : unable to connect to node: $nodeIpAddress" $global:failNodes += $nodeIpAddress } } write-host "reset trusted hosts to original values" -foregroundcolor green set-item wsman:\localhost\Client\TrustedHosts -value $curValue -Force Write-Progress -Completed -Activity "complete" if ($global:successNodes) { $successUnique = $global:successNodes | sort-object -Unique write-host "total node success: $(@($successUnique).Count)" -ForegroundColor green write-host ($successUnique | fl * | out-string) } if ($global:failNodes) { $failUnique = $global:failNodes | sort-object -Unique write-warning "`r`ntotal node connection errors: $(@($failUnique).Count). review output" write-host ($failUnique | fl * | out-string) write-warning "for any failed nodes, rdp to node and run this script with '-localOnly' switch" } write-host "finished. total minutes: $(((get-date) - $startTime).TotalMinutes.ToString("0.0"))" -foregroundcolor green