Core/Import-TestCredentialRecord.ps1
|
function Import-TestCredentialRecord { <# .SYNOPSIS Reads a provider's credential record and recovers its secret .DESCRIPTION The inverse of Export-TestCredentialRecord. The record says where the secret is, and this follows it: a SecretStore pointer is resolved through the vault it names, a platform-protected value is decrypted, and a value stored unprotected is returned with a warning that says how to fix that. A record that names a vault but not the secret, or claims platform protection and carries no protected value, is refused rather than guessed at. So is a record missing any field the caller says it needs, and a record that yields an empty secret. .PARAMETER Path The record to read. .PARAMETER Required Fields the record must carry, beyond the protection fields. .PARAMETER SecretField The field the protected secret is in when it stays in the record. .PARAMETER SecretLabel What the secret is called in messages: token, password, key. .PARAMETER MissingRecordMessage The message to throw when there is no record at Path; it should say what to run. .PARAMETER VaultPassword The vault's password, when the secret is in a vault whose password is not a default. .OUTPUTS PSCustomObject with Record (the parsed JSON), Secret, Protection, VaultName, SecretName, CreatedUtc and Path. .EXAMPLE PS> $read = Import-TestCredentialRecord -Path $path -Required baseUrl, username -SecretField tokenProtected -SecretLabel token -MissingRecordMessage 'Run New-TestServiceApp first.' DESCRIPTION: Reads a record and recovers its token OUTPUT: The record's fields and the plaintext token USE CASE: Every Import-<Provider>Credential .NOTES Author: Jeffrey Stuhr Blog: https://www.techbyjeff.net LinkedIn: https://www.linkedin.com/in/jeffrey-stuhr-034214aa/ #> [CmdletBinding()] [OutputType([PSCustomObject])] param( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [string]$Path, [Parameter()] [string[]]$Required = @(), [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [string]$SecretField, [Parameter()] [ValidateNotNullOrEmpty()] [string]$SecretLabel = 'secret', [Parameter()] [ValidateNotNullOrEmpty()] [string]$MissingRecordMessage = 'Run New-TestServiceApp first.', [Parameter()] [System.Security.SecureString]$VaultPassword ) if (-not (Test-Path -LiteralPath $Path)) { throw "No credential record at $Path. $MissingRecordMessage" } # A UTF-8 BOM is legal in a file and illegal in JSON, and something else may have written # this file. Strip it rather than failing on it. $text = [System.Text.Encoding]::UTF8.GetString([System.IO.File]::ReadAllBytes($Path)).TrimStart([char]0xFEFF) $record = $null try { $record = $text | ConvertFrom-Json } catch { throw "'$Path' is not valid JSON: $($_.Exception.Message)" } foreach ($field in $Required) { if (-not $record.PSObject.Properties[$field] -or [string]::IsNullOrWhiteSpace([string]$record.$field)) { throw "The credential record at $Path has no '$field'. Re-run New-TestServiceApp -Force." } } $protection = if ($record.PSObject.Properties['protection'] -and $record.protection) { [string]$record.protection } else { 'None' } $secret = switch ($protection) { 'SecretStore' { if (-not $record.PSObject.Properties['vaultName'] -or -not $record.vaultName -or -not $record.PSObject.Properties['secretName'] -or -not $record.secretName) { throw "'$Path' says the $SecretLabel is in a vault but does not name the vault or secret." } Get-TestVaultSecret -VaultName $record.vaultName -SecretName $record.secretName -VaultPassword $VaultPassword } 'DPAPI' { if (-not $record.PSObject.Properties[$SecretField] -or -not $record.$SecretField) { throw "'$Path' is marked DPAPI-protected but carries no protected $SecretLabel." } Unprotect-TestSecret -Method DPAPI -Value $record.$SecretField } default { if (-not $record.PSObject.Properties[$SecretField]) { throw "'$Path' carries no $SecretLabel in any recognised form." } Write-Warning "The $SecretLabel in $Path is stored unprotected. Re-run New-TestServiceApp -Force -UseSecretStore to encrypt it." [string]$record.$SecretField } } if ([string]::IsNullOrWhiteSpace($secret)) { throw "The credential record at $Path yielded no $SecretLabel. Re-run New-TestServiceApp -Force." } return [PSCustomObject]@{ Record = $record Secret = $secret Protection = $protection VaultName = $(if ($record.PSObject.Properties['vaultName']) { $record.vaultName } else { $null }) SecretName = $(if ($record.PSObject.Properties['secretName']) { $record.secretName } else { $null }) CreatedUtc = $(if ($record.PSObject.Properties['createdUtc']) { $record.createdUtc } else { $null }) Path = $Path } } |