Add support for SecretManagement and SecretStore providers, enhance configuration data handling, and introduce new utility functions
This commit is contained in:
101
Private/ConvertFrom-ConfigurationDataSecretValue.ps1
Normal file
101
Private/ConvertFrom-ConfigurationDataSecretValue.ps1
Normal 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
|
||||
}
|
||||
63
Private/ConvertTo-PowerShellDataFileText.ps1
Normal file
63
Private/ConvertTo-PowerShellDataFileText.ps1
Normal 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("'", "''"))'"
|
||||
}
|
||||
28
Private/Export-PowerShellDataFile.ps1
Normal file
28
Private/Export-PowerShellDataFile.ps1
Normal 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
|
||||
}
|
||||
14
Private/Get-ConfigurationDataSecretProviderName.ps1
Normal file
14
Private/Get-ConfigurationDataSecretProviderName.ps1
Normal 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 }
|
||||
)
|
||||
}
|
||||
31
Private/New-ConfigurationDataAesKeyFile.ps1
Normal file
31
Private/New-ConfigurationDataAesKeyFile.ps1
Normal 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
|
||||
}
|
||||
51
Private/Resolve-ConfigurationDataProviderSecureString.ps1
Normal file
51
Private/Resolve-ConfigurationDataProviderSecureString.ps1
Normal 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."
|
||||
}
|
||||
25
Private/Resolve-ConfigurationDataProviderSettingsPath.ps1
Normal file
25
Private/Resolve-ConfigurationDataProviderSettingsPath.ps1
Normal 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
|
||||
}
|
||||
@@ -45,6 +45,10 @@ $KeePassProvider = @{
|
||||
}
|
||||
}
|
||||
|
||||
if($ProviderSettings.ContainsKey("MasterKey")){
|
||||
$CommandParameters["MasterKey"] = Resolve-ConfigurationDataProviderSecureString -Value $ProviderSettings.MasterKey
|
||||
}
|
||||
|
||||
if(-not $CommandParameters.ContainsKey("Title") -and -not $CommandParameters.ContainsKey("Path")){
|
||||
$CommandParameters["Title"] = $Name
|
||||
}
|
||||
|
||||
57
Providers/Provider.SecretManagement.ps1
Normal file
57
Providers/Provider.SecretManagement.ps1
Normal file
@@ -0,0 +1,57 @@
|
||||
$SecretManagementProvider = @{
|
||||
Name = "SecretManagement"
|
||||
SupportedTypes = @(
|
||||
"credential",
|
||||
"securestring",
|
||||
"string"
|
||||
)
|
||||
Resolver = {
|
||||
Param(
|
||||
[Parameter(Mandatory=$true)]
|
||||
[System.Collections.IDictionary]
|
||||
$Reference,
|
||||
|
||||
[Parameter(Mandatory=$true)]
|
||||
[ValidateSet("credential", "securestring", "string")]
|
||||
[string]
|
||||
$ExpectedType,
|
||||
|
||||
[Parameter(Mandatory=$false)]
|
||||
[hashtable]
|
||||
$ProviderSettings = @{}
|
||||
)
|
||||
|
||||
$GetSecretCommand = Get-Command -Name Get-Secret -ErrorAction SilentlyContinue
|
||||
if($null -eq $GetSecretCommand){
|
||||
throw "SecretManagement provider requires the module [Microsoft.PowerShell.SecretManagement] and command [Get-Secret]."
|
||||
}
|
||||
|
||||
$Vault = [string](Get-ConfigurationDataMapValue -Map $Reference -Key "Vault" -DefaultValue "")
|
||||
$Name = [string](Get-ConfigurationDataMapValue -Map $Reference -Key "Name")
|
||||
$UserName = [string](Get-ConfigurationDataMapValue -Map $Reference -Key "UserName" -DefaultValue "")
|
||||
$Options = Get-ConfigurationDataMapValue -Map $Reference -Key "Options" -DefaultValue @{}
|
||||
|
||||
if([string]::IsNullOrWhiteSpace($Vault) -and $ProviderSettings.ContainsKey("DefaultVault")){
|
||||
$Vault = [string]$ProviderSettings.DefaultVault
|
||||
}
|
||||
|
||||
$CommandParameters = @{
|
||||
Name = $Name
|
||||
}
|
||||
|
||||
if(-not [string]::IsNullOrWhiteSpace($Vault)){
|
||||
$CommandParameters["Vault"] = $Vault
|
||||
}
|
||||
|
||||
if($Options -is [System.Collections.IDictionary]){
|
||||
foreach($Key in $Options.Keys){
|
||||
$CommandParameters[$Key] = $Options[$Key]
|
||||
}
|
||||
}
|
||||
|
||||
$Secret = & $GetSecretCommand @CommandParameters
|
||||
return ConvertFrom-ConfigurationDataSecretValue -Secret $Secret -ExpectedType $ExpectedType -Name $Name -UserName $UserName
|
||||
}
|
||||
}
|
||||
|
||||
Register-ConfigurationDataSecretProvider @SecretManagementProvider
|
||||
57
Providers/Provider.SecretStore.ps1
Normal file
57
Providers/Provider.SecretStore.ps1
Normal file
@@ -0,0 +1,57 @@
|
||||
$SecretStoreProvider = @{
|
||||
Name = "SecretStore"
|
||||
SupportedTypes = @(
|
||||
"credential",
|
||||
"securestring",
|
||||
"string"
|
||||
)
|
||||
Resolver = {
|
||||
Param(
|
||||
[Parameter(Mandatory=$true)]
|
||||
[System.Collections.IDictionary]
|
||||
$Reference,
|
||||
|
||||
[Parameter(Mandatory=$true)]
|
||||
[ValidateSet("credential", "securestring", "string")]
|
||||
[string]
|
||||
$ExpectedType,
|
||||
|
||||
[Parameter(Mandatory=$false)]
|
||||
[hashtable]
|
||||
$ProviderSettings = @{}
|
||||
)
|
||||
|
||||
$GetSecretCommand = Get-Command -Name Get-Secret -ErrorAction SilentlyContinue
|
||||
if($null -eq $GetSecretCommand){
|
||||
throw "SecretStore provider requires the module [Microsoft.PowerShell.SecretManagement] and command [Get-Secret]."
|
||||
}
|
||||
|
||||
$Vault = [string](Get-ConfigurationDataMapValue -Map $Reference -Key "Vault" -DefaultValue "")
|
||||
$Name = [string](Get-ConfigurationDataMapValue -Map $Reference -Key "Name")
|
||||
$UserName = [string](Get-ConfigurationDataMapValue -Map $Reference -Key "UserName" -DefaultValue "")
|
||||
$Options = Get-ConfigurationDataMapValue -Map $Reference -Key "Options" -DefaultValue @{}
|
||||
|
||||
if([string]::IsNullOrWhiteSpace($Vault) -and $ProviderSettings.ContainsKey("DefaultVault")){
|
||||
$Vault = [string]$ProviderSettings.DefaultVault
|
||||
}
|
||||
|
||||
$CommandParameters = @{
|
||||
Name = $Name
|
||||
}
|
||||
|
||||
if(-not [string]::IsNullOrWhiteSpace($Vault)){
|
||||
$CommandParameters["Vault"] = $Vault
|
||||
}
|
||||
|
||||
if($Options -is [System.Collections.IDictionary]){
|
||||
foreach($Key in $Options.Keys){
|
||||
$CommandParameters[$Key] = $Options[$Key]
|
||||
}
|
||||
}
|
||||
|
||||
$Secret = & $GetSecretCommand @CommandParameters
|
||||
return ConvertFrom-ConfigurationDataSecretValue -Secret $Secret -ExpectedType $ExpectedType -Name $Name -UserName $UserName
|
||||
}
|
||||
}
|
||||
|
||||
Register-ConfigurationDataSecretProvider @SecretStoreProvider
|
||||
247
Public/Register-DSCConfigurationDataCredentialProvider.ps1
Normal file
247
Public/Register-DSCConfigurationDataCredentialProvider.ps1
Normal file
@@ -0,0 +1,247 @@
|
||||
function Register-DSCConfigurationDataCredentialProvider {
|
||||
[CmdletBinding(SupportsShouldProcess=$true)]
|
||||
Param(
|
||||
[Parameter(Mandatory=$true)]
|
||||
[string]
|
||||
$Vault,
|
||||
|
||||
[Parameter(Mandatory=$false)]
|
||||
[string]
|
||||
$KeyPath,
|
||||
|
||||
[Parameter(Mandatory=$false)]
|
||||
[string]
|
||||
$SettingsPath,
|
||||
|
||||
[Parameter(Mandatory=$false)]
|
||||
[System.Security.SecureString]
|
||||
$MasterKey,
|
||||
|
||||
[Parameter(Mandatory=$false)]
|
||||
[string]
|
||||
$ModuleName,
|
||||
|
||||
[Parameter(Mandatory=$false)]
|
||||
[hashtable]
|
||||
$VaultParameters = @{},
|
||||
|
||||
[Parameter(Mandatory=$false)]
|
||||
[switch]
|
||||
$RegisterVault,
|
||||
|
||||
[Parameter(Mandatory=$false)]
|
||||
[switch]
|
||||
$DefaultVault,
|
||||
|
||||
[Parameter(Mandatory=$false)]
|
||||
[switch]
|
||||
$AllowClobber,
|
||||
|
||||
[Parameter(Mandatory=$false)]
|
||||
[switch]
|
||||
$ConfigureSecretStore,
|
||||
|
||||
[Parameter(Mandatory=$false)]
|
||||
[ValidateSet("None", "Password")]
|
||||
[string]
|
||||
$Authentication = "None",
|
||||
|
||||
[Parameter(Mandatory=$false)]
|
||||
[ValidateSet("None", "Prompt")]
|
||||
[string]
|
||||
$Interaction = "None",
|
||||
|
||||
[Parameter(Mandatory=$false)]
|
||||
[System.Security.SecureString]
|
||||
$Password,
|
||||
|
||||
[Parameter(Mandatory=$false)]
|
||||
[int]
|
||||
$PasswordTimeout,
|
||||
|
||||
[Parameter(Mandatory=$false)]
|
||||
[switch]
|
||||
$Force,
|
||||
|
||||
[Parameter(Mandatory=$false)]
|
||||
[switch]
|
||||
$PassThru
|
||||
)
|
||||
|
||||
DynamicParam {
|
||||
$ProviderNames = @(Get-ConfigurationDataSecretProviderName)
|
||||
if($ProviderNames.Count -eq 0){
|
||||
$ProviderNames = @("__NoProvidersRegistered__")
|
||||
}
|
||||
|
||||
$ParameterAttribute = [System.Management.Automation.ParameterAttribute]::new()
|
||||
$ParameterAttribute.Mandatory = $false
|
||||
$ParameterAttribute.Position = 0
|
||||
|
||||
$ValidateSetAttribute = New-Object System.Management.Automation.ValidateSetAttribute -ArgumentList (,[string[]]$ProviderNames)
|
||||
$Attributes = [System.Collections.ObjectModel.Collection[System.Attribute]]::new()
|
||||
$Attributes.Add($ParameterAttribute)
|
||||
$Attributes.Add($ValidateSetAttribute)
|
||||
|
||||
$RuntimeParameter = [System.Management.Automation.RuntimeDefinedParameter]::new(
|
||||
"Provider",
|
||||
[string],
|
||||
$Attributes
|
||||
)
|
||||
|
||||
$Dictionary = [System.Management.Automation.RuntimeDefinedParameterDictionary]::new()
|
||||
$Dictionary.Add("Provider", $RuntimeParameter)
|
||||
return $Dictionary
|
||||
}
|
||||
|
||||
begin {
|
||||
$Provider = [string]$PSBoundParameters["Provider"]
|
||||
if([string]::IsNullOrWhiteSpace($Provider)){
|
||||
$ProviderNames = @(Get-ConfigurationDataSecretProviderName)
|
||||
if($ProviderNames.Count -eq 1){
|
||||
$Provider = $ProviderNames[0]
|
||||
}elseif($ProviderNames.Count -eq 0){
|
||||
throw "No configuration data secret providers are registered."
|
||||
}else{
|
||||
throw "Provider is required. Registered providers: $($ProviderNames -join ', ')."
|
||||
}
|
||||
}
|
||||
|
||||
$SettingsPath = Resolve-ConfigurationDataProviderSettingsPath -Provider $Provider -SettingsPath $SettingsPath
|
||||
}
|
||||
|
||||
process {
|
||||
switch($Provider){
|
||||
"KeePass" {
|
||||
if([string]::IsNullOrWhiteSpace($KeyPath)){
|
||||
throw "Provider [KeePass] requires [KeyPath]."
|
||||
}
|
||||
|
||||
if($null -eq $MasterKey){
|
||||
$MasterKey = Read-Host -Prompt "KeePass MasterKey for vault [$Vault]" -AsSecureString
|
||||
}
|
||||
|
||||
if($PSCmdlet.ShouldProcess($SettingsPath, "Create provider settings for [$Provider]")){
|
||||
$Key = New-ConfigurationDataAesKeyFile -Path $KeyPath -Force:$Force
|
||||
$ProtectedValue = $MasterKey | ConvertFrom-SecureString -Key $Key
|
||||
|
||||
$ProviderSettings = [ordered]@{
|
||||
KeePass = [ordered]@{
|
||||
DefaultVault = $Vault
|
||||
MasterKey = [ordered]@{
|
||||
ProtectedValue = $ProtectedValue
|
||||
KeyPath = $KeyPath
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Export-PowerShellDataFile -InputObject $ProviderSettings -Path $SettingsPath -Force:$Force
|
||||
|
||||
if($PassThru){
|
||||
return $ProviderSettings
|
||||
}
|
||||
|
||||
Write-Verbose "Provider settings written to [$SettingsPath]."
|
||||
}
|
||||
}
|
||||
"SecretManagement" {
|
||||
if([string]::IsNullOrWhiteSpace($ModuleName)){
|
||||
$ModuleName = "Microsoft.PowerShell.SecretStore"
|
||||
}
|
||||
|
||||
if($RegisterVault -or $PSBoundParameters.ContainsKey("ModuleName")){
|
||||
$CommandParameters = @{
|
||||
Name = $Vault
|
||||
ModuleName = $ModuleName
|
||||
}
|
||||
|
||||
if($VaultParameters.Count -gt 0){
|
||||
$CommandParameters["VaultParameters"] = $VaultParameters
|
||||
}
|
||||
|
||||
if($DefaultVault){
|
||||
$CommandParameters["DefaultVault"] = $true
|
||||
}
|
||||
|
||||
if($AllowClobber){
|
||||
$CommandParameters["AllowClobber"] = $true
|
||||
}
|
||||
|
||||
if($PSCmdlet.ShouldProcess($Vault, "Register SecretManagement vault [$ModuleName]")){
|
||||
Register-SecretVault @CommandParameters
|
||||
}
|
||||
}
|
||||
|
||||
$ProviderSettings = [ordered]@{
|
||||
SecretManagement = [ordered]@{
|
||||
DefaultVault = $Vault
|
||||
}
|
||||
}
|
||||
|
||||
if($PSCmdlet.ShouldProcess($SettingsPath, "Create provider settings for [$Provider]")){
|
||||
Export-PowerShellDataFile -InputObject $ProviderSettings -Path $SettingsPath -Force:$Force
|
||||
}
|
||||
|
||||
if($PassThru){
|
||||
return $ProviderSettings
|
||||
}
|
||||
}
|
||||
"SecretStore" {
|
||||
if($ConfigureSecretStore){
|
||||
$CommandParameters = @{
|
||||
Authentication = $Authentication
|
||||
Interaction = $Interaction
|
||||
}
|
||||
|
||||
if($null -ne $Password){
|
||||
$CommandParameters["Password"] = $Password
|
||||
}
|
||||
|
||||
if($PasswordTimeout -gt 0){
|
||||
$CommandParameters["PasswordTimeout"] = $PasswordTimeout
|
||||
}
|
||||
|
||||
if($PSCmdlet.ShouldProcess("SecretStore", "Configure local SecretStore")){
|
||||
Set-SecretStoreConfiguration @CommandParameters -Confirm:$false
|
||||
}
|
||||
}
|
||||
|
||||
if($RegisterVault){
|
||||
$CommandParameters = @{
|
||||
Name = $Vault
|
||||
ModuleName = "Microsoft.PowerShell.SecretStore"
|
||||
}
|
||||
|
||||
if($DefaultVault){
|
||||
$CommandParameters["DefaultVault"] = $true
|
||||
}
|
||||
|
||||
if($AllowClobber){
|
||||
$CommandParameters["AllowClobber"] = $true
|
||||
}
|
||||
|
||||
if($PSCmdlet.ShouldProcess($Vault, "Register SecretStore vault")){
|
||||
Register-SecretVault @CommandParameters
|
||||
}
|
||||
}
|
||||
|
||||
$ProviderSettings = [ordered]@{
|
||||
SecretStore = [ordered]@{
|
||||
DefaultVault = $Vault
|
||||
}
|
||||
}
|
||||
|
||||
if($PSCmdlet.ShouldProcess($SettingsPath, "Create provider settings for [$Provider]")){
|
||||
Export-PowerShellDataFile -InputObject $ProviderSettings -Path $SettingsPath -Force:$Force
|
||||
}
|
||||
|
||||
if($PassThru){
|
||||
return $ProviderSettings
|
||||
}
|
||||
}
|
||||
default {
|
||||
throw "Provider [$Provider] does not provide an initialization implementation."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -9,12 +9,24 @@ function Resolve-DSCConfigurationData {
|
||||
[hashtable]
|
||||
$ProviderSettings = @{},
|
||||
|
||||
[Parameter(Mandatory=$false)]
|
||||
[string]
|
||||
$ProviderSettingsPath,
|
||||
|
||||
[Parameter(Mandatory=$false)]
|
||||
[switch]
|
||||
$SkipSecrets
|
||||
)
|
||||
|
||||
process {
|
||||
if(-not [string]::IsNullOrWhiteSpace($ProviderSettingsPath)){
|
||||
if(-not (Test-Path -Path $ProviderSettingsPath -PathType Leaf)){
|
||||
throw "Provider settings file [$ProviderSettingsPath] was not found."
|
||||
}
|
||||
|
||||
$ProviderSettings = Import-PowerShellDataFile -Path $ProviderSettingsPath
|
||||
}
|
||||
|
||||
if(-not $SkipSecrets){
|
||||
$ConfigurationData = Resolve-ConfigurationDataParameterSecrets -ConfigurationData $ConfigurationData -ProviderSettings $ProviderSettings
|
||||
}
|
||||
|
||||
142
Public/Unregister-DSCConfigurationDataCredentialProvider.ps1
Normal file
142
Public/Unregister-DSCConfigurationDataCredentialProvider.ps1
Normal file
@@ -0,0 +1,142 @@
|
||||
function Unregister-DSCConfigurationDataCredentialProvider {
|
||||
[CmdletBinding(SupportsShouldProcess=$true, ConfirmImpact="High")]
|
||||
Param(
|
||||
[Parameter(Mandatory=$true)]
|
||||
[string]
|
||||
$Vault,
|
||||
|
||||
[Parameter(Mandatory=$false)]
|
||||
[string]
|
||||
$KeyPath,
|
||||
|
||||
[Parameter(Mandatory=$false)]
|
||||
[string]
|
||||
$SettingsPath,
|
||||
|
||||
[Parameter(Mandatory=$false)]
|
||||
[switch]
|
||||
$RemoveSettings,
|
||||
|
||||
[Parameter(Mandatory=$false)]
|
||||
[switch]
|
||||
$RemoveKeyFile,
|
||||
|
||||
[Parameter(Mandatory=$false)]
|
||||
[switch]
|
||||
$UnregisterVault,
|
||||
|
||||
[Parameter(Mandatory=$false)]
|
||||
[switch]
|
||||
$ResetSecretStore,
|
||||
|
||||
[Parameter(Mandatory=$false)]
|
||||
[switch]
|
||||
$Force,
|
||||
|
||||
[Parameter(Mandatory=$false)]
|
||||
[switch]
|
||||
$PassThru
|
||||
)
|
||||
|
||||
DynamicParam {
|
||||
$ProviderNames = @(Get-ConfigurationDataSecretProviderName)
|
||||
if($ProviderNames.Count -eq 0){
|
||||
$ProviderNames = @("__NoProvidersRegistered__")
|
||||
}
|
||||
|
||||
$ParameterAttribute = [System.Management.Automation.ParameterAttribute]::new()
|
||||
$ParameterAttribute.Mandatory = $false
|
||||
$ParameterAttribute.Position = 0
|
||||
|
||||
$ValidateSetAttribute = New-Object System.Management.Automation.ValidateSetAttribute -ArgumentList (,[string[]]$ProviderNames)
|
||||
$Attributes = [System.Collections.ObjectModel.Collection[System.Attribute]]::new()
|
||||
$Attributes.Add($ParameterAttribute)
|
||||
$Attributes.Add($ValidateSetAttribute)
|
||||
|
||||
$RuntimeParameter = [System.Management.Automation.RuntimeDefinedParameter]::new(
|
||||
"Provider",
|
||||
[string],
|
||||
$Attributes
|
||||
)
|
||||
|
||||
$Dictionary = [System.Management.Automation.RuntimeDefinedParameterDictionary]::new()
|
||||
$Dictionary.Add("Provider", $RuntimeParameter)
|
||||
return $Dictionary
|
||||
}
|
||||
|
||||
begin {
|
||||
$Provider = [string]$PSBoundParameters["Provider"]
|
||||
if([string]::IsNullOrWhiteSpace($Provider)){
|
||||
$ProviderNames = @(Get-ConfigurationDataSecretProviderName)
|
||||
if($ProviderNames.Count -eq 1){
|
||||
$Provider = $ProviderNames[0]
|
||||
}elseif($ProviderNames.Count -eq 0){
|
||||
throw "No configuration data secret providers are registered."
|
||||
}else{
|
||||
throw "Provider is required. Registered providers: $($ProviderNames -join ', ')."
|
||||
}
|
||||
}
|
||||
|
||||
$SettingsPath = Resolve-ConfigurationDataProviderSettingsPath -Provider $Provider -SettingsPath $SettingsPath
|
||||
}
|
||||
|
||||
process {
|
||||
$Result = [ordered]@{
|
||||
Provider = $Provider
|
||||
Vault = $Vault
|
||||
SettingsPath = $SettingsPath
|
||||
RemovedSettings = $false
|
||||
RemovedKeyFile = $false
|
||||
UnregisteredVault = $false
|
||||
ResetSecretStore = $false
|
||||
}
|
||||
|
||||
if($RemoveSettings -or (-not $PSBoundParameters.ContainsKey("RemoveSettings"))){
|
||||
if(Test-Path -Path $SettingsPath -PathType Leaf){
|
||||
if($PSCmdlet.ShouldProcess($SettingsPath, "Remove provider settings file")){
|
||||
Remove-Item -Path $SettingsPath -Force:$Force
|
||||
$Result.RemovedSettings = $true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if($RemoveKeyFile){
|
||||
if([string]::IsNullOrWhiteSpace($KeyPath)){
|
||||
throw "RemoveKeyFile requires [KeyPath]."
|
||||
}
|
||||
|
||||
if(Test-Path -Path $KeyPath -PathType Leaf){
|
||||
if($PSCmdlet.ShouldProcess($KeyPath, "Remove provider key file")){
|
||||
Remove-Item -Path $KeyPath -Force:$Force
|
||||
$Result.RemovedKeyFile = $true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if($UnregisterVault){
|
||||
if($Provider -notin @("SecretManagement", "SecretStore")){
|
||||
Write-Verbose "Provider [$Provider] does not register a SecretManagement vault."
|
||||
}else{
|
||||
if($PSCmdlet.ShouldProcess($Vault, "Unregister SecretManagement vault")){
|
||||
Unregister-SecretVault -Name $Vault
|
||||
$Result.UnregisteredVault = $true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if($ResetSecretStore){
|
||||
if($Provider -ne "SecretStore"){
|
||||
Write-Verbose "ResetSecretStore is only applicable to provider [SecretStore]."
|
||||
}else{
|
||||
if($PSCmdlet.ShouldProcess("SecretStore", "Reset local SecretStore and delete all contained secrets")){
|
||||
Reset-SecretStore -Force -Confirm:$false
|
||||
$Result.ResetSecretStore = $true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if($PassThru){
|
||||
return [PSCustomObject]$Result
|
||||
}
|
||||
}
|
||||
}
|
||||
146
Readme.md
146
Readme.md
@@ -111,7 +111,7 @@ FarmPassphrase = @{
|
||||
Sensitive = $true
|
||||
Value = @{
|
||||
Provider = 'KeePass'
|
||||
Vault = 'BGW'
|
||||
Vault = 'Contoso'
|
||||
Name = 'SharePoint/FarmPassphrase'
|
||||
}
|
||||
}
|
||||
@@ -122,9 +122,9 @@ SetupCredential = @{
|
||||
Sensitive = $true
|
||||
Value = @{
|
||||
Provider = 'KeePass'
|
||||
Vault = 'BGW'
|
||||
Name = 'SharePoint/SetupAccount'
|
||||
UserName = 'BGW\SVC_SHP_SETUP'
|
||||
Vault = 'Contoso'
|
||||
Name = 'Application/SetupAccount'
|
||||
UserName = 'CONTOSO\svc-app-setup'
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -134,13 +134,113 @@ Secret references are validated and resolved by `Resolve-DSCConfigurationData` b
|
||||
```powershell
|
||||
$resolved = Resolve-DSCConfigurationData -ConfigurationData $merged -ProviderSettings @{
|
||||
KeePass = @{
|
||||
DefaultVault = 'BGW'
|
||||
DefaultVault = 'Contoso'
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Use `-SkipSecrets` when you only want structural validation/resolution without loading provider secrets.
|
||||
|
||||
For unattended KeePass access, pass the KeePass master key through `ProviderSettings`, not through the PSD1 parameter definition:
|
||||
|
||||
```powershell
|
||||
$resolved = Resolve-DSCConfigurationData -ConfigurationData $merged -ProviderSettings @{
|
||||
KeePass = @{
|
||||
DefaultVault = 'Contoso'
|
||||
MasterKey = (Get-Secret -Name 'KeePass-Contoso-MasterKey')
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
You can generate the provider settings file with:
|
||||
|
||||
```powershell
|
||||
Register-DSCConfigurationDataCredentialProvider `
|
||||
-Provider KeePass `
|
||||
-Vault Contoso `
|
||||
-KeyPath 'C:\DSC\Contoso\KeePass-Contoso.key' `
|
||||
-SettingsPath 'C:\DSC\Contoso'
|
||||
```
|
||||
|
||||
SecretManagement can be initialized through the same entry point:
|
||||
|
||||
```powershell
|
||||
Register-DSCConfigurationDataCredentialProvider `
|
||||
-Provider SecretManagement `
|
||||
-Vault LocalStore `
|
||||
-ModuleName Microsoft.PowerShell.SecretStore `
|
||||
-RegisterVault `
|
||||
-DefaultVault `
|
||||
-SettingsPath 'C:\DSC\Contoso'
|
||||
```
|
||||
|
||||
SecretStore can also be configured for unattended local usage:
|
||||
|
||||
```powershell
|
||||
Register-DSCConfigurationDataCredentialProvider `
|
||||
-Provider SecretStore `
|
||||
-Vault LocalStore `
|
||||
-ConfigureSecretStore `
|
||||
-Authentication None `
|
||||
-Interaction None `
|
||||
-RegisterVault `
|
||||
-DefaultVault `
|
||||
-SettingsPath 'C:\DSC\Contoso'
|
||||
```
|
||||
|
||||
Provider setup can be removed again:
|
||||
|
||||
```powershell
|
||||
Unregister-DSCConfigurationDataCredentialProvider `
|
||||
-Provider KeePass `
|
||||
-Vault Contoso `
|
||||
-SettingsPath 'C:\DSC\Contoso' `
|
||||
-KeyPath 'C:\DSC\Contoso\KeePass-Contoso.key' `
|
||||
-RemoveKeyFile
|
||||
```
|
||||
|
||||
For SecretManagement or SecretStore vault registration:
|
||||
|
||||
```powershell
|
||||
Unregister-DSCConfigurationDataCredentialProvider `
|
||||
-Provider SecretStore `
|
||||
-Vault LocalStore `
|
||||
-SettingsPath 'C:\DSC\Contoso' `
|
||||
-UnregisterVault
|
||||
```
|
||||
|
||||
Use `-ResetSecretStore` only when you intentionally want to delete all secrets from the local SecretStore.
|
||||
|
||||
Then use it directly:
|
||||
|
||||
```powershell
|
||||
$resolved = Resolve-DSCConfigurationData `
|
||||
-ConfigurationData $merged `
|
||||
-ProviderSettingsPath 'C:\DSC\Contoso\ProviderSettings.KeePass.psd1'
|
||||
```
|
||||
|
||||
For portable unattended tests, use an AES key file with `ConvertFrom-SecureString -Key`:
|
||||
|
||||
```powershell
|
||||
$resolved = Resolve-DSCConfigurationData -ConfigurationData $merged -ProviderSettings @{
|
||||
KeePass = @{
|
||||
DefaultVault = 'Test'
|
||||
MasterKey = @{
|
||||
ProtectedValue = '76492d1116743f0423413b16050a5345...'
|
||||
KeyPath = 'F:\Secrets\KeePass-Test.key'
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
`MasterKey` also supports:
|
||||
|
||||
```powershell
|
||||
MasterKey = @{
|
||||
EnvironmentVariable = 'KEEPASS_TEST_MASTERKEY'
|
||||
}
|
||||
```
|
||||
|
||||
### Secret Provider Files
|
||||
|
||||
Secret providers are loaded automatically from the module folder `Providers`.
|
||||
@@ -182,6 +282,42 @@ The KeePass provider is implemented in:
|
||||
Providers\Provider.KeePass.ps1
|
||||
```
|
||||
|
||||
Built-in providers:
|
||||
|
||||
- `KeePass`: uses `PoShKeePass` / `Get-KeePassEntry`
|
||||
- `SecretManagement`: uses `Microsoft.PowerShell.SecretManagement` / `Get-Secret`
|
||||
- `SecretStore`: convenience provider for local SecretStore vaults through `Get-Secret`
|
||||
|
||||
SecretManagement example:
|
||||
|
||||
```powershell
|
||||
SetupCredential = @{
|
||||
Type = 'credential'
|
||||
Required = $true
|
||||
Sensitive = $true
|
||||
Value = @{
|
||||
Provider = 'SecretManagement'
|
||||
Vault = 'LocalStore'
|
||||
Name = 'SharePointSetupCredential'
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
SecretStore example:
|
||||
|
||||
```powershell
|
||||
FarmPassphrase = @{
|
||||
Type = 'secureString'
|
||||
Required = $true
|
||||
Sensitive = $true
|
||||
Value = @{
|
||||
Provider = 'SecretStore'
|
||||
Vault = 'LocalStore'
|
||||
Name = 'SharePointFarmPassphrase'
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Array values can be restricted item by item:
|
||||
|
||||
```powershell
|
||||
|
||||
@@ -7,7 +7,9 @@
|
||||
Description = "Resolves parameters, variables, and expressions in DSC configuration data."
|
||||
PowerShellVersion = "5.1"
|
||||
FunctionsToExport = @(
|
||||
"Resolve-DSCConfigurationData"
|
||||
"Resolve-DSCConfigurationData",
|
||||
"Register-DSCConfigurationDataCredentialProvider",
|
||||
"Unregister-DSCConfigurationDataCredentialProvider"
|
||||
)
|
||||
CmdletsToExport = @()
|
||||
VariablesToExport = @()
|
||||
|
||||
@@ -14,5 +14,7 @@ foreach($File in @($Private + $Providers + $Public)){
|
||||
}
|
||||
|
||||
Export-ModuleMember -Function @(
|
||||
"Resolve-DSCConfigurationData"
|
||||
"Resolve-DSCConfigurationData",
|
||||
"Register-DSCConfigurationDataCredentialProvider",
|
||||
"Unregister-DSCConfigurationDataCredentialProvider"
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user