Files
winutil/functions/private/Invoke-WinUtilSSHServer.ps1
T
AshvinandGitHub 9fdadd1c8f Point OpenSSH key setup at the file sshd actually reads (#4936)
* Point OpenSSH key setup at the file sshd actually reads

The Remote Access feature created %USERPROFILE%\.ssh\authorized_keys and
told the user to put their public keys there. sshd does not read that
file for a member of the administrators group; the "Match Group
administrators" block in sshd_config sends those logons to
C:\ProgramData\ssh\administrators_authorized_keys instead. WinUtil always
relaunches itself elevated, so the account it was setting up is always an
administrator, and key auth for it never worked.

The function tried to work around that by commenting the block out, but
those regexes anchor on $, and .NET puts $ before the \n of a CRLF pair.
The sshd_config Windows ships is CRLF throughout, so the replace was a
silent no-op on a stock install. On an sshd_config with LF endings it did
apply, and that is worse than not working: sshd gives an administrator
logon a full token with no UAC prompt, which is why Windows keeps those
keys in ProgramData behind an ACL that requires elevation to write.
Moving the lookup into the profile lets anything running as the user at
medium integrity append a key and get an elevated shell unprompted.

Use administrators_authorized_keys and give it the ACL sshd requires
(inheritance off, Administrators and SYSTEM only, by SID so localized
installs work). Where the sshd_config edit did land, undo it and copy any
keys out of the profile file first so key auth is not cut off mid-session.
Keys are only copied when the block needs restoring, so a default config
never grants access sshd was not already granting.

Also stop creating the profile .ssh directory: under elevation it was the
elevating administrator's profile, not necessarily the caller's.

* Document where to put SSH keys for the OpenSSH server feature
2026-08-09 13:08:33 -05:00

91 lines
4.3 KiB
PowerShell

function Invoke-WinUtilSSHServer {
<#
.SYNOPSIS
Enables OpenSSH server to remote into your windows device
#>
# Install the OpenSSH Server feature if not already installed
if ((Get-WindowsCapability -Name OpenSSH.Server -Online).State -ne "Installed") {
Write-Host "Enabling OpenSSH Server... This will take a long time."
Add-WindowsCapability -Name OpenSSH.Server -Online
}
Write-Host "Starting the services"
Set-Service -Name sshd -StartupType Automatic
Start-Service -Name sshd
Set-Service -Name ssh-agent -StartupType Automatic
Start-Service -Name ssh-agent
#Adding Firewall rule for port 22
Write-Host "Setting up firewall rules"
if (-not ((Get-NetFirewallRule -Name 'sshd').Enabled)) {
New-NetFirewallRule -Name sshd -DisplayName 'OpenSSH Server (sshd)' -Enabled True -Direction Inbound -Protocol TCP -Action Allow -LocalPort 22
Write-Host "Firewall rule for OpenSSH Server created and enabled."
}
# An SSH logon for a member of the administrators group gets a full token
# with no UAC prompt, so sshd reads administrator keys from a machine-wide
# file that only Administrators and SYSTEM may write. WinUtil always runs
# elevated, so the account being set up here is always an administrator.
$sshProgramDataPath = Join-Path $env:ProgramData "ssh"
$sshdConfigPath = Join-Path $sshProgramDataPath "sshd_config"
$authorizedKeysPath = Join-Path $sshProgramDataPath "administrators_authorized_keys"
$profileKeysPath = Join-Path $env:USERPROFILE ".ssh\authorized_keys"
if (-not (Test-Path -Path $sshProgramDataPath)) {
New-Item -Path $sshProgramDataPath -ItemType Directory -Force | Out-Null
}
# Earlier WinUtil versions commented out the administrators block in
# sshd_config. Detect that state before restoring it, so administrator keys
# already in use are carried over instead of silently stopping working.
$configContent = if (Test-Path -Path $sshdConfigPath) { [string](Get-Content -Path $sshdConfigPath -Raw) } else { "" }
$restoredContent = $configContent -replace '(?m)^# (Match Group administrators)$', '$1'
$restoredContent = $restoredContent -replace '(?m)^# (\s+AuthorizedKeysFile __PROGRAMDATA__/ssh/administrators_authorized_keys)$', '$1'
$configWasOverridden = $restoredContent -ne $configContent
if (-not (Test-Path -Path $authorizedKeysPath)) {
Write-Host "Creating administrators_authorized_keys file..."
New-Item -Path $authorizedKeysPath -ItemType File -Force | Out-Null
Write-Host "administrators_authorized_keys file created at $authorizedKeysPath."
}
if ($configWasOverridden -and (Test-Path -Path $profileKeysPath)) {
$currentKeys = @(Get-Content -Path $authorizedKeysPath)
$keysToMove = @(Get-Content -Path $profileKeysPath | Where-Object {
$_.Trim() -and -not $_.TrimStart().StartsWith("#") -and $currentKeys -notcontains $_
})
if ($keysToMove.Count -gt 0) {
Add-Content -Path $authorizedKeysPath -Value $keysToMove
Write-Host "Moved $($keysToMove.Count) key(s) from $profileKeysPath to $authorizedKeysPath."
}
}
# sshd ignores the file unless inheritance is off and access is limited to
# Administrators (S-1-5-32-544) and SYSTEM (S-1-5-18). SIDs keep this
# working on localized installs, where the group names differ.
$acl = Get-Acl -Path $authorizedKeysPath
$acl.SetAccessRuleProtection($true, $false)
foreach ($rule in @($acl.Access)) {
[void]$acl.RemoveAccessRule($rule)
}
foreach ($sid in @("S-1-5-32-544", "S-1-5-18")) {
[void]$acl.AddAccessRule([System.Security.AccessControl.FileSystemAccessRule]::new(
[System.Security.Principal.SecurityIdentifier]::new($sid), "FullControl", "Allow"))
}
Set-Acl -Path $authorizedKeysPath -AclObject $acl
if ($configWasOverridden) {
Set-Content -Path $sshdConfigPath -Value $restoredContent -Force
Write-Host "Restored the administrator key file setting in sshd_config."
Restart-Service -Name sshd -Force
}
Write-Host "OpenSSH server was successfully enabled."
Write-Host "The config file can be located at $sshdConfigPath"
Write-Host "Add your public keys to this file -> $authorizedKeysPath"
}