Skip to content

Improve certificate rotation process #253

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 10 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions src/modules/SdnDiag.Common/private/Confirm-IsCertSelfSigned.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
function Confirm-IsCertSelfSigned {
[CmdletBinding()]
param (
[Parameter(Mandatory = $true)]
[System.Security.Cryptography.X509Certificates.X509Certificate2]$Certificate
)

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
}

return $false
}
188 changes: 61 additions & 127 deletions src/modules/SdnDiag.Common/private/Copy-CertificateToFabric.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -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) {
Expand All @@ -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'
}
}
39 changes: 33 additions & 6 deletions src/modules/SdnDiag.Common/public/Get-SdnCertificate.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -29,26 +29,53 @@ function Get-SdnCertificate {

[Parameter(Mandatory = $false, ParameterSetName = 'Thumbprint')]
[ValidateNotNullorEmpty()]
[System.String]$Thumbprint
[System.String]$Thumbprint,

[Parameter(Mandatory = $false, ParameterSetName = 'Default')]
[Parameter(Mandatory = $false, ParameterSetName = 'Subject')]
[Parameter(Mandatory = $false, ParameterSetName = 'Thumbprint')]
[switch]$NetworkControllerOid
)

[string]$objectIdentifier = @('1.3.6.1.4.1.311.95.1.1.1') # this is a custom OID used for Network Controller
$array = @()

try {
$certificateList = Get-ChildItem -Path $Path -Recurse | Where-Object {$_.PSISContainer -eq $false} -ErrorAction Stop
$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
}

switch ($PSCmdlet.ParameterSetName) {
'Subject' {
$filteredCert = $certificateList | Where-Object {$_.Subject -ieq $Subject}
$filteredCert = $array | Where-Object {$_.Subject -ieq $Subject}
}
'Thumbprint' {
$filteredCert = $certificateList | Where-Object {$_.Thumbprint -ieq $Thumbprint}
$filteredCert = $array | Where-Object {$_.Thumbprint -ieq $Thumbprint}
}
default {
return $certificateList
return $array
}
}

if ($null -eq $filteredCert) {
"Unable to locate certificate using {0}" -f $PSCmdlet.ParameterSetName | Trace-Output -Level:Warning
return $null
}

Expand Down
Loading
Loading