Functions/Remove-SshAccess.ps1
|
<#
.SYNOPSIS Revokes an SSH key: removes its public key from a server's authorized_keys, and optionally removes the local private key and ~/.ssh/config alias. .DESCRIPTION SSH keys have no native expiry; a key is valid while its public key sits in the server's authorized_keys. This cmdlet removes the matching line (matched by base64 blob or by SHA256 fingerprint), with a backup, an atomic write, and a lockout guard (it refuses to leave zero keys unless -Force). Use it to retire a key or to complete a rotation (New-SshAccess for the new key, verify, then Remove-SshAccess for the old one). .PARAMETER Server ssh config alias of the target. HostName/User/Port are resolved from ~/.ssh/config. Alternatively pass -HostName and -User explicitly. .PARAMETER HostName Server IP/DNS (when not using -Server). .PARAMETER User Target account whose authorized_keys is edited (when not using -Server). .PARAMETER Port SSH port (default 22). .PARAMETER PublicKey Path to (or content of) the public key to revoke. Matched by its base64 blob. .PARAMETER Fingerprint SHA256 fingerprint of the key to revoke (e.g. SHA256:...). Alternative to -PublicKey. .PARAMETER BootstrapUser Account used to connect (defaults to the target user). Use your own account + -Sudo to edit a service account's authorized_keys. .PARAMETER Sudo Edit the target user's authorized_keys via sudo. Implied when BootstrapUser differs. .PARAMETER BootstrapIdentityFile Private key used to authenticate the revoke connection (passed to ssh as -i). Use it to bootstrap WITHOUT ssh-agent - e.g. revoke the old key while authenticating with it (still valid until removed). When omitted, ssh uses its default auth (agent/password). .PARAMETER RemoveLocal Also remove the local private/public key files and the ~/.ssh/config alias. .PARAMETER Force Allow removing the last remaining key (lockout override). .EXAMPLE Remove-SshAccess -HostName 192.168.10.18 -User deploy -PublicKey C:\old\publickey -BootstrapUser me -Sudo #> function Remove-SshAccess { [CmdletBinding()] param( [string]$Server, [string]$HostName, [string]$User, [int]$Port = 22, [string]$PublicKey, [string]$Fingerprint, [string]$BootstrapUser, [string]$BootstrapIdentityFile, [switch]$Sudo, [switch]$RemoveLocal, [switch]$Force ) $ErrorActionPreference = 'Stop' . "$PSScriptRoot/../Private/SshHelpers.ps1" . "$PSScriptRoot/../Private/PublishHelpers.ps1" . "$PSScriptRoot/../Private/Read-SSHConfig.ps1" # Validate target. if (-not $Server -and -not ($HostName -and $User)) { throw "Specify -Server (an ssh config alias) or both -HostName and -User." } # Validate key identification. if (-not $PublicKey -and -not $Fingerprint) { throw "Specify the key to revoke with -PublicKey or -Fingerprint." } if ($PublicKey -and $Fingerprint) { throw "Use only one of -PublicKey or -Fingerprint." } # Resolve connection details. $keyPathLocal = $null if ($Server) { $cfg = Read-SSHConfig -HostAlias $Server if (-not $HostName) { $HostName = $cfg.HostName } if (-not $User) { $User = $cfg.User } if ($cfg.Port) { $Port = [int]$cfg.Port } $keyPathLocal = $cfg.IdentityFile } # Determine match mode/value. if ($PublicKey) { $pubContent = if (Test-Path $PublicKey) { (Get-Content $PublicKey -Raw).Trim() } else { $PublicKey.Trim() } $matchMode = 'blob' $matchValue = Get-SshPublicKeyBlob -PublicKey $pubContent } else { $matchMode = 'fingerprint' $matchValue = $Fingerprint } $bootstrap = if ($BootstrapUser) { $BootstrapUser } else { $User } $useSudo = ($Sudo -or ($bootstrap -ne $User)) Write-Host "" Write-Host " Remove-SshAccess - $User@$HostName (match: $matchMode)" -ForegroundColor Cyan $script = Get-BashScript -ScriptName 'Remove-AuthorizedKey.sh' -Placeholders @{ '__TARGET_USER__' = $User '__MATCH_MODE__' = $matchMode '__MATCH_VALUE__' = $matchValue '__USE_SUDO__' = ($(if ($useSudo) { '1' } else { '0' })) '__FORCE__' = ($(if ($Force) { '1' } else { '0' })) } Invoke-RemoteBash -ScriptContent $script -User $bootstrap -HostName $HostName -Port $Port -IdentityFile $BootstrapIdentityFile -Tty:$useSudo -Prefix 'macss_revoke_' $rc = $LASTEXITCODE if ($rc -ne 0) { throw "Key revocation failed (exit $rc). Nothing was left in a partial state (backup kept on server)." } Write-Host " Revoked on $HostName (backup kept as authorized_keys.bak.*)" -ForegroundColor Green # Optional local cleanup. if ($RemoveLocal) { $sshDir = Get-SshUserDir $configPath = Join-Path $sshDir 'config' if ($Server) { if (Remove-SshConfigHost -ConfigPath $configPath -Alias $Server) { Write-Host " Removed Host '$Server' from ssh config" -ForegroundColor DarkGray } } $localKey = if ($keyPathLocal) { $keyPathLocal } elseif ($PublicKey -and (Test-Path $PublicKey)) { ($PublicKey -replace '\.pub$', '') } else { $null } if ($localKey -and (Test-Path $localKey)) { Remove-Item -LiteralPath $localKey, "$localKey.pub" -Force -ErrorAction SilentlyContinue Write-Host " Removed local key files: $localKey(.pub)" -ForegroundColor DarkGray } } return [pscustomobject]@{ HostName = $HostName User = $User MatchMode = $matchMode Revoked = $true } } |