Public/Import-SshId.ps1
|
function Import-SshId { <# .SYNOPSIS Imports SSH public keys published by an online identity into a local authorized_keys file, PowerShell-native re-implementation of the classic `ssh-import-id` tool. .DESCRIPTION Fetches public keys published at a well-known URL for a given '<prefix>:<username>' identity (built-in: gh, gl, lp - see Get-SshImportIdSource for custom prefixes) and appends any keys not already present to the target authorized_keys file. Each imported key is tagged with a trailing comment identifying its source, and duplicate keys already on file are skipped. After a successful import, NTFS permissions on the authorized_keys file and its parent .ssh directory are locked down so the Windows OpenSSH server will accept the file. .PARAMETER UserId One or more identities in '<prefix>:<username>' form, e.g. 'gh:octocat', 'gl:someone', 'lp:someone'. .PARAMETER AuthorizedKeysPath Path to the authorized_keys file to update. Defaults to '$HOME\.ssh\authorized_keys', or to the Windows OpenSSH administrators_authorized_keys path when -ForAdministrators is set. .PARAMETER ForAdministrators Target Windows OpenSSH's shared '%ProgramData%\ssh\administrators_authorized_keys' file instead of the per-user authorized_keys file. On Windows, sshd routes members of the local Administrators group to this file regardless of their own '.ssh\authorized_keys', and requires it to grant access to only SYSTEM and Administrators (not even the admin's own account may hold an explicit ACE). Writing to it requires an elevated (Run as Administrator) session, and the '%ProgramData%\ssh' directory must already exist (created by installing the OpenSSH Server Windows feature) - this command will not create it, nor will it touch that directory's own permissions. .PARAMETER PassThru Emit a result object per identity describing how many keys were added/skipped. .EXAMPLE Import-SshId gh:octocat Imports all public keys published on octocat's GitHub profile. .EXAMPLE 'gh:octocat', 'lp:someone' | Import-SshId -Verbose Imports keys for multiple identities piped in, with verbose logging. .EXAMPLE ssh-import-id gh:octocat -WhatIf Uses the ssh-import-id alias and previews the change without writing. .EXAMPLE Import-SshId gh:octocat -ForAdministrators Imports into '%ProgramData%\ssh\administrators_authorized_keys' for an account that logs in as a member of Administrators. Must be run elevated. #> [CmdletBinding(SupportsShouldProcess)] [OutputType([pscustomobject])] param( [Parameter(Mandatory, Position = 0, ValueFromPipeline, ValueFromPipelineByPropertyName)] [Alias('Id')] [string[]] $UserId, [string] $AuthorizedKeysPath, [switch] $ForAdministrators, [switch] $PassThru ) begin { if (-not $PSBoundParameters.ContainsKey('AuthorizedKeysPath')) { $AuthorizedKeysPath = Get-SshImportIdDefaultAuthorizedKeysPath -ForAdministrators:$ForAdministrators } } process { foreach ($uid in $UserId) { try { $resolved = Resolve-SshImportIdUrl -UserId $uid if (-not $PSCmdlet.ShouldProcess($AuthorizedKeysPath, "Import SSH keys for '$uid'")) { continue } $isAdminKeyFile = Test-SshImportIdAdministratorsAuthorizedKeysPath -Path $AuthorizedKeysPath if ($isAdminKeyFile) { if (-not (Test-SshImportIdElevated)) { throw "Writing to '$AuthorizedKeysPath' requires an elevated (Run as Administrator) PowerShell session." } $parentDir = Split-Path -Parent $AuthorizedKeysPath if (-not (Test-Path -Path $parentDir -PathType Container)) { throw "'$parentDir' does not exist. Install the OpenSSH Server Windows feature before importing keys for administrators." } } Write-Verbose "Fetching keys for '$uid' from $($resolved.Url)" $lines = Invoke-SshImportIdFetch -Url $resolved.Url $validLines = @($lines | Where-Object { $_ -match '^(ssh-|ecdsa-|sk-)' }) if (-not $validLines) { Write-Warning "No valid SSH public keys found for '$uid'." continue } $result = Add-SshImportIdAuthorizedKey -Path $AuthorizedKeysPath -KeyLine $validLines -Tag $uid if ($result.Added.Count -gt 0) { if (-not $isAdminKeyFile) { # %ProgramData%\ssh is shared by sshd (host keys, # sshd_config, ...); only the file itself is ours to # lock down, never its parent directory. Set-SshImportIdPermission -Path (Split-Path -Parent $AuthorizedKeysPath) } Set-SshImportIdPermission -Path $AuthorizedKeysPath } foreach ($key in $result.Added) { $preview = if ($key.Length -gt 60) { $key.Substring(0, 60) + '...' } else { $key } Write-Host "[+] $uid : $preview" -ForegroundColor Green } foreach ($key in $result.Skipped) { Write-Verbose "Skipped duplicate key for '$uid': $key" } $obj = [pscustomobject]@{ UserId = $uid Source = $resolved.Prefix Added = $result.Added.Count Skipped = $result.Skipped.Count Path = $AuthorizedKeysPath } if ($PassThru) { $obj } } catch { Write-Error "Failed to import '$uid': $($_.Exception.Message)" } } } } Set-Alias -Name ssh-import-id -Value Import-SshId |