Add support for SecretManagement and SecretStore providers, enhance configuration data handling, and introduce new utility functions

This commit is contained in:
Torsten Brendgen
2026-07-02 23:57:14 +02:00
parent 115b8be385
commit 9b36693444
16 changed files with 979 additions and 7 deletions

View File

@@ -0,0 +1,101 @@
function ConvertFrom-ConfigurationDataSecretValue {
[CmdletBinding()]
Param(
[Parameter(Mandatory=$true)]
[AllowNull()]
$Secret,
[Parameter(Mandatory=$true)]
[ValidateSet("credential", "securestring", "string")]
[string]
$ExpectedType,
[Parameter(Mandatory=$false)]
[string]
$Name = "",
[Parameter(Mandatory=$false)]
[string]
$UserName = ""
)
if($null -eq $Secret){
throw "Secret [$Name] was not found."
}
if($ExpectedType -eq "credential"){
if($Secret -is [System.Management.Automation.PSCredential]){
return $Secret
}
if($Secret -is [System.Collections.IDictionary]){
if([string]::IsNullOrWhiteSpace($UserName) -and $Secret.Contains("UserName")){
$UserName = [string]$Secret["UserName"]
}
if($Secret.Contains("Password")){
return ConvertTo-ConfigurationDataCredential -UserName $UserName -Password $Secret["Password"]
}
}
if($Secret.PSObject.Properties["UserName"] -and [string]::IsNullOrWhiteSpace($UserName)){
$UserName = [string]$Secret.UserName
}
if($Secret.PSObject.Properties["Password"]){
return ConvertTo-ConfigurationDataCredential -UserName $UserName -Password $Secret.Password
}
if([string]::IsNullOrWhiteSpace($UserName)){
throw "Secret [$Name] cannot be converted to [PSCredential] because no username was provided."
}
return ConvertTo-ConfigurationDataCredential -UserName $UserName -Password $Secret
}
if($ExpectedType -eq "securestring"){
if($Secret -is [System.Security.SecureString]){
return $Secret
}
if($Secret -is [System.Management.Automation.PSCredential]){
return $Secret.Password
}
if($Secret -is [System.Collections.IDictionary] -and $Secret.Contains("Password")){
$Secret = $Secret["Password"]
}elseif($Secret.PSObject.Properties["Password"]){
$Secret = $Secret.Password
}
if($Secret -is [System.Security.SecureString]){
return $Secret
}
return ConvertTo-SecureString -String ([string]$Secret) -AsPlainText -Force
}
if($Secret -is [System.Management.Automation.PSCredential]){
return $Secret.GetNetworkCredential().Password
}
if($Secret -is [System.Security.SecureString]){
$Pointer = [Runtime.InteropServices.Marshal]::SecureStringToBSTR($Secret)
try {
return [Runtime.InteropServices.Marshal]::PtrToStringBSTR($Pointer)
}
finally {
[Runtime.InteropServices.Marshal]::ZeroFreeBSTR($Pointer)
}
}
if($Secret -is [System.Collections.IDictionary] -and $Secret.Contains("Password")){
return [string]$Secret["Password"]
}
if($Secret.PSObject.Properties["Password"]){
return [string]$Secret.Password
}
return [string]$Secret
}

View File

@@ -0,0 +1,63 @@
function ConvertTo-PowerShellDataFileText {
[CmdletBinding()]
Param(
[Parameter(Mandatory=$true)]
[AllowNull()]
$InputObject,
[Parameter(Mandatory=$false)]
[int]
$Indent = 0
)
$IndentText = " " * $Indent
$ChildIndent = $Indent + 4
$ChildIndentText = " " * $ChildIndent
if($null -eq $InputObject){
return '$null'
}
if($InputObject -is [bool]){
if($InputObject){
return '$true'
}
return '$false'
}
if($InputObject -is [int] -or $InputObject -is [long] -or $InputObject -is [decimal] -or $InputObject -is [double]){
return ([string]$InputObject)
}
if($InputObject -is [string]){
return "'$($InputObject.Replace("'", "''"))'"
}
if($InputObject -is [System.Collections.IDictionary]){
$Lines = @("@{")
foreach($Key in $InputObject.Keys){
$ValueText = ConvertTo-PowerShellDataFileText -InputObject $InputObject[$Key] -Indent $ChildIndent
$Lines += "$ChildIndentText$Key = $ValueText"
}
$Lines += "$IndentText}"
return ($Lines -join [Environment]::NewLine)
}
if($InputObject -is [System.Collections.IEnumerable] -and $InputObject -isnot [string]){
$Items = @($InputObject)
if($Items.Count -eq 0){
return '@()'
}
$Lines = @("@(")
foreach($Item in $Items){
$ValueText = ConvertTo-PowerShellDataFileText -InputObject $Item -Indent $ChildIndent
$Lines += "$ChildIndentText$ValueText"
}
$Lines += "$IndentText)"
return ($Lines -join [Environment]::NewLine)
}
return "'$(([string]$InputObject).Replace("'", "''"))'"
}

View File

@@ -0,0 +1,28 @@
function Export-PowerShellDataFile {
[CmdletBinding()]
Param(
[Parameter(Mandatory=$true)]
[System.Collections.IDictionary]
$InputObject,
[Parameter(Mandatory=$true)]
[string]
$Path,
[Parameter(Mandatory=$false)]
[switch]
$Force
)
if((Test-Path -Path $Path -PathType Leaf) -and (-not $Force)){
throw "File [$Path] already exists. Use -Force to overwrite it."
}
$Parent = Split-Path -Path $Path -Parent
if(-not [string]::IsNullOrWhiteSpace($Parent) -and -not (Test-Path -Path $Parent)){
New-Item -Path $Parent -ItemType Directory -Force | Out-Null
}
$Text = ConvertTo-PowerShellDataFileText -InputObject $InputObject
Set-Content -Path $Path -Value $Text -Encoding UTF8
}

View File

@@ -0,0 +1,14 @@
function Get-ConfigurationDataSecretProviderName {
[CmdletBinding()]
Param()
if($null -eq $script:ConfigurationDataSecretProviders){
$script:ConfigurationDataSecretProviders = @{}
}
return @(
$script:ConfigurationDataSecretProviders.Values |
Sort-Object -Property Name |
ForEach-Object { $_.Name }
)
}

View File

@@ -0,0 +1,31 @@
function New-ConfigurationDataAesKeyFile {
[CmdletBinding()]
Param(
[Parameter(Mandatory=$true)]
[string]
$Path,
[Parameter(Mandatory=$false)]
[switch]
$Force
)
if((Test-Path -Path $Path -PathType Leaf) -and (-not $Force)){
$Key = [Convert]::FromBase64String((Get-Content -Path $Path -Raw).Trim())
if($Key.Length -notin @(16, 24, 32)){
throw "Existing key file [$Path] does not contain a valid AES key length."
}
return $Key
}
$Parent = Split-Path -Path $Path -Parent
if(-not [string]::IsNullOrWhiteSpace($Parent) -and -not (Test-Path -Path $Parent)){
New-Item -Path $Parent -ItemType Directory -Force | Out-Null
}
$Key = New-Object byte[] 32
[System.Security.Cryptography.RandomNumberGenerator]::Create().GetBytes($Key)
Set-Content -Path $Path -Value ([Convert]::ToBase64String($Key)) -Encoding ASCII
return $Key
}

View File

@@ -0,0 +1,51 @@
function Resolve-ConfigurationDataProviderSecureString {
[CmdletBinding()]
Param(
[Parameter(Mandatory=$true)]
$Value
)
if($Value -is [System.Security.SecureString]){
return $Value
}
if($Value -is [string]){
return ConvertTo-SecureString -String $Value -AsPlainText -Force
}
if(-not (Test-ConfigurationDataMap -Value $Value)){
throw "Provider secure string value must be a SecureString, string, or hashtable."
}
if(Test-ConfigurationDataMapContainsKey -Map $Value -Key "EnvironmentVariable"){
$VariableName = [string](Get-ConfigurationDataMapValue -Map $Value -Key "EnvironmentVariable")
$EnvironmentValue = [Environment]::GetEnvironmentVariable($VariableName)
if([string]::IsNullOrEmpty($EnvironmentValue)){
throw "Environment variable [$VariableName] is not defined or empty."
}
return ConvertTo-SecureString -String $EnvironmentValue -AsPlainText -Force
}
if(Test-ConfigurationDataMapContainsKey -Map $Value -Key "ProtectedValue"){
$ProtectedValue = [string](Get-ConfigurationDataMapValue -Map $Value -Key "ProtectedValue")
$Key = $null
if(Test-ConfigurationDataMapContainsKey -Map $Value -Key "Key"){
$Key = [Convert]::FromBase64String([string](Get-ConfigurationDataMapValue -Map $Value -Key "Key"))
}elseif(Test-ConfigurationDataMapContainsKey -Map $Value -Key "KeyPath"){
$KeyPath = [string](Get-ConfigurationDataMapValue -Map $Value -Key "KeyPath")
if(-not (Test-Path -Path $KeyPath -PathType Leaf)){
throw "Secure string key file [$KeyPath] was not found."
}
$Key = [Convert]::FromBase64String((Get-Content -Path $KeyPath -Raw).Trim())
}else{
throw "Protected provider secure string requires [Key] or [KeyPath]."
}
return ConvertTo-SecureString -String $ProtectedValue -Key $Key
}
throw "Unsupported provider secure string reference."
}

View File

@@ -0,0 +1,25 @@
function Resolve-ConfigurationDataProviderSettingsPath {
[CmdletBinding()]
Param(
[Parameter(Mandatory=$true)]
[string]
$Provider,
[Parameter(Mandatory=$false)]
[AllowEmptyString()]
[string]
$SettingsPath
)
$FileName = "ProviderSettings.$Provider.psd1"
if([string]::IsNullOrWhiteSpace($SettingsPath)){
return (Join-Path -Path (Get-Location).Path -ChildPath $FileName)
}
if((Test-Path -Path $SettingsPath -PathType Container) -or $SettingsPath.EndsWith("\") -or $SettingsPath.EndsWith("/")){
return (Join-Path -Path $SettingsPath -ChildPath $FileName)
}
return $SettingsPath
}