Install-SshKey.ps1
|
$ErrorActionPreference = "Stop" $administratorSid = "S-1-5-32-544" $isAdministrator = whoami /groups /fo csv | ConvertFrom-Csv | Where-Object { $_.SID -eq $administratorSid -and $_.Attributes -match "Enabled group" } function Resolve-AuthorizedKeysPath { param( [string]$ConfiguredPath ) $path = $ConfiguredPath.Trim().Trim('"') -replace '/', '\' if([string]::IsNullOrWhiteSpace($path) -or $path -ieq 'none') { return $null } $path = $path -replace '^__PROGRAMDATA__', $env:ProgramData $path = $path.Replace('%h', $env:USERPROFILE).Replace('%u', $env:USERNAME) if($path.StartsWith('~\')) { return Join-Path $env:USERPROFILE $path.Substring(2) } if([System.IO.Path]::IsPathRooted($path)) { return [Environment]::ExpandEnvironmentVariables($path) } return Join-Path $env:USERPROFILE $path } function Get-ConfiguredAuthorizedKeysFiles { $defaultAuthorizedKeys = @('.ssh/authorized_keys') $configPath = Join-Path $env:ProgramData 'ssh\sshd_config' if(!(Test-Path -LiteralPath $configPath)) { return $defaultAuthorizedKeys } $globalAuthorizedKeys = $null $administratorAuthorizedKeys = $null $inMatchBlock = $false $matchAppliesToAdministrator = $false foreach($rawLine in Get-Content -LiteralPath $configPath) { $line = ($rawLine -replace '\s+#.*$', '').Trim() if([string]::IsNullOrWhiteSpace($line) -or $line.StartsWith('#')) { continue } if($line -match '^Match\s+(.+)$') { $inMatchBlock = $true $matchCondition = $Matches[1] $matchAppliesToAdministrator = $isAdministrator -and ( $matchCondition -match '(^|\s)all(\s|$)' -or $matchCondition -match '(^|\s)group\s+("[^"]*administrators[^"]*"|\S*administrators\S*)' ) continue } if($line -match '^AuthorizedKeysFile\s+(.+)$') { $authorizedKeysFiles = $Matches[1] -split '\s+' | Where-Object { -not [string]::IsNullOrWhiteSpace($_) } if($inMatchBlock) { if($matchAppliesToAdministrator -and -not $administratorAuthorizedKeys) { $administratorAuthorizedKeys = $authorizedKeysFiles } } elseif(-not $globalAuthorizedKeys) { $globalAuthorizedKeys = $authorizedKeysFiles } } } if($administratorAuthorizedKeys) { return $administratorAuthorizedKeys } if($globalAuthorizedKeys) { return $globalAuthorizedKeys } return $defaultAuthorizedKeys } $authorizedKeys = Get-ConfiguredAuthorizedKeysFiles | ForEach-Object { Resolve-AuthorizedKeysPath $_ } | Where-Object { $_ } | Select-Object -First 1 if(-not $authorizedKeys) { throw 'No usable AuthorizedKeysFile path is configured.' } $sshDirectory = Split-Path -Parent $authorizedKeys New-Item -ItemType Directory -Path $sshDirectory -Force | Out-Null $encodedKeyText = "__ENCODED_KEY_TEXT__" $keyText = [System.Text.Encoding]::UTF8.GetString([Convert]::FromBase64String($encodedKeyText)) $keyLines = $keyText -split "`r?`n" | Where-Object { -not [string]::IsNullOrWhiteSpace($_) } if ($keyLines.Count -eq 0) { throw "No public key content was received." } $existingKeys = @() if (Test-Path -LiteralPath $authorizedKeys) { $existingKeys = Get-Content -LiteralPath $authorizedKeys -Raw -ErrorAction SilentlyContinue if ($null -ne $existingKeys -and $existingKeys.Length -gt 0 -and -not $existingKeys.EndsWith("`n")) { [System.IO.File]::AppendAllText($authorizedKeys, [Environment]::NewLine, [Text.UTF8Encoding]::new($false)) } $existingKeys = @(Get-Content -LiteralPath $authorizedKeys -ErrorAction SilentlyContinue) } # 提取密钥材质(类型+Base64,不含注释)用于去重比较 $existingKeyMaterials = $existingKeys | ForEach-Object { ($_ -split '\s+')[0..1] -join ' ' } foreach ($keyLine in $keyLines) { $keyMaterial = ($keyLine -split '\s+')[0..1] -join ' ' if ($existingKeyMaterials -notcontains $keyMaterial) { [System.IO.File]::AppendAllText($authorizedKeys, $keyLine + [Environment]::NewLine, [Text.UTF8Encoding]::new($false)) $existingKeys += $keyLine $existingKeyMaterials += $keyMaterial } } if ($isAdministrator -and ([string]::Equals($authorizedKeys, (Join-Path $env:ProgramData 'ssh\administrators_authorized_keys'), [System.StringComparison]::OrdinalIgnoreCase))) { $systemSid = New-Object System.Security.Principal.SecurityIdentifier("S-1-5-18") $administratorsSid = New-Object System.Security.Principal.SecurityIdentifier($administratorSid) $acl = New-Object System.Security.AccessControl.FileSecurity $acl.SetOwner($administratorsSid) $acl.SetAccessRuleProtection($true, $false) $acl.AddAccessRule((New-Object System.Security.AccessControl.FileSystemAccessRule($systemSid, "FullControl", "Allow"))) $acl.AddAccessRule((New-Object System.Security.AccessControl.FileSystemAccessRule($administratorsSid, "FullControl", "Allow"))) Set-Acl -LiteralPath $authorizedKeys -AclObject $acl } |