Private/Backend/Invoke-SecretRotationCustomHandler.ps1
|
function Invoke-SecretRotationCustomHandler { <# .SYNOPSIS Loads and invokes a config-defined custom backend script to set a password. .DESCRIPTION Resolves customHandler.scriptPath relative to the live config file's own directory (not the module root, not the current working directory) - so a custom handler ships alongside the config that references it rather than inside the versioned module - dot-sources it, verifies it actually defined a function named customHandler.functionName (a silent no-op here would be far worse than a loud failure for a security-relevant script), and calls that function with a fixed contract: -Identifier, -Password, -BackendConfig. The dot-sourced script is confined to this function's own call-local scope: dot-sourcing (the leading '.') runs in the *current* scope rather than a child scope, and since this statement lives inside Invoke-SecretRotationCustomHandler's own function body, "current scope" is this call's local scope - created fresh per call and discarded when it returns. Nothing from the handler script lingers in, or pollutes, the module's own scope, so two different custom targets can define same-named functions in different scripts without colliding. A custom handler signals failure only by throwing - a normal return (no exception) means success, matching the failure model Set-ADAccountPassword and Update-MgUser both already use. No boolean/status return value is inspected. .PARAMETER Identifier The account identity on the custom backend (target.backendConfig.identity, post -BackendParameter merge). .PARAMETER Password The new password, as SecureString or String per the target's passwordRepresentation - passed through to the custom handler function unchanged and untyped (unlike the AD/EntraID backends, a custom handler's own required type is declared by the target's config, not fixed by this function). .PARAMETER BackendConfig The target's full merged backendConfig hashtable, passed through unchanged. .PARAMETER CustomHandler The target's 'customHandler' section: an object with 'scriptPath' and 'functionName'. .PARAMETER ConfigDirectory Directory the live config file lives in - customHandler.scriptPath is resolved relative to this when it isn't already an absolute path. .EXAMPLE Invoke-SecretRotationCustomHandler -Identifier 'vault-secret-1' -Password $securePassword -BackendConfig @{} -CustomHandler $target.customHandler -ConfigDirectory (Split-Path (Get-SecretRotationConfigPath) -Parent) #> [CmdletBinding()] [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '', Justification = 'ShouldProcess confirmation happens once at the public cmdlet boundary (Update-SecretRotationAccountPassword); this private helper is only ever called after that confirmation already succeeded and must not prompt again.')] [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingPlainTextForPassword', 'Password', Justification = 'Deliberately untyped: passed through as-is to the custom handler function, whose own required type (SecureString or String) is declared by the target''s passwordRepresentation config, not fixed by this functions')] param( [Parameter(Mandatory)] [string] $Identifier, [Parameter(Mandatory)] [AllowNull()] $Password, [Parameter(Mandatory)] [hashtable] $BackendConfig, [Parameter(Mandatory)] [PSCustomObject] $CustomHandler, [Parameter(Mandatory)] [string] $ConfigDirectory ) if (-not $CustomHandler.scriptPath) { throw "Target backend 'Custom' requires customHandler.scriptPath in its config." } if (-not $CustomHandler.functionName) { throw "Target backend 'Custom' requires customHandler.functionName in its config." } $scriptPath = $CustomHandler.scriptPath if (-not [System.IO.Path]::IsPathRooted($scriptPath)) { $scriptPath = Join-Path -Path $ConfigDirectory -ChildPath $scriptPath } if (-not (Test-Path -Path $scriptPath -PathType Leaf)) { throw "Custom backend handler script '$scriptPath' (customHandler.scriptPath) was not found." } try { . $scriptPath } catch { throw "Failed to load custom backend handler script '$scriptPath': $_" } $functionName = $CustomHandler.functionName if (-not (Get-Command -Name $functionName -CommandType Function -ErrorAction SilentlyContinue)) { throw "Custom backend handler script '$scriptPath' did not define a function named '$functionName' (customHandler.functionName)." } & $functionName -Identifier $Identifier -Password $Password -BackendConfig $BackendConfig } |