modules/NetworkController/private/Copy-UserProvidedCertificateToFabric.ps1

# Copyright (c) Microsoft Corporation.
# Licensed under the MIT License.


function Copy-UserProvidedCertificateToFabric {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [System.IO.DirectoryInfo]$CertPath,

        [Parameter(Mandatory = $true)]
        [System.Security.SecureString]$CertPassword,

        [Parameter(Mandatory = $true)]
        [System.Object]$FabricDetails,

        [Parameter(Mandatory = $false)]
        [System.Boolean]$RotateNodeCerts = $false,

        [Parameter(Mandatory = $false)]
        [System.Boolean]$NetworkControllerHealthy = $false,

        [Parameter(Mandatory = $false)]
        [System.Management.Automation.PSCredential]
        [System.Management.Automation.Credential()]
        $Credential = [System.Management.Automation.PSCredential]::Empty
    )

    $certificateCache = @()
    $certificateConfig = @{
        RestCert          = $null
        NetworkController = @{}
    }

    "Scanning certificates within {0}" -f $CertPath.FullName | Trace-Output
    $pfxFiles = Get-ChildItem -Path $CertPath.FullName -Filter '*.pfx'
    if ($null -eq $pfxFiles) {
        throw New-Object System.NullReferenceException("Unable to locate .pfx files under the specified CertPath location")
    }

    foreach ($pfxFile in $pfxFiles) {
        "Retrieving PfxData for {0}" -f $pfxFile.FullName | Trace-Output
        $pfxData = Get-PfxData -FilePath $pfxFile.FullName -Password $CertPassword -ErrorAction Stop

        $object = [PSCustomObject]@{
            FileInfo = $pfxFile
            PfxData  = $pfxData
            SelfSigned = $false
        }

        $certificateCache += $object
    }

    "Retrieving Rest Certificate" | Trace-Output -Level:Verbose
    $currentRestCertificate = Get-SdnNetworkControllerRestCertificate

    # enumerate the current certificates within the cache to isolate the rest certificate
    foreach ($cert in $certificateCache) {
        if ($cert.pfxdata.EndEntityCertificates.Subject -ieq $currentRestCertificate.Subject) {
            "Matched {0} [Subject: {1}; Thumbprint: {2}] to NC Rest Certificate" -f `
                $cert.pfxFile.FileInfo.FullName, $cert.pfxData.EndEntityCertificates.Subject, $cert.pfxData.EndEntityCertificates.Thumbprint | Trace-Output -Level:Verbose

            $cert | Add-Member -MemberType NoteProperty -Name 'CertificateType' -Value 'NetworkControllerRest'
            $restCertificate = $cert
            $certificateConfig.RestCert = $restCertificate.pfxData.EndEntityCertificates.Thumbprint
        }

        if ($cert.pfxdata.EndEntityCertificates.Subject -ieq $cert.pfxdata.EndEntityCertificates.Issuer) {
            $cert.SelfSigned = $true
        }
    }

    # enumerate the certificates for network controller nodes
    if ($RotateNodeCerts) {
        foreach ($node in $FabricDetails.NetworkController) {
            "Retrieving current node certificate for {0}" -f $node | Trace-Output
            $currentNodeCert = Invoke-PSRemoteCommand -ComputerName $node -Credential $Credential -ScriptBlock { Get-SdnNetworkControllerNodeCertificate } -ErrorAction Stop
            foreach ($cert in $certificateCache) {
                $updatedNodeCert = $null
                if ($cert.PfxData.EndEntityCertificates.Subject -ieq $currentNodeCert.Subject) {
                    $updatedNodeCert = $cert
                    "Matched {0} [Subject: {1}; Thumbprint: {2}] to {3}" -f `
                        $updatedNodeCert.FileInfo.Name, $cert.PfxData.EndEntityCertificates.Subject, $cert.PfxData.EndEntityCertificates.Thumbprint, $node | Trace-Output

                    $cert | Add-Member -MemberType NoteProperty -Name 'CertificateType' -Value 'NetworkControllerNode'
                    break
                }
            }

            $certificateConfig.NetworkController[$node] = @{
                Cert = $updatedNodeCert
            }
        }
    }

    # install the rest certificate to the network controllers to this node first
    # then seed out to the rest of the fabric
    $null = Import-SdnCertificate -FilePath $restCertificate.FileInfo.FullName -CertPassword $CertPassword -CertStore 'Cert:\LocalMachine\My'
    Copy-CertificateToFabric -CertFile $restCertificate.FileInfo.FullName -CertPassword $CertPassword -FabricDetails $FabricDetails `
    -NetworkControllerRestCertificate -InstallToSouthboundDevices:$NetworkControllerHealthy -Credential $Credential

    # install the nc node certificate to other network controller nodes if self-signed
    if ($RotateNodeCerts) {
        foreach ($controller in $FabricDetails.NetworkController) {
            "Processing {0} for node certificates" -f $controller | Trace-Output -Level:Verbose
            $nodeCertConfig = $certificateConfig.NetworkController[$controller]

            # if we have identified a network controller node certificate then proceed
            # with installing the cert locally (if matches current node)
            if ($null -ne $nodeCertConfig.Cert.FileInfo.FullName) {
                if (Test-ComputerNameIsLocal -ComputerName $controller) {
                    $null = Import-SdnCertificate -FilePath $nodeCertConfig.Cert.FileInfo.FullName -CertPassword $CertPassword -CertStore 'Cert:\LocalMachine\My'
                }


                # pass the certificate to sub-function to be seeded across the fabric if necassary
                Copy-CertificateToFabric -CertFile $nodeCertConfig.Cert.FileInfo.FullName -CertPassword $CertPassword -FabricDetails $FabricDetails -NetworkControllerNodeCert -Credential $Credential
            }
            else {
                "Unable to locate self-signed certificate file for {0}. Node certificate may need to be manually installed to other Network Controllers manually." -f $controller | Trace-Output -Level:Exception
            }
        }
    }

    return $certificateCache
}