From f233e66e0bda422b7ff25e2cb01261eee18d7432 Mon Sep 17 00:00:00 2001 From: Adam Rudell Date: Thu, 18 Apr 2024 21:59:35 -0500 Subject: [PATCH] save latest changes from testing --- .../private/Confirm-IsCertSelfSigned.ps1 | 1 + .../private/Copy-CertificateToFabric.ps1 | 188 +++++---------- .../public/Get-SdnCertificate.ps1 | 10 + .../public/Import-SdnCertificate.ps1 | 7 +- .../Start-SdnMuxCertificateRotation.ps1 | 15 +- .../SdnDiag.NetworkController.Helper.psm1 | 20 +- .../private/Confirm-IsNetworkController.ps1 | 7 + .../Start-SdnExpiredCertificateRotation.ps1 | 50 ++-- .../Test-NetworkControllerIsHealthy.ps1 | 9 + ...Update-NetworkControllerCertificateAcl.ps1 | 48 ---- .../Wait-ServiceFabricClusterHealthy.ps1 | 22 +- ...et-SdnNetworkControllerRestCertificate.ps1 | 26 +-- .../New-SdnCertificateRotationConfig.ps1 | 15 +- ...ew-SdnNetworkControllerRestCertificate.ps1 | 37 +-- ...tworkControllerRestCertificateRotation.ps1 | 215 ++++++++++++++---- .../Test-SdnCertificateRotationConfig.ps1 | 90 ++++---- .../public/New-SdnServerCertificate.ps1 | 5 +- .../Start-SdnServerCertificateRotation.ps1 | 9 +- 18 files changed, 374 insertions(+), 400 deletions(-) rename src/modules/{SdnDiag.Utilities => SdnDiag.Common}/private/Confirm-IsCertSelfSigned.ps1 (70%) rename src/modules/{SdnDiag.NetworkController => SdnDiag.LoadBalancerMux}/public/Start-SdnMuxCertificateRotation.ps1 (91%) create mode 100644 src/modules/SdnDiag.NetworkController/private/Confirm-IsNetworkController.ps1 create mode 100644 src/modules/SdnDiag.NetworkController/private/Test-NetworkControllerIsHealthy.ps1 delete mode 100644 src/modules/SdnDiag.NetworkController/private/Update-NetworkControllerCertificateAcl.ps1 rename src/modules/{SdnDiag.NetworkController => SdnDiag.Server}/public/Start-SdnServerCertificateRotation.ps1 (94%) diff --git a/src/modules/SdnDiag.Utilities/private/Confirm-IsCertSelfSigned.ps1 b/src/modules/SdnDiag.Common/private/Confirm-IsCertSelfSigned.ps1 similarity index 70% rename from src/modules/SdnDiag.Utilities/private/Confirm-IsCertSelfSigned.ps1 rename to src/modules/SdnDiag.Common/private/Confirm-IsCertSelfSigned.ps1 index 21cd059b..3327f9aa 100644 --- a/src/modules/SdnDiag.Utilities/private/Confirm-IsCertSelfSigned.ps1 +++ b/src/modules/SdnDiag.Common/private/Confirm-IsCertSelfSigned.ps1 @@ -6,6 +6,7 @@ function Confirm-IsCertSelfSigned { ) if ($Certificate.Issuer -eq $Certificate.Subject) { + "Detected the certificate subject and issuer are the same. Setting SelfSigned to true" | Trace-Output -Level:Verbose return $true } diff --git a/src/modules/SdnDiag.Common/private/Copy-CertificateToFabric.ps1 b/src/modules/SdnDiag.Common/private/Copy-CertificateToFabric.ps1 index 312def8d..2e12ee42 100644 --- a/src/modules/SdnDiag.Common/private/Copy-CertificateToFabric.ps1 +++ b/src/modules/SdnDiag.Common/private/Copy-CertificateToFabric.ps1 @@ -43,6 +43,21 @@ function Copy-CertificateToFabric { $Credential = [System.Management.Automation.PSCredential]::Empty ) + $createRemoteDirectorySB = { + param([Parameter(Position = 0)][String]$param1) + if (-NOT (Test-Path -Path $param1 -PathType Container)) { + New-Item -Path $param1 -ItemType Directory -Force + } + } + + # scriptblock to import the certificate + # this function will automatically install the certificate to the localmachine\root cert directory + # if the certificate passed to it is self-signed and it is being installed to localmachine\my cert directory + $importCertSB = { + param([Parameter(Position = 0)][String]$param1, [Parameter(Position = 1)][SecureString]$param2, [Parameter(Position = 2)][String]$param3) + Import-SdnCertificate -FilePath $param1 -CertPassword $param2 -CertStore $param3 + } + # if we are installing the rest certificate and need to seed certificate to southbound devices # then define the variables to know which nodes must be updated if ($PSCmdlet.ParameterSetName -ieq 'NetworkControllerRest' -and $InstallToSouthboundDevices) { @@ -57,6 +72,8 @@ function Copy-CertificateToFabric { } $certFileInfo = Get-Item -Path $CertFile -ErrorAction Stop + [System.String]$remoteFilePath = Join-Path -Path $certFileInfo.Directory.FullName -ChildPath $certFileInfo.Name + switch ($certFileInfo.Extension) { '.pfx' { if ($CertPassword) { @@ -79,151 +96,68 @@ function Copy-CertificateToFabric { switch ($PSCmdlet.ParameterSetName) { 'LoadBalancerMuxNode' { - foreach ($controller in $FabricDetails.NetworkController) { - # if the certificate being passed is self-signed, we will need to copy the certificate to the other controller nodes - # within the fabric and install under localmachine\root as appropriate - if ($certData.Subject -ieq $certData.Issuer) { - "Importing certificate [Subject: {0} Thumbprint:{1}] to {2}" -f ` - $certData.Subject, $certData.Thumbprint, $controller | Trace-Output - - [System.String]$remoteFilePath = Join-Path -Path $certFileInfo.Directory.FullName -ChildPath $certFileInfo.Name - $null = Invoke-PSRemoteCommand -ComputerName $controller -Credential $Credential -ScriptBlock { - param([Parameter(Position = 0)][String]$param1) - if (-NOT (Test-Path -Path $param1 -PathType Container)) { - New-Item -Path $param1 -ItemType Directory -Force - } - } -ArgumentList $certFileInfo.Directory.FullName - - Copy-FileToRemoteComputer -ComputerName $controller -Credential $Credential -Path $certFileInfo.FullName -Destination $remoteFilePath - - $null = Invoke-PSRemoteCommand -ComputerName $controller -Credential $Credential -ScriptBlock { - param([Parameter(Position = 0)][String]$param1, [Parameter(Position = 1)][SecureString]$param2, [Parameter(Position = 2)][String]$param3) - Import-SdnCertificate -FilePath $param1 -CertPassword $param2 -CertStore $param3 - } -ArgumentList @($remoteFilePath, $CertPassword, 'Cert:\LocalMachine\Root') -ErrorAction Stop - } - - else { - "No action required for {0}" -f $certData.Thumbprint | Trace-Output -Level:Verbose - } + if (Confirm-IsCertSelfSigned -Certificate $certData) { + $certStore = 'Cert:\LocalMachine\Root' + $computersToInstallCert = $FabricDetails.NetworkController } } 'NetworkControllerRest' { - # copy the pfx certificate for the rest certificate to all network controllers within the cluster - # and import to localmachine\my cert directory - foreach ($controller in $FabricDetails.NetworkController) { - "Processing {0}" -f $controller | Trace-Output -Level:Verbose - - "[REST CERT] Importing certificate [Subject: {0} Thumbprint:{1}] to {2}" -f ` - $certData.Subject, $certData.Thumbprint, $controller | Trace-Output - - if (Test-ComputerNameIsLocal -ComputerName $controller) { - $importCert = Import-SdnCertificate -FilePath $certFileInfo.FullName -CertPassword $CertPassword -CertStore 'Cert:\LocalMachine\My' - - # if the certificate was detected as self signed - # we will then copy the .cer file returned from the previous command to all the southbound nodes to install - if ($importCert.SelfSigned -and $InstallToSouthboundDevices) { - Install-SdnDiagnostics -ComputerName $southBoundNodes -Credential $Credential -ErrorAction Stop - - "[REST CERT] Installing self-signed certificate to southbound devices" | Trace-Output - Invoke-PSRemoteCommand -ComputerName $southBoundNodes -Credential $Credential -ScriptBlock { - param([Parameter(Position = 0)][String]$param1) - if (-NOT (Test-Path -Path $param1 -PathType Container)) { - $null = New-Item -Path $param1 -ItemType Directory -Force - } - } -ArgumentList $importCert.CerFileInfo.Directory.FullName - - foreach ($sbNode in $southBoundNodes) { - "[REST CERT] Installing self-signed certificate to {0}" -f $sbNode | Trace-Output - Copy-FileToRemoteComputer -ComputerName $sbNode -Credential $Credential -Path $importCert.CerFileInfo.FullName -Destination $importCert.CerFileInfo.FullName - $null = Invoke-PSRemoteCommand -ComputerName $sbNode -Credential $Credential -ScriptBlock { - param([Parameter(Position = 0)][String]$param1,[Parameter(Position = 1)][String]$param2) - Import-SdnCertificate -FilePath $param1 -CertStore $param2 - } -ArgumentList @($importCert.CerFileInfo.FullName, 'Cert:\LocalMachine\Root') -ErrorAction Stop - } - } + if (Confirm-IsCertSelfSigned -Certificate $certData) { + if ($InstallToSouthboundDevices) { + # for southbound devices, if the certificate is self-signed, we will install the certificate under the localmachine\root cert directory + $certStore = 'Cert:\LocalMachine\Root' + $computersToInstallCert = $southBoundNodes } else { - [System.String]$remoteFilePath = Join-Path -Path $certFileInfo.Directory.FullName -ChildPath $certFileInfo.Name - $null = Invoke-PSRemoteCommand -ComputerName $controller -Credential $Credential -ScriptBlock { - param([Parameter(Position = 0)][String]$param1) - if (-NOT (Test-Path -Path $param1 -PathType Container)) { - New-Item -Path $param1 -ItemType Directory -Force - } - } -ArgumentList $certFileInfo.Directory.FullName - - Copy-FileToRemoteComputer -ComputerName $controller -Credential $Credential -Path $certFileInfo.FullName -Destination $remoteFilePath - - $null = Invoke-PSRemoteCommand -ComputerName $controller -Credential $Credential -ScriptBlock { - param([Parameter(Position = 0)][String]$param1, [Parameter(Position = 1)][SecureString]$param2, [Parameter(Position = 2)][String]$param3) - Import-SdnCertificate -FilePath $param1 -CertPassword $param2 -CertStore $param3 - } -ArgumentList @($remoteFilePath, $CertPassword, 'Cert:\LocalMachine\My') + # for network controller, we will install the certificate under the localmachine\my cert directory + $certStore = 'Cert:\LocalMachine\My' + $computersToInstallCert = $FabricDetails.NetworkController } } } 'NetworkControllerNode' { - foreach ($controller in $FabricDetails.NetworkController) { - "Processing {0}" -f $controller | Trace-Output -Level:Verbose - - # if the certificate being passed is self-signed, we will need to copy the certificate to the other controller nodes - # within the fabric and install under localmachine\root as appropriate - if ($certData.Subject -ieq $certData.Issuer) { - "Importing certificate [Subject: {0} Thumbprint:{1}] to {2}" -f ` - $certData.Subject, $certData.Thumbprint, $controller | Trace-Output - - [System.String]$remoteFilePath = Join-Path -Path $certFileInfo.Directory.FullName -ChildPath $certFileInfo.Name - $null = Invoke-PSRemoteCommand -ComputerName $controller -Credential $Credential -ScriptBlock { - param([Parameter(Position = 0)][String]$param1) - if (-NOT (Test-Path -Path $param1 -PathType Container)) { - New-Item -Path $param1 -ItemType Directory -Force - } - } -ArgumentList $certFileInfo.Directory.FullName - - Copy-FileToRemoteComputer -ComputerName $controller -Credential $Credential -Path $certFileInfo.FullName -Destination $remoteFilePath - - $null = Invoke-PSRemoteCommand -ComputerName $controller -Credential $Credential -ScriptBlock { - param([Parameter(Position = 0)][String]$param1, [Parameter(Position = 1)][SecureString]$param2, [Parameter(Position = 2)][String]$param3) - Import-SdnCertificate -FilePath $param1 -CertPassword $param2 -CertStore $param3 - } -ArgumentList @($remoteFilePath, $CertPassword, 'Cert:\LocalMachine\Root') -ErrorAction Stop - } - - else { - "No action required for {0}" -f $certData.Thumbprint | Trace-Output -Level:Verbose - } + if (Confirm-IsCertSelfSigned -Certificate $certData) { + $certStore = 'Cert:\LocalMachine\Root' + $computersToInstallCert = $FabricDetails.NetworkController } } # for ServerNodes, we must distribute the server certificate and install to the cert:\localmachine\root directory on each of the # network controller nodes 'ServerNode' { - foreach ($controller in $FabricDetails.NetworkController) { - # if the certificate being passed is self-signed, we will need to copy the certificate to the other controller nodes - # within the fabric and install under localmachine\root as appropriate - if ($certData.Subject -ieq $certData.Issuer) { - "Importing certificate [Subject: {0} Thumbprint:{1}] to {2}" -f ` - $certData.Subject, $certData.Thumbprint, $controller | Trace-Output - - [System.String]$remoteFilePath = Join-Path -Path $certFileInfo.Directory.FullName -ChildPath $certFileInfo.Name - $null = Invoke-PSRemoteCommand -ComputerName $controller -Credential $Credential -ScriptBlock { - param([Parameter(Position = 0)][String]$param1) - if (-NOT (Test-Path -Path $param1 -PathType Container)) { - New-Item -Path $param1 -ItemType Directory -Force - } - } -ArgumentList $certFileInfo.Directory.FullName - - Copy-FileToRemoteComputer -ComputerName $controller -Credential $Credential -Path $certFileInfo.FullName -Destination $remoteFilePath - - $null = Invoke-PSRemoteCommand -ComputerName $controller -Credential $Credential -ScriptBlock { - param([Parameter(Position = 0)][String]$param1, [Parameter(Position = 1)][SecureString]$param2, [Parameter(Position = 2)][String]$param3) - Import-SdnCertificate -FilePath $param1 -CertPassword $param2 -CertStore $param3 - } -ArgumentList @($remoteFilePath, $CertPassword, 'Cert:\LocalMachine\Root') -ErrorAction Stop - } - - else { - "No action required for {0}" -f $certData.Thumbprint | Trace-Output -Level:Verbose - } + if (Confirm-IsCertSelfSigned -Certificate $certData) { + $certStore = 'Cert:\LocalMachine\Root' + $computersToInstallCert = $FabricDetails.NetworkController } } } + + # create the remote directory we need to copy certificate to + Invoke-PSRemoteCommand @{ + ComputerName = $computersToInstallCert + Credential = $Credential + ScriptBlock = $createRemoteDirectorySB + ArgumentList = @($certFileInfo.Directory.FullName) + ErrorAction = 'Stop' + } + + # copy the file + Copy-FileToRemoteComputer @{ + ComputerName = $computersToInstallCert + Credential = $Credential + Path = $certFileInfo.FullName + Destination = $remoteFilePath + ErrorAction = 'Stop' + } + + # import the certificate + Invoke-PSRemoteCommand @{ + ComputerName = $computersToInstallCert + Credential = $Credential + ScriptBlock = $importCertSB + ArgumentList = @($remoteFilePath, $CertPassword, $certStore) + ErrorAction = 'Stop' + } } diff --git a/src/modules/SdnDiag.Common/public/Get-SdnCertificate.ps1 b/src/modules/SdnDiag.Common/public/Get-SdnCertificate.ps1 index 582716d9..35f6c5f0 100644 --- a/src/modules/SdnDiag.Common/public/Get-SdnCertificate.ps1 +++ b/src/modules/SdnDiag.Common/public/Get-SdnCertificate.ps1 @@ -42,12 +42,22 @@ function Get-SdnCertificate { try { $certificateList = Get-ChildItem -Path $Path | Where-Object {$_.PSISContainer -eq $false} -ErrorAction Ignore + if ($null -eq $certificateList) { + return $null + } + if ($NetworkControllerOid) { $certificateList | ForEach-Object { if ($objectIdentifier -iin $_.EnhancedKeyUsageList.ObjectId) { $array += $_ } } + + # if no certificates are found based on the OID, search based on other criteria + if (!$array) { + "Unable to locate certificates that match Network Controller OID: {0}. Searching based on other criteria." -f $objectIdentifier | Trace-Output -Level:Warning + $array = $certificateList + } } else { $array = $certificateList diff --git a/src/modules/SdnDiag.Common/public/Import-SdnCertificate.ps1 b/src/modules/SdnDiag.Common/public/Import-SdnCertificate.ps1 index e7ce7e67..c28d7b38 100644 --- a/src/modules/SdnDiag.Common/public/Import-SdnCertificate.ps1 +++ b/src/modules/SdnDiag.Common/public/Import-SdnCertificate.ps1 @@ -30,7 +30,7 @@ function Import-SdnCertificate { $certObject = [PSCustomObject]@{ SelfSigned = $false CertInfo = $null - CerFileInfo = $null + SelfSignedCertFileInfo = $null } try { @@ -73,8 +73,7 @@ function Import-SdnCertificate { } # determine if the certificates being used are self signed - if ($certObject.CertInfo.Subject -ieq $certObject.CertInfo.Issuer) { - "Detected the certificate subject and issuer are the same. Setting SelfSigned to true" | Trace-Output -Level:Verbose + if (Confirm-IsCertSelfSigned -Certificate $certObject.CertInfo) { $certObject.SelfSigned = $true # check to see if we installed to root store with above operation @@ -84,7 +83,7 @@ function Import-SdnCertificate { $selfSignedCerExists = Get-SdnCertificate -Path $trustedRootStore -Thumbprint $certObject.CertInfo.Thumbprint [System.String]$selfSignedCerPath = "{0}\{1}.cer" -f (Split-Path $fileInfo.FullName -Parent), ($certObject.CertInfo.Subject).Replace('=','_') $selfSignedCer = Export-Certificate -Cert $certObject.CertInfo -FilePath $selfSignedCerPath -ErrorAction Stop - $certObject.CerFileInfo = $selfSignedCer + $certObject.SelfSignedCertFileInfo = $selfSignedCer if (-NOT ($selfSignedCerExists)) { # import the certificate to the trusted root store diff --git a/src/modules/SdnDiag.NetworkController/public/Start-SdnMuxCertificateRotation.ps1 b/src/modules/SdnDiag.LoadBalancerMux/public/Start-SdnMuxCertificateRotation.ps1 similarity index 91% rename from src/modules/SdnDiag.NetworkController/public/Start-SdnMuxCertificateRotation.ps1 rename to src/modules/SdnDiag.LoadBalancerMux/public/Start-SdnMuxCertificateRotation.ps1 index 482fa6f2..754d3a56 100644 --- a/src/modules/SdnDiag.NetworkController/public/Start-SdnMuxCertificateRotation.ps1 +++ b/src/modules/SdnDiag.LoadBalancerMux/public/Start-SdnMuxCertificateRotation.ps1 @@ -18,8 +18,8 @@ function Start-SdnMuxCertificateRotation { [CmdletBinding(DefaultParameterSetName = 'GenerateCertificate')] param ( - [Parameter(Mandatory = $false, ParameterSetName = 'GenerateCertificate')] - [System.String]$NetworkController = $env:COMPUTERNAME, + [Parameter(Mandatory = $true, ParameterSetName = 'GenerateCertificate')] + [System.String]$NetworkController, [Parameter(Mandatory = $true, ParameterSetName = 'GenerateCertificate')] [System.Management.Automation.PSCredential] @@ -45,16 +45,7 @@ function Start-SdnMuxCertificateRotation { ) # ensure that the module is running as local administrator - $elevated = ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator) - if (-NOT $elevated) { - throw New-Object System.Exception("This function requires elevated permissions. Run PowerShell as an Administrator and import the module again.") - } - - $config = Get-SdnModuleConfiguration -Role 'NetworkController' - $confirmFeatures = Confirm-RequiredFeaturesInstalled -Name $config.windowsFeature - if (-NOT ($confirmFeatures)) { - throw New-Object System.NotSupportedException("The current machine is not a NetworkController, run this on NetworkController.") - } + Confirm-IsAdmin $array = @() $headers = @{"Accept"="application/json"} diff --git a/src/modules/SdnDiag.NetworkController/SdnDiag.NetworkController.Helper.psm1 b/src/modules/SdnDiag.NetworkController/SdnDiag.NetworkController.Helper.psm1 index e66a304d..8ec58cdf 100644 --- a/src/modules/SdnDiag.NetworkController/SdnDiag.NetworkController.Helper.psm1 +++ b/src/modules/SdnDiag.NetworkController/SdnDiag.NetworkController.Helper.psm1 @@ -16,8 +16,8 @@ class BaseCert { [bool]$IsSelfSigned } -class RestCert : BaseCert { - [CertType]$CertificateType = [CertType]::Rest +class RestCertificate : BaseCert { + [CertType]$CertificateType = [CertType]::RestCertificate } class NodeCert : BaseCert { @@ -27,19 +27,19 @@ class NodeCert : BaseCert { } class NetworkControllerNodeCert : NodeCert { - [CertType]$CertificateType = [CertType]::NetworkController + [CertType]$CertificateType = [CertType]::NetworkControllerNodeCert } class LoadBalancerMuxNodeCert : NodeCert { - [CertType]$CertificateType = [CertType]::LoadBalancerMux + [CertType]$CertificateType = [CertType]::LoadBalancerMuxNodeCert } class ServerNodeCert : NodeCert { - [CertType]$CertificateType = [CertType]::Server + [CertType]$CertificateType = [CertType]::ServerNodeCert } class CertRotateConfig { - [RestCert]$RestCert + [RestCertificate]$RestCertificate [ClusterCredentialType]$ClusterCredentialType = [ClusterCredentialType]::Kerberos [Object[]]$NodeCerts } @@ -50,10 +50,10 @@ enum ClusterCredentialType { } enum CertType { - Rest - NetworkController - Server - LoadBalancerMux + RestCertificate + NetworkControllerNodeCert + ServerNodeCert + LoadBalancerMuxNodeCert } enum NcManagedRoles { diff --git a/src/modules/SdnDiag.NetworkController/private/Confirm-IsNetworkController.ps1 b/src/modules/SdnDiag.NetworkController/private/Confirm-IsNetworkController.ps1 new file mode 100644 index 00000000..e67233d7 --- /dev/null +++ b/src/modules/SdnDiag.NetworkController/private/Confirm-IsNetworkController.ps1 @@ -0,0 +1,7 @@ +function Confirm-IsNetworkController { + $config = Get-SdnModuleConfiguration -Role 'NetworkController' + $confirmFeatures = Confirm-RequiredFeaturesInstalled -Name $config.windowsFeature + if (-NOT ($confirmFeatures)) { + throw New-Object System.NotSupportedException("The current machine is not a NetworkController, run this on NetworkController.") + } +} diff --git a/src/modules/SdnDiag.NetworkController/private/Start-SdnExpiredCertificateRotation.ps1 b/src/modules/SdnDiag.NetworkController/private/Start-SdnExpiredCertificateRotation.ps1 index ab9a0a81..d934165d 100644 --- a/src/modules/SdnDiag.NetworkController/private/Start-SdnExpiredCertificateRotation.ps1 +++ b/src/modules/SdnDiag.NetworkController/private/Start-SdnExpiredCertificateRotation.ps1 @@ -22,8 +22,7 @@ function Start-SdnExpiredCertificateRotation { param ( [Parameter(Mandatory = $true)] - [hashtable] - $CertRotateConfig, + [CertRotateConfig]$CertRotateConfig, [System.Management.Automation.PSCredential] [System.Management.Automation.Credential()] @@ -60,25 +59,40 @@ function Start-SdnExpiredCertificateRotation { throw New-Object System.NotSupportedException("Current Network Controller Rest certificate not found.") } - $NcVms = $NcNodeList.IpAddressOrFQDN - - if (Test-Path -Path $NcUpdateFolder) { - $items = Get-ChildItem -Path $NcUpdateFolder -ErrorAction Ignore - if ($items.Count -gt 0) { - $confirmCleanup = Read-Host "The Folder $NcUpdateFolder not empty. Need to be cleared. Enter Y to confirm" - if ($confirmCleanup -eq "Y") { - $items | Remove-Item -Force -Recurse + if (Test-Path -Path $NcUpdateFolder -ItemType Container) { + $items = Get-ChildItem -Path $NcUpdateFolder + if ($items) { + $confirm = Confirm-UserInput -Message "$NcUpdateFolder not empty. Proceed with cleanup? [Y/N]: " + if (-NOT $confirm) { + # throw terminating exception here + # this will stop the execution Start-SdnCertificateRotation which calls into this function + throw New-Object System.OperationCanceledException("User cancelled the operation") } else { - return + $items | Remove-Item -Force -Recurse } } } - foreach ($nc in $NcVms) { - Invoke-Command -ComputerName $nc -ScriptBlock { - Write-Host "[$(HostName)] Stopping Service Fabric Service" - Stop-Service FabricHostSvc -Force + # stop service fabric service + $stopSfService = Invoke-PSRemoteCommand -ComputerName $NcNodeList.IpAddressOrFQDN -Credential $Credential -ScriptBlock { + Stop-Service -Name 'FabricHostSvc' -Force -ErrorAction Ignore 3>$null # redirect warning to null + if ((Get-Service -Name 'FabricHostSvc' -ErrorAction Ignore).Status -eq 'Stopped') { + return $true + } + else { + return $false + } + } -AsJob -PassThru -Activity 'Stopping Service Fabric Service on Network Controller' -ExecutionTimeOut 900 + + # enumerate the results of stopping service fabric service + # if any of the service fabric service is not stopped, throw an exception as we do not want to proceed further + $stopSfService | ForEach-Object { + if ($_) { + "Service Fabric Service stopped on {0}" -f $_.PSComputerName | Trace-Output + } + else { + throw "Failed to stop Service Fabric Service on $($_.PSComputerName)" } } @@ -93,16 +107,14 @@ function Start-SdnExpiredCertificateRotation { Trace-Output -Message "Step 3 Copy the new files back to the NC vms" Copy-ServiceFabricManifestToNetworkController -NcNodeList $NcNodeList -ManifestFolder $ManifestFolderNew -Credential $Credential - # Step 5 Start FabricHostSvc and wait for SF system service to become healty + # Step 4 Start FabricHostSvc and wait for SF system service to become healty Trace-Output -Message "Step 4 Start FabricHostSvc and wait for SF system service to become healty" - Trace-Output -Message "Step 4.1 Update Network Controller Certificate ACL to allow 'Network Service' Access" - Update-NetworkControllerCertificateAcl -NcNodeList $NcNodeList -CertRotateConfig $CertRotateConfig -Credential $Credential - Trace-Output -Message "Step 4.2 Start Service Fabric Host Service and wait" $clusterHealthy = Wait-ServiceFabricClusterHealthy -NcNodeList $NcNodeList -CertRotateConfig $CertRotateConfig -Credential $Credential Trace-Output -Message "ClusterHealthy: $clusterHealthy" if($clusterHealthy -ne $true){ throw New-Object System.NotSupportedException("Cluster unheathy after manifest update, we cannot continue with current situation") } + # Step 6 Invoke SF Cluster Upgrade Trace-Output -Message "Step 5 Invoke SF Cluster Upgrade" Update-ServiceFabricCluster -NcNodeList $NcNodeList -CertRotateConfig $CertRotateConfig -ManifestFolderNew $ManifestFolderNew -Credential $Credential diff --git a/src/modules/SdnDiag.NetworkController/private/Test-NetworkControllerIsHealthy.ps1 b/src/modules/SdnDiag.NetworkController/private/Test-NetworkControllerIsHealthy.ps1 new file mode 100644 index 00000000..dc129d60 --- /dev/null +++ b/src/modules/SdnDiag.NetworkController/private/Test-NetworkControllerIsHealthy.ps1 @@ -0,0 +1,9 @@ +function Test-NetworkControllerIsHealthy { + try { + $null = Get-NetworkController -ErrorAction 'Stop' + return $true + } + catch { + return $false + } +} diff --git a/src/modules/SdnDiag.NetworkController/private/Update-NetworkControllerCertificateAcl.ps1 b/src/modules/SdnDiag.NetworkController/private/Update-NetworkControllerCertificateAcl.ps1 deleted file mode 100644 index 87f1d4ff..00000000 --- a/src/modules/SdnDiag.NetworkController/private/Update-NetworkControllerCertificateAcl.ps1 +++ /dev/null @@ -1,48 +0,0 @@ -function Update-NetworkControllerCertificateAcl { - <# - .SYNOPSIS - Update the Network Controller Certificate to grant Network Service account read access to the private key. - .PARAMETER NcNodeList - The NcNodeList that retrieved via Get-SdnNetworkControllerInfoOffline. - .PARAMETER CertRotateConfig - The Config generated by New-SdnCertificateRotationConfig to include NC REST certificate thumbprint and node certificate thumbprint. - .PARAMETER Credential - Specifies a user account that has permission to perform this action. The default is the current user. - #> - - param ( - [Parameter(Mandatory = $true)] - [PSCustomObject[]] - $NcNodeList, - [Parameter(Mandatory = $true)] - [hashtable] - $CertRotateConfig, - [Parameter(Mandatory = $false)] - [System.Management.Automation.PSCredential] - [System.Management.Automation.Credential()] - $Credential = [System.Management.Automation.PSCredential]::Empty - ) - - try { - $NcRestCertThumbprint = $CertRotateConfig["NcRestCert"] - - foreach ($ncNode in $NcNodeList) { - $ncNodeCertThumbprint = $CertRotateConfig[$ncNode.NodeName.ToLower()] - Invoke-PSRemoteCommand -ComputerName $ncNode.IpAddressOrFQDN -Credential $Credential -ScriptBlock { - param([Parameter(Position = 0)][String]$param1, [Parameter(Position = 1)][String]$param2) - Set-SdnCertificateAcl -Path $param1 -Thumbprint $param2 - } -ArgumentList @('Cert:\LocalMachine\My', $NcRestCertThumbprint) - - if ($CertRotateConfig["ClusterCredentialType"] -ieq "X509") { - Invoke-PSRemoteCommand -ComputerName $ncNode.IpAddressOrFQDN -Credential $Credential -ScriptBlock { - param([Parameter(Position = 0)][String]$param1, [Parameter(Position = 1)][String]$param2) - Set-SdnCertificateAcl -Path $param1 -Thumbprint $param2 - } -ArgumentList @('Cert:\LocalMachine\My', $ncNodeCertThumbprint) - } - } - } - catch { - $_ | Trace-Exception - $_ | Write-Error - } -} diff --git a/src/modules/SdnDiag.NetworkController/private/Wait-ServiceFabricClusterHealthy.ps1 b/src/modules/SdnDiag.NetworkController/private/Wait-ServiceFabricClusterHealthy.ps1 index 44cb6dbe..d83ae0ba 100644 --- a/src/modules/SdnDiag.NetworkController/private/Wait-ServiceFabricClusterHealthy.ps1 +++ b/src/modules/SdnDiag.NetworkController/private/Wait-ServiceFabricClusterHealthy.ps1 @@ -13,12 +13,10 @@ function Wait-ServiceFabricClusterHealthy { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] - [PSCustomObject[]] - $NcNodeList, + [PSCustomObject[]]$NcNodeList, [Parameter(Mandatory = $true)] - [hashtable] - $CertRotateConfig, + [CertRotateConfig]$CertRotateConfig, [Parameter(Mandatory = $false)] [System.Management.Automation.PSCredential] @@ -26,8 +24,7 @@ function Wait-ServiceFabricClusterHealthy { $Credential = [System.Management.Automation.PSCredential]::Empty, [Parameter(Mandatory = $false)] - [switch] - $Restart + [switch]$Restart ) try { @@ -35,7 +32,7 @@ function Wait-ServiceFabricClusterHealthy { # Start Service Fabric Service for each NC foreach ($ncNode in $NcNodeList) { - if(Test-ComputerNameIsLocal -ComputerName $ncNode.IpAddressOrFQDN){ + if (Test-ComputerNameIsLocal -ComputerName $ncNode.IpAddressOrFQDN) { $currentNcNode = $ncNode } @@ -59,10 +56,10 @@ function Wait-ServiceFabricClusterHealthy { $maxRetry = 10 $clusterConnected = $false while ($maxRetry -gt 0) { - if(!$clusterConnected){ - try{ + if (!$clusterConnected) { + try { "Service fabric cluster connect attempt $(11 - $maxRetry)/10" | Trace-Output - if ($CertRotateConfig["ClusterCredentialType"] -ieq "X509") { + if ($CertRotateConfig.ClusterCredentialType -ieq "X509") { "Connecting to Service Fabric Cluster using cert with thumbprint: {0}" -f $certThumb | Trace-Output Connect-ServiceFabricCluster -X509Credential -FindType FindByThumbprint -FindValue $certThumb -ConnectionEndpoint "$($NodeFQDN):49006" | Out-Null } @@ -70,13 +67,14 @@ function Wait-ServiceFabricClusterHealthy { Connect-ServiceFabricCluster | Out-Null } $clusterConnected = $true - }catch{ + } + catch { $maxRetry -- continue } } - if($clusterConnected){ + if ($clusterConnected) { $services = @() $services = Get-ServiceFabricService -ApplicationName fabric:/System $allServiceHealth = $true diff --git a/src/modules/SdnDiag.NetworkController/public/Get-SdnNetworkControllerRestCertificate.ps1 b/src/modules/SdnDiag.NetworkController/public/Get-SdnNetworkControllerRestCertificate.ps1 index 6e8d2536..1c5dfe7d 100644 --- a/src/modules/SdnDiag.NetworkController/public/Get-SdnNetworkControllerRestCertificate.ps1 +++ b/src/modules/SdnDiag.NetworkController/public/Get-SdnNetworkControllerRestCertificate.ps1 @@ -4,27 +4,21 @@ function Get-SdnNetworkControllerRestCertificate { Returns the current Network Controller REST Certificate #> - try { - - $config = Get-SdnModuleConfiguration -Role 'NetworkController' - $confirmFeatures = Confirm-RequiredFeaturesInstalled -Name $config.windowsFeature - if (-NOT ($confirmFeatures)) { - "The current machine is not a NetworkController, run this on NetworkController or use -NetworkController parameter to specify one" | Trace-Output -Level:Warning - return # don't throw exception, since this is a controlled scenario and we do not need stack exception tracing - } + Confirm-IsNetworkController - $networkController = Get-SdnNetworkController + try { + $networkController = Get-SdnNetworkController -ErrorAction 'Stop' $ncRestCertThumprint = $($networkController.ServerCertificate.Thumbprint).ToString() - $certificate = Get-SdnCertificate -Path 'Cert:\LocalMachine\My' -Thumbprint $ncRestCertThumprint - - if ($null -eq $certificate) { - throw New-Object System.NullReferenceException("Unable to locate Network Controller Rest Certificate") - } - - return $certificate + $certificate = Get-SdnCertificate -Path 'Cert:\LocalMachine\My' -Thumbprint $ncRestCertThumprint -ErrorAction 'Stop' } catch { $_ | Trace-Exception $_ | Write-Error } + + if ($null -eq $certificate) { + throw New-Object System.NullReferenceException("Unable to locate Network Controller Rest Certificate") + } + + return $certificate } diff --git a/src/modules/SdnDiag.NetworkController/public/New-SdnCertificateRotationConfig.ps1 b/src/modules/SdnDiag.NetworkController/public/New-SdnCertificateRotationConfig.ps1 index 3c8ca441..62da82f1 100644 --- a/src/modules/SdnDiag.NetworkController/public/New-SdnCertificateRotationConfig.ps1 +++ b/src/modules/SdnDiag.NetworkController/public/New-SdnCertificateRotationConfig.ps1 @@ -11,8 +11,7 @@ function New-SdnCertificateRotationConfig { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] - [ValidateSet('Rest','NetworkController','Server','LoadBalancerMux')] - [String]$CertificateType, + [CertType]$CertificateType, [Parameter(Mandatory = $false)] [System.Management.Automation.PSCredential] @@ -37,7 +36,7 @@ function New-SdnCertificateRotationConfig { [uri]$ncUrl = "https://$($NcInfraInfo.NcRestName)" switch ($CertificateType) { - 'LoadBalancerMux' { + 'LoadBalancerMuxNodeCert' { $servers = Get-SdnLoadBalancerMux -NcUri $ncUrl -Credential $NcRestCredential $servers | ForEach-Object { $virtualServer = Get-SdnResource -NcUri $ncUrl -ResourceRef $_.properties.virtualServer.resourceRef @@ -62,10 +61,10 @@ function New-SdnCertificateRotationConfig { } } - 'NetworkController' { + 'NetworkControllerNodeCert' { } - 'Rest' { + 'RestCertificate' { # grab the rest certificate with the latest expiration date $restCertificate = Get-SdnCertificate -Path 'Cert:\LocalMachine\My' -Subject $restSubjectName -NetworkControllerOid:$NetworkControllerOid ` | Sort-Object -Property NotAfter -Descending | Select-Object -First 1 @@ -74,17 +73,15 @@ function New-SdnCertificateRotationConfig { throw New-Object System.NullReferenceException("Failed to locate Rest certificate") } - $restCertObject = [RestCert]@{ + $CertRotateConfig.RestCertificate = [RestCertificate]@{ CertificateType = 'Rest' Thumbprint = $restCertificate.Thumbprint SubjectName = $restCertificate.Subject IsSelfSigned = (Confirm-IsCertSelfSigned -Certificate $restCertificate) } - - $certRotateConfig.RestCert = $restCertObject } - 'Server' { + 'ServerNodeCert' { $servers = Get-SdnServer -NcUri $ncUrl -Credential $NcRestCredential $servers | ForEach-Object { $connection = $_.properties.connections | Where-Object { $_.credentialType -ieq "X509Certificate" -or $_.credentialType -ieq "X509CertificateSubjectName" } diff --git a/src/modules/SdnDiag.NetworkController/public/New-SdnNetworkControllerRestCertificate.ps1 b/src/modules/SdnDiag.NetworkController/public/New-SdnNetworkControllerRestCertificate.ps1 index 7178b163..c10053f7 100644 --- a/src/modules/SdnDiag.NetworkController/public/New-SdnNetworkControllerRestCertificate.ps1 +++ b/src/modules/SdnDiag.NetworkController/public/New-SdnNetworkControllerRestCertificate.ps1 @@ -5,7 +5,7 @@ function New-SdnNetworkControllerRestCertificate { .PARAMETER NotAfter Specifies the date and time, as a DateTime object, that the certificate expires. To obtain a DateTime object, use the Get-Date cmdlet. The default value for this parameter is one year after the certificate was created. .PARAMETER CertPassword - Specifies the password for the imported PFX file in the form of a secure string. + Specifies the password for the PFX file in the form of a secure string. #> [CmdletBinding()] @@ -22,9 +22,6 @@ function New-SdnNetworkControllerRestCertificate { [Parameter(Mandatory = $false)] [System.String]$Path = "$(Get-WorkingDirectory)\NcRest_{0}" -f (Get-FormattedDateTimeUTC), - [Parameter(Mandatory = $false)] - [System.Object]$FabricDetails, - [System.Management.Automation.PSCredential] [System.Management.Automation.Credential()] $Credential = [System.Management.Automation.PSCredential]::Empty @@ -37,28 +34,9 @@ function New-SdnNetworkControllerRestCertificate { } # ensure that the module is running as local administrator - $elevated = ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator) - if (-NOT $elevated) { - throw New-Object System.Exception("This function requires elevated permissions. Run PowerShell as an Administrator and import the module again.") - } + Confirm-IsAdmin try { - if ($FabricDetails) { - if ($FabricDetails.LoadBalancerMux -or $FabricDetails.Server) { - $installToSouthboundDevices = $true - } - else { - $installToSouthboundDevices = $false - } - } - else { - $installToSouthboundDevices = $false - - $FabricDetails = [SdnFabricInfrastructure]@{ - NetworkController = (Get-SdnNetworkControllerNode).Server - } - } - if (-NOT (Test-Path -Path $Path -PathType Container)) { "Creating directory {0}" -f $Path | Trace-Output $certPath = New-Item -Path $Path -ItemType Directory -Force @@ -71,24 +49,15 @@ function New-SdnNetworkControllerRestCertificate { $certificate = New-SdnSelfSignedCertificate -Subject $formattedSubject -NotAfter $NotAfter # after the certificate has been generated, we want to export the certificate using the $CertPassword provided by the operator - # and save the file to directory. This allows the rest of the function to pick up these files and perform the steps as normal [System.String]$pfxFilePath = "$(Join-Path -Path $certPath.FullName -ChildPath $RestName.ToLower().Replace('.','_').Replace('=','_').Trim()).pfx" "Exporting pfx certificate to {0}" -f $pfxFilePath | Trace-Output $exportedCertificate = Export-PfxCertificate -Cert $certificate -FilePath $pfxFilePath -Password $CertPassword -CryptoAlgorithmOption AES256_SHA256 $null = Import-SdnCertificate -FilePath $exportedCertificate.FullName -CertStore 'Cert:\LocalMachine\Root' -CertPassword $CertPassword - Copy-CertificateToFabric -CertFile $exportedCertificate.FullName -CertPassword $CertPassword -FabricDetails $FabricDetails ` - -NetworkControllerRestCertificate -InstallToSouthboundDevices:$installToSouthboundDevices -Credential $Credential - - return ([PSCustomObject]@{ - Certificate = $certificate - FileInfo = $exportedCertificate - }) + return $exportedCertificate } catch { $_ | Trace-Exception $_ | Write-Error } - - return $null } diff --git a/src/modules/SdnDiag.NetworkController/public/Start-SdnNetworkControllerRestCertificateRotation.ps1 b/src/modules/SdnDiag.NetworkController/public/Start-SdnNetworkControllerRestCertificateRotation.ps1 index 0afa3a92..43550cad 100644 --- a/src/modules/SdnDiag.NetworkController/public/Start-SdnNetworkControllerRestCertificateRotation.ps1 +++ b/src/modules/SdnDiag.NetworkController/public/Start-SdnNetworkControllerRestCertificateRotation.ps1 @@ -6,8 +6,8 @@ function Start-SdnNetworkControllerRestCertificateRotation { Specifies a user account that has permission to perform this action. The default is the current user. .PARAMETER NcRestCredential Specifies a user account that has permission to access the northbound NC API interface. The default is the current user. - .PARAMETER CertPath - Path directory where certificate(s) .pfx files are located for use with certificate rotation. + .PARAMETER Certificate + Specifies the certificate file path to be used for certificate rotation. .PARAMETER GenerateCertificate Switch to determine if certificate rotate function should generate self-signed certificates. .PARAMETER CertPassword @@ -22,6 +22,19 @@ function Start-SdnNetworkControllerRestCertificateRotation { [CmdletBinding(DefaultParameterSetName = 'GenerateCertificate')] param ( + [Parameter(Mandatory = $true, ParameterSetName = 'Pfx')] + [System.String]$Certificate, + + [Parameter(Mandatory = $true, ParameterSetName = 'GenerateCertificate')] + [Switch]$GenerateCertificate, + + [Parameter(Mandatory = $true, ParameterSetName = 'Pfx')] + [Parameter(Mandatory = $true, ParameterSetName = 'GenerateCertificate')] + [System.Security.SecureString]$CertPassword, + + [Parameter(Mandatory = $true, ParameterSetName = 'CertConfig')] + [CertRotateConfig]$CertRotateConfig, + [Parameter(Mandatory = $true, ParameterSetName = 'Pfx')] [Parameter(Mandatory = $true, ParameterSetName = 'GenerateCertificate')] [Parameter(Mandatory = $true, ParameterSetName = 'CertConfig')] @@ -36,22 +49,9 @@ function Start-SdnNetworkControllerRestCertificateRotation { [System.Management.Automation.Credential()] $NcRestCredential = [System.Management.Automation.PSCredential]::Empty, - [Parameter(Mandatory = $true, ParameterSetName = 'Pfx')] - [System.String]$CertPath, - - [Parameter(Mandatory = $true, ParameterSetName = 'GenerateCertificate')] - [Switch]$GenerateCertificate, - - [Parameter(Mandatory = $true, ParameterSetName = 'Pfx')] - [Parameter(Mandatory = $true, ParameterSetName = 'GenerateCertificate')] - [System.Security.SecureString]$CertPassword, - [Parameter(Mandatory = $false, ParameterSetName = 'GenerateCertificate')] [datetime]$NotAfter = (Get-Date).AddYears(3), - [Parameter(Mandatory = $true, ParameterSetName = 'CertConfig')] - [hashtable]$CertRotateConfig, - [Parameter(Mandatory = $false, ParameterSetName = 'Pfx')] [Parameter(Mandatory = $false, ParameterSetName = 'GenerateCertificate')] [Parameter(Mandatory = $false, ParameterSetName = 'CertConfig')] @@ -59,46 +59,159 @@ function Start-SdnNetworkControllerRestCertificateRotation { ) # ensure that the module is running as local administrator + # and that we are operating the cmdlet on a network controller Confirm-IsAdmin + Confirm-IsNetworkController - $config = Get-SdnModuleConfiguration -Role 'NetworkController' - $confirmFeatures = Confirm-RequiredFeaturesInstalled -Name $config.windowsFeature - if (-NOT ($confirmFeatures)) { - throw New-Object System.NotSupportedException("The current machine is not a NetworkController, run this on NetworkController.") + # if we are generating a certificate, we will want to create specify the certificate path + if ([String]::IsNullOrEmpty($CertPath)) { + [System.String]$CertPath = "$(Get-WorkingDirectory)\Cert_{0}" -f (Get-FormattedDateTimeUTC) } - # purge any existing remote sessions to prevent situation where - # we leverage a session without credentials - Remove-PSRemotingSession - try { - # grab the rest certificate with the latest expiration date - $updatedRestCertificate = Get-SdnCertificate -Path 'Cert:\LocalMachine\My' -Subject $currentRestCert.Subject -NetworkControllerOid ` - | Sort-Object -Property NotAfter -Descending | Select-Object -First 1 + # create the directory if it does not exist + if (-NOT (Test-Path -Path $CertPath -PathType Container)) { + $null = New-Item -Path $CertPath -ItemType Directory -Force + } + $CertPathDir = Get-Item -Path $CertPath -ErrorAction 'Stop' + + "Retrieving current SDN environment details" | Trace-Output + $NcInfraInfo = Get-SdnNetworkControllerInfoOffline -Credential $Credential + + # get the network controller infrastructure information + # and determine if the rest certificate is expired or if the network controller is healthy + $currentRestCert = Get-SdnNetworkControllerRestCertificate -ErrorAction 'Stop' + $restCertExpired = (Get-Date) -gt $($currentRestCert.NotAfter) + if ($restCertExpired) { + "Network Controller Rest Certificate {0} expired at {1}" -f $currentRestCert.Thumbprint, $currentRestCert.NotAfter | Trace-Output -Level:Warning + $isNetworkControllerHealthy = $false + } + else { + $isNetworkControllerHealthy = Test-NetworkControllerIsHealthy + } + + if ($isNetworkControllerHealthy) { + $sdnFabricDetails = Get-SdnInfrastructureInfo -NetworkController $env:COMPUTERNAME -Credential $Credential -NcRestCredential $NcRestCredential + } + else { + $sdnFabricDetails = [SdnFabricInfrastructure]@{ + NetworkController = $NcInfraInfo.NodeList.IpAddressOrFQDN + } + } - "Network Controller Rest Certificate {0} will be updated:`n`tCurrent: [Thumbprint:{1} NotAfter:{2}]`n`tUpdated: [Thumbprint:{3} NotAfter:{4}]" ` - -f $currentRestCert.Subject, $currentRestCert.Thumbprint, $currentRestCert.NotAfter, $CertRotateConfig["NcRestCert"], $updatedRestCertificate.NotAfter ` - | Trace-Output -Level:Warning + switch ($PSCmdlet.ParameterSetName) { + 'GenerateCertificate' { + "== STAGE: CREATE SELF SIGNED REST CERTIFICATE ==" | Trace-Output + $restCertFileParams = @{ + RestName = $NcInfraInfo.NcRestName + NotAfter = $NotAfter + Path = $CertPathDir.FullName + CertPassword = $CertPassword + Credential = $Credential + FabricDetails = $sdnFabricDetails + ErrorAction = 'Stop' + } - $null = Invoke-CertRotateCommand -Command 'Set-NetworkController' -Credential $Credential -Thumbprint $CertRotateConfig["NcRestCert"] + New-SdnNetworkControllerRestCertificate @restCertFileParams + } + 'Pfx' { + "== STAGE: PARSE PFX CERTIFICATE ==" | Trace-Output + $pfxData = Get-PfxData -FilePath $Certificate -Password $CertPassword -ErrorAction 'Stop' + if ($pfxdata.EndEntityCertificates.Subject -ieq $currentRestCert.Subject) { + "Matched {0} [Subject: {1}; Thumbprint: {2}] to NC Rest Certificate" -f ` + $Certificate, $pfxData.EndEntityCertificates.Subject, $pfxData.EndEntityCertificates.Thumbprint | Trace-Output + } + $restCertFile = Get-Item -Path $Certificate -ErrorAction 'Stop' + } + 'CertConfig' { + "== STAGE: DETERMINE CERTIFICATE CONFIG ==" | Trace-Output + $certValidated = Test-SdnCertificateRotationConfig -NcNodeList $NcInfraInfo.NodeList -CertRotateConfig $CertRotateConfig -Credential $Credential + + if ($certValidated -ne $true) { + throw New-Object System.NotSupportedException("Unable to validate certificate configuration") + } + } + } + + # we will import the certificate, even though it may already exist + # as we want to ensure we return the properties that contain self-signed information + $importRestCert = Import-SdnCertificate -FilePath $restCertFile.FullName -CertStore 'Cert:\LocalMachine\My' -CertPassword $CertPassword -ErrorAction 'Stop' + + # in this instance, we will want to copy the certificate to the fabric + # however we will not copy to southbound devices at this time as we will do that later + # to account for situations where the current certificate is expired or network controller is unhealthy + # resulting in us not being able to determine the southbound devices + + "== STAGE: COPY CERTIFICATE TO FABRIC ==" | Trace-Output + $copyCertToFabricParams = @{ + CertFile = $restCertFile.FullName + CertPassword = $CertPassword + FabricDetails = $sdnFabricDetails + NetworkControllerRestCertificate = $true + InstallToSouthboundDevices = $false + Credential = $Credential + ErrorAction = 'Stop' + } + + Copy-CertificateToFabric @copyCertToFabricParams + + # generate the certificate rotation configuration + if (!$CertRotateConfig) { + $certRotateConfigParams = @{ + CertificateType = 'Rest' + Credential = $Credential + NcRestCredential = $NcRestCredential + NetworkControllerOid = $true + ErrorAction = 'Stop' + } + + $CertRotateConfig = New-SdnCertificateRotationConfig @certRotateConfigParams + } + + "Network Controller Rest Certificate {0} will be updated:`n`tCurrent: [Thumbprint:{1}]`n`tUpdated: [Thumbprint:{2}]" ` + -f $currentRestCert.Subject, $currentRestCert.Thumbprint, $CertRotateConfig.RestCertificate.Thumbprint | Trace-Output -Level:Warning + + # if we are not forcing the rotation, we will want to prompt the user to confirm the operation + if (!$Force) { + $confirm = Confirm-UserInput + if (-NOT $confirm) { + "User has opted to abort the operation. Terminating operation" | Trace-Output -Level:Warning + return + } + } + + # in situations where the rest certificate may be already expired, or the network controller is unhealthy + # we will want to leverage the expired certificate rotation function to fix up things so we may proceed with certificate rotate + if (!$isNetworkControllerHealthy) { + Start-SdnExpiredCertificateRotation @{ + CertRotateConfig = $CertRotateConfig + Credential = $Credential + NcRestCredential = $NcRestCredential + } + } + + # Rotate NC Northbound Certificate (REST) + "== STAGE: ROTATE NC REST CERTIFICATE ==" | Trace-Output + $null = Invoke-CertRotateCommand @{ + Command = 'Set-NetworkController' + Credential = $Credential + Thumbprint = $CertRotateConfig.RestCertificate.Thumbprint + } "Waiting for 5 minutes before proceeding to the next step. Script will resume at {0}" -f (Get-Date).AddMinutes(5).ToUniversalTime().ToString() | Trace-Output Start-Sleep -Seconds 300 # Rotate Cluster Certificate - $null = Invoke-CertRotateCommand -Command 'Set-NetworkControllerCluster' -Credential $Credential -Thumbprint $CertRotateConfig["NcRestCert"] + "== STAGE: ROTATE NC CLUSTER CERTIFICATE ==" | Trace-Output + $null = Invoke-CertRotateCommand @{ + Command = 'Set-NetworkControllerCluster' + Credential = $Credential + Thumbprint = $CertRotateConfig.RestCertificate.Thumbprint + } "Waiting for 5 minutes before proceeding to the next step. Script will resume at {0}" -f (Get-Date).AddMinutes(5).ToUniversalTime().ToString() | Trace-Output Start-Sleep -Seconds 300 - # Update Credential Resource - $null = Update-NetworkControllerCredentialResource @{ - NcUri = "https://$($NcInfraInfo.NcRestName)" - Credential = $NcRestCredential - NewRestCertThumbprint = $CertRotateConfig["NcRestCert"] - ErrorAction = Stop - } - ##################################### # # Certificate Seeding (Southbound Nodes) @@ -108,7 +221,7 @@ function Start-SdnNetworkControllerRestCertificateRotation { # in situation where maybe the Network Controller was down and we could not previously determine the southbound devices (if they exist) # we will want to get updated infrastructure information to determine if we need to seed the certificate to the southbound devices # if we get an error with returning the infrastructure information, we will terminate the script - if ($selfSignedRestCertFile) { + if ($CertRotateConfig.RestCertificate.IsSelfSigned) { $sdnFabricDetails = Get-SdnInfrastructureInfo -Credential $Credential -NcRestCredential $NcRestCredential -Force -ErrorAction Stop $southBoundNodes = @() if ($null -ne $sdnFabricDetails.LoadBalancerMux) { @@ -124,16 +237,24 @@ function Start-SdnNetworkControllerRestCertificateRotation { # ensure that we have the latest version of sdnDiagnostics module on the southbound devices Install-SdnDiagnostics -ComputerName $southBoundNodes -Credential $Credential -ErrorAction Stop - "[REST CERT] Installing self-signed certificate to {0}" -f ($southBoundNodes -join ', ') | Trace-Output - [System.String]$remoteFilePath = Join-Path -Path $CertPath.FullName -ChildPath $selfSignedRestCertFile.Name - Copy-FileToRemoteComputer -ComputerName $southBoundNodes -Credential $Credential -Path $selfSignedRestCertFile.FullName -Destination $remoteFilePath - $null = Invoke-PSRemoteCommand -ComputerName $southBoundNodes -Credential $Credential -ScriptBlock { - param([Parameter(Position = 0)][String]$param1, [Parameter(Position = 1)][String]$param2) - Import-SdnCertificate -FilePath $param1 -CertStore $param2 - } -ArgumentList @($remoteFilePath, 'Cert:\LocalMachine\Root') -ErrorAction Stop + Copy-CertificateToFabric @{ + CertFile = $importRestCert.SelfSignedCertFileInfo.FullName + FabricDetails = $sdnFabricDetails + NetworkControllerRestCertificate = $true + InstallToSouthboundDevices = $true + Credential = $Credential + } } } + "== STAGE: UPDATE X509 CREDENTIALS ==" | Trace-Output + $null = Update-NetworkControllerCredentialResource @{ + NcUri = "https://$($NcInfraInfo.NcRestName)" + Credential = $NcRestCredential + NewRestCertThumbprint = $CertRotateConfig.RestCertificate.Thumbprint + ErrorAction = 'Stop' + } + ##################################### # # Restart services diff --git a/src/modules/SdnDiag.NetworkController/public/Test-SdnCertificateRotationConfig.ps1 b/src/modules/SdnDiag.NetworkController/public/Test-SdnCertificateRotationConfig.ps1 index 04afebe6..e74dab68 100644 --- a/src/modules/SdnDiag.NetworkController/public/Test-SdnCertificateRotationConfig.ps1 +++ b/src/modules/SdnDiag.NetworkController/public/Test-SdnCertificateRotationConfig.ps1 @@ -15,7 +15,7 @@ function Test-SdnCertificateRotationConfig { [PSCustomObject[]]$NcNodeList, [Parameter(Mandatory = $true)] - [hashtable]$CertRotateConfig, + [CertRotateConfig]$CertRotateConfig, [Parameter(Mandatory = $false)] [System.Management.Automation.PSCredential] @@ -23,66 +23,52 @@ function Test-SdnCertificateRotationConfig { $Credential = [System.Management.Automation.PSCredential]::Empty ) - try { + $sb1 = { + param([Parameter(Position = 0)][String]$param1, [Parameter(Position = 1)][String]$param2) + $nodeCertObj = Get-SdnCertificate -Path $param1 -Thumbprint $param2 - if ([string]::IsNullOrEmpty($CertRotateConfig["NcRestCert"])) { - Trace-Output -Message "NcRestCert not specified in CertRotateConfig" -Level:Error + # ensure that certificate is present + if ($null -eq $nodeCertObj) { return $false } - $ncRestCert = $CertRotateConfig["NcRestCert"] - foreach ($ncNode in $NcNodeList) { - if ($CertRotateConfig["ClusterCredentialType"] -ieq "X509") { - $nodeCert = $CertRotateConfig[$ncNode.NodeName.ToLower()] - if ([string]::IsNullOrEmpty($nodeCert)) { - Trace-Output -Message "The ClusterCredentialType is X509 but Node $($ncNode.NodeName) does not have certificate specified" -Level:Error - return $false - } - else { - $certValid = Invoke-PSRemoteCommand -ComputerName $ncNode.IpAddressOrFQDN -Credential $Credential -ScriptBlock { - param([Parameter(Position = 0)][String]$param1) - $nodeCertObj = Get-SdnCertificate -Path "Cert:\LocalMachine\My" -Thumbprint $param1 - if ($null -eq $nodeCertObj) { - return $false - } - else { - if ($nodeCertObj.NotAfter -le (Get-Date)) { - return $false - } - } - return $true - } -ArgumentList $nodeCert + # ensure that certificate is not expired + if ($nodeCertObj.NotAfter -le (Get-Date)) { + return $false + } - if (!$certValid) { - Trace-Output -Message "Node $($ncNode.NodeName) does not have validate Node certificate with thumbprint $nodeCert installed" -Level:Error - return $false - } - } - } + return $true + } + + # validate that the certificates are installed on the nodes + $CertRotateConfig.NodeCerts | ForEach-Object { + if (Test-ComputerNameIsLocal -ComputerName $_.IpAddressOrFQDN) { + $certValid = Invoke-Command -ScriptBlock $sb1 -ArgumentList @("Cert:\LocalMachine\My", $_.Thumbprint) + } + else { + $certValid = Invoke-PSRemoteCommand -ComputerName $_.IpAddressOrFQDN -Credential $Credential -ScriptBlock $sb1 -ArgumentList @("Cert:\LocalMachine\My", $_.Thumbprint) + } - $certValid = Invoke-PSRemoteCommand -ComputerName $ncNode.IpAddressOrFQDN -Credential $Credential -ScriptBlock { - param([Parameter(Position = 0)][String]$param1) - $ncRestCertObj = Get-SdnCertificate -Path "Cert:\LocalMachine\My" -Thumbprint $param1 - if ($null -eq $ncRestCertObj) { - return $false - } - else { - if ($ncRestCertObj.NotAfter -le (Get-Date)) { - return $false - } - } - return $true - } -ArgumentList $ncRestCert + if (!$certValid) { + throw "$($_.NodeName) does not have valid certificate with thumbprint $($_.Thumbprint) installed" + } + } + + # validate the rest certificate exists on each of the nodes + if ($CertRotateConfig.RestCertificate) { + $NcNodeList | ForEach-Object { + if (Test-ComputerNameIsLocal -ComputerName $_.IpAddressOrFQDN) { + $certValid = Invoke-Command -ScriptBlock $sb1 -ArgumentList @("Cert:\LocalMachine\My", $CertRotateConfig.RestCertificate.Thumbprint) + } + else { + $certValid = Invoke-PSRemoteCommand -ComputerName $_.IpAddressOrFQDN -Credential $Credential -ScriptBlock $sb1 -ArgumentList @("Cert:\LocalMachine\My", $CertRotateConfig.RestCertificate.Thumbprint) + } if (!$certValid) { - Trace-Output -Message "Node $($ncNode.NodeName) does not have validate NcRest certificate with thumbprint $ncRestCert installed" -Level:Error - return $false + throw "$($_.NodeName) does not have valid rest certificate with thumbprint $($CertRotateConfig.RestCertificate.Thumbprint) installed" } } - return $true - } - catch { - $_ | Trace-Exception - $_ | Write-Error } + + return $true } diff --git a/src/modules/SdnDiag.Server/public/New-SdnServerCertificate.ps1 b/src/modules/SdnDiag.Server/public/New-SdnServerCertificate.ps1 index 94f0054c..f4c53dda 100644 --- a/src/modules/SdnDiag.Server/public/New-SdnServerCertificate.ps1 +++ b/src/modules/SdnDiag.Server/public/New-SdnServerCertificate.ps1 @@ -37,10 +37,7 @@ function New-SdnServerCertificate { } # ensure that the module is running as local administrator - $elevated = ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator) - if (-NOT $elevated) { - throw New-Object System.Exception("This function requires elevated permissions. Run PowerShell as an Administrator and import the module again.") - } + Confirm-IsAdmin try { if (-NOT (Test-Path -Path $Path -PathType Container)) { diff --git a/src/modules/SdnDiag.NetworkController/public/Start-SdnServerCertificateRotation.ps1 b/src/modules/SdnDiag.Server/public/Start-SdnServerCertificateRotation.ps1 similarity index 94% rename from src/modules/SdnDiag.NetworkController/public/Start-SdnServerCertificateRotation.ps1 rename to src/modules/SdnDiag.Server/public/Start-SdnServerCertificateRotation.ps1 index e3f26279..b5e77afd 100644 --- a/src/modules/SdnDiag.NetworkController/public/Start-SdnServerCertificateRotation.ps1 +++ b/src/modules/SdnDiag.Server/public/Start-SdnServerCertificateRotation.ps1 @@ -18,8 +18,8 @@ function Start-SdnServerCertificateRotation { [CmdletBinding(DefaultParameterSetName = 'GenerateCertificate')] param ( - [Parameter(Mandatory = $false, ParameterSetName = 'GenerateCertificate')] - [System.String]$NetworkController = $env:COMPUTERNAME, + [Parameter(Mandatory = $true, ParameterSetName = 'GenerateCertificate')] + [System.String]$NetworkController, [Parameter(Mandatory = $true, ParameterSetName = 'GenerateCertificate')] [System.Management.Automation.PSCredential] @@ -45,10 +45,7 @@ function Start-SdnServerCertificateRotation { ) # ensure that the module is running as local administrator - $elevated = ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator) - if (-NOT $elevated) { - throw New-Object System.Exception("This function requires elevated permissions. Run PowerShell as an Administrator and import the module again.") - } + Confirm-IsAdmin $config = Get-SdnModuleConfiguration -Role 'NetworkController' $confirmFeatures = Confirm-RequiredFeaturesInstalled -Name $config.windowsFeature