|
1 | 1 | function Install-SSHD |
2 | 2 | { |
3 | | - param ( |
4 | | - [string]$SSHZipFile = $( Throw "Provide an SSHD zipfile" ) |
5 | | - ) |
6 | | - |
7 | | - New-Item "$env:PROGRAMFILES\SSHTemp" -Type Directory -Force |
8 | | - Open-Zip -ZipFile $SSHZipFile -OutPath "$env:PROGRAMFILES\SSHTemp" |
9 | | - |
10 | | - $ConfigPath = "$env:PROGRAMFILES\SSHTemp\OpenSSH-Win64\sshd_config_default" |
11 | | - $ModifiedConfigContents = Edit-DefaultOpenSSHConfig -ConfigPath $ConfigPath |
12 | | - Remove-Item -Force $ConfigPath |
13 | | - Out-File -FilePath $ConfigPath -InputObject $ModifiedConfigContents -Encoding UTF8 |
14 | | - |
15 | | - Move-Item -Force "$env:PROGRAMFILES\SSHTemp\OpenSSH-Win64" "$env:PROGRAMFILES\OpenSSH" |
16 | | - Remove-Item -Force "$env:PROGRAMFILES\SSHTemp" |
17 | | - |
18 | | - # Remove users from 'OpenSSH' before installing. The install process |
19 | | - # will add back permissions for the NT AUTHORITY\Authenticated Users for some files |
20 | | - Protect-Dir -path "$env:PROGRAMFILES\OpenSSH" |
21 | | - |
22 | | - Push-Location "$env:PROGRAMFILES\OpenSSH" |
23 | | - powershell -ExecutionPolicy Bypass -File install-sshd.ps1 |
24 | | - Pop-Location |
| 3 | + if (Get-OSVersion == "windows2019") { |
| 4 | + # Microsoft privided OpenSSH must be installed on Windows 2019 |
| 5 | + # => https://learn.microsoft.com/en-us/windows-server/administration/openssh/openssh_install_firstuse?tabs=powershell&pivots=windows-server-2019 |
| 6 | + $sshPackages = @("OpenSSH.Client", "OpenSSH.Server") |
| 7 | + foreach ($sshPackage in $sshPackages) |
| 8 | + { |
| 9 | + Get-WindowsCapability -Online -Name "$sshPackage*" ` |
| 10 | + | Where-Object { $_.State -eq "NotPresent" } ` |
| 11 | + | Add-WindowsCapability -Online |
| 12 | + } |
| 13 | + } |
25 | 14 |
|
26 | | - # # Grant NT AUTHORITY\Authenticated Users access to .EXEs and the .DLL in OpenSSH |
27 | | - $FileNames = @( |
28 | | - "libcrypto.dll", |
29 | | - "scp.exe", |
30 | | - "sftp-server.exe", |
31 | | - "sftp.exe", |
32 | | - "ssh-add.exe", |
33 | | - "ssh-agent.exe", |
34 | | - "ssh-keygen.exe", |
35 | | - "ssh-keyscan.exe", |
36 | | - "ssh-shellhost.exe", |
37 | | - "ssh.exe", |
38 | | - "sshd.exe" |
39 | | - ) |
40 | | - Invoke-CACL -FileNames $FileNames |
| 15 | + Edit-DefaultOpenSSHConfig |
41 | 16 |
|
42 | 17 | Set-Service -Name sshd -StartupType Disabled |
43 | | - # ssh-agent is not the same as ssh-agent in *nix openssh |
44 | 18 | Set-Service -Name ssh-agent -StartupType Disabled |
45 | 19 | } |
46 | 20 |
|
47 | 21 | function Enable-SSHD |
48 | 22 | { |
49 | | - if ($null -eq (Get-NetFirewallRule | Where-Object { $_.DisplayName -ieq 'SSH' })) |
50 | | - { |
51 | | - "Creating firewall rule for SSH" |
52 | | - New-NetFirewallRule -Protocol TCP -LocalPort 22 -Direction Inbound -Action Allow -DisplayName SSH |
53 | | - } |
54 | | - else |
55 | | - { |
56 | | - "Firewall rule for SSH already exists" |
57 | | - } |
58 | | - |
59 | | - $InfFilePath = "$env:WINDIR\Temp\enable-ssh.inf" |
60 | | - |
61 | | - $InfFileContents = @' |
62 | | -[Unicode] |
63 | | -Unicode=yes |
64 | | -[Version] |
65 | | -signature=$CHICAGO$ |
66 | | -Revision=1 |
67 | | -[Registry Values] |
68 | | -[System Access] |
69 | | -[Privilege Rights] |
70 | | -SeDenyNetworkLogonRight=*S-1-5-32-546 |
71 | | -SeAssignPrimaryTokenPrivilege=*S-1-5-19,*S-1-5-20,*S-1-5-80-3847866527-469524349-687026318-516638107-1125189541 |
72 | | -'@ |
73 | | - $LGPOPath = "$env:WINDIR\LGPO.exe" |
74 | | - if (Test-Path $LGPOPath) |
75 | | - { |
76 | | - Out-File -FilePath $InfFilePath -Encoding unicode -InputObject $InfFileContents -Force |
77 | | - Try |
78 | | - { |
79 | | - Invoke-LGPO -LGPOPath $LGPOPath -InfFilePath $InfFilePath |
80 | | - } |
81 | | - Catch |
82 | | - { |
83 | | - throw "LGPO.exe failed with: $_.Exception.Message" |
84 | | - } |
85 | | - } |
86 | | - else |
87 | | - { |
88 | | - "Did not find $LGPOPath. Assuming existing security policies are sufficient to support ssh." |
| 23 | + # Remove existing OpenSSH firewall rule and recreate with '-Profile Any' option |
| 24 | + if (Get-NetFirewallRule -Name "OpenSSH-Server-In-TCP" -ErrorAction SilentlyContinue) { |
| 25 | + "Removing firewall rule: 'OpenSSH-Server-In-TCP'" |
| 26 | + Remove-NetFirewallRule -Name "OpenSSH-Server-In-TCP" |
89 | 27 | } |
| 28 | + Write-Output "Creating firewall rule 'OpenSSH-Server-In-TCP'" |
| 29 | + New-NetFirewallRule -Name 'OpenSSH-Server-In-TCP' -DisplayName 'OpenSSH Server (sshd)' -Enabled True -Direction Inbound -Protocol TCP -Action Allow -Profile Any -LocalPort 22 |
90 | 30 |
|
91 | 31 | Set-Service -Name sshd -StartupType Automatic |
92 | | - # ssh-agent is not the same as ssh-agent in *nix openssh |
93 | 32 | Set-Service -Name ssh-agent -StartupType Automatic |
94 | 33 |
|
95 | 34 | Remove-SSHKeys |
96 | 35 | } |
97 | 36 |
|
98 | 37 | function Remove-SSHKeys |
99 | 38 | { |
100 | | - $SSHDir = "C:\Program Files\OpenSSH" |
101 | | - |
102 | | - Push-Location $SSHDir |
103 | | - New-Item -ItemType Directory -Path "$env:ProgramData\ssh" -ErrorAction Ignore |
104 | | - |
105 | 39 | "Removing any existing host keys" |
106 | 40 | Remove-Item -Path "$env:ProgramData\ssh\ssh_host_*" -ErrorAction Ignore |
107 | | - Pop-Location |
108 | 41 | } |
109 | 42 |
|
110 | | -function Invoke-CACL |
| 43 | +function Edit-DefaultOpenSSHConfig |
111 | 44 | { |
112 | 45 | param ( |
113 | | - [string[]] $FileNames = $( Throw "Files not provided" ) |
| 46 | + [string]$ConfigPath = "$env:windir\System32\OpenSSH\sshd_config_default", |
| 47 | + [string]$GeneratedConfigPath = "$env:ProgramData\ssh\sshd_config" |
114 | 48 | ) |
115 | 49 |
|
116 | | - foreach ($name in $FileNames) |
117 | | - { |
118 | | - $path = Join-Path "$env:PROGRAMFILES\OpenSSH" $name |
119 | | - cacls.exe $Path /E /P "NT AUTHORITY\Authenticated Users:R" |
120 | | - } |
121 | | -} |
| 50 | + Copy-Item -Path $ConfigPath -Destination "$ConfigPath.bak" |
122 | 51 |
|
123 | | -function Invoke-LGPO |
124 | | -{ |
125 | | - param ( |
126 | | - [string]$LGPOPath = $( Throw "Provide LGPO path" ), |
127 | | - [string]$InfFilePath = $( Throw "Provide Inf file path" ) |
128 | | - ) |
129 | | - & $LGPOPath /s $InfFilePath |
130 | | -} |
| 52 | + $OriginalConfig = Get-Content $ConfigPath |
| 53 | + Write-Output "Original SSH config at $ConfigPath :" |
| 54 | + Write-Output $OriginalConfig |
131 | 55 |
|
132 | | -function Edit-DefaultOpenSSHConfig |
133 | | -{ |
134 | | - param ( |
135 | | - [string]$ConfigPath = $( Throw "Provide openssh default config path" ) |
136 | | - ) |
| 56 | + $ModifiedConfig = $OriginalConfig ` |
| 57 | + | ForEach-Object{ $_ -replace ".*Match Group administrators.*", "#$&" } ` |
| 58 | + | ForEach-Object{ $_ -replace ".*AllowGroups administrators.*", "#$&" } ` |
| 59 | + | ForEach-Object{ $_ -replace ".*AuthorizedKeysFile __PROGRAMDATA__/ssh/administrators_authorized_keys.*", "#$&" } ` |
| 60 | + | ForEach-Object{ $_ -replace "#RekeyLimit default none", "$&`r`n# Disable cipher to mitigate CVE-2023-48795`r`nCiphers [email protected]`r`n" } |
137 | 61 |
|
138 | | - $ModifiedConfig = Get-Content $ConfigPath ` |
139 | | - | ForEach-Object{ $_ -replace ".*Match Group administrators.*", "#$&" } ` |
140 | | - | ForEach-Object{ $_ -replace ".*AuthorizedKeysFile __PROGRAMDATA__/ssh/administrators_authorized_keys.*", "#$&" } ` |
141 | | - | ForEach-Object{ $_ -replace "#RekeyLimit default none", "$&`r`n# Disable cipher to mitigate CVE-2023-48795`r`nCiphers [email protected]`r`n" } |
| 62 | + Write-Output "Modified SSH config at $ConfigPath :" |
| 63 | + Write-Output $ModifiedConfig |
| 64 | + |
| 65 | + Remove-Item -Force $ConfigPath |
| 66 | + Out-File -FilePath $ConfigPath -InputObject $ModifiedConfig -Encoding UTF8 |
142 | 67 |
|
143 | | - return $ModifiedConfig |
| 68 | + # We need to make sure that the generated config is cleared, so our above changes are applied when the config |
| 69 | + # is next generated. If this isnt done, then we may have a config from the prior template. |
| 70 | + Remove-Item -Path $GeneratedConfigPath -ErrorAction Ignore |
144 | 71 | } |
0 commit comments