Public/Enable-Fido2SshKeys.ps1
|
function Enable-Fido2SshKeys { <# .SYNOPSIS Bootstraps a Windows machine for using resident FIDO2 SSH keys. .DESCRIPTION One-shot bootstrapper that prepares a Windows workstation to use the rest of the Fido2Ssh module: * Installs the OpenSSH Client Windows capability if missing (provides `ssh`, `ssh-keygen`, `ssh-add`). * Sets the `ssh-agent` service start type to `Automatic` (override via `-SshAgentStartupType`) and starts it. * Verifies the OpenSSH client tools resolve on PATH. Azure CLI is intentionally **not** installed. If you plan to use `Publish-Fido2SshKeyToAzureVM`, install `az` separately (e.g. `winget install Microsoft.AzureCLI`). Installing the OpenSSH Windows capability and changing the `ssh-agent` service require an elevated PowerShell session. If the capability is already installed and the service is already configured correctly, the script can run unelevated. .PARAMETER SshAgentStartupType Desired Windows service startup type for `ssh-agent`. Defaults to `Automatic`. Use `Manual` if you prefer to start the agent on demand. .PARAMETER SkipOpenSsh Don't touch the OpenSSH Client Windows capability. Use when OpenSSH is provided by another mechanism (e.g. a portable install on PATH). .PARAMETER SkipAgent Don't configure or start the `ssh-agent` service. .EXAMPLE Enable-Fido2SshKeys Installs OpenSSH Client (if missing) and configures + starts the ssh-agent service. Run from an elevated PowerShell session. .EXAMPLE Enable-Fido2SshKeys -SshAgentStartupType Manual Same as above but leaves ssh-agent on manual startup. #> [CmdletBinding(SupportsShouldProcess = $true)] param( [ValidateSet('Automatic', 'Manual', 'Disabled')] [string]$SshAgentStartupType = 'Automatic', [switch]$SkipOpenSsh, [switch]$SkipAgent ) if (-not $IsWindows -and $PSVersionTable.PSVersion.Major -ge 6) { throw "Enable-Fido2SshKeys is Windows-only. On Linux/macOS, install OpenSSH client via your package manager (e.g. ``apt install openssh-client`` / ``brew install openssh``) and start ssh-agent yourself (``eval `$(ssh-agent -s)``)." } $isAdmin = ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole( [Security.Principal.WindowsBuiltInRole]::Administrator ) # -- 1. OpenSSH Client Windows capability --------------------------------- if (-not $SkipOpenSsh) { $capabilityName = 'OpenSSH.Client~~~~0.0.1.0' $capability = $null try { $capability = Get-WindowsCapability -Online -Name $capabilityName -ErrorAction Stop } catch { Write-Warning "Could not query Windows capabilities ($($_.Exception.Message)). Falling back to PATH detection." } if ($capability -and $capability.State -eq 'Installed') { Write-Host "OpenSSH Client capability already installed." } elseif ($capability) { if (-not $isAdmin) { throw "OpenSSH Client capability is not installed. Re-run from an elevated PowerShell session, or install it manually: Add-WindowsCapability -Online -Name $capabilityName" } if ($PSCmdlet.ShouldProcess($capabilityName, 'Add-WindowsCapability -Online')) { Write-Host "Installing OpenSSH Client Windows capability..." $result = Add-WindowsCapability -Online -Name $capabilityName -ErrorAction Stop if ($result.RestartNeeded) { Write-Warning "OpenSSH Client installed. A reboot is recommended before using ssh-agent." } else { Write-Host "OpenSSH Client installed." } } } else { # Couldn't query capability state; rely on PATH check below. } foreach ($tool in @('ssh', 'ssh-keygen', 'ssh-add')) { if (-not (Get-Command $tool -ErrorAction SilentlyContinue)) { Write-Warning "$tool was not found on PATH after installation. You may need to open a new shell or reboot." } } } else { Write-Verbose "Skipping OpenSSH Client install (-SkipOpenSsh)." } # -- 2. ssh-agent service ------------------------------------------------- if (-not $SkipAgent) { $service = Get-Service -Name ssh-agent -ErrorAction SilentlyContinue if (-not $service) { Write-Warning "ssh-agent service is not registered. Install OpenSSH Client first (run without -SkipOpenSsh from an elevated shell)." } else { $currentStartup = (Get-CimInstance -ClassName Win32_Service -Filter "Name='ssh-agent'" -ErrorAction SilentlyContinue).StartMode # Win32_Service.StartMode returns 'Auto' / 'Manual' / 'Disabled'. $desired = $SshAgentStartupType $needChange = $true if ($currentStartup -eq 'Auto' -and $desired -eq 'Automatic') { $needChange = $false } elseif ($currentStartup -eq $desired) { $needChange = $false } if ($needChange) { if (-not $isAdmin) { Write-Warning "ssh-agent startup type is '$currentStartup'; cannot change to '$desired' without elevation. Re-run elevated or pass -SkipAgent." } elseif ($PSCmdlet.ShouldProcess('ssh-agent', "Set-Service -StartupType $desired")) { Set-Service -Name ssh-agent -StartupType $desired -ErrorAction Stop Write-Host "ssh-agent startup type set to $desired." } } else { Write-Verbose "ssh-agent startup type already '$desired'." } $service.Refresh() if ($service.Status -ne 'Running' -and $desired -ne 'Disabled') { if ($PSCmdlet.ShouldProcess('ssh-agent', 'Start-Service')) { try { Start-Service -Name ssh-agent -ErrorAction Stop Write-Host "ssh-agent service started." } catch { if (-not $isAdmin) { Write-Warning "Could not start ssh-agent: $($_.Exception.Message). Re-run from an elevated session." } else { throw } } } } else { Write-Host "ssh-agent service status: $($service.Status)." } } } else { Write-Verbose "Skipping ssh-agent configuration (-SkipAgent)." } Write-Host "" Write-Host "Fido2Ssh prerequisites OK. Next steps:" Write-Host " - Plug in your FIDO2 authenticator." Write-Host " - Create a key: New-Fido2SshKey" Write-Host " - Or import existing resident keys: Import-Fido2SshKey" } |