Public/Update-SecretRotationAccountPassword.ps1
|
function Update-SecretRotationAccountPassword { <# .SYNOPSIS Generates a new password, applies it to a configured target's identity backend, and splits it via Shamir Secret Sharing. .DESCRIPTION Reads the named target from the Posh-SecretRotation config, generates a new random password at that target's configured entropy (Posh-SecretSharing's New-SecretSharingPassword), applies it to the target's backend (Active Directory, Entra ID, or a config-defined Custom handler script), and - only once that backend update actually succeeds - splits the new password into Shamir shares (Posh-SecretSharing's Split-SecretSharingSecret) per the target's configured quorum scheme. A password that was generated but never confirmed applied to the backend is never split or returned: Set-SecretRotationBackendPassword throws on any backend failure, and this cmdlet does not catch that exception before it reaches the caller - it only adds a log entry first. There would be nothing for the returned shares to reconstruct into a live credential otherwise. Supports -WhatIf/-Confirm (ConfirmImpact High) since this changes a real account's password on a real backend - unlike Posh-SecretSharing's pure-computation New-* cmdlets, this one has a real, external side effect. .PARAMETER Target Name of a target block under the config's 'targets' section (e.g. 'corp-ad-svcaccount1'). Validated against the live config at call time; throws with the list of available target names if not found. .PARAMETER BackendParameter Optional hashtable merged over the target's own 'backendConfig', caller's values winning on key collision. Lets a caller override or supply backend properties (e.g. a different 'identity') without editing the config file. .PARAMETER OutputPath Optional folder. When given, writes one file per returned share to this folder (see Export-SecretRotationShareFile) - never a single combined file, since that would defeat the point of splitting the password. .OUTPUTS PSCustomObject[]. The share objects returned by Split-SecretSharingSecret, unchanged. .EXAMPLE Update-SecretRotationAccountPassword -Target 'corp-ad-svcaccount1' Rotates the AD account's password and returns its Shamir shares to the pipeline. .EXAMPLE Update-SecretRotationAccountPassword -Target 'corp-ad-svcaccount1' -OutputPath 'C:\rotation-output' Same, and also writes one file per share under C:\rotation-output. .EXAMPLE Update-SecretRotationAccountPassword -Target 'corp-ad-svcaccount1' -BackendParameter @{ identity = 'svc-account2' } -WhatIf Shows what would happen for a different identity than the one in the config, without changing anything. #> [CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'High')] [OutputType([PSCustomObject])] param( [Parameter(Mandatory)] [string] $Target, [Parameter()] [hashtable] $BackendParameter = @{}, [Parameter()] [string] $OutputPath ) $config = Read-SecretRotationConfigFile $targetConfig = Get-SecretRotationTargetConfig -Config $config -TargetName $Target Write-SecretRotationLog -Config $config -Level Debug -CmdletName $MyInvocation.MyCommand.Name ` -TargetName $Target -Message 'Cmdlet invoked' -BoundParameters $PSBoundParameters # Merge -BackendParameter over the target's own backendConfig - caller's values win. $mergedBackendConfig = @{} if ($targetConfig.backendConfig) { foreach ($property in $targetConfig.backendConfig.PSObject.Properties) { $mergedBackendConfig[$property.Name] = $property.Value } } foreach ($key in $BackendParameter.Keys) { $mergedBackendConfig[$key] = $BackendParameter[$key] } $identifier = $mergedBackendConfig.identity if (-not $identifier) { throw "Target '$Target' has no 'identity' in its backendConfig (or -BackendParameter) - nothing to rotate." } if (-not $PSCmdlet.ShouldProcess($identifier, "Rotate password on backend '$($targetConfig.backend)' and split via Shamir Secret Sharing")) { return } $password = New-SecretSharingPassword -Entropy ([int]$targetConfig.entropy) $backendPassword = $password if ($targetConfig.passwordRepresentation -eq 'String') { $ptr = [System.Runtime.InteropServices.Marshal]::SecureStringToGlobalAllocUnicode($password) try { $backendPassword = [System.Runtime.InteropServices.Marshal]::PtrToStringUni($ptr) } finally { [System.Runtime.InteropServices.Marshal]::ZeroFreeGlobalAllocUnicode($ptr) } } try { Set-SecretRotationBackendPassword -Backend $targetConfig.backend -Identifier $identifier ` -Password $backendPassword -BackendConfig $mergedBackendConfig ` -CustomHandler $targetConfig.customHandler -ConfigDirectory (Split-Path -Path (Get-SecretRotationConfigPath) -Parent) } catch { Write-SecretRotationLog -Config $config -Level Error -CmdletName $MyInvocation.MyCommand.Name ` -TargetName $Target -Message "Backend password update failed: $_" throw } Write-SecretRotationLog -Config $config -Level Information -CmdletName $MyInvocation.MyCommand.Name ` -TargetName $Target -Message "Password rotated on backend '$($targetConfig.backend)' for identity '$identifier'" $groupSplat = @{} if ($targetConfig.shamir.groupThreshold) { $groupSplat.GroupThreshold = [int]$targetConfig.shamir.groupThreshold } $groups = @() foreach ($groupConfig in $targetConfig.shamir.groups) { $groups += @{ Threshold = [int]$groupConfig.threshold; Count = [int]$groupConfig.count } } $shares = Split-SecretSharingSecret -Secret $password -Group $groups @groupSplat Write-SecretRotationLog -Config $config -Level Information -CmdletName $MyInvocation.MyCommand.Name ` -TargetName $Target -Message "Password split into $(@($shares).Count) share(s)" if ($OutputPath) { $shares | Export-SecretRotationShareFile -OutputPath $OutputPath | Out-Null } $shares } |