Private/Resolve-AzLocalSideloadCredential.ps1

function Resolve-AzLocalSideloadCredential {
    <#
    .SYNOPSIS
        Builds the Active Directory [pscredential] used to PowerShell-remote into
        an Azure Local cluster, from the two Key Vault secrets named in the
        sideload auth-map row for the cluster's UpdateAuthAccountId.
 
    .DESCRIPTION
        Private helper for the v0.8.7 on-prem sideloading automation. Given an
        auth-map row (from Get-AzLocalSideloadAuthMap), reads the username and
        password secrets from the row's Key Vault and returns a [pscredential].
 
        Key Vault authentication is assumed to be already established by the
        pipeline (azure/login OIDC, managed identity, or service principal) per
        the SIDELOAD_KV_AUTH variable - this helper only calls
        Get-AzKeyVaultSecret. The actual KV read is isolated in the thin wrapper
        Get-AzLocalKeyVaultSecretText so it can be mocked in unit tests.
 
        The username may be a UPN ('svc@contoso.com') or DOMAIN\user form; both
        are accepted by [pscredential]. The plaintext password is converted to a
        SecureString and the plaintext copy is cleared from memory before return.
 
    .PARAMETER AuthRow
        A single auth-map row [PSCustomObject] with KeyVaultName,
        UsernameSecretName, PasswordSecretName.
 
    .OUTPUTS
        [System.Management.Automation.PSCredential]
    #>

    [CmdletBinding()]
    [OutputType([System.Management.Automation.PSCredential])]
    param(
        [Parameter(Mandatory = $true)]
        [ValidateNotNull()]
        [PSCustomObject]$AuthRow
    )

    foreach ($field in @('KeyVaultName', 'UsernameSecretName', 'PasswordSecretName')) {
        if ([string]::IsNullOrWhiteSpace([string]$AuthRow.$field)) {
            throw "Resolve-AzLocalSideloadCredential: auth-map row (UpdateAuthAccountId '$($AuthRow.UpdateAuthAccountId)') has an empty $field."
        }
    }

    $username = $null
    $password = $null
    try {
        $username = Get-AzLocalKeyVaultSecretText -VaultName $AuthRow.KeyVaultName -SecretName $AuthRow.UsernameSecretName
        $password = Get-AzLocalKeyVaultSecretText -VaultName $AuthRow.KeyVaultName -SecretName $AuthRow.PasswordSecretName

        if ([string]::IsNullOrWhiteSpace($username)) {
            throw "Username secret '$($AuthRow.UsernameSecretName)' in vault '$($AuthRow.KeyVaultName)' resolved to an empty value."
        }
        if ([string]::IsNullOrEmpty($password)) {
            throw "Password secret '$($AuthRow.PasswordSecretName)' in vault '$($AuthRow.KeyVaultName)' resolved to an empty value."
        }

        $securePassword = ConvertTo-SecureString -String $password -AsPlainText -Force
        return New-Object System.Management.Automation.PSCredential ($username.Trim(), $securePassword)
    }
    finally {
        # Clear the plaintext secret copies from memory as soon as possible.
        if ($null -ne $password) { $password = $null }
        Remove-Variable -Name password -ErrorAction SilentlyContinue
    }
}

function Get-AzLocalKeyVaultSecretText {
    <#
    .SYNOPSIS
        Thin wrapper around Get-AzKeyVaultSecret -AsPlainText so the parent
        function can be unit-tested by mocking just this call.
    .OUTPUTS
        [string] the plaintext secret value.
    #>

    [CmdletBinding()]
    [OutputType([string])]
    param(
        [Parameter(Mandatory = $true)][string]$VaultName,
        [Parameter(Mandatory = $true)][string]$SecretName
    )

    if (-not (Get-Command Get-AzKeyVaultSecret -ErrorAction SilentlyContinue)) {
        throw "Az.KeyVault module is not loaded. Install with: Install-Module Az.KeyVault -Scope CurrentUser"
    }

    try {
        $value = Get-AzKeyVaultSecret -VaultName $VaultName -Name $SecretName -AsPlainText -ErrorAction Stop
    }
    catch {
        throw "Failed to read Key Vault secret '$SecretName' from vault '$VaultName': $($_.Exception.Message)"
    }

    return $value
}