Private/Secrets.ps1
function Initialize-PodeSecretVault { param( [Parameter(Mandatory = $true, Position = 0, ValueFromPipeline = $true)] [hashtable] $VaultConfig, [Parameter(Mandatory = $true)] [scriptblock] $ScriptBlock ) process { $null = Invoke-PodeScriptBlock -ScriptBlock $ScriptBlock -Splat -Arguments @($VaultConfig.Parameters) } } function Register-PodeSecretManagementVault { param( [Parameter(Mandatory = $true, Position = 0, ValueFromPipeline = $true)] [hashtable] $VaultConfig, [Parameter()] [string] $VaultName, [Parameter(Mandatory = $true)] [string] $ModuleName ) begin { $pipelineItemCount = 0 } process { $pipelineItemCount++ } end { if ($pipelineItemCount -gt 1) { throw ($PodeLocale.fnDoesNotAcceptArrayAsPipelineInputExceptionMessage -f $($MyInvocation.MyCommand.Name)) } # use the Name for VaultName if not passed if ([string]::IsNullOrWhiteSpace($VaultName)) { $VaultName = $VaultConfig.Name } # import the modules $null = Import-Module -Name Microsoft.PowerShell.SecretManagement -Force -DisableNameChecking -Scope Global -ErrorAction Stop -Verbose:$false $null = Import-Module -Name $ModuleName -Force -DisableNameChecking -Scope Global -ErrorAction Stop -Verbose:$false # export the modules for pode Export-PodeModule -Name @('Microsoft.PowerShell.SecretManagement', $ModuleName) # is this the local SecretStore provider? $isSecretStore = ($ModuleName -ieq 'Microsoft.PowerShell.SecretStore') # check if we have an unlock password for local secret store if ($isSecretStore) { if ([string]::IsNullOrEmpty($VaultConfig.Unlock.Secret)) { # An 'UnlockSecret' is required when using Microsoft.PowerShell.SecretStore throw ($PodeLocale.unlockSecretRequiredExceptionMessage) } } # does the local secret store already exist? $secretStoreExists = ($isSecretStore -and (Test-PodeSecretVaultInternal -Name $VaultName)) # do we have vault params? $hasVaultParams = ($null -ne $VaultConfig.Parameters) # attempt to register the vault $registerParams = @{ Name = $VaultName ModuleName = $ModuleName Confirm = $false AllowClobber = $true ErrorAction = 'Stop' } if (!$isSecretStore -and $hasVaultParams) { $registerParams['VaultParameters'] = $VaultConfig.Parameters } $null = Register-SecretVault @registerParams # all is good, so set the config $VaultConfig['SecretManagement'] = @{ VaultName = $VaultName ModuleName = $ModuleName } # set local secret store config if ($isSecretStore) { if (!$hasVaultParams) { $VaultConfig.Parameters = @{} } $vaultParams = $VaultConfig.Parameters # remove the password $vaultParams.Remove('Password') # set default authentication and interaction flags if ([string]::IsNullOrEmpty($vaultParams.Authentication)) { $vaultParams['Authentication'] = 'Password' } if ([string]::IsNullOrEmpty($vaultParams.Interaction)) { $vaultParams['Interaction'] = 'None' } # set default password timeout and unlock interval to 1 minute if ($VaultConfig.Unlock.Interval -le 0) { $VaultConfig.Unlock.Interval = 1 } # unlock the vault, and set password $VaultConfig | Unlock-PodeSecretManagementVault # set the password timeout for the vault if (!$secretStoreExists) { if ($VaultConfig.Parameters.PasswordTimeout -le 0) { $vaultParams['PasswordTimeout'] = ($VaultConfig.Unlock.Interval * 60) + 10 } } # set config $null = Set-SecretStoreConfiguration @vaultParams -Confirm:$false -ErrorAction Stop } } } function Register-PodeSecretCustomVault { param( [Parameter(Mandatory = $true, Position = 0, ValueFromPipeline = $true)] [hashtable] $VaultConfig, [Parameter(Mandatory = $true)] [scriptblock] $ScriptBlock, [Parameter()] [scriptblock] $UnlockScriptBlock, [Parameter()] [scriptblock] $RemoveScriptBlock, [Parameter()] [scriptblock] $SetScriptBlock, [Parameter()] [scriptblock] $UnregisterScriptBlock ) process { # unlock secret with no script? if ($VaultConfig.Unlock.Enabled -and (Test-PodeIsEmpty $UnlockScriptBlock)) { # Unlock secret supplied for custom Secret Vault type, but not Unlock ScriptBlock supplied throw ($PodeLocale.unlockSecretButNoScriptBlockExceptionMessage) } # all is good, so set the config $VaultConfig['Custom'] = @{ Read = $ScriptBlock Unlock = $UnlockScriptBlock Remove = $RemoveScriptBlock Set = $SetScriptBlock Unregister = $UnregisterScriptBlock } } } function Unlock-PodeSecretManagementVault { param( [Parameter(Mandatory = $true, Position = 0, ValueFromPipeline = $true)] [hashtable] $VaultConfig ) process { # do we need to unlock the vault? if (!$VaultConfig.Unlock.Enabled) { return $null } # unlock the vault $null = Unlock-SecretVault -Name $VaultConfig.SecretManagement.VaultName -Password $VaultConfig.Unlock.Secret -ErrorAction Stop # interval? if ($VaultConfig.Unlock.Interval -gt 0) { return ([datetime]::UtcNow.AddMinutes($VaultConfig.Unlock.Interval)) } return $null } } function Unlock-PodeSecretCustomVault { param( [Parameter(Mandatory = $true, Position = 0, ValueFromPipeline = $true)] [hashtable] $VaultConfig ) process { # do we need to unlock the vault? if (!$VaultConfig.Unlock.Enabled) { return } # do we have an unlock scriptblock if ($null -eq $VaultConfig.Custom.Unlock) { # No Unlock ScriptBlock supplied for unlocking the vault '$($VaultConfig.Name)' throw ($PodeLocale.noUnlockScriptBlockForVaultExceptionMessage -f $VaultConfig.Name) } # unlock the vault, and get back an expiry $expiry = Invoke-PodeScriptBlock -ScriptBlock $VaultConfig.Custom.Unlock -Splat -Return -Arguments @( $VaultConfig.Parameters, (ConvertFrom-SecureString -SecureString $VaultConfig.Unlock.Secret -AsPlainText) ) # return expiry if given, otherwise check interval if ($null -ne $expiry) { return $expiry } if ($VaultConfig.Unlock.Interval -gt 0) { return ([datetime]::UtcNow.AddMinutes($VaultConfig.Unlock.Interval)) } return $null } } function Unregister-PodeSecretManagementVault { param( [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [hashtable] $VaultConfig ) process { # do we need to unregister the vault? if ($VaultConfig.AutoImported) { return } # unregister the vault $null = Unregister-SecretVault -Name $VaultConfig.SecretManagement.VaultName -Confirm:$false -ErrorAction Stop } } function Unregister-PodeSecretCustomVault { param( [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [hashtable] $VaultConfig ) process { # do we need to unregister the vault? if ($VaultConfig.AutoImported) { return } # do we have an unregister scriptblock? if not, just do nothing if ($null -eq $VaultConfig.Custom.Unregister) { return } # unregister the vault $null = Invoke-PodeScriptBlock -ScriptBlock $VaultConfig.Custom.Unregister -Splat -Arguments @( $VaultConfig.Parameters ) } } function Get-PodeSecretManagementKey { param( [Parameter(Mandatory = $true)] [string] $Vault, [Parameter(Mandatory = $true)] [string] $Key ) # get the vault $_vault = $PodeContext.Server.Secrets.Vaults[$Vault] # fetch the secret return (Get-Secret -Name $Key -Vault $_vault.SecretManagement.VaultName -AsPlainText -ErrorAction Stop) } function Get-PodeSecretCustomKey { param( [Parameter(Mandatory = $true)] [string] $Vault, [Parameter(Mandatory = $true)] [string] $Key, [Parameter()] [object[]] $ArgumentList ) # get the vault $_vault = $PodeContext.Server.Secrets.Vaults[$Vault] # fetch the secret return Invoke-PodeScriptBlock -ScriptBlock $_vault.Custom.Read -Splat -Return -Arguments (@( $_vault.Parameters, $Key ) + $ArgumentList) } function Set-PodeSecretManagementKey { param( [Parameter(Mandatory = $true)] [string] $Vault, [Parameter(Mandatory = $true)] [string] $Key, [Parameter(Mandatory = $true)] [object] $Value, [Parameter()] [hashtable] $Metadata ) # get the vault $_vault = $PodeContext.Server.Secrets.Vaults[$Vault] # set the secret $null = Set-Secret -Name $Key -Secret $Value -Vault $_vault.SecretManagement.VaultName -Metadata $Metadata -Confirm:$false -ErrorAction Stop } function Set-PodeSecretCustomKey { param( [Parameter(Mandatory = $true)] [string] $Vault, [Parameter(Mandatory = $true)] [string] $Key, [Parameter(Mandatory = $true)] [object] $Value, [Parameter()] [hashtable] $Metadata, [Parameter()] [object[]] $ArgumentList ) # get the vault $_vault = $PodeContext.Server.Secrets.Vaults[$Vault] # do we have a set scriptblock? if ($null -eq $_vault.Custom.Set) { throw ($PodeLocale.noSetScriptBlockForVaultExceptionMessage -f $_vault.Name) #"No Set ScriptBlock supplied for updating/creating secrets in the vault '$($_vault.Name)'" } # set the secret $null = Invoke-PodeScriptBlock -ScriptBlock $_vault.Custom.Set -Splat -Arguments (@( $_vault.Parameters, $Key, $Value, $Metadata ) + $ArgumentList) } function Remove-PodeSecretManagementKey { param( [Parameter(Mandatory = $true)] [string] $Vault, [Parameter(Mandatory = $true)] [string] $Key ) # get the vault $_vault = $PodeContext.Server.Secrets.Vaults[$Vault] # remove the secret $null = Remove-Secret -Name $Key -Vault $_vault.SecretManagement.VaultName -Confirm:$false -ErrorAction Stop } function Remove-PodeSecretCustomKey { param( [Parameter(Mandatory = $true)] [string] $Vault, [Parameter(Mandatory = $true)] [string] $Key, [Parameter()] [object[]] $ArgumentList ) # get the vault $_vault = $PodeContext.Server.Secrets.Vaults[$Vault] # do we have a remove scriptblock? if ($null -eq $_vault.Custom.Remove) { throw ($PodeLocale.noRemoveScriptBlockForVaultExceptionMessage -f $_vault.Name) #"No Remove ScriptBlock supplied for removing secrets from the vault '$($_vault.Name)'" } # remove the secret $null = Invoke-PodeScriptBlock -ScriptBlock $_vault.Custom.Remove -Splat -Arguments (@( $_vault.Parameters, $Key ) + $ArgumentList) } function Start-PodeSecretCacheHousekeeper { if (Test-PodeTimer -Name '__pode_secrets_cache_expiry__') { return } Add-PodeTimer -Name '__pode_secrets_cache_expiry__' -Interval 60 -ScriptBlock { $now = [datetime]::UtcNow foreach ($key in $PodeContext.Server.Secrets.Keys.Values) { if (!$key.Cache.Enabled -or ($null -eq $key.Cache.Expiry) -or ($key.Cache.Expiry -gt $now)) { continue } $key.Cache.Expiry = $null $key.Cache.Value = $null } } } function Start-PodeSecretVaultUnlocker { if (Test-PodeTimer -Name '__pode_secrets_vault_unlock__') { return } Add-PodeTimer -Name '__pode_secrets_vault_unlock__' -Interval 60 -ScriptBlock { $now = [datetime]::UtcNow foreach ($vault in $PodeContext.Server.Secrets.Vaults.Values) { if (!$vault.Unlock.Enabled -or ($null -eq $vault.Unlock.Expiry) -or ($vault.Unlock.Expiry -gt $now)) { continue } Unlock-PodeSecretVault -Name $vault.Name } } } <# .SYNOPSIS Unregisters multiple secret vaults within Pode. .DESCRIPTION The `Unregister-PodeSecretVaultsInternal` function iterates through the list of secret vaults stored in the PodeContext and unregisters each one. If an error occurs during unregistration, it can either throw an exception or log the error. .PARAMETER ThrowError If specified, the function will throw an exception when an error occurs during unregistration. Otherwise, it will log the error and continue processing. .INPUTS None. You cannot pipe objects to Unregister-PodeSecretVaultsInternal. .OUTPUTS None. The function modifies the state of secret vaults in the PodeContext. .EXAMPLE # Example usage: Unregister-PodeSecretVaultsInternal -ThrowError # All registered secret vaults are unregistered, and any errors are thrown as exceptions. .NOTES This is an internal function and may change in future releases of Pode. #> function Unregister-PodeSecretVaultsInternal { [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseSingularNouns', '')] param( [switch] $ThrowError ) # Check if there are any secret vaults to unregister if (Test-PodeIsEmpty $PodeContext.Server.Secrets.Vaults) { return } # Iterate through each vault and attempt unregistration foreach ($vault in $PodeContext.Server.Secrets.Vaults.Values.Name) { if ([string]::IsNullOrEmpty($vault)) { continue } try { Unregister-PodeSecretVault -Name $vault } catch { if ($ThrowError) { throw } else { $_ | Write-PodeErrorLog } } } } function Protect-PodeSecretValueType { param( [Parameter(Mandatory = $true)] [object] $Value ) if ($Value -is [System.ValueType]) { $Value = $Value.ToString() } if ([string]::IsNullOrEmpty($Value)) { $Value = [string]::Empty } if ($Value -is [System.Collections.Specialized.OrderedDictionary]) { $Value = [hashtable]$Value } if (!( ($Value -is [string]) -or ($Value -is [securestring]) -or ($Value -is [hashtable]) -or ($Value -is [byte[]]) -or ($Value -is [pscredential]) -or ($Value -is [System.Management.Automation.OrderedHashtable]) )) { throw ($PodeLocale.invalidSecretValueTypeExceptionMessage -f $Value.GetType().Name) #"Value to set secret to is of an invalid type. Expected either String, SecureString, HashTable, Byte[], or PSCredential. But got: $($Value.GetType().Name)" } return $Value } function Test-PodeSecretVaultInternal { param( [Parameter(Mandatory = $true)] [string] $Name ) return ($null -ne (Get-SecretVault -Name $Name -ErrorAction Ignore)) } |