diff --git a/AutomationHelpers.Tests.ps1 b/AutomationHelpers.Tests.ps1 index 4c4be50..8372fb5 100644 --- a/AutomationHelpers.Tests.ps1 +++ b/AutomationHelpers.Tests.ps1 @@ -723,3 +723,161 @@ Describe "Set-RegKeys" { } } +function CreateFakeOpenSSHZip +{ + param([string]$dir, [string]$installScriptSpyStatus, [string]$fakeZipPath) + + mkdir "$dir\OpenSSH-Win64" + $installSpyBehavior = "echo installed > $installScriptSpyStatus" + echo $installSpyBehavior > "$dir\OpenSSH-Win64\install-sshd.ps1" + echo "fake sshd" > "$dir\OpenSSH-Win64\sshd.exe" + + Compress-Archive -Force -Path "$dir\OpenSSH-Win64" -DestinationPath $fakeZipPath +} + +Describe "Enable-SSHD" { + BeforeEach { + Mock Set-Service { } + Mock Run-LGPO { } + + $guid = $( New-Guid ).Guid + $TMP_DIR = "$env:TEMP\BOSH.SSH.Tests-$guid" + + $FAKE_ZIP = "$TMP_DIR\OpenSSH-TestFake.zip" + $INSTALL_SCRIPT_SPY_STATUS = "$TMP_DIR\install-script-status" + + CreateFakeOpenSSHZip -dir $TMP_DIR -installScriptSpyStatus $INSTALL_SCRIPT_SPY_STATUS -fakeZipPath $FAKE_ZIP + + mkdir -p "$TMP_DIR\Windows\Temp" + echo "fake LGPO" > "$TMP_DIR\Windows\LGPO.exe" + + $ORIGINAL_WINDIR = $env:WINDIR + $env:WINDIR = "$TMP_DIR\Windows" + + $ORIGINAL_PROGRAMDATA = $env:ProgramData + $env:PROGRAMDATA = "$TMP_DIR\ProgramData" + } + + AfterEach { + rmdir $TMP_DIR -Recurse -ErrorAction Ignore + $env:WINDIR = $ORIGINAL_WINDIR + $env:PROGRAMDATA = $ORIGINAL_PROGRAMDATA + } + + It "sets the startup type of sshd to automatic" { + Mock Set-Service { } -Verifiable -ParameterFilter { $Name -eq "sshd" -and $StartupType -eq "Automatic" } + + Enable-SSHD -SSHZipFile $FAKE_ZIP + + Assert-VerifiableMock + } + + It "sets the startup type of ssh-agent to automatic" { + Mock Set-Service { } -Verifiable -ParameterFilter { $Name -eq "ssh-agent" -and $StartupType -eq "Automatic" } + + Enable-SSHD -SSHZipFile $FAKE_ZIP + + Assert-VerifiableMock + } + + It "sets up firewall when ssh not already set up" { + Mock Get-NetFirewallRule { + return [ordered]@{ + "Name" = "{3c06039b-ece1-4da3-8ece-255894975894}" + "DisplayName" = "NTP" + "Description" = "" + "DisplayGroup" = "" + "Group" = "" + "Enabled" = "True" + "Profile" = "Any" + "Platform" = "{}" + "Direction" = "Outbound" + "Action" = "Allow" + "EdgeTraversalPolicy" = "Block" + "LooseSourceMapping" = "False" + "LocalOnlyMapping" = "False" + "Owner" = "" + "PrimaryStatus" = "OK" + "Status" = "The rule was parsed successfully from the store. (65536)" + "EnforcementStatus" = "NotApplicable" + "PolicyStoreSource" = "PersistentStore" + "PolicyStoreSourceType" = "Local" + } + } + + Mock New-NetFirewallRule { } + Enable-SSHD -SSHZipFile $FAKE_ZIP + Assert-MockCalled New-NetFirewallRule -Times 1 -Scope It + } + + It "doesn't set up firewall when ssh is already set up " { + Mock Get-NetFirewallRule { + return [ordered]@{ + "Name" = "{ E02857AB-8EA8-4358-8119-ED7D20DA7712 }" + "DisplayName" = "SSH" + "Description" = "" + "DisplayGroup" = "" + "Group" = "" + "Enabled" = "True" + "Profile" = "Any" + "Platform" = "{ }" + "Direction" = "Inbound" + "Action" = "Allow" + "EdgeTraversalPolicy" = "Block" + "LooseSourceMapping" = "False" + "LocalOnlyMapping" = "False" + "Owner" = "" + "PrimaryStatus" = "OK" + "Status" = "The rule was parsed successfully from the store. (65536)" + "EnforcementStatus" = "NotApplicable" + "PolicyStoreSource" = "PersistentStore" + "PolicyStoreSourceType" = "Local" + } + } + + Mock New-NetFirewallRule { } + Enable-SSHD -SSHZipFile $FAKE_ZIP + Assert-MockCalled New-NetFirewallRule -Times 0 -Scope It + } + + It "Generates inf and invokes LGPO if LGPO exists" { + Mock Run-LGPO -Verifiable -ParameterFilter { $LGPOPath -eq "$TMP_DIR\Windows\LGPO.exe" -and $InfFilePath -eq "$TMP_DIR\Windows\Temp\enable-ssh.inf" } + + Enable-SSHD -SSHZipFile $FAKE_ZIP + + Assert-VerifiableMock + } + + It "Skips LGPO if LGPO.exe not found" { + rm "$TMP_DIR\Windows\LGPO.exe" + + Enable-SSHD -SSHZipFile $FAKE_ZIP + + Assert-MockCalled Run-LGPO -Times 0 -Scope It + } + + Context "When LGPO executable fails" { + It "Throws an appropriate error" { + Mock Run-LGPO { throw "some error" } -Verifiable -ParameterFilter { $LGPOPath -eq "$TMP_DIR\Windows\LGPO.exe" -and $InfFilePath -eq "$TMP_DIR\Windows\Temp\enable-ssh.inf" } + { Enable-SSHD -SSHZipFile $FAKE_ZIP } | Should -Throw "LGPO.exe failed with: some error" + } + } + + It "removes existing SSH keys" { + New-Item -ItemType Directory -Path "$TMP_DIR\ProgramData\ssh" -ErrorAction Ignore + echo "delete" > "$TMP_DIR\ProgramData\ssh\ssh_host_1" + echo "delete" > "$TMP_DIR\ProgramData\ssh\ssh_host_2" + echo "delete" > "$TMP_DIR\ProgramData\ssh\ssh_host_3" + echo "ignore" > "$TMP_DIR\ProgramData\ssh\not_ssh_host_4" + + Enable-SSHD -SSHZipFile $FAKE_ZIP + + $numHosts = (Get-ChildItem "$TMP_DIR\ProgramData\ssh\").count + $numHosts | Should -eq 1 + } + + It "creates empty ssh program dir if it doesn't exist" { + Enable-SSHD -SSHZipFile $FAKE_ZIP + { Test-Path "$TMP_DIR\ProgramData\ssh" } | Should -eq $True + } +} diff --git a/AutomationHelpers.ps1 b/AutomationHelpers.ps1 index 3f1d9a9..aceed33 100644 --- a/AutomationHelpers.ps1 +++ b/AutomationHelpers.ps1 @@ -332,3 +332,77 @@ function Create-VMPrepTaskAction { New-ScheduledTaskAction -Execute "powershell.exe" -Argument $arguments } + +function Remove-SSHKeys +{ + $SSHDir = "C:\Program Files\OpenSSH" + + Push-Location $SSHDir + New-Item -ItemType Directory -Path "$env:ProgramData\ssh" -ErrorAction Ignore + + "Removing any existing host keys" + Remove-Item -Path "$env:ProgramData\ssh\ssh_host_*" -ErrorAction Ignore + Pop-Location +} + +function Run-LGPO +{ + param ( + [string]$LGPOPath = $( Throw "Provide LGPO path" ), + [string]$InfFilePath = $( Throw "Provide Inf file path" ) + ) + & $LGPOPath /s $InfFilePath +} + +function Enable-SSHD +{ + if ((Get-NetFirewallRule | where { $_.DisplayName -ieq 'SSH' }) -eq $null) + { + "Creating firewall rule for SSH" + New-NetFirewallRule -Protocol TCP -LocalPort 22 -Direction Inbound -Action Allow -DisplayName SSH + } + else + { + "Firewall rule for SSH already exists" + } + + $InfFilePath = "$env:WINDIR\Temp\enable-ssh.inf" + + $InfFileContents = @' +[Unicode] +Unicode=yes +[Version] +signature=$CHICAGO$ +Revision=1:w +[Registry Values] +[System Access] +[Privilege Rights] +SeDenyNetworkLogonRight=*S-1-5-32-546 +SeAssignPrimaryTokenPrivilege=*S-1-5-19,*S-1-5-20,*S-1-5-80-3847866527-469524349-687026318-516638107-1125189541 +'@ + $LGPOPath = "$env:WINDIR\LGPO.exe" + if (Test-Path $LGPOPath) + { + Out-File -FilePath $InfFilePath -Encoding unicode -InputObject $InfFileContents -Force + Try + { + Run-LGPO -LGPOPath $LGPOPath -InfFilePath $InfFilePath + } + Catch + { + throw "LGPO.exe failed with: $_.Exception.Message" + } + } + else + { + "Did not find $LGPOPath. Assuming existing security policies are sufficient to support ssh." + } + + Set-Service -Name sshd -StartupType Automatic + # ssh-agent is not the same as ssh-agent in *nix openssh + Set-Service -Name ssh-agent -StartupType Automatic + + Remove-SSHKeys +} + + diff --git a/Setup.ps1 b/Setup.ps1 index 0f766f2..1afaef3 100644 --- a/Setup.ps1 +++ b/Setup.ps1 @@ -44,6 +44,7 @@ try { CopyPSModules InstallBoshAgent InstallOpenSSH + Enable-SSHD InstallCFFeatures } catch [Exception] { Write-Log "Failed to install Bosh dependencies. See 'c:\provisions\log.log' for more info."