Compare commits
11 Commits
8c6642672e
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4b67c74ac0 | ||
|
|
45e71c093c | ||
|
|
3fa52d7ada | ||
|
|
4b7ebe59ef | ||
|
|
943894b8fe | ||
|
|
9b36693444 | ||
|
|
115b8be385 | ||
|
|
1cd225c7a0 | ||
| 88b842c8e9 | |||
|
|
a96fb5bd07 | ||
|
|
086020c3df |
@@ -42,6 +42,11 @@ function Assert-ConfigurationDataParameter {
|
||||
return
|
||||
}
|
||||
|
||||
if((Test-ConfigurationDataValueIsEmpty -Value $Value) -and (-not $Required)){
|
||||
return
|
||||
}
|
||||
|
||||
Assert-ConfigurationDataParameterType -Name $Name -Definition $Definition -Value $Value
|
||||
Assert-ConfigurationDataParameterAllowedValue -Name $Name -Definition $Definition -Value $Value
|
||||
|
||||
if(Test-ConfigurationDataMapContainsKey -Map $Definition -Key "MinLength"){
|
||||
@@ -64,4 +69,20 @@ function Assert-ConfigurationDataParameter {
|
||||
throw "Parameter [$Name] value [$Value] does not match pattern [$Pattern]."
|
||||
}
|
||||
}
|
||||
|
||||
if(Test-ConfigurationDataMapContainsKey -Map $Definition -Key "MinValue"){
|
||||
Assert-ConfigurationDataParameterNumericValue -Name $Name -Value $Value
|
||||
$MinValue = [decimal](Get-ConfigurationDataMapValue -Map $Definition -Key "MinValue")
|
||||
if(([decimal]$Value) -lt $MinValue){
|
||||
throw "Parameter [$Name] value [$Value] is less than MinValue [$MinValue]."
|
||||
}
|
||||
}
|
||||
|
||||
if(Test-ConfigurationDataMapContainsKey -Map $Definition -Key "MaxValue"){
|
||||
Assert-ConfigurationDataParameterNumericValue -Name $Name -Value $Value
|
||||
$MaxValue = [decimal](Get-ConfigurationDataMapValue -Map $Definition -Key "MaxValue")
|
||||
if(([decimal]$Value) -gt $MaxValue){
|
||||
throw "Parameter [$Name] value [$Value] is greater than MaxValue [$MaxValue]."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,6 +21,16 @@ function Assert-ConfigurationDataParameterAllowedValue {
|
||||
return
|
||||
}
|
||||
|
||||
if($Value -is [System.Array] -and $Value -isnot [string]){
|
||||
foreach($Item in @($Value)){
|
||||
if($AllowedValues -notcontains $Item){
|
||||
throw "Parameter [$Name] value [$Item] is not allowed. Allowed values are: $($AllowedValues -join ', ')."
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if($AllowedValues -notcontains $Value){
|
||||
throw "Parameter [$Name] value [$Value] is not allowed. Allowed values are: $($AllowedValues -join ', ')."
|
||||
}
|
||||
|
||||
21
Private/Assert-ConfigurationDataParameterNumericValue.ps1
Normal file
21
Private/Assert-ConfigurationDataParameterNumericValue.ps1
Normal file
@@ -0,0 +1,21 @@
|
||||
function Assert-ConfigurationDataParameterNumericValue {
|
||||
[CmdletBinding()]
|
||||
Param(
|
||||
[Parameter(Mandatory=$true)]
|
||||
[string]
|
||||
$Name,
|
||||
|
||||
[AllowNull()]
|
||||
$Value
|
||||
)
|
||||
|
||||
if($null -eq $Value -or $Value -is [bool] -or ($Value -isnot [ValueType])){
|
||||
throw "Parameter [$Name] value [$Value] is not numeric."
|
||||
}
|
||||
|
||||
try {
|
||||
[void]([decimal]$Value)
|
||||
} catch {
|
||||
throw "Parameter [$Name] value [$Value] is not numeric."
|
||||
}
|
||||
}
|
||||
70
Private/Assert-ConfigurationDataParameterType.ps1
Normal file
70
Private/Assert-ConfigurationDataParameterType.ps1
Normal file
@@ -0,0 +1,70 @@
|
||||
function Assert-ConfigurationDataParameterType {
|
||||
[CmdletBinding()]
|
||||
Param(
|
||||
[Parameter(Mandatory=$true)]
|
||||
[string]
|
||||
$Name,
|
||||
|
||||
[Parameter(Mandatory=$true)]
|
||||
$Definition,
|
||||
|
||||
[AllowNull()]
|
||||
$Value
|
||||
)
|
||||
|
||||
if(-not (Test-ConfigurationDataMapContainsKey -Map $Definition -Key "Type")){
|
||||
return
|
||||
}
|
||||
|
||||
$TypeName = ([string](Get-ConfigurationDataMapValue -Map $Definition -Key "Type")).ToLowerInvariant()
|
||||
|
||||
switch($TypeName){
|
||||
"string" {
|
||||
if(Test-ConfigurationDataSecretReference -Value $Value){
|
||||
Assert-ConfigurationDataSecretReference -Name $Name -TypeName "string" -Value $Value
|
||||
return
|
||||
}
|
||||
|
||||
if($Value -isnot [string]){
|
||||
throw "Parameter [$Name] expects type [string], but received [$($Value.GetType().Name)]."
|
||||
}
|
||||
}
|
||||
{ $_ -in @("int", "integer") } {
|
||||
if(-not (Test-ConfigurationDataValueIsInteger -Value $Value)){
|
||||
throw "Parameter [$Name] expects type [int], but received [$($Value.GetType().Name)]."
|
||||
}
|
||||
}
|
||||
{ $_ -in @("bool", "boolean") } {
|
||||
if($Value -isnot [bool]){
|
||||
throw "Parameter [$Name] expects type [bool], but received [$($Value.GetType().Name)]."
|
||||
}
|
||||
}
|
||||
"array" {
|
||||
if($Value -isnot [System.Array] -or $Value -is [string]){
|
||||
throw "Parameter [$Name] expects type [array], but received [$($Value.GetType().Name)]."
|
||||
}
|
||||
}
|
||||
{ $_ -in @("hashtable", "object") } {
|
||||
if(-not (Test-ConfigurationDataMap -Value $Value)){
|
||||
throw "Parameter [$Name] expects type [hashtable], but received [$($Value.GetType().Name)]."
|
||||
}
|
||||
}
|
||||
"securestring" {
|
||||
if(($Value -isnot [string]) -and ($Value -isnot [System.Security.SecureString]) -and (-not (Test-ConfigurationDataMap -Value $Value))){
|
||||
throw "Parameter [$Name] expects type [secureString], but received [$($Value.GetType().Name)]."
|
||||
}
|
||||
|
||||
Assert-ConfigurationDataSecretReference -Name $Name -TypeName "securestring" -Value $Value
|
||||
}
|
||||
"credential" {
|
||||
if(($Value -isnot [System.Management.Automation.PSCredential]) -and (-not (Test-ConfigurationDataMap -Value $Value))){
|
||||
throw "Parameter [$Name] expects type [credential], but received [$($Value.GetType().Name)]."
|
||||
}
|
||||
|
||||
Assert-ConfigurationDataSecretReference -Name $Name -TypeName "credential" -Value $Value
|
||||
}
|
||||
default {
|
||||
throw "Parameter [$Name] uses unsupported type [$TypeName]."
|
||||
}
|
||||
}
|
||||
}
|
||||
47
Private/Assert-ConfigurationDataSecretReference.ps1
Normal file
47
Private/Assert-ConfigurationDataSecretReference.ps1
Normal file
@@ -0,0 +1,47 @@
|
||||
function Assert-ConfigurationDataSecretReference {
|
||||
[CmdletBinding()]
|
||||
Param(
|
||||
[Parameter(Mandatory=$true)]
|
||||
[string]
|
||||
$Name,
|
||||
|
||||
[Parameter(Mandatory=$true)]
|
||||
[ValidateSet("credential", "securestring", "string")]
|
||||
[string]
|
||||
$TypeName,
|
||||
|
||||
[Parameter(Mandatory=$true)]
|
||||
$Value
|
||||
)
|
||||
|
||||
if(-not (Test-ConfigurationDataMap -Value $Value)){
|
||||
return
|
||||
}
|
||||
|
||||
if(-not (Test-ConfigurationDataMapContainsKey -Map $Value -Key "Provider")){
|
||||
throw "Parameter [$Name] uses type [$TypeName] and a secret reference, but [Provider] is not defined."
|
||||
}
|
||||
|
||||
if(-not (Test-ConfigurationDataMapContainsKey -Map $Value -Key "Name")){
|
||||
throw "Parameter [$Name] uses type [$TypeName] and a secret reference, but [Name] is not defined."
|
||||
}
|
||||
|
||||
$Provider = [string](Get-ConfigurationDataMapValue -Map $Value -Key "Provider")
|
||||
if([string]::IsNullOrWhiteSpace($Provider)){
|
||||
throw "Parameter [$Name] secret reference [Provider] must not be empty."
|
||||
}
|
||||
|
||||
$SecretName = [string](Get-ConfigurationDataMapValue -Map $Value -Key "Name")
|
||||
if([string]::IsNullOrWhiteSpace($SecretName)){
|
||||
throw "Parameter [$Name] secret reference [Name] must not be empty."
|
||||
}
|
||||
|
||||
$ProviderDefinition = Get-ConfigurationDataSecretProvider -Name $Provider
|
||||
if($null -eq $ProviderDefinition){
|
||||
throw "Parameter [$Name] uses unsupported secret provider [$Provider]."
|
||||
}
|
||||
|
||||
if($ProviderDefinition.SupportedTypes -notcontains $TypeName.ToLowerInvariant()){
|
||||
throw "Parameter [$Name] uses provider [$Provider], but it does not support type [$TypeName]."
|
||||
}
|
||||
}
|
||||
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
|
||||
}
|
||||
19
Private/ConvertTo-ConfigurationDataCredential.ps1
Normal file
19
Private/ConvertTo-ConfigurationDataCredential.ps1
Normal file
@@ -0,0 +1,19 @@
|
||||
function ConvertTo-ConfigurationDataCredential {
|
||||
[CmdletBinding()]
|
||||
Param(
|
||||
[Parameter(Mandatory=$true)]
|
||||
[string]
|
||||
$UserName,
|
||||
|
||||
[Parameter(Mandatory=$true)]
|
||||
$Password
|
||||
)
|
||||
|
||||
if($Password -is [System.Security.SecureString]){
|
||||
$SecurePassword = $Password
|
||||
}else{
|
||||
$SecurePassword = ConvertTo-SecureString -String ([string]$Password) -AsPlainText -Force
|
||||
}
|
||||
|
||||
return [System.Management.Automation.PSCredential]::new($UserName, $SecurePassword)
|
||||
}
|
||||
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
|
||||
}
|
||||
@@ -6,8 +6,15 @@ function Get-ConfigurationDataMapValue {
|
||||
|
||||
[Parameter(Mandatory=$true)]
|
||||
[string]
|
||||
$Key
|
||||
$Key,
|
||||
|
||||
[AllowNull()]
|
||||
$DefaultValue = $null
|
||||
)
|
||||
|
||||
if(-not (Test-ConfigurationDataMapContainsKey -Map $Map -Key $Key)){
|
||||
return $DefaultValue
|
||||
}
|
||||
|
||||
return $Map[$Key]
|
||||
}
|
||||
|
||||
39
Private/Get-ConfigurationDataObjectPropertyValue.ps1
Normal file
39
Private/Get-ConfigurationDataObjectPropertyValue.ps1
Normal file
@@ -0,0 +1,39 @@
|
||||
function Get-ConfigurationDataObjectPropertyValue {
|
||||
[CmdletBinding()]
|
||||
Param(
|
||||
[Parameter(Mandatory=$true)]
|
||||
[AllowNull()]
|
||||
$Value,
|
||||
|
||||
[Parameter(Mandatory=$true)]
|
||||
[string]
|
||||
$Property,
|
||||
|
||||
[Parameter(Mandatory=$false)]
|
||||
[string]
|
||||
$Path = ""
|
||||
)
|
||||
|
||||
if([string]::IsNullOrWhiteSpace($Property)){
|
||||
throw "Configuration data reference property must not be empty."
|
||||
}
|
||||
|
||||
if($null -eq $Value){
|
||||
throw "Configuration data reference property [$Property] was requested from a null value at path [$Path]."
|
||||
}
|
||||
|
||||
if($Value -is [System.Collections.IDictionary]){
|
||||
if(-not $Value.Contains($Property)){
|
||||
throw "Configuration data reference property [$Property] was not found at path [$Path]."
|
||||
}
|
||||
|
||||
return $Value[$Property]
|
||||
}
|
||||
|
||||
$ObjectProperty = $Value.PSObject.Properties[$Property]
|
||||
if($null -eq $ObjectProperty){
|
||||
throw "Configuration data reference property [$Property] was not found at path [$Path]."
|
||||
}
|
||||
|
||||
return $ObjectProperty.Value
|
||||
}
|
||||
61
Private/Get-ConfigurationDataPathValue.ps1
Normal file
61
Private/Get-ConfigurationDataPathValue.ps1
Normal file
@@ -0,0 +1,61 @@
|
||||
function Get-ConfigurationDataPathValue {
|
||||
[CmdletBinding()]
|
||||
Param(
|
||||
[Parameter(Mandatory=$true)]
|
||||
[AllowNull()]
|
||||
$Value,
|
||||
|
||||
[Parameter(Mandatory=$true)]
|
||||
[string]
|
||||
$Path
|
||||
)
|
||||
|
||||
if([string]::IsNullOrWhiteSpace($Path)){
|
||||
throw "Configuration data reference path must not be empty."
|
||||
}
|
||||
|
||||
$Current = $Value
|
||||
$Segments = @($Path -split '\.')
|
||||
|
||||
foreach($Segment in $Segments){
|
||||
if([string]::IsNullOrWhiteSpace($Segment)){
|
||||
throw "Configuration data reference path [$Path] contains an empty segment."
|
||||
}
|
||||
|
||||
if($null -eq $Current){
|
||||
throw "Configuration data reference path [$Path] was not found. Segment [$Segment] resolved from a null value."
|
||||
}
|
||||
|
||||
if($Current -is [System.Collections.IDictionary]){
|
||||
if(-not $Current.Contains($Segment)){
|
||||
throw "Configuration data reference path [$Path] was not found. Segment [$Segment] is missing."
|
||||
}
|
||||
|
||||
$Current = $Current[$Segment]
|
||||
continue
|
||||
}
|
||||
|
||||
if($Current -is [System.Array] -and $Current -isnot [string]){
|
||||
$Index = 0
|
||||
if(-not [int]::TryParse($Segment, [ref]$Index)){
|
||||
throw "Configuration data reference path [$Path] expected a numeric array index at segment [$Segment]."
|
||||
}
|
||||
|
||||
if($Index -lt 0 -or $Index -ge @($Current).Count){
|
||||
throw "Configuration data reference path [$Path] array index [$Index] is out of range."
|
||||
}
|
||||
|
||||
$Current = @($Current)[$Index]
|
||||
continue
|
||||
}
|
||||
|
||||
$Property = $Current.PSObject.Properties[$Segment]
|
||||
if($null -eq $Property){
|
||||
throw "Configuration data reference path [$Path] was not found. Property [$Segment] is missing."
|
||||
}
|
||||
|
||||
$Current = $Property.Value
|
||||
}
|
||||
|
||||
return $Current
|
||||
}
|
||||
19
Private/Get-ConfigurationDataSecretProvider.ps1
Normal file
19
Private/Get-ConfigurationDataSecretProvider.ps1
Normal file
@@ -0,0 +1,19 @@
|
||||
function Get-ConfigurationDataSecretProvider {
|
||||
[CmdletBinding()]
|
||||
Param(
|
||||
[Parameter(Mandatory=$true)]
|
||||
[string]
|
||||
$Name
|
||||
)
|
||||
|
||||
if($null -eq $script:ConfigurationDataSecretProviders){
|
||||
$script:ConfigurationDataSecretProviders = @{}
|
||||
}
|
||||
|
||||
$ProviderName = $Name.ToLowerInvariant()
|
||||
if(-not $script:ConfigurationDataSecretProviders.ContainsKey($ProviderName)){
|
||||
return $null
|
||||
}
|
||||
|
||||
return $script:ConfigurationDataSecretProviders[$ProviderName]
|
||||
}
|
||||
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 }
|
||||
)
|
||||
}
|
||||
@@ -23,12 +23,40 @@ function Invoke-ConfigurationDataExpression {
|
||||
return [bool]::Parse($Expression)
|
||||
}
|
||||
|
||||
if(-not (Test-ConfigurationDataExpressionParentheses -Expression $Expression)){
|
||||
throw "Invalid configuration data expression [$Expression]."
|
||||
}
|
||||
|
||||
if($Expression -notmatch "^([A-Za-z][A-Za-z0-9]*)\((.*)\)$"){
|
||||
throw "Invalid configuration data expression [$Expression]."
|
||||
}
|
||||
|
||||
$FunctionName = $Matches[1]
|
||||
$ArgumentText = $Matches[2]
|
||||
|
||||
if($FunctionName -ieq "coalesce"){
|
||||
$RawArguments = @(Split-ConfigurationDataExpressionArguments -ArgumentText $ArgumentText)
|
||||
Assert-ConfigurationDataExpressionMinimumArgumentCount -Name $FunctionName -Arguments $RawArguments -Count 1
|
||||
|
||||
foreach($Argument in $RawArguments){
|
||||
try {
|
||||
$Value = Invoke-ConfigurationDataExpressionArgument -Argument $Argument -Context $Context
|
||||
if(-not (Test-ConfigurationDataValueIsEmpty -Value $Value)){
|
||||
return $Value
|
||||
}
|
||||
}
|
||||
catch {
|
||||
if(Test-ConfigurationDataMissingReferenceError -ErrorRecord $_){
|
||||
continue
|
||||
}
|
||||
|
||||
throw
|
||||
}
|
||||
}
|
||||
|
||||
return $null
|
||||
}
|
||||
|
||||
$Arguments = @()
|
||||
foreach($Argument in @(Split-ConfigurationDataExpressionArguments -ArgumentText $ArgumentText)){
|
||||
$Arguments += ,(Invoke-ConfigurationDataExpressionArgument -Argument $Argument -Context $Context)
|
||||
|
||||
@@ -24,6 +24,17 @@ function Invoke-ConfigurationDataExpressionFunction {
|
||||
Assert-ConfigurationDataExpressionArgumentCount -Name $Name -Arguments $Arguments -Count 1
|
||||
return Resolve-ConfigurationDataVariable -Name ([string]$Arguments[0]) -Context $Context
|
||||
}
|
||||
"reference" {
|
||||
if($Arguments.Count -eq 1){
|
||||
return Resolve-ConfigurationDataReference -Path ([string]$Arguments[0]) -Context $Context
|
||||
}
|
||||
|
||||
if($Arguments.Count -eq 2){
|
||||
return Resolve-ConfigurationDataReference -Path ([string]$Arguments[0]) -Property ([string]$Arguments[1]) -Context $Context
|
||||
}
|
||||
|
||||
throw "Function [$Name] expects 1 or 2 arguments, but received [$($Arguments.Count)]."
|
||||
}
|
||||
"concat" {
|
||||
return (@($Arguments) | ForEach-Object { [string]$_ }) -join ""
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
68
Private/New-ConfigurationDataDummySecretValue.ps1
Normal file
68
Private/New-ConfigurationDataDummySecretValue.ps1
Normal file
@@ -0,0 +1,68 @@
|
||||
function New-ConfigurationDataDummySecretValue {
|
||||
[CmdletBinding()]
|
||||
Param(
|
||||
[Parameter(Mandatory=$true)]
|
||||
[ValidateSet("credential", "securestring", "string")]
|
||||
[string]
|
||||
$ExpectedType,
|
||||
|
||||
[Parameter(Mandatory=$false)]
|
||||
[System.Collections.IDictionary]
|
||||
$Reference,
|
||||
|
||||
[Parameter(Mandatory=$false)]
|
||||
[string]
|
||||
$ParameterName = ""
|
||||
)
|
||||
|
||||
$Name = ""
|
||||
if($null -ne $Reference -and (Test-ConfigurationDataMapContainsKey -Map $Reference -Key "Name")){
|
||||
$Name = [string](Get-ConfigurationDataMapValue -Map $Reference -Key "Name" -DefaultValue "")
|
||||
}
|
||||
|
||||
$UserName = ""
|
||||
if($null -ne $Reference -and (Test-ConfigurationDataMapContainsKey -Map $Reference -Key "UserName")){
|
||||
$UserName = [string](Get-ConfigurationDataMapValue -Map $Reference -Key "UserName" -DefaultValue "")
|
||||
}
|
||||
|
||||
if([string]::IsNullOrWhiteSpace($UserName)){
|
||||
$UserNameSource = $ParameterName
|
||||
if([string]::IsNullOrWhiteSpace($UserNameSource)){
|
||||
$UserNameSource = $Name
|
||||
}
|
||||
|
||||
if([string]::IsNullOrWhiteSpace($UserNameSource)){
|
||||
$UserNameSource = "Credential"
|
||||
}
|
||||
|
||||
$UserNameLeaf = @($UserNameSource -split '[\\/]+')[-1]
|
||||
if([string]::IsNullOrWhiteSpace($UserNameLeaf)){
|
||||
$UserNameLeaf = "Credential"
|
||||
}
|
||||
|
||||
$UserName = "DUMMY\$UserNameLeaf"
|
||||
}
|
||||
|
||||
switch($ExpectedType.ToLowerInvariant()){
|
||||
"credential" {
|
||||
return [pscredential]::new(
|
||||
$UserName,
|
||||
(ConvertTo-SecureString -String "DummyPassword!" -AsPlainText -Force)
|
||||
)
|
||||
}
|
||||
"securestring" {
|
||||
return ConvertTo-SecureString -String "DummySecret!" -AsPlainText -Force
|
||||
}
|
||||
"string" {
|
||||
if([string]::IsNullOrWhiteSpace($Name)){
|
||||
$Name = $ParameterName
|
||||
}
|
||||
|
||||
if([string]::IsNullOrWhiteSpace($Name)){
|
||||
return "DummySecret"
|
||||
}
|
||||
|
||||
return "DummySecret:$Name"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,10 +7,13 @@ function New-ConfigurationDataResolutionContext {
|
||||
)
|
||||
|
||||
$Context = [PSCustomObject]@{
|
||||
ConfigurationData = $ConfigurationData
|
||||
Parameters = @{}
|
||||
Variables = @{}
|
||||
VariableDefinitions = @{}
|
||||
ResolvingVariables = @{}
|
||||
ReferenceCache = @{}
|
||||
ResolvingReferences = @{}
|
||||
}
|
||||
|
||||
if($ConfigurationData.ContainsKey("Parameters") -and $null -ne $ConfigurationData.Parameters){
|
||||
|
||||
40
Private/Register-ConfigurationDataSecretProvider.ps1
Normal file
40
Private/Register-ConfigurationDataSecretProvider.ps1
Normal file
@@ -0,0 +1,40 @@
|
||||
function Register-ConfigurationDataSecretProvider {
|
||||
[CmdletBinding()]
|
||||
Param(
|
||||
[Parameter(Mandatory=$true)]
|
||||
[string]
|
||||
$Name,
|
||||
|
||||
[Parameter(Mandatory=$true)]
|
||||
[string[]]
|
||||
$SupportedTypes,
|
||||
|
||||
[Parameter(Mandatory=$true)]
|
||||
[scriptblock]
|
||||
$Resolver
|
||||
)
|
||||
|
||||
if([string]::IsNullOrWhiteSpace($Name)){
|
||||
throw "Secret provider [Name] must not be empty."
|
||||
}
|
||||
|
||||
if($SupportedTypes.Count -eq 0){
|
||||
throw "Secret provider [$Name] must define at least one supported type."
|
||||
}
|
||||
|
||||
foreach($Type in $SupportedTypes){
|
||||
if($Type -notin @("credential", "securestring", "string")){
|
||||
throw "Secret provider [$Name] uses unsupported type [$Type]."
|
||||
}
|
||||
}
|
||||
|
||||
if($null -eq $script:ConfigurationDataSecretProviders){
|
||||
$script:ConfigurationDataSecretProviders = @{}
|
||||
}
|
||||
|
||||
$script:ConfigurationDataSecretProviders[$Name.ToLowerInvariant()] = [PSCustomObject]@{
|
||||
Name = $Name
|
||||
SupportedTypes = @($SupportedTypes | ForEach-Object { $_.ToLowerInvariant() })
|
||||
Resolver = $Resolver
|
||||
}
|
||||
}
|
||||
50
Private/Resolve-ConfigurationDataParameterSecrets.ps1
Normal file
50
Private/Resolve-ConfigurationDataParameterSecrets.ps1
Normal file
@@ -0,0 +1,50 @@
|
||||
function Resolve-ConfigurationDataParameterSecrets {
|
||||
[CmdletBinding()]
|
||||
Param(
|
||||
[Parameter(Mandatory=$true)]
|
||||
[System.Collections.Hashtable]
|
||||
$ConfigurationData,
|
||||
|
||||
[Parameter(Mandatory=$false)]
|
||||
[hashtable]
|
||||
$ProviderSettings = @{},
|
||||
|
||||
[Parameter(Mandatory=$false)]
|
||||
[switch]
|
||||
$UseDummySecrets
|
||||
)
|
||||
|
||||
$ResolvedConfigurationData = $ConfigurationData.Clone()
|
||||
|
||||
if($ResolvedConfigurationData.ContainsKey("Parameters")){
|
||||
$ResolvedConfigurationData.Parameters = $ResolvedConfigurationData.Parameters.Clone()
|
||||
|
||||
foreach($Parameter in @($ResolvedConfigurationData.Parameters.GetEnumerator())){
|
||||
$Definition = $Parameter.Value
|
||||
if(-not (Test-ConfigurationDataMap -Value $Definition)){
|
||||
continue
|
||||
}
|
||||
|
||||
$Definition = $Definition.Clone()
|
||||
$ResolvedConfigurationData.Parameters[$Parameter.Name] = $Definition
|
||||
|
||||
$TypeName = [string](Get-ConfigurationDataMapValue -Map $Definition -Key "Type" -DefaultValue "")
|
||||
if($TypeName -notin @("credential", "secureString", "string")){
|
||||
continue
|
||||
}
|
||||
|
||||
$ExpectedType = $TypeName.ToLowerInvariant()
|
||||
foreach($ValueKey in @("Value", "DefaultValue")){
|
||||
if((Test-ConfigurationDataMapContainsKey -Map $Definition -Key $ValueKey) -and (Test-ConfigurationDataSecretReference -Value $Definition[$ValueKey])){
|
||||
if($UseDummySecrets){
|
||||
$Definition[$ValueKey] = New-ConfigurationDataDummySecretValue -ExpectedType $ExpectedType -Reference $Definition[$ValueKey] -ParameterName $Parameter.Name
|
||||
}else{
|
||||
$Definition[$ValueKey] = Resolve-ConfigurationDataSecretReference -Reference $Definition[$ValueKey] -ExpectedType $ExpectedType -ProviderSettings $ProviderSettings
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $ResolvedConfigurationData
|
||||
}
|
||||
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."
|
||||
}
|
||||
28
Private/Resolve-ConfigurationDataProviderSettingsPath.ps1
Normal file
28
Private/Resolve-ConfigurationDataProviderSettingsPath.ps1
Normal file
@@ -0,0 +1,28 @@
|
||||
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("/") -or
|
||||
[string]::IsNullOrWhiteSpace([System.IO.Path]::GetExtension($SettingsPath))){
|
||||
return (Join-Path -Path $SettingsPath -ChildPath $FileName)
|
||||
}
|
||||
|
||||
return $SettingsPath
|
||||
}
|
||||
46
Private/Resolve-ConfigurationDataReference.ps1
Normal file
46
Private/Resolve-ConfigurationDataReference.ps1
Normal file
@@ -0,0 +1,46 @@
|
||||
function Resolve-ConfigurationDataReference {
|
||||
[CmdletBinding()]
|
||||
Param(
|
||||
[Parameter(Mandatory=$true)]
|
||||
[string]
|
||||
$Path,
|
||||
|
||||
[Parameter(Mandatory=$false)]
|
||||
[AllowEmptyString()]
|
||||
[string]
|
||||
$Property = "",
|
||||
|
||||
[Parameter(Mandatory=$true)]
|
||||
$Context
|
||||
)
|
||||
|
||||
$ReferenceKey = $Path
|
||||
if(-not [string]::IsNullOrWhiteSpace($Property)){
|
||||
$ReferenceKey = "$Path::$Property"
|
||||
}
|
||||
|
||||
if($Context.ReferenceCache.ContainsKey($ReferenceKey)){
|
||||
return $Context.ReferenceCache[$ReferenceKey]
|
||||
}
|
||||
|
||||
if($Context.ResolvingReferences.ContainsKey($ReferenceKey)){
|
||||
$Stack = @($Context.ResolvingReferences.Keys) + $ReferenceKey
|
||||
throw "Circular configuration data reference detected: $($Stack -join ' -> ')."
|
||||
}
|
||||
|
||||
$Context.ResolvingReferences[$ReferenceKey] = $true
|
||||
try {
|
||||
$RawValue = Get-ConfigurationDataPathValue -Value $Context.ConfigurationData -Path $Path
|
||||
$ResolvedValue = Resolve-ConfigurationDataValue -Value $RawValue -Context $Context
|
||||
|
||||
if(-not [string]::IsNullOrWhiteSpace($Property)){
|
||||
$ResolvedValue = Get-ConfigurationDataObjectPropertyValue -Value $ResolvedValue -Property $Property -Path $Path
|
||||
}
|
||||
|
||||
$Context.ReferenceCache[$ReferenceKey] = $ResolvedValue
|
||||
return $ResolvedValue
|
||||
}
|
||||
finally {
|
||||
$Context.ResolvingReferences.Remove($ReferenceKey)
|
||||
}
|
||||
}
|
||||
35
Private/Resolve-ConfigurationDataSecretReference.ps1
Normal file
35
Private/Resolve-ConfigurationDataSecretReference.ps1
Normal file
@@ -0,0 +1,35 @@
|
||||
function Resolve-ConfigurationDataSecretReference {
|
||||
[CmdletBinding()]
|
||||
Param(
|
||||
[Parameter(Mandatory=$true)]
|
||||
[System.Collections.IDictionary]
|
||||
$Reference,
|
||||
|
||||
[Parameter(Mandatory=$true)]
|
||||
[ValidateSet("credential", "securestring", "string")]
|
||||
[string]
|
||||
$ExpectedType,
|
||||
|
||||
[Parameter(Mandatory=$false)]
|
||||
[hashtable]
|
||||
$ProviderSettings = @{}
|
||||
)
|
||||
|
||||
$Provider = [string](Get-ConfigurationDataMapValue -Map $Reference -Key "Provider")
|
||||
$ProviderDefinition = Get-ConfigurationDataSecretProvider -Name $Provider
|
||||
|
||||
if($null -eq $ProviderDefinition){
|
||||
throw "Unsupported secret provider [$Provider]."
|
||||
}
|
||||
|
||||
if($ProviderDefinition.SupportedTypes -notcontains $ExpectedType.ToLowerInvariant()){
|
||||
throw "Secret provider [$($ProviderDefinition.Name)] does not support type [$ExpectedType]."
|
||||
}
|
||||
|
||||
$CurrentProviderSettings = @{}
|
||||
if($ProviderSettings.ContainsKey($ProviderDefinition.Name)){
|
||||
$CurrentProviderSettings = $ProviderSettings[$ProviderDefinition.Name]
|
||||
}
|
||||
|
||||
return & $ProviderDefinition.Resolver -Reference $Reference -ExpectedType $ExpectedType -ProviderSettings $CurrentProviderSettings
|
||||
}
|
||||
@@ -15,6 +15,9 @@ function Resolve-ConfigurationDataValue {
|
||||
if($Value -is [System.Collections.Specialized.OrderedDictionary]){
|
||||
$Resolved = [ordered]@{}
|
||||
foreach($Entry in $Value.GetEnumerator()){
|
||||
if($Entry.Name -eq "Sealed"){
|
||||
continue
|
||||
}
|
||||
$Resolved[$Entry.Name] = Resolve-ConfigurationDataValue -Value $Entry.Value -Context $Context
|
||||
}
|
||||
return $Resolved
|
||||
@@ -23,6 +26,9 @@ function Resolve-ConfigurationDataValue {
|
||||
if($Value -is [System.Collections.Hashtable]){
|
||||
$Resolved = @{}
|
||||
foreach($Entry in $Value.GetEnumerator()){
|
||||
if($Entry.Name -eq "Sealed"){
|
||||
continue
|
||||
}
|
||||
$Resolved[$Entry.Name] = Resolve-ConfigurationDataValue -Value $Entry.Value -Context $Context
|
||||
}
|
||||
return $Resolved
|
||||
|
||||
39
Private/Test-ConfigurationDataExpressionParentheses.ps1
Normal file
39
Private/Test-ConfigurationDataExpressionParentheses.ps1
Normal file
@@ -0,0 +1,39 @@
|
||||
function Test-ConfigurationDataExpressionParentheses {
|
||||
[CmdletBinding()]
|
||||
Param(
|
||||
[AllowNull()]
|
||||
[string]
|
||||
$Expression
|
||||
)
|
||||
|
||||
if([string]::IsNullOrWhiteSpace($Expression)){
|
||||
return $true
|
||||
}
|
||||
|
||||
$Depth = 0
|
||||
$InString = $false
|
||||
|
||||
for($Index = 0; $Index -lt $Expression.Length; $Index++){
|
||||
$Character = $Expression[$Index]
|
||||
|
||||
if($Character -eq "'"){
|
||||
$InString = -not $InString
|
||||
continue
|
||||
}
|
||||
|
||||
if($InString){
|
||||
continue
|
||||
}
|
||||
|
||||
if($Character -eq "("){
|
||||
$Depth++
|
||||
}elseif($Character -eq ")"){
|
||||
$Depth--
|
||||
if($Depth -lt 0){
|
||||
return $false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $Depth -eq 0 -and -not $InString
|
||||
}
|
||||
11
Private/Test-ConfigurationDataMissingReferenceError.ps1
Normal file
11
Private/Test-ConfigurationDataMissingReferenceError.ps1
Normal file
@@ -0,0 +1,11 @@
|
||||
function Test-ConfigurationDataMissingReferenceError {
|
||||
[CmdletBinding()]
|
||||
Param(
|
||||
[Parameter(Mandatory=$true)]
|
||||
[System.Management.Automation.ErrorRecord]
|
||||
$ErrorRecord
|
||||
)
|
||||
|
||||
$Message = $ErrorRecord.Exception.Message
|
||||
return $Message -match "^(Parameter|Variable) \[.+\] is not defined\.$"
|
||||
}
|
||||
14
Private/Test-ConfigurationDataSecretReference.ps1
Normal file
14
Private/Test-ConfigurationDataSecretReference.ps1
Normal file
@@ -0,0 +1,14 @@
|
||||
function Test-ConfigurationDataSecretReference {
|
||||
[CmdletBinding()]
|
||||
Param(
|
||||
[AllowNull()]
|
||||
$Value
|
||||
)
|
||||
|
||||
if(-not (Test-ConfigurationDataMap -Value $Value)){
|
||||
return $false
|
||||
}
|
||||
|
||||
return (Test-ConfigurationDataMapContainsKey -Map $Value -Key "Provider") -and
|
||||
(Test-ConfigurationDataMapContainsKey -Map $Value -Key "Name")
|
||||
}
|
||||
20
Private/Test-ConfigurationDataValueIsInteger.ps1
Normal file
20
Private/Test-ConfigurationDataValueIsInteger.ps1
Normal file
@@ -0,0 +1,20 @@
|
||||
function Test-ConfigurationDataValueIsInteger {
|
||||
[CmdletBinding()]
|
||||
Param(
|
||||
[AllowNull()]
|
||||
$Value
|
||||
)
|
||||
|
||||
if($null -eq $Value -or $Value -is [bool]){
|
||||
return $false
|
||||
}
|
||||
|
||||
return ($Value -is [byte]) -or
|
||||
($Value -is [sbyte]) -or
|
||||
($Value -is [int16]) -or
|
||||
($Value -is [uint16]) -or
|
||||
($Value -is [int]) -or
|
||||
($Value -is [uint32]) -or
|
||||
($Value -is [long]) -or
|
||||
($Value -is [uint64])
|
||||
}
|
||||
49
Private/Unlock-ConfigurationDataSecretManagementVault.ps1
Normal file
49
Private/Unlock-ConfigurationDataSecretManagementVault.ps1
Normal file
@@ -0,0 +1,49 @@
|
||||
function Unlock-ConfigurationDataSecretManagementVault {
|
||||
[CmdletBinding()]
|
||||
Param(
|
||||
[Parameter(Mandatory=$false)]
|
||||
[hashtable]
|
||||
$ProviderSettings = @{}
|
||||
)
|
||||
|
||||
if(-not $ProviderSettings.ContainsKey("SecretManagement")){
|
||||
return
|
||||
}
|
||||
|
||||
$SecretManagementSettings = $ProviderSettings.SecretManagement
|
||||
if(-not ($SecretManagementSettings -is [System.Collections.IDictionary])){
|
||||
return
|
||||
}
|
||||
|
||||
if(-not (Test-ConfigurationDataMapContainsKey -Map $SecretManagementSettings -Key "MasterPassword")){
|
||||
return
|
||||
}
|
||||
|
||||
$Vault = [string](Get-ConfigurationDataMapValue -Map $SecretManagementSettings -Key "DefaultVault" -DefaultValue "")
|
||||
if([string]::IsNullOrWhiteSpace($Vault)){
|
||||
throw "SecretManagement provider settings define [MasterPassword], but [DefaultVault] is not defined."
|
||||
}
|
||||
|
||||
$UnlockSecretVaultCommand = Get-Command -Name Unlock-SecretVault -ErrorAction SilentlyContinue
|
||||
if($null -eq $UnlockSecretVaultCommand){
|
||||
throw "Command [Unlock-SecretVault] was not found. Install module [Microsoft.PowerShell.SecretManagement]."
|
||||
}
|
||||
|
||||
$MasterPassword = Resolve-ConfigurationDataProviderSecureString -Value $SecretManagementSettings.MasterPassword
|
||||
& $UnlockSecretVaultCommand -Name $Vault -Password $MasterPassword
|
||||
|
||||
$TestSecretVaultCommand = Get-Command -Name Test-SecretVault -ErrorAction SilentlyContinue
|
||||
if($null -ne $TestSecretVaultCommand){
|
||||
$IsUnlocked = $false
|
||||
try {
|
||||
$IsUnlocked = [bool](& $TestSecretVaultCommand -Name $Vault -ErrorAction Stop)
|
||||
}
|
||||
catch {
|
||||
throw "SecretManagement vault [$Vault] could not be unlocked. $($_.Exception.Message)"
|
||||
}
|
||||
|
||||
if(-not $IsUnlocked){
|
||||
throw "SecretManagement vault [$Vault] could not be unlocked. Verify the KeePass database path, KeePass key file, and protected master password in the provider settings."
|
||||
}
|
||||
}
|
||||
}
|
||||
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
|
||||
119
Public/Get-DSCConfigurationDataCredentialProvider.ps1
Normal file
119
Public/Get-DSCConfigurationDataCredentialProvider.ps1
Normal file
@@ -0,0 +1,119 @@
|
||||
function Get-DSCConfigurationDataCredentialProvider {
|
||||
[CmdletBinding()]
|
||||
Param(
|
||||
[Parameter(Mandatory=$false)]
|
||||
[string]
|
||||
$Vault,
|
||||
|
||||
[Parameter(Mandatory=$false)]
|
||||
[string]
|
||||
$SettingsPath,
|
||||
|
||||
[Parameter(Mandatory=$false)]
|
||||
[switch]
|
||||
$TestVault
|
||||
)
|
||||
|
||||
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 ', ')."
|
||||
}
|
||||
}
|
||||
|
||||
$ResolvedSettingsPath = Resolve-ConfigurationDataProviderSettingsPath -Provider $Provider -SettingsPath $SettingsPath
|
||||
}
|
||||
|
||||
process {
|
||||
$Settings = @{}
|
||||
$SettingsExists = Test-Path -LiteralPath $ResolvedSettingsPath -PathType Leaf
|
||||
if($SettingsExists){
|
||||
$Settings = Import-PowerShellDataFile -LiteralPath $ResolvedSettingsPath
|
||||
}
|
||||
|
||||
$ProviderSettings = @{}
|
||||
if($Settings -is [System.Collections.IDictionary] -and $Settings.ContainsKey($Provider)){
|
||||
$ProviderSettings = $Settings[$Provider]
|
||||
}
|
||||
|
||||
$DefaultVault = ""
|
||||
if($ProviderSettings -is [System.Collections.IDictionary] -and $ProviderSettings.ContainsKey("DefaultVault")){
|
||||
$DefaultVault = [string]$ProviderSettings.DefaultVault
|
||||
}
|
||||
|
||||
if([string]::IsNullOrWhiteSpace($Vault)){
|
||||
$Vault = $DefaultVault
|
||||
}
|
||||
|
||||
$Vaults = @()
|
||||
$GetSecretVaultCommand = Get-Command -Name Get-SecretVault -ErrorAction SilentlyContinue
|
||||
if($null -ne $GetSecretVaultCommand){
|
||||
if([string]::IsNullOrWhiteSpace($Vault)){
|
||||
$Vaults = @(& $GetSecretVaultCommand)
|
||||
}else{
|
||||
$Vaults = @(& $GetSecretVaultCommand -Name $Vault -ErrorAction SilentlyContinue)
|
||||
}
|
||||
}
|
||||
|
||||
$TestResult = $null
|
||||
$TestError = $null
|
||||
if($TestVault -and -not [string]::IsNullOrWhiteSpace($Vault)){
|
||||
$TestSecretVaultCommand = Get-Command -Name Test-SecretVault -ErrorAction SilentlyContinue
|
||||
if($null -eq $TestSecretVaultCommand){
|
||||
$TestError = "Command [Test-SecretVault] was not found."
|
||||
}else{
|
||||
try {
|
||||
$TestResult = [bool](& $TestSecretVaultCommand -Name $Vault -ErrorAction Stop)
|
||||
}
|
||||
catch {
|
||||
$TestResult = $false
|
||||
$TestError = $_.Exception.Message
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[PSCustomObject]@{
|
||||
Provider = $Provider
|
||||
SettingsPath = $ResolvedSettingsPath
|
||||
SettingsExists = $SettingsExists
|
||||
DefaultVault = $DefaultVault
|
||||
RequestedVault = $Vault
|
||||
ProviderSettings = $ProviderSettings
|
||||
RegisteredVaults = $Vaults
|
||||
TestVault = $TestResult
|
||||
TestError = $TestError
|
||||
}
|
||||
}
|
||||
}
|
||||
243
Public/Register-DSCConfigurationDataCredentialProvider.ps1
Normal file
243
Public/Register-DSCConfigurationDataCredentialProvider.ps1
Normal file
@@ -0,0 +1,243 @@
|
||||
function Register-DSCConfigurationDataCredentialProvider {
|
||||
[CmdletBinding(SupportsShouldProcess=$true)]
|
||||
Param(
|
||||
[Parameter(Mandatory=$true, Position=1)]
|
||||
[string]
|
||||
$Vault,
|
||||
|
||||
[Parameter(Mandatory=$false)]
|
||||
[ValidateSet("Generic", "KeePass")]
|
||||
[string]
|
||||
$VaultType = "Generic",
|
||||
|
||||
[Parameter(Mandatory=$false)]
|
||||
[string]
|
||||
$DatabasePath,
|
||||
|
||||
[Parameter(Mandatory=$false)]
|
||||
[string]
|
||||
$KeyPath,
|
||||
|
||||
[Parameter(Mandatory=$false)]
|
||||
[string]
|
||||
$SettingsPath,
|
||||
|
||||
[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]
|
||||
$UseMasterPassword,
|
||||
|
||||
[Parameter(Mandatory=$false)]
|
||||
[Alias("MasterKey")]
|
||||
[System.Security.SecureString]
|
||||
$MasterPassword,
|
||||
|
||||
[Parameter(Mandatory=$false)]
|
||||
[Alias("MasterKeyPath")]
|
||||
[string]
|
||||
$MasterPasswordKeyPath,
|
||||
|
||||
[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){
|
||||
"SecretManagement" {
|
||||
if([string]::IsNullOrWhiteSpace($ModuleName)){
|
||||
if($VaultType -eq "KeePass"){
|
||||
$ModuleName = "SecretManagement.KeePass"
|
||||
}else{
|
||||
$ModuleName = "Microsoft.PowerShell.SecretStore"
|
||||
}
|
||||
}
|
||||
|
||||
$ShouldRegisterVault = $RegisterVault -or $PSBoundParameters.ContainsKey("ModuleName")
|
||||
|
||||
if($VaultType -eq "KeePass" -and $ShouldRegisterVault){
|
||||
if([string]::IsNullOrWhiteSpace($DatabasePath) -and -not $VaultParameters.ContainsKey("Path")){
|
||||
throw "SecretManagement KeePass vault registration requires [DatabasePath] or VaultParameters['Path']."
|
||||
}
|
||||
|
||||
if(-not [string]::IsNullOrWhiteSpace($DatabasePath)){
|
||||
$VaultParameters["Path"] = $DatabasePath
|
||||
}
|
||||
|
||||
if(-not [string]::IsNullOrWhiteSpace($KeyPath)){
|
||||
$VaultParameters["KeyPath"] = $KeyPath
|
||||
}
|
||||
|
||||
if($UseMasterPassword){
|
||||
$VaultParameters["UseMasterPassword"] = $true
|
||||
}
|
||||
}
|
||||
|
||||
if($ShouldRegisterVault){
|
||||
if($VaultType -eq "KeePass"){
|
||||
$KeePassModule = Get-Module -Name SecretManagement.KeePass -ListAvailable | Sort-Object -Property Version -Descending | Select-Object -First 1
|
||||
if($null -eq $KeePassModule){
|
||||
throw "SecretManagement KeePass vault registration requires module [SecretManagement.KeePass]."
|
||||
}
|
||||
|
||||
$ModuleReference = $KeePassModule.Name
|
||||
if(-not [string]::IsNullOrWhiteSpace($KeePassModule.Path)){
|
||||
$ModuleReference = $KeePassModule.Path
|
||||
}
|
||||
|
||||
$CommandParameters = @{
|
||||
Name = $Vault
|
||||
ModuleName = $ModuleReference
|
||||
VaultParameters = @{
|
||||
Path = $VaultParameters["Path"]
|
||||
UseMasterPassword = $false
|
||||
UseWindowsAccount = $false
|
||||
KeyPath = $null
|
||||
ShowFullTitle = $false
|
||||
ShowRecycleBin = $false
|
||||
}
|
||||
}
|
||||
|
||||
foreach($Key in @("KeyPath", "UseMasterPassword", "UseWindowsAccount", "ShowFullTitle", "ShowRecycleBin", "SkipValidate")){
|
||||
if($VaultParameters.ContainsKey($Key)){
|
||||
$CommandParameters.VaultParameters[$Key] = $VaultParameters[$Key]
|
||||
}
|
||||
}
|
||||
|
||||
if($DefaultVault){
|
||||
$CommandParameters["DefaultVault"] = $true
|
||||
}
|
||||
|
||||
if($AllowClobber){
|
||||
$CommandParameters["AllowClobber"] = $true
|
||||
}
|
||||
|
||||
if($PSCmdlet.ShouldProcess($Vault, "Register KeePass SecretManagement vault [$ModuleReference]")){
|
||||
Register-SecretVault @CommandParameters
|
||||
}
|
||||
}else{
|
||||
$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($VaultType -ne "Generic"){
|
||||
$ProviderSettings.SecretManagement["VaultType"] = $VaultType
|
||||
}
|
||||
|
||||
if($null -ne $MasterPassword){
|
||||
if([string]::IsNullOrWhiteSpace($MasterPasswordKeyPath)){
|
||||
throw "MasterPassword requires [MasterPasswordKeyPath]."
|
||||
}
|
||||
|
||||
$Key = New-ConfigurationDataAesKeyFile -Path $MasterPasswordKeyPath -Force:$Force
|
||||
$ProtectedValue = $MasterPassword | ConvertFrom-SecureString -Key $Key
|
||||
$ProviderSettings.SecretManagement["MasterPassword"] = [ordered]@{
|
||||
ProtectedValue = $ProtectedValue
|
||||
KeyPath = $MasterPasswordKeyPath
|
||||
}
|
||||
}
|
||||
|
||||
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."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,10 +3,37 @@ function Resolve-DSCConfigurationData {
|
||||
Param(
|
||||
[Parameter(Mandatory=$true, ValueFromPipeline=$true)]
|
||||
[System.Collections.Hashtable]
|
||||
$ConfigurationData
|
||||
$ConfigurationData,
|
||||
|
||||
[Parameter(Mandatory=$false)]
|
||||
[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($SkipSecrets){
|
||||
$ConfigurationData = Resolve-ConfigurationDataParameterSecrets -ConfigurationData $ConfigurationData -ProviderSettings $ProviderSettings -UseDummySecrets
|
||||
}else{
|
||||
Unlock-ConfigurationDataSecretManagementVault -ProviderSettings $ProviderSettings
|
||||
$ConfigurationData = Resolve-ConfigurationDataParameterSecrets -ConfigurationData $ConfigurationData -ProviderSettings $ProviderSettings
|
||||
}
|
||||
|
||||
$Context = New-ConfigurationDataResolutionContext -ConfigurationData $ConfigurationData
|
||||
return Resolve-ConfigurationDataValue -Value $ConfigurationData -Context $Context
|
||||
}
|
||||
|
||||
258
Public/Set-DSCConfigurationDataCredentialProvider.ps1
Normal file
258
Public/Set-DSCConfigurationDataCredentialProvider.ps1
Normal file
@@ -0,0 +1,258 @@
|
||||
function Set-DSCConfigurationDataCredentialProvider {
|
||||
[CmdletBinding(SupportsShouldProcess=$true)]
|
||||
Param(
|
||||
[Parameter(Mandatory=$false)]
|
||||
[string]
|
||||
$Vault,
|
||||
|
||||
[Parameter(Mandatory=$false)]
|
||||
[AllowEmptyString()]
|
||||
[ValidateSet("", "Generic", "KeePass")]
|
||||
[string]
|
||||
$VaultType = "",
|
||||
|
||||
[Parameter(Mandatory=$false)]
|
||||
[string]
|
||||
$DatabasePath,
|
||||
|
||||
[Parameter(Mandatory=$false)]
|
||||
[string]
|
||||
$KeyPath,
|
||||
|
||||
[Parameter(Mandatory=$false)]
|
||||
[switch]
|
||||
$DisableKeePassKeyFile,
|
||||
|
||||
[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)]
|
||||
[Alias("UseMasterKey")]
|
||||
[switch]
|
||||
$UseMasterPassword,
|
||||
|
||||
[Parameter(Mandatory=$false)]
|
||||
[Alias("NoMasterKey", "NoMasterPassword")]
|
||||
[switch]
|
||||
$DisableMasterPassword,
|
||||
|
||||
[Parameter(Mandatory=$false)]
|
||||
[Alias("MasterKey")]
|
||||
[System.Security.SecureString]
|
||||
$MasterPassword,
|
||||
|
||||
[Parameter(Mandatory=$false)]
|
||||
[Alias("MasterKeyPath")]
|
||||
[string]
|
||||
$MasterPasswordKeyPath,
|
||||
|
||||
[Parameter(Mandatory=$false)]
|
||||
[switch]
|
||||
$UseWindowsAccount,
|
||||
|
||||
[Parameter(Mandatory=$false)]
|
||||
[switch]
|
||||
$ShowFullTitle,
|
||||
|
||||
[Parameter(Mandatory=$false)]
|
||||
[switch]
|
||||
$ShowRecycleBin,
|
||||
|
||||
[Parameter(Mandatory=$false)]
|
||||
[switch]
|
||||
$SkipValidate,
|
||||
|
||||
[Parameter(Mandatory=$false)]
|
||||
[string]
|
||||
$SettingsPath,
|
||||
|
||||
[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 ', ')."
|
||||
}
|
||||
}
|
||||
|
||||
$ProviderInfo = Get-DSCConfigurationDataCredentialProvider -Provider $Provider -Vault $Vault -SettingsPath $SettingsPath
|
||||
if([string]::IsNullOrWhiteSpace($Vault)){
|
||||
$Vault = [string]$ProviderInfo.DefaultVault
|
||||
}
|
||||
|
||||
if([string]::IsNullOrWhiteSpace($Vault)){
|
||||
throw "Vault is required. Pass [Vault] or define [DefaultVault] in the provider settings file."
|
||||
}
|
||||
}
|
||||
|
||||
process {
|
||||
$ExistingVault = @($ProviderInfo.RegisteredVaults | Select-Object -First 1)
|
||||
$ExistingVaultParameters = @{}
|
||||
if($ExistingVault.Count -gt 0 -and $ExistingVault[0].PSObject.Properties["VaultParameters"] -and $ExistingVault[0].VaultParameters -is [System.Collections.IDictionary]){
|
||||
foreach($Key in $ExistingVault[0].VaultParameters.Keys){
|
||||
$ExistingVaultParameters[$Key] = $ExistingVault[0].VaultParameters[$Key]
|
||||
}
|
||||
}
|
||||
|
||||
$EffectiveVaultType = $VaultType
|
||||
if([string]::IsNullOrWhiteSpace($EffectiveVaultType) -and $ProviderInfo.ProviderSettings -is [System.Collections.IDictionary] -and $ProviderInfo.ProviderSettings.ContainsKey("VaultType")){
|
||||
$EffectiveVaultType = [string]$ProviderInfo.ProviderSettings.VaultType
|
||||
}
|
||||
|
||||
if([string]::IsNullOrWhiteSpace($EffectiveVaultType) -and $ExistingVault.Count -gt 0 -and $ExistingVault[0].PSObject.Properties["ModuleName"]){
|
||||
if([string]$ExistingVault[0].ModuleName -match "SecretManagement\.KeePass"){
|
||||
$EffectiveVaultType = "KeePass"
|
||||
}
|
||||
}
|
||||
|
||||
if([string]::IsNullOrWhiteSpace($EffectiveVaultType)){
|
||||
$EffectiveVaultType = "Generic"
|
||||
}
|
||||
|
||||
$EffectiveVaultParameters = @{}
|
||||
foreach($Key in $ExistingVaultParameters.Keys){
|
||||
$EffectiveVaultParameters[$Key] = $ExistingVaultParameters[$Key]
|
||||
}
|
||||
|
||||
foreach($Key in $VaultParameters.Keys){
|
||||
$EffectiveVaultParameters[$Key] = $VaultParameters[$Key]
|
||||
}
|
||||
|
||||
if(-not [string]::IsNullOrWhiteSpace($DatabasePath)){
|
||||
$EffectiveVaultParameters["Path"] = $DatabasePath
|
||||
}
|
||||
|
||||
if(-not [string]::IsNullOrWhiteSpace($KeyPath)){
|
||||
$EffectiveVaultParameters["KeyPath"] = $KeyPath
|
||||
}
|
||||
|
||||
if($DisableKeePassKeyFile){
|
||||
$EffectiveVaultParameters["KeyPath"] = $null
|
||||
}
|
||||
|
||||
if($UseMasterPassword -and $DisableMasterPassword){
|
||||
throw "Use either [UseMasterPassword] or [DisableMasterPassword], not both."
|
||||
}
|
||||
|
||||
foreach($SwitchName in @("UseMasterPassword", "UseWindowsAccount", "ShowFullTitle", "ShowRecycleBin", "SkipValidate")){
|
||||
if($PSBoundParameters.ContainsKey($SwitchName)){
|
||||
$EffectiveVaultParameters[$SwitchName] = [bool]$PSBoundParameters[$SwitchName]
|
||||
}
|
||||
}
|
||||
|
||||
if($DisableMasterPassword){
|
||||
$EffectiveVaultParameters["UseMasterPassword"] = $false
|
||||
}
|
||||
|
||||
if($PSCmdlet.ShouldProcess($Vault, "Update DSC configuration data credential provider settings")){
|
||||
$EffectiveMasterPasswordKeyPath = $MasterPasswordKeyPath
|
||||
if([string]::IsNullOrWhiteSpace($EffectiveMasterPasswordKeyPath) -and
|
||||
$ProviderInfo.ProviderSettings -is [System.Collections.IDictionary] -and
|
||||
$ProviderInfo.ProviderSettings.ContainsKey("MasterPassword") -and
|
||||
$ProviderInfo.ProviderSettings.MasterPassword -is [System.Collections.IDictionary] -and
|
||||
$ProviderInfo.ProviderSettings.MasterPassword.ContainsKey("KeyPath")){
|
||||
$EffectiveMasterPasswordKeyPath = [string]$ProviderInfo.ProviderSettings.MasterPassword.KeyPath
|
||||
}
|
||||
|
||||
$CommandParameters = @{
|
||||
Provider = $Provider
|
||||
Vault = $Vault
|
||||
VaultType = $EffectiveVaultType
|
||||
SettingsPath = $SettingsPath
|
||||
VaultParameters = $EffectiveVaultParameters
|
||||
Force = $Force
|
||||
PassThru = $true
|
||||
}
|
||||
|
||||
if(-not [string]::IsNullOrWhiteSpace($ModuleName)){
|
||||
$CommandParameters["ModuleName"] = $ModuleName
|
||||
}
|
||||
|
||||
if($RegisterVault){
|
||||
$CommandParameters["RegisterVault"] = $true
|
||||
}
|
||||
|
||||
if($DefaultVault){
|
||||
$CommandParameters["DefaultVault"] = $true
|
||||
}
|
||||
|
||||
if($AllowClobber){
|
||||
$CommandParameters["AllowClobber"] = $true
|
||||
}
|
||||
|
||||
if($null -ne $MasterPassword){
|
||||
$CommandParameters["MasterPassword"] = $MasterPassword
|
||||
}
|
||||
|
||||
if(-not [string]::IsNullOrWhiteSpace($EffectiveMasterPasswordKeyPath)){
|
||||
$CommandParameters["MasterPasswordKeyPath"] = $EffectiveMasterPasswordKeyPath
|
||||
}
|
||||
|
||||
$Result = Register-DSCConfigurationDataCredentialProvider @CommandParameters
|
||||
}
|
||||
|
||||
if($PassThru){
|
||||
if($null -ne $Result){
|
||||
return $Result
|
||||
}
|
||||
|
||||
return (Get-DSCConfigurationDataCredentialProvider -Provider $Provider -Vault $Vault -SettingsPath $SettingsPath)
|
||||
}
|
||||
}
|
||||
}
|
||||
100
Public/Unregister-DSCConfigurationDataCredentialProvider.ps1
Normal file
100
Public/Unregister-DSCConfigurationDataCredentialProvider.ps1
Normal file
@@ -0,0 +1,100 @@
|
||||
function Unregister-DSCConfigurationDataCredentialProvider {
|
||||
[CmdletBinding(SupportsShouldProcess=$true, ConfirmImpact="High")]
|
||||
Param(
|
||||
[Parameter(Mandatory=$true)]
|
||||
[string]
|
||||
$Vault,
|
||||
|
||||
[Parameter(Mandatory=$false)]
|
||||
[string]
|
||||
$SettingsPath,
|
||||
|
||||
[Parameter(Mandatory=$false)]
|
||||
[switch]
|
||||
$RemoveSettings,
|
||||
|
||||
[Parameter(Mandatory=$false)]
|
||||
[switch]
|
||||
$UnregisterVault,
|
||||
|
||||
[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
|
||||
UnregisteredVault = $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($UnregisterVault){
|
||||
if($PSCmdlet.ShouldProcess($Vault, "Unregister SecretManagement vault")){
|
||||
Unregister-SecretVault -Name $Vault
|
||||
$Result.UnregisteredVault = $true
|
||||
}
|
||||
}
|
||||
|
||||
if($PassThru){
|
||||
return [PSCustomObject]$Result
|
||||
}
|
||||
}
|
||||
}
|
||||
342
Readme.md
342
Readme.md
@@ -1,4 +1,4 @@
|
||||
# Resolve-DSCConfigurationData
|
||||
# Resolve-DSCConfigurationData
|
||||
|
||||
`Resolve-DSCConfigurationData` resolves parameter, variable, and expression references in DSC configuration data.
|
||||
|
||||
@@ -9,6 +9,13 @@ $merged = Merge-DSCConfigurationData -Template $service -Deployment $environment
|
||||
$resolved = Resolve-DSCConfigurationData -ConfigurationData $merged
|
||||
```
|
||||
|
||||
Pipeline flow:
|
||||
|
||||
```powershell
|
||||
$resolved = Merge-DSCConfigurationData -Template $service -Deployment $environment |
|
||||
Resolve-DSCConfigurationData
|
||||
```
|
||||
|
||||
Expressions use an ARM-like syntax:
|
||||
|
||||
```powershell
|
||||
@@ -47,12 +54,19 @@ This block shows all currently supported parameter properties.
|
||||
MinLength = 2
|
||||
MaxLength = 32
|
||||
|
||||
# Optional numeric range validation. Applies to numeric values such as Type = 'int'.
|
||||
MinValue = 1
|
||||
MaxValue = 65535
|
||||
|
||||
# Optional regex validation.
|
||||
Pattern = '^[A-Za-z][A-Za-z0-9_-]*$'
|
||||
|
||||
# Metadata for later reporting/output tooling. The resolver does not mask values yet.
|
||||
Sensitive = $false
|
||||
|
||||
# Prevents child templates from changing this parameter definition during merge.
|
||||
Sealed = $false
|
||||
|
||||
# Emits a warning when the parameter is present.
|
||||
Deprecated = @{
|
||||
Message = @{
|
||||
@@ -75,11 +89,277 @@ This block shows all currently supported parameter properties.
|
||||
Notes:
|
||||
|
||||
- `Value` wins over `DefaultValue`.
|
||||
- `Required`, `AllowedValues`, `MinLength`, `MaxLength`, and `Pattern` are validated by the resolver.
|
||||
- `Type`, `Required`, `AllowedValues`, `MinLength`, `MaxLength`, `MinValue`, `MaxValue`, and `Pattern` are validated by the resolver.
|
||||
- `AllowedValues` validates scalar values directly and array values item by item.
|
||||
- `Sensitive` is currently metadata only.
|
||||
- `Sealed = $true` on a parameter seals the whole parameter definition during merge. Child templates cannot change any property of that parameter.
|
||||
- `Deprecated.Message` can be a string or a localized hashtable.
|
||||
- Optional string parameters can allow empty values with a pattern like `'^$|^[A-Za-z][A-Za-z0-9_-]*$'`.
|
||||
|
||||
Supported parameter types:
|
||||
|
||||
- `string`
|
||||
- `int` / `integer`
|
||||
- `bool` / `boolean`
|
||||
- `array`
|
||||
- `hashtable` / `object`
|
||||
- `secureString`
|
||||
- `credential`
|
||||
|
||||
`secureString` and `credential` are intended for secret references, not raw secrets in PSD1 files:
|
||||
|
||||
```powershell
|
||||
FarmPassphrase = @{
|
||||
Type = 'secureString'
|
||||
Required = $true
|
||||
Sensitive = $true
|
||||
Value = @{
|
||||
Provider = 'SecretManagement'
|
||||
Vault = 'Contoso'
|
||||
Name = 'SharePoint/FarmPassphrase'
|
||||
}
|
||||
}
|
||||
|
||||
SetupCredential = @{
|
||||
Type = 'credential'
|
||||
Required = $true
|
||||
Sensitive = $true
|
||||
Value = @{
|
||||
Provider = 'SecretManagement'
|
||||
Vault = 'Contoso'
|
||||
Name = 'Application/SetupAccount'
|
||||
UserName = 'CONTOSO\svc-app-setup'
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Secret references are validated and resolved by `Resolve-DSCConfigurationData` before the normal parameter and variable resolve step:
|
||||
|
||||
```powershell
|
||||
$resolved = Resolve-DSCConfigurationData -ConfigurationData $merged -ProviderSettings @{
|
||||
SecretManagement = @{
|
||||
DefaultVault = 'Contoso'
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Use `-SkipSecrets` when you only want structural validation/resolution without loading provider secrets. Secret references are replaced with typed dummy values, so references such as `reference(..., 'UserName')` keep working in tests and previews.
|
||||
|
||||
```powershell
|
||||
$preview = Resolve-DSCConfigurationData -ConfigurationData $merged -SkipSecrets
|
||||
```
|
||||
|
||||
SecretManagement is the only built-in secret resolver provider. KeePass, SecretStore, Azure Key Vault, and other backends should be registered as SecretManagement vaults.
|
||||
|
||||
KeePass can be registered through the `SecretManagement.KeePass` vault extension:
|
||||
|
||||
```powershell
|
||||
Register-DSCConfigurationDataCredentialProvider `
|
||||
-Provider SecretManagement `
|
||||
-Vault Contoso `
|
||||
-VaultType KeePass `
|
||||
-DatabasePath 'C:\DSC\Contoso\Secrets.kdbx' `
|
||||
-KeyPath 'C:\DSC\Contoso\Secrets.key' `
|
||||
-UseMasterPassword `
|
||||
-RegisterVault `
|
||||
-DefaultVault `
|
||||
-SettingsPath 'C:\DSC\Contoso'
|
||||
```
|
||||
|
||||
For unattended KeePass vaults that still require a master password, store the master password as a protected SecureString in the provider settings. The password is encrypted with an AES key file and the vault is unlocked automatically during `Resolve-DSCConfigurationData`:
|
||||
|
||||
```powershell
|
||||
$masterPassword = Read-Host -Prompt 'KeePass Master Password' -AsSecureString
|
||||
|
||||
Register-DSCConfigurationDataCredentialProvider `
|
||||
-Provider SecretManagement `
|
||||
-Vault Contoso `
|
||||
-VaultType KeePass `
|
||||
-DatabasePath 'C:\DSC\Contoso\Secrets.kdbx' `
|
||||
-KeyPath 'C:\DSC\Contoso\Secrets.key' `
|
||||
-UseMasterPassword `
|
||||
-MasterPassword $masterPassword `
|
||||
-MasterPasswordKeyPath 'C:\DSC\Contoso\KeePass-MasterPassword.key' `
|
||||
-RegisterVault `
|
||||
-DefaultVault `
|
||||
-SettingsPath 'C:\DSC\Contoso'
|
||||
```
|
||||
|
||||
Generic SecretManagement vaults can be registered through the same entry point:
|
||||
|
||||
```powershell
|
||||
Register-DSCConfigurationDataCredentialProvider `
|
||||
-Provider SecretManagement `
|
||||
-Vault LocalStore `
|
||||
-ModuleName Microsoft.PowerShell.SecretStore `
|
||||
-RegisterVault `
|
||||
-DefaultVault `
|
||||
-SettingsPath 'C:\DSC\Contoso'
|
||||
```
|
||||
|
||||
Provider setup can be removed again:
|
||||
|
||||
```powershell
|
||||
Unregister-DSCConfigurationDataCredentialProvider `
|
||||
-Provider SecretManagement `
|
||||
-Vault Contoso `
|
||||
-SettingsPath 'C:\DSC\Contoso' `
|
||||
-UnregisterVault
|
||||
```
|
||||
|
||||
Then use it directly:
|
||||
|
||||
```powershell
|
||||
$resolved = Resolve-DSCConfigurationData `
|
||||
-ConfigurationData $merged `
|
||||
-ProviderSettingsPath 'C:\DSC\Contoso\ProviderSettings.SecretManagement.psd1'
|
||||
```
|
||||
|
||||
## Sealed Template Blocks
|
||||
|
||||
`Sealed = $true` can also be placed on any hashtable block in the configuration data. During merge, child templates cannot add or overwrite anything at that node or below it.
|
||||
|
||||
```powershell
|
||||
Resources = @{
|
||||
NonNodeData = @{
|
||||
Services = @{
|
||||
SharePoint = @{
|
||||
Farm = @{
|
||||
ManagedAccounts = @{
|
||||
Sealed = $true
|
||||
|
||||
FarmAccount = "[parameters('FarmCredential')]"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The `Sealed` marker is kept during merge so later merge steps can enforce it. `Resolve-DSCConfigurationData` removes the marker from the final resolved data so DSC resource loops do not see it as a normal configuration item.
|
||||
|
||||
Inspect the configured provider and registered vault:
|
||||
|
||||
```powershell
|
||||
Get-DSCConfigurationDataCredentialProvider `
|
||||
-Provider SecretManagement `
|
||||
-SettingsPath 'C:\DSC\Contoso' `
|
||||
-TestVault
|
||||
```
|
||||
|
||||
Update an existing KeePass-backed SecretManagement provider registration, for example to enable master-password based unlocks:
|
||||
|
||||
```powershell
|
||||
$masterPassword = Read-Host -Prompt 'KeePass Master Password' -AsSecureString
|
||||
|
||||
Set-DSCConfigurationDataCredentialProvider `
|
||||
-Provider SecretManagement `
|
||||
-SettingsPath 'C:\DSC\Contoso' `
|
||||
-UseMasterPassword `
|
||||
-MasterPassword $masterPassword `
|
||||
-MasterPasswordKeyPath 'C:\DSC\Contoso\KeePass-MasterPassword.key' `
|
||||
-VaultParameters @{
|
||||
ShowFullTitle = $true
|
||||
} `
|
||||
-RegisterVault `
|
||||
-AllowClobber `
|
||||
-Force
|
||||
```
|
||||
|
||||
### Secret Provider
|
||||
|
||||
Only one built-in provider is registered:
|
||||
|
||||
- `SecretManagement`: uses `Microsoft.PowerShell.SecretManagement` / `Get-Secret`
|
||||
|
||||
Backend-specific behavior belongs to the registered SecretManagement vault extension. For example, KeePass is handled by `SecretManagement.KeePass`, SecretStore by `Microsoft.PowerShell.SecretStore`, and Azure Key Vault by the matching SecretManagement vault extension.
|
||||
|
||||
SecretManagement example:
|
||||
|
||||
```powershell
|
||||
SetupCredential = @{
|
||||
Type = 'credential'
|
||||
Required = $true
|
||||
Sensitive = $true
|
||||
Value = @{
|
||||
Provider = 'SecretManagement'
|
||||
Vault = 'LocalStore'
|
||||
Name = 'SharePointSetupCredential'
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
SecretStore example through SecretManagement:
|
||||
|
||||
```powershell
|
||||
FarmPassphrase = @{
|
||||
Type = 'secureString'
|
||||
Required = $true
|
||||
Sensitive = $true
|
||||
Value = @{
|
||||
Provider = 'SecretManagement'
|
||||
Vault = 'LocalStore'
|
||||
Name = 'SharePointFarmPassphrase'
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Azure Key Vault example through SecretManagement:
|
||||
|
||||
```powershell
|
||||
SetupCredential = @{
|
||||
Type = 'credential'
|
||||
Required = $true
|
||||
Sensitive = $true
|
||||
Value = @{
|
||||
Provider = 'SecretManagement'
|
||||
Vault = 'contoso-kv'
|
||||
Name = 'app-setup-password'
|
||||
UserName = 'CONTOSO\svc-app-setup'
|
||||
}
|
||||
}
|
||||
|
||||
FarmPassphrase = @{
|
||||
Type = 'secureString'
|
||||
Required = $true
|
||||
Sensitive = $true
|
||||
Value = @{
|
||||
Provider = 'SecretManagement'
|
||||
Vault = 'contoso-kv'
|
||||
Name = 'farm-passphrase'
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Array values can be restricted item by item:
|
||||
|
||||
```powershell
|
||||
ServerRoles = @{
|
||||
Type = 'array'
|
||||
Value = @(
|
||||
'WebFrontEnd',
|
||||
'Application'
|
||||
)
|
||||
AllowedValues = @(
|
||||
'WebFrontEnd',
|
||||
'Application',
|
||||
'Search'
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
Numeric values can be restricted with `MinValue` and `MaxValue`:
|
||||
|
||||
```powershell
|
||||
SqlPort = @{
|
||||
Type = 'int'
|
||||
DefaultValue = 1433
|
||||
MinValue = 1
|
||||
MaxValue = 65535
|
||||
}
|
||||
```
|
||||
|
||||
## Variable Example
|
||||
|
||||
Variables may reference parameters and other variables. Nested variable references are supported. Circular references are rejected.
|
||||
@@ -93,7 +373,7 @@ Variables may reference parameters and other variables. Nested variable referenc
|
||||
}
|
||||
DomainLabel = @{
|
||||
Type = 'string'
|
||||
Value = 'LAN'
|
||||
Value = 'corp'
|
||||
}
|
||||
Landscape = @{
|
||||
Type = 'string'
|
||||
@@ -122,9 +402,9 @@ Example output:
|
||||
|
||||
```text
|
||||
StageCode : TST
|
||||
DatabasePrefix : SharePoint_LAN_TST
|
||||
ServiceDbPrefix : SharePoint_LAN_TST_Services
|
||||
ConfigDbName : SharePoint_LAN_TST_Farm_Config
|
||||
DatabasePrefix : SharePoint_corp_TST
|
||||
ServiceDbPrefix : SharePoint_corp_TST_Services
|
||||
ConfigDbName : SharePoint_corp_TST_Farm_Config
|
||||
```
|
||||
|
||||
## Functions
|
||||
@@ -134,12 +414,16 @@ ConfigDbName : SharePoint_LAN_TST_Farm_Config
|
||||
```powershell
|
||||
"[parameters('DatabasePrefix')]"
|
||||
"[variables('ServiceDbPrefix')]"
|
||||
"[reference('Resources.NonNodeData.Services.SharePoint.Farm.ManagedAccounts.FarmAccount')]"
|
||||
"[reference('Resources.NonNodeData.Services.SharePoint.Farm.ManagedAccounts.FarmAccount', 'UserName')]"
|
||||
```
|
||||
|
||||
`parameters(name)` returns the effective parameter value. `Value` is used before `DefaultValue`.
|
||||
|
||||
`variables(name)` resolves another variable. Variables may reference other variables.
|
||||
|
||||
`reference(path)` resolves another value from the configuration data by path. `reference(path, property)` resolves the value and then returns a property from it, such as `UserName` from a `PSCredential`.
|
||||
|
||||
### String Composition
|
||||
|
||||
```powershell
|
||||
@@ -149,12 +433,12 @@ ConfigDbName : SharePoint_LAN_TST_Farm_Config
|
||||
|
||||
```powershell
|
||||
"[format('{0}_{1}_{2}', parameters('DatabasePrefix'), parameters('DomainLabel'), parameters('Landscape'))]"
|
||||
# SharePoint_LAN_Test
|
||||
# SharePoint_corp_Test
|
||||
```
|
||||
|
||||
```powershell
|
||||
"[joinNotEmpty('_', parameters('DatabasePrefix'), parameters('DomainLabel'), '', 'Services')]"
|
||||
# SharePoint_LAN_Services
|
||||
# SharePoint_corp_Services
|
||||
```
|
||||
|
||||
```powershell
|
||||
@@ -185,13 +469,13 @@ ConfigDbName : SharePoint_LAN_TST_Farm_Config
|
||||
### Case And Text
|
||||
|
||||
```powershell
|
||||
"[toLower('BGW-LAN')]"
|
||||
# bgw-lan
|
||||
"[toLower('Contoso-CORP')]"
|
||||
# contoso-corp
|
||||
```
|
||||
|
||||
```powershell
|
||||
"[toUpper('bgw-lan')]"
|
||||
# BGW-LAN
|
||||
"[toUpper('contoso-corp')]"
|
||||
# CONTOSO-CORP
|
||||
```
|
||||
|
||||
```powershell
|
||||
@@ -201,8 +485,8 @@ ConfigDbName : SharePoint_LAN_TST_Farm_Config
|
||||
```
|
||||
|
||||
```powershell
|
||||
"[replace('BGW-LAN', '-', '_')]"
|
||||
# BGW_LAN
|
||||
"[replace('Contoso-CORP', '-', '_')]"
|
||||
# Contoso_CORP
|
||||
```
|
||||
|
||||
```powershell
|
||||
@@ -211,7 +495,7 @@ ConfigDbName : SharePoint_LAN_TST_Farm_Config
|
||||
```
|
||||
|
||||
```powershell
|
||||
"[indexOf('BGW', 1)]"
|
||||
"[indexOf('CON', 1)]"
|
||||
# G
|
||||
```
|
||||
|
||||
@@ -228,23 +512,23 @@ ConfigDbName : SharePoint_LAN_TST_Farm_Config
|
||||
### Name Cleanup
|
||||
|
||||
```powershell
|
||||
"[sanitizeName(' SharePoint LAN/Test DB ')]"
|
||||
# SharePoint_LAN_Test_DB
|
||||
"[sanitizeName(' SharePoint corp/Test DB ')]"
|
||||
# SharePoint_corp_Test_DB
|
||||
```
|
||||
|
||||
```powershell
|
||||
"[sanitizeName(' SharePoint LAN/Test DB ', '-')]"
|
||||
# SharePoint-LAN-Test-DB
|
||||
"[sanitizeName(' SharePoint corp/Test DB ', '-')]"
|
||||
# SharePoint-corp-Test-DB
|
||||
```
|
||||
|
||||
```powershell
|
||||
"[normalizeSeparator('__SharePoint___LAN_Test__', '_')]"
|
||||
# SharePoint_LAN_Test
|
||||
"[normalizeSeparator('__SharePoint___corp_Test__', '_')]"
|
||||
# SharePoint_corp_Test
|
||||
```
|
||||
|
||||
```powershell
|
||||
"[prefixIfNotEmpty('LAN', 'BGW-')]"
|
||||
# BGW-LAN
|
||||
"[prefixIfNotEmpty('corp', 'Contoso-')]"
|
||||
# Contoso-corp
|
||||
```
|
||||
|
||||
```powershell
|
||||
@@ -256,17 +540,17 @@ ConfigDbName : SharePoint_LAN_TST_Farm_Config
|
||||
|
||||
```powershell
|
||||
"[split(parameters('DomainFQDN'), '.')]"
|
||||
# @('bgw-online', 'de')
|
||||
# @('contoso', 'com')
|
||||
```
|
||||
|
||||
```powershell
|
||||
"[join(split(parameters('DomainFQDN'), '.'), '_')]"
|
||||
# bgw-online_de
|
||||
# contoso_com
|
||||
```
|
||||
|
||||
```powershell
|
||||
"[first(split(parameters('DomainFQDN'), '.'))]"
|
||||
# bgw-online
|
||||
# contoso
|
||||
```
|
||||
|
||||
```powershell
|
||||
@@ -276,7 +560,7 @@ ConfigDbName : SharePoint_LAN_TST_Farm_Config
|
||||
|
||||
```powershell
|
||||
"[take(split(parameters('DomainFQDN'), '.'), 1)]"
|
||||
# @('bgw-online')
|
||||
# @('contoso')
|
||||
```
|
||||
|
||||
```powershell
|
||||
@@ -307,7 +591,7 @@ ConfigDbName : SharePoint_LAN_TST_Farm_Config
|
||||
```
|
||||
|
||||
```powershell
|
||||
"[startsWith(parameters('DomainFQDN'), 'bgw')]"
|
||||
"[startsWith(parameters('DomainFQDN'), 'contoso')]"
|
||||
# True
|
||||
```
|
||||
|
||||
@@ -342,6 +626,8 @@ ConfigDbName : SharePoint_LAN_TST_Farm_Config
|
||||
|
||||
- `parameters(name)`
|
||||
- `variables(name)`
|
||||
- `reference(path)`
|
||||
- `reference(path, property)`
|
||||
- `concat(value1, value2, ...)`
|
||||
- `format(formatString, value1, value2, ...)`
|
||||
- `coalesce(value1, value2, ...)`
|
||||
|
||||
@@ -1,13 +1,17 @@
|
||||
@{
|
||||
@{
|
||||
RootModule = "Resolve-DSCConfigurationData.psm1"
|
||||
ModuleVersion = "1.0.0"
|
||||
ModuleVersion = "1.1.0"
|
||||
GUID = "1d6ba0d4-93d5-4b0f-94c7-bf10a30fe0e8"
|
||||
Author = "Torsten Brendgen"
|
||||
Copyright = "(c) Torsten Brendgen. All rights reserved."
|
||||
Description = "Resolves parameters, variables, and expressions in DSC configuration data."
|
||||
PowerShellVersion = "5.1"
|
||||
FunctionsToExport = @(
|
||||
"Resolve-DSCConfigurationData"
|
||||
"Get-DSCConfigurationDataCredentialProvider",
|
||||
"Resolve-DSCConfigurationData",
|
||||
"Register-DSCConfigurationDataCredentialProvider",
|
||||
"Set-DSCConfigurationDataCredentialProvider",
|
||||
"Unregister-DSCConfigurationDataCredentialProvider"
|
||||
)
|
||||
CmdletsToExport = @()
|
||||
VariablesToExport = @()
|
||||
|
||||
@@ -1,13 +1,22 @@
|
||||
$PrivatePath = Join-Path -Path $PSScriptRoot -ChildPath "Private"
|
||||
$ProviderPath = Join-Path -Path $PSScriptRoot -ChildPath "Providers"
|
||||
$PublicPath = Join-Path -Path $PSScriptRoot -ChildPath "Public"
|
||||
|
||||
$Private = @(Get-ChildItem -Path $PrivatePath -Filter "*.ps1" -File -ErrorAction Stop | Sort-Object -Property FullName)
|
||||
$Providers = @()
|
||||
if(Test-Path -Path $ProviderPath){
|
||||
$Providers = @(Get-ChildItem -Path $ProviderPath -Filter "Provider.*.ps1" -File -ErrorAction Stop | Sort-Object -Property FullName)
|
||||
}
|
||||
$Public = @(Get-ChildItem -Path $PublicPath -Filter "*.ps1" -File -ErrorAction Stop | Sort-Object -Property FullName)
|
||||
|
||||
foreach($File in @($Private + $Public)){
|
||||
foreach($File in @($Private + $Providers + $Public)){
|
||||
. $File.FullName
|
||||
}
|
||||
|
||||
Export-ModuleMember -Function @(
|
||||
"Resolve-DSCConfigurationData"
|
||||
"Get-DSCConfigurationDataCredentialProvider",
|
||||
"Resolve-DSCConfigurationData",
|
||||
"Register-DSCConfigurationDataCredentialProvider",
|
||||
"Set-DSCConfigurationDataCredentialProvider",
|
||||
"Unregister-DSCConfigurationDataCredentialProvider"
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user