diff --git a/ci/pipelines/stemcells-windows.yml b/ci/pipelines/stemcells-windows.yml index 635151b71..f35dbf802 100644 --- a/ci/pipelines/stemcells-windows.yml +++ b/ci/pipelines/stemcells-windows.yml @@ -218,19 +218,12 @@ resources: password: ((docker.password)) # type: github-release - - name: openssh-release - type: github-release - source: - owner: PowerShell - repository: Win32-OpenSSH - access_token: ((github_public_repo_token)) - tag_filter: v([^v].*) - name: stemcell-builder-github-release - type: github-release - source: - owner: cloudfoundry - repository: bosh-windows-stemcell-builder - access_token: ((github_public_repo_token)) + type: github-release + source: + owner: cloudfoundry + repository: bosh-windows-stemcell-builder + access_token: ((github_public_repo_token)) - name: bosh-agent-release type: metalink-repository @@ -816,20 +809,18 @@ jobs: tags: [*worker_tag] - get: bosh-windows-stemcell-builder-ci-image tags: [*worker_tag] - - get: open-ssh - resource: openssh-release - get: stemcell-builder - passed: [build] - tags: [*worker_tag] - - get: version - resource: stembuild-windows-build-number - passed: [build] - trigger: true - tags: [*worker_tag] - - get: main-version - passed: [build] - tags: [*worker_tag] - - get: bosh-agent-release + passed: [build] + tags: [*worker_tag] + - get: version + resource: stembuild-windows-build-number + passed: [build] + trigger: true + tags: [*worker_tag] + - get: main-version + passed: [build] + tags: [*worker_tag] + - get: bosh-agent-release passed: [build] - get: ovftool - get: blobstore-dav-cli @@ -947,20 +938,18 @@ jobs: tags: [*worker_tag] - get: bosh-windows-stemcell-builder-ci-image tags: [*worker_tag] - - get: open-ssh - resource: openssh-release - get: stemcell-builder - passed: [build] - tags: [*worker_tag] - - get: version - resource: stembuild-linux-build-number - passed: [build] - trigger: true - tags: [*worker_tag] - - get: main-version - passed: [build] - tags: [*worker_tag] - - get: bosh-agent-release + passed: [build] + tags: [*worker_tag] + - get: version + resource: stembuild-linux-build-number + passed: [build] + trigger: true + tags: [*worker_tag] + - get: main-version + passed: [build] + tags: [*worker_tag] + - get: bosh-agent-release passed: [build] - get: ovftool - get: blobstore-dav-cli @@ -1232,9 +1221,10 @@ jobs: SSH_TUNNEL_IP: ((nimbus_windows-demo_bosh_jumpbox_ip)) SSH_TUNNEL_PRIVATE_KEY: ((nimbus_windows-demo_bosh_jumpbox_ssh.private_key)) SSH_TUNNEL_USER: ((nimbus_windows-demo_bosh_jumpbox_username)) - STEMCELL_OS: *windows_os_version - VM_EXTENSIONS: "" - VM_TYPE: default # TODO: Add "put" output of this passing test to release candidates bucket (?) + WINDOWS_SSH_FIREWALL_RULE_NAME: OpenSSH-Server-In-TCP + STEMCELL_OS: *windows_os_version + VM_EXTENSIONS: "" + VM_TYPE: default # TODO: Add "put" output of this passing test to release candidates bucket (?) - name: create-stembuild-windows-stemcell serial: true @@ -1401,9 +1391,10 @@ jobs: SSH_TUNNEL_IP: ((nimbus_windows-demo_bosh_jumpbox_ip)) SSH_TUNNEL_PRIVATE_KEY: ((nimbus_windows-demo_bosh_jumpbox_ssh.private_key)) SSH_TUNNEL_USER: ((nimbus_windows-demo_bosh_jumpbox_username)) - STEMCELL_OS: *windows_os_version - VM_EXTENSIONS: "" - VM_TYPE: default + WINDOWS_SSH_FIREWALL_RULE_NAME: OpenSSH-Server-In-TCP + STEMCELL_OS: *windows_os_version + VM_EXTENSIONS: "" + VM_TYPE: default - name: create-aws serial: true @@ -1429,15 +1420,12 @@ jobs: - get: main-version passed: [build] tags: [*worker_tag] - - get: sshd - resource: openssh-release - get: bosh-agent-release - passed: [build] - - get: blobstore-dav-cli - - get: blobstore-s3-cli - - get: blobstore-gcs-cli - - get: windows-winsw - - task: lgpo-binary + passed: [build] + - get: blobstore-dav-cli + - get: blobstore-s3-cli + - get: blobstore-gcs-cli + - get: windows-winsw- task: lgpo-binary file: bosh-windows-stemcell-builder-ci/ci/tasks/lgpo-binary/task.yml image: bosh-windows-stemcell-builder-ci-image - put: version @@ -1591,9 +1579,10 @@ jobs: SSH_TUNNEL_IP: ((iaas_directors_aws-director_bosh_jumpbox_ip)) SSH_TUNNEL_PRIVATE_KEY: ((iaas_directors_aws-director_bosh_jumpbox_ssh.private_key)) SSH_TUNNEL_USER: ((iaas_directors_aws-director_bosh_jumpbox_username)) - STEMCELL_OS: *windows_os_version - VM_EXTENSIONS: "" - VM_TYPE: large + WINDOWS_SSH_FIREWALL_RULE_NAME: OpenSSH-Server-In-TCP + STEMCELL_OS: *windows_os_version + VM_EXTENSIONS: "" + VM_TYPE: large - name: create-aws-govcloud serial: true @@ -1619,15 +1608,12 @@ jobs: - get: main-version passed: [wuts-aws] tags: [*worker_tag] - - get: sshd - resource: openssh-release - get: bosh-agent-release - passed: [wuts-aws] - - get: blobstore-dav-cli - - get: blobstore-s3-cli - - get: blobstore-gcs-cli - - get: windows-winsw - - task: lgpo-binary + passed: [wuts-aws] + - get: blobstore-dav-cli + - get: blobstore-s3-cli + - get: blobstore-gcs-cli + - get: windows-winsw- task: lgpo-binary file: bosh-windows-stemcell-builder-ci/ci/tasks/lgpo-binary/task.yml image: bosh-windows-stemcell-builder-ci-image - task: build-agent-zip @@ -1762,10 +1748,11 @@ jobs: SSH_TUNNEL_IP: ((iaas_directors_aws-govcloud-director_bosh_jumpbox_ip)) SSH_TUNNEL_PRIVATE_KEY: ((iaas_directors_aws-govcloud-director_bosh_jumpbox_ssh.private_key)) SSH_TUNNEL_USER: ((iaas_directors_aws-govcloud-director_bosh_jumpbox_username)) - STEMCELL_OS: *windows_os_version - NETWORK: default - VM_EXTENSIONS: "" - VM_TYPE: large + WINDOWS_SSH_FIREWALL_RULE_NAME: OpenSSH-Server-In-TCP + STEMCELL_OS: *windows_os_version + NETWORK: default + VM_EXTENSIONS: "" + VM_TYPE: large - name: create-azure serial: true @@ -1789,15 +1776,12 @@ jobs: - get: main-version passed: [build] tags: [*worker_tag] - - get: sshd - resource: openssh-release - get: bosh-agent-release - passed: [build] - - get: blobstore-dav-cli - - get: blobstore-s3-cli - - get: blobstore-gcs-cli - - get: windows-winsw - - task: lgpo-binary + passed: [build] + - get: blobstore-dav-cli + - get: blobstore-s3-cli + - get: blobstore-gcs-cli + - get: windows-winsw- task: lgpo-binary file: bosh-windows-stemcell-builder-ci/ci/tasks/lgpo-binary/task.yml image: bosh-windows-stemcell-builder-ci-image - put: version @@ -1966,9 +1950,10 @@ jobs: SSH_TUNNEL_IP: ((iaas_directors_azure-director_bosh_jumpbox_ip)) SSH_TUNNEL_PRIVATE_KEY: ((iaas_directors_azure-director_bosh_jumpbox_ssh.private_key)) SSH_TUNNEL_USER: ((iaas_directors_azure-director_bosh_jumpbox_username)) - STEMCELL_OS: *windows_os_version - VM_EXTENSIONS: "" - VM_TYPE: ((AZURE_HEAVY_VM_TYPE)) + WINDOWS_SSH_FIREWALL_RULE_NAME: OpenSSH-Server-In-TCP + STEMCELL_OS: *windows_os_version + VM_EXTENSIONS: "" + VM_TYPE: ((AZURE_HEAVY_VM_TYPE)) - name: create-gcp serial: true @@ -1995,15 +1980,12 @@ jobs: - get: main-version passed: [build] tags: [*worker_tag] - - get: sshd - resource: openssh-release - get: bosh-agent-release - passed: [build] - - get: blobstore-dav-cli - - get: blobstore-s3-cli - - get: blobstore-gcs-cli - - get: windows-winsw - - task: lgpo-binary + passed: [build] + - get: blobstore-dav-cli + - get: blobstore-s3-cli + - get: blobstore-gcs-cli + - get: windows-winsw- task: lgpo-binary file: bosh-windows-stemcell-builder-ci/ci/tasks/lgpo-binary/task.yml image: bosh-windows-stemcell-builder-ci-image - put: version @@ -2128,10 +2110,11 @@ jobs: BOSH_CLIENT: ((iaas_directors_labs-gcp-director_bosh_client.username)) BOSH_CLIENT_SECRET: ((iaas_directors_labs-gcp-director_bosh_client.password)) BOSH_ENVIRONMENT: ((iaas_directors_labs-gcp-director_bosh_environment)) - NETWORK: default - STEMCELL_OS: *windows_os_version - VM_EXTENSIONS: "50GB_ephemeral_disk" - VM_TYPE: large + WINDOWS_SSH_FIREWALL_RULE_NAME: OpenSSH-Server-In-TCP + NETWORK: default + STEMCELL_OS: *windows_os_version + VM_EXTENSIONS: "50GB_ephemeral_disk" + VM_TYPE: large - name: promote diff --git a/ci/tasks/create-aws-stemcell/task.yml b/ci/tasks/create-aws-stemcell/task.yml index c19cc4ec6..37689d870 100644 --- a/ci/tasks/create-aws-stemcell/task.yml +++ b/ci/tasks/create-aws-stemcell/task.yml @@ -7,7 +7,6 @@ inputs: - name: base-amis - name: version - name: lgpo-binary - - name: sshd - name: bosh-agent-release - name: blobstore-dav-cli - name: blobstore-s3-cli diff --git a/ci/tasks/create-azure-stemcell/task.yml b/ci/tasks/create-azure-stemcell/task.yml index 6790cca60..7400921c6 100644 --- a/ci/tasks/create-azure-stemcell/task.yml +++ b/ci/tasks/create-azure-stemcell/task.yml @@ -6,7 +6,6 @@ inputs: - name: version - name: stemcell-builder - name: lgpo-binary - - name: sshd - name: bosh-agent-release - name: blobstore-dav-cli - name: blobstore-s3-cli diff --git a/ci/tasks/create-gcp-stemcell/task.yml b/ci/tasks/create-gcp-stemcell/task.yml index e1e54e79b..3a9bcff12 100644 --- a/ci/tasks/create-gcp-stemcell/task.yml +++ b/ci/tasks/create-gcp-stemcell/task.yml @@ -7,7 +7,6 @@ inputs: - name: base-gcp-image - name: version - name: lgpo-binary - - name: sshd - name: bosh-agent-release - name: blobstore-dav-cli - name: blobstore-s3-cli diff --git a/ci/tasks/generate-deps-file/run.bash b/ci/tasks/generate-deps-file/run.bash index 0880f1e11..d627fb627 100755 --- a/ci/tasks/generate-deps-file/run.bash +++ b/ci/tasks/generate-deps-file/run.bash @@ -1,9 +1,6 @@ #!/usr/bin/env bash set -euo pipefail -openssh_win64_sha256="$(shasum -a 256 open-ssh/OpenSSH-Win64.zip | cut -d " " -f 1)" -openssh_win64_version="$(cat open-ssh/version)" - psmodules_sha256="$(shasum -a 256 psmodules-zip-output/bosh-psmodules.zip | cut -d " " -f 1)" psmodules_version="$(cat version/version)" @@ -15,10 +12,6 @@ lgpo_version="3" cat < deps-file/deps.json { - "OpenSSH-Win64.zip": { - "sha": "${openssh_win64_sha256}", - "version": "${openssh_win64_version}" - }, "bosh-psmodules.zip": { "sha": "${psmodules_sha256}", "version": "${psmodules_version}" diff --git a/ci/tasks/generate-deps-file/task.yml b/ci/tasks/generate-deps-file/task.yml index 73900befa..3638ca209 100644 --- a/ci/tasks/generate-deps-file/task.yml +++ b/ci/tasks/generate-deps-file/task.yml @@ -4,7 +4,6 @@ platform: linux inputs: - name: bosh-windows-stemcell-builder-ci - name: stemcell-builder - - name: open-ssh - name: lgpo-binary - name: version - name: bosh-agent diff --git a/ci/tasks/zip-files/run.bash b/ci/tasks/zip-files/run.bash index 3d791ec23..4265b6b86 100755 --- a/ci/tasks/zip-files/run.bash +++ b/ci/tasks/zip-files/run.bash @@ -6,7 +6,6 @@ ROOT_DIR=$(pwd) REPO_ROOT="${REPO_ROOT:-"$(cd "$(dirname "${BASH_SOURCE[0]}")/../../.." && pwd)"}" ZIP_FILE_DESTINATION="${ZIP_FILE_DESTINATION:-"${ROOT_DIR}/zip-file/StemcellAutomation-$(date +"%s").zip"}" -OPENSSH_ZIP="${OPENSSH_ZIP:-"${ROOT_DIR}/open-ssh/OpenSSH-Win64.zip"}" BOSH_PSMODULES_ZIP="${BOSH_PSMODULES_ZIP:-"${ROOT_DIR}/psmodules-zip-output/bosh-psmodules.zip"}" AGENT_ZIP="${AGENT_ZIP:-"${ROOT_DIR}/bosh-agent/agent.zip"}" DEPS_JSON="${DEPS_JSON:-"${ROOT_DIR}/deps-file/deps.json"}" @@ -20,7 +19,7 @@ mkdir -p "${stemcell_automation_dir}" declare -a files_to_zip mapfile -t files_to_zip < <(find "${REPO_ROOT}/stembuild/stemcell-automation" -type f -not -name "*Test*" -name "*.ps*1") -files_to_zip+=("${OPENSSH_ZIP}" "${BOSH_PSMODULES_ZIP}" "${AGENT_ZIP}" "${DEPS_JSON}") +files_to_zip+=("${BOSH_PSMODULES_ZIP}" "${AGENT_ZIP}" "${DEPS_JSON}") cp "${files_to_zip[@]}" "${stemcell_automation_dir}" diff --git a/ci/tasks/zip-files/task.yml b/ci/tasks/zip-files/task.yml index a660fe999..ccbde4240 100644 --- a/ci/tasks/zip-files/task.yml +++ b/ci/tasks/zip-files/task.yml @@ -4,7 +4,6 @@ platform: linux inputs: - name: bosh-windows-stemcell-builder-ci - name: stemcell-builder - - name: open-ssh - name: deps-file - name: bosh-agent - name: psmodules-zip-output diff --git a/lib/packer/config/azure.rb b/lib/packer/config/azure.rb index 2242c0c0b..d764750e7 100644 --- a/lib/packer/config/azure.rb +++ b/lib/packer/config/azure.rb @@ -45,7 +45,8 @@ def builders "winrm_use_ssl" => "true", "winrm_insecure" => "true", "winrm_timeout" => "1h", - "winrm_username" => "packer" + "winrm_username" => "packer", + "custom_script" => 'powershell -ExecutionPolicy Unrestricted -NoProfile -NonInteractive -Command "Add-WindowsCapability -Online -Name (Get-WindowsCapability -Online -Name OpenSSH.Server* | ForEach-Object Name)"' } ] end diff --git a/lib/packer/config/gcp.rb b/lib/packer/config/gcp.rb index 2f712ddfa..b022ba6ea 100644 --- a/lib/packer/config/gcp.rb +++ b/lib/packer/config/gcp.rb @@ -39,6 +39,7 @@ def initialize( end def builders + stemcell_builder_dir = File.expand_path("../../../../", __FILE__) [ { "type" => "googlecompute", @@ -62,10 +63,10 @@ def builders "winrm_timeout" => "1h", "state_timeout" => "10m", "metadata" => { - "sysprep-specialize-script-url" => "https://raw.githubusercontent.com/cloudfoundry/bosh-windows-stemcell-builder/master/scripts/gcp/setup-winrm.ps1", + "sysprep-specialize-script-ps1" => File.read(File.join(stemcell_builder_dir, "scripts", "gcp", "setup-winrm.ps1")), "name" => "#{@vm_prefix}-#{Time.now.to_i}" - }.compact_blank! - } + } + }.compact_blank! ] end diff --git a/lib/packer/config/templates/provision_windows2019.json.erb b/lib/packer/config/templates/provision_windows2019.json.erb index 57ae7f6ff..fd898a0ef 100644 --- a/lib/packer/config/templates/provision_windows2019.json.erb +++ b/lib/packer/config/templates/provision_windows2019.json.erb @@ -130,16 +130,19 @@ }, <% end %> { - "type": "file", - "source": "../sshd/OpenSSH-Win64.zip", - "destination": "C:\\provision\\OpenSSH-Win64.zip" + "type": "powershell", + "inline": [ + "$ErrorActionPreference = \"Stop\";", + "trap { $host.SetShouldExit(1) }", + "Protect-CFCell -IaaS <%= iaas %>" + ] }, { "type": "powershell", "inline": [ "$ErrorActionPreference = \"Stop\";", "trap { $host.SetShouldExit(1) }", - "Install-SSHD -SSHZipFile 'C:\\provision\\OpenSSH-Win64.zip'" + "Install-SSHD" ] }, { diff --git a/modules/BOSH.SSH/BOSH.SSH.Tests.ps1 b/modules/BOSH.SSH/BOSH.SSH.Tests.ps1 index f55df1566..30afa247f 100644 --- a/modules/BOSH.SSH/BOSH.SSH.Tests.ps1 +++ b/modules/BOSH.SSH/BOSH.SSH.Tests.ps1 @@ -1,4 +1,8 @@ BeforeAll { + Remove-Module -Name BOSH.Utils -ErrorAction Ignore + Import-Module ../BOSH.Utils/BOSH.Utils.psm1 + + Remove-Module -Name BOSH.SSH -ErrorAction Ignore Import-Module ./BOSH.SSH.psm1 function Get-FileEncoding @@ -45,302 +49,211 @@ BeforeAll { $encoding_found } } - - function CreateFakeOpenSSHZip - { - param([string]$dir, [string]$installScriptSpyStatus, [string]$fakeZipPath) - - mkdir "$dir\OpenSSH-Win64" - $installSpyBehavior = "echo installed > $installScriptSpyStatus" - Write-Output $installSpyBehavior > "$dir\OpenSSH-Win64\install-sshd.ps1" - Write-Output "fake sshd" > "$dir\OpenSSH-Win64\sshd.exe" - Write-Output "fake config" > "$dir\OpenSSH-Win64\sshd_config_default" - - Compress-Archive -Force -Path "$dir\OpenSSH-Win64" -DestinationPath $fakeZipPath - } } Describe "BOSH.SSH" { BeforeEach { + Mock -ModuleName BOSH.Utils Write-Log { } Mock -ModuleName BOSH.SSH Write-Log { } } - Describe "Enable-SSHD" { + Describe "Install-SSHD" { BeforeEach { - Mock -ModuleName BOSH.SSH Set-Service { } - Mock -ModuleName BOSH.SSH Invoke-LGPO { } - - $guid = $( New-Guid ).Guid - $TMP_DIR = "$env:TEMP\BOSH.SSH.Tests-$guid" + Mock Set-Service { } -ModuleName BOSH.SSH + Mock Edit-DefaultOpenSSHConfig { } -ModuleName BOSH.SSH + } - $FAKE_ZIP = "$TMP_DIR\OpenSSH-TestFake.zip" - $INSTALL_SCRIPT_SPY_STATUS = "$TMP_DIR\install-script-status" + It "sets the startup type of sshd to disabled" { + Mock Set-Service { } -Verifiable -ModuleName BOSH.SSH -ParameterFilter { $Name -eq "sshd" -and $StartupType -eq "Disabled" } - CreateFakeOpenSSHZip -dir $TMP_DIR -installScriptSpyStatus $INSTALL_SCRIPT_SPY_STATUS -fakeZipPath $FAKE_ZIP + Install-SSHD - mkdir -p "$TMP_DIR\Windows\Temp" - Write-Output "fake LGPO" > "$TMP_DIR\Windows\LGPO.exe" + Assert-VerifiableMock + } - $ORIGINAL_WINDIR = $env:WINDIR - $env:WINDIR = "$TMP_DIR\Windows" + It "sets the startup type of ssh-agent to disabled" { + Mock Set-Service { } -Verifiable -ModuleName BOSH.SSH -ParameterFilter { $Name -eq "ssh-agent" -and $StartupType -eq "Disabled" } - $ORIGINAL_PROGRAMDATA = $env:ProgramData - $env:PROGRAMDATA = "$TMP_DIR\ProgramData" - } + Install-SSHD - AfterEach { - rmdir $TMP_DIR -Recurse -ErrorAction Ignore - $env:WINDIR = $ORIGINAL_WINDIR - $env:PROGRAMDATA = $ORIGINAL_PROGRAMDATA + Assert-VerifiableMock } - It "sets the startup type of sshd to automatic" { - Mock -ModuleName BOSH.SSH Set-Service { } -Verifiable -ParameterFilter { $Name -eq "sshd" -and $StartupType -eq "Automatic" } + It "calls Edit-DefaultOpenSSHConfig" { + Mock Edit-DefaultOpenSSHConfig { } -Verifiable -ModuleName BOSH.SSH - Enable-SSHD -SSHZipFile $FAKE_ZIP + Install-SSHD Assert-VerifiableMock } + } - It "sets the startup type of ssh-agent to automatic" { - Mock -ModuleName BOSH.SSH Set-Service { } -Verifiable -ParameterFilter { $Name -eq "ssh-agent" -and $StartupType -eq "Automatic" } + Describe "Edit-DefaultOpenSSHConfig" { + BeforeEach { + Mock -ModuleName BOSH.SSH Set-Service { } - Enable-SSHD -SSHZipFile $FAKE_ZIP + $guid = $( New-Guid ).Guid + $TMP_DIR = "$env:TEMP\BOSH.SSH.Tests_Edit-DefaultOpenSSHConfig-$guid" - Assert-VerifiableMock - } + $FAKE_WINDIR = "$TMP_DIR\Windows" + mkdir -p "$FAKE_WINDIR\System32\OpenSSH\" + New-Item -ItemType Directory -Path "$FAKE_WINDIR\System32\OpenSSH\" -Force - 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" - } - } -ModuleName BOSH.SSH + $ORIGINAL_WINDIR = $env:WINDIR + $env:WINDIR = $FAKE_WINDIR - Mock -ModuleName BOSH.SSH New-NetFirewallRule { } - Enable-SSHD -SSHZipFile $FAKE_ZIP - Assert-MockCalled New-NetFirewallRule -Times 1 -ModuleName BOSH.SSH -Scope It + $GeneratedConfigPath = "$TMP_DIR/sshd_config" } - 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" - } - } -ModuleName BOSH.SSH - - Mock -ModuleName BOSH.SSH New-NetFirewallRule { } - Enable-SSHD -SSHZipFile $FAKE_ZIP - Assert-MockCalled New-NetFirewallRule -Times 0 -ModuleName BOSH.SSH -Scope It + AfterEach { + $env:WINDIR = $ORIGINAL_WINDIR + rmdir $TMP_DIR -Recurse -ErrorAction Ignore } - It "Generates inf and invokes LGPO if LGPO exists" { - Mock -ModuleName BOSH.SSH Invoke-LGPO -Verifiable -ParameterFilter { $LGPOPath -eq "$TMP_DIR\Windows\LGPO.exe" -and $InfFilePath -eq "$TMP_DIR\Windows\Temp\enable-ssh.inf" } + It "Comments out default configuration for where administrator keys are stored" { + $ConfigPath = "$TMP_DIR/sshd_config_default" + $Content = @" +Match Group administrators +AllowGroups administrators "openssh users" +AuthorizedKeysFile __PROGRAMDATA__/ssh/administrators_authorized_keys +"@ + Out-File -FilePath $ConfigPath -InputObject $Content -Encoding UTF8 + + Edit-DefaultOpenSSHConfig -ConfigPath $ConfigPath -GeneratedConfigPath $GeneratedConfigPath - Enable-SSHD -SSHZipFile $FAKE_ZIP + $ExpectedContent = @" +#Match Group administrators +#AllowGroups administrators "openssh users" +#AuthorizedKeysFile __PROGRAMDATA__/ssh/administrators_authorized_keys +"@ - Assert-VerifiableMock + ((Get-Content $ConfigPath) -join "`n") | Should -Be $ExpectedContent } - It "Skips LGPO if LGPO.exe not found" { - rm "$TMP_DIR\Windows\LGPO.exe" + It "Disables the chacha20-poly1305 cipher to mitigate CVE-2023-48795" { + $ConfigPath = "$TMP_DIR/sshd_config_default" + $Content = @" +#RekeyLimit default none +"@ + Out-File -FilePath $ConfigPath -InputObject $Content -Encoding UTF8 - Enable-SSHD -SSHZipFile $FAKE_ZIP + Edit-DefaultOpenSSHConfig -ConfigPath $ConfigPath -GeneratedConfigPath $GeneratedConfigPath - Assert-MockCalled Invoke-LGPO -Times 0 -ModuleName BOSH.SSH -Scope It - } + $ExpectedContent = @" +#RekeyLimit default none +# Disable cipher to mitigate CVE-2023-48795 +Ciphers -chacha20-poly1305@openssh.com +"@ - Context "When LGPO executable fails" { - It "Throws an appropriate error" { - Mock Invoke-LGPO { throw "some error" } -Verifiable -ModuleName BOSH.SSH -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*" - } + ((Get-Content $ConfigPath) -join "`n") | Should -Match $ExpectedContent } - It "removes existing SSH keys" { - New-Item -ItemType Directory -Path "$TMP_DIR\ProgramData\ssh" -ErrorAction Ignore - Write-Output "delete" > "$TMP_DIR\ProgramData\ssh\ssh_host_1" - Write-Output "delete" > "$TMP_DIR\ProgramData\ssh\ssh_host_2" - Write-Output "delete" > "$TMP_DIR\ProgramData\ssh\ssh_host_3" - Write-Output "ignore" > "$TMP_DIR\ProgramData\ssh\not_ssh_host_4" + It "sets the file encoding to be UTF8" { + $ConfigPath = "$TMP_DIR/sshd_config_default" - Enable-SSHD -SSHZipFile $FAKE_ZIP + Out-File -FilePath $ConfigPath -InputObject "some-fake-content" -Encoding UTF8 - $numHosts = (Get-ChildItem "$TMP_DIR\ProgramData\ssh\").count - $numHosts | Should -eq 1 - } + Edit-DefaultOpenSSHConfig -ConfigPath $ConfigPath -GeneratedConfigPath $GeneratedConfigPath - 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 + Get-FileEncoding $ConfigPath | Should -BeLike "System.Text.UTF8Encoding" } } - Describe "Install-SSHD" { + Describe "Enable-SSHD" { BeforeEach { Mock Set-Service { } -ModuleName BOSH.SSH - Mock Protect-Dir { } -ModuleName BOSH.SSH - Mock Invoke-CACL { } -ModuleName BOSH.SSH - Mock Write-Log { } -ModuleName BOSH.Utils + Mock Get-NetFirewallRule { } -ModuleName BOSH.SSH + Mock New-NetFirewallRule { } -ModuleName BOSH.SSH - $guid = $( New-Guid ).Guid - $TMP_DIR = "$env:TEMP\BOSH.SSH.Tests-$guid" - - mkdir -p "$TMP_DIR\Windows\Temp" - mkdir -p "$TMP_DIR\ProgramData" - - $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 - - $ORIGINAL_PROGRAMFILES = $env:PROGRAMFILES - $env:PROGRAMFILES = "$TMP_DIR\ProgramFiles" + Mock Remove-SSHKeys { } -ModuleName BOSH.SSH } - AfterEach { - rmdir $TMP_DIR -Recurse -ErrorAction Ignore - $env:PROGRAMFILES = $ORIGINAL_PROGRAMFILES - } - - It "extracts OpenSSH to Program Files" { - Install-SSHD -SSHZipFile $FAKE_ZIP - - Get-Item $env:PROGRAMFILES\OpenSSH | Should -Exist - Get-Item $env:PROGRAMFILES\OpenSSH\sshd.exe | Should -Exist - } + It "sets the startup type of sshd to automatic" { + Mock Set-Service { } -ModuleName BOSH.SSH -Verifiable -ParameterFilter { + $Name -eq "sshd" -and $StartupType -eq "Automatic" + } - It "runs the install-sshd script" { - Install-SSHD -SSHZipFile $FAKE_ZIP + Enable-SSHD - "$INSTALL_SCRIPT_SPY_STATUS" | Should -FileContentMatchExactly 'installed' + Assert-VerifiableMock } - It "calls Protect-Dir to lock down permissions" { - Mock Protect-Dir { } -Verifiable -ModuleName BOSH.SSH -ParameterFilter { $path -eq "$env:PROGRAMFILES\OpenSSH" } + It "sets the startup type of ssh-agent to automatic" { + Mock -ModuleName BOSH.SSH Set-Service { } -Verifiable -ParameterFilter { $Name -eq "ssh-agent" -and $StartupType -eq "Automatic" } - Install-SSHD -SSHZipFile $FAKE_ZIP + Enable-SSHD Assert-VerifiableMock } - It "calls Invoke-CACL with expected files" { - Mock Invoke-CACL { } -Verifiable -ModuleName BOSH.SSH -ParameterFilter { - @( - "libcrypto.dll", - "scp.exe", - "sftp-server.exe", - "sftp.exe", - "ssh-add.exe", - "ssh-agent.exe", - "ssh-keygen.exe", - "ssh-keyscan.exe", - "ssh-shellhost.exe", - "ssh.exe", - "sshd.exe" - ) - } + It "sets up firewall when ssh not already set up" { + Mock -ModuleName BOSH.SSH New-NetFirewallRule { } -Verifiable - Install-SSHD -SSHZipFile $FAKE_ZIP + Enable-SSHD - Assert-VerifiableMock + Assert-MockCalled New-NetFirewallRule -ModuleName BOSH.SSH -Times 1 } - It "sets the startup type of sshd to disabled" { - Mock Set-Service { } -Verifiable -ModuleName BOSH.SSH -ParameterFilter { $Name -eq "sshd" -and $StartupType -eq "Disabled" } - - Install-SSHD -SSHZipFile $FAKE_ZIP + It "removes the existing SSH firewall rule and recreates it " { + Mock Get-NetFirewallRule { + return [ordered]@{ + "Name" = "OpenSSH-Server-In-TCP" + } + } -ModuleName BOSH.SSH - Assert-VerifiableMock + Mock Remove-NetFirewallRule { } -ModuleName BOSH.SSH -Verifiable -ParameterFilter { $Name -eq "OpenSSH-Server-In-TCP" } + Mock New-NetFirewallRule { } -ModuleName BOSH.SSH -Verifiable -ParameterFilter { + $Name -eq "OpenSSH-Server-In-TCP" -and + $Enabled -eq "True" -and + $Direction -eq "Inbound" -and + $Protocol -eq "TCP" -and + $Action -eq "Allow" -and + $Profile -eq "Any" -and + $LocalPort -eq 22 + } + Enable-SSHD + Assert-MockCalled Remove-NetFirewallRule -ModuleName BOSH.SSH -Times 1 + Assert-MockCalled New-NetFirewallRule -ModuleName BOSH.SSH -Times 1 } - It "sets the startup type of ssh-agent to disabled" { - Mock Set-Service { } -Verifiable -ModuleName BOSH.SSH -ParameterFilter { $Name -eq "ssh-agent" -and $StartupType -eq "Disabled" } + It "invokes Remove-SSHKeys" { + Mock Remove-SSHKeys { } -ModuleName BOSH.SSH -Verifiable - Install-SSHD -SSHZipFile $FAKE_ZIP + Enable-SSHD Assert-VerifiableMock } - - It "modifies the openssh configuration to remove default admin key location while maintaining UTF-8 encoding" { - Mock Get-Content { - @" -Match Group administrators -AuthorizedKeysFile __PROGRAMDATA__/ssh/administrators_authorized_keys -"@ - } -ModuleName BOSH.SSH -ParameterFilter { $Path -like "*sshd_config_default" } - - Install-SSHD -SSHZipFile $FAKE_ZIP - Get-Content $env:PROGRAMFILES\OpenSSH\sshd_config_default | Out-String | Should -BeLike "#*#*" - Get-FileEncoding $env:PROGRAMFILES\OpenSSH\sshd_config_default | Should -BeLike "System.Text.UTF8Encoding" - } } - Describe "Edit-DefaultOpenSSHConfig"{ - It "Comments out default configuration for where administrator keys are stored" { + Describe "Remove-SSHKeys" { + BeforeEach { + $guid = $( New-Guid ).Guid + $TMP_DIR = "$env:TEMP\BOSH.SSH.Tests_Remove-SSHKeys-$guid" - Mock Get-Content { - @" -Match Group administrators -AuthorizedKeysFile __PROGRAMDATA__/ssh/administrators_authorized_keys -"@ - } -ModuleName BOSH.SSH + $ORIGINAL_PROGRAMDATA = $env:ProgramData + $FAKE_PROGRAMDATA = "$TMP_DIR\ProgramData" - $result = Edit-DefaultOpenSSHConfig -ConfigPath "some/path/sshd_config_default" + $env:ProgramData = $FAKE_PROGRAMDATA - Assert-MockCalled Get-Content -Times 1 -ModuleName BOSH.SSH -Scope It -ParameterFilter { $Path -like "*sshd_config_default" } - $result | Should -BeLike "#*#*" + New-Item -ItemType Directory -Path "$FAKE_PROGRAMDATA\ssh" -Force + Out-File -InputObject "delete" -Encoding UTF8 -FilePath "$FAKE_PROGRAMDATA\ssh\ssh_host_1" + Out-File -InputObject "delete" -Encoding UTF8 -FilePath "$FAKE_PROGRAMDATA\ssh\ssh_host_2" + Out-File -InputObject "delete" -Encoding UTF8 -FilePath "$FAKE_PROGRAMDATA\ssh\ssh_host_3" + Out-File -InputObject "ignore" -Encoding UTF8 -FilePath "$FAKE_PROGRAMDATA\ssh\not_ssh_host_4" } - It "Disables the chacha20-poly1305 cipher to mitigate CVE-2023-48795" { + AfterEach { + $env:ProgramData = $ORIGINAL_PROGRAMDATA + } - Mock Get-Content { - @" -# Ciphers and keying -#RekeyLimit default none -"@ - } -ModuleName BOSH.SSH + It "removes existing SSH keys under 'env:ProgramData\ssh\ssh_host_*'" { + $initialNumFiles = (Get-ChildItem "$FAKE_PROGRAMDATA\ssh\").Count + $initialNumFiles | Should -eq 4 - $result = Edit-DefaultOpenSSHConfig -ConfigPath "some/path/sshd_config_default" + Remove-SSHKeys - Assert-MockCalled Get-Content -Times 1 -ModuleName BOSH.SSH -Scope It -ParameterFilter { $Path -like "*sshd_config_default" } - $result | Should -BeLike "*#RekeyLimit default none`r`n# Disable cipher to mitigate CVE-2023-48795`r`nCiphers -chacha20-poly1305@openssh.com`r`n" + $expectedNumFiles = (Get-ChildItem "$FAKE_PROGRAMDATA\ssh\").Count + $expectedNumFiles | Should -eq 1 } } } diff --git a/modules/BOSH.SSH/BOSH.SSH.psd1 b/modules/BOSH.SSH/BOSH.SSH.psd1 index 46c870a22..3e7c439ab 100644 --- a/modules/BOSH.SSH/BOSH.SSH.psd1 +++ b/modules/BOSH.SSH/BOSH.SSH.psd1 @@ -6,9 +6,8 @@ Copyright = '(c) 2017 BOSH' Description = 'Install Microsoft SSHD' PowerShellVersion = '4.0' - FunctionsToExport = @('Install-SSHD', - 'Enable-SSHD', - 'Remove-SSHKeys') + RequiredModules = @('BOSH.Utils') + FunctionsToExport = @('Install-SSHD', 'Enable-SSHD', 'Remove-SSHKeys') CmdletsToExport = @() VariablesToExport = '*' AliasesToExport = @() diff --git a/modules/BOSH.SSH/BOSH.SSH.psm1 b/modules/BOSH.SSH/BOSH.SSH.psm1 index 86f941625..6e5d0e0bf 100644 --- a/modules/BOSH.SSH/BOSH.SSH.psm1 +++ b/modules/BOSH.SSH/BOSH.SSH.psm1 @@ -1,95 +1,22 @@ function Install-SSHD { - param ( - [string]$SSHZipFile = $( Throw "Provide an SSHD zipfile" ) - ) - - New-Item "$env:PROGRAMFILES\SSHTemp" -Type Directory -Force - Open-Zip -ZipFile $SSHZipFile -OutPath "$env:PROGRAMFILES\SSHTemp" - - $ConfigPath = "$env:PROGRAMFILES\SSHTemp\OpenSSH-Win64\sshd_config_default" - $ModifiedConfigContents = Edit-DefaultOpenSSHConfig -ConfigPath $ConfigPath - Remove-Item -Force $ConfigPath - Out-File -FilePath $ConfigPath -InputObject $ModifiedConfigContents -Encoding UTF8 - - Move-Item -Force "$env:PROGRAMFILES\SSHTemp\OpenSSH-Win64" "$env:PROGRAMFILES\OpenSSH" - Remove-Item -Force "$env:PROGRAMFILES\SSHTemp" - - # Remove users from 'OpenSSH' before installing. The install process - # will add back permissions for the NT AUTHORITY\Authenticated Users for some files - Protect-Dir -path "$env:PROGRAMFILES\OpenSSH" - - Push-Location "$env:PROGRAMFILES\OpenSSH" - powershell -ExecutionPolicy Bypass -File install-sshd.ps1 - Pop-Location - - # # Grant NT AUTHORITY\Authenticated Users access to .EXEs and the .DLL in OpenSSH - $FileNames = @( - "libcrypto.dll", - "scp.exe", - "sftp-server.exe", - "sftp.exe", - "ssh-add.exe", - "ssh-agent.exe", - "ssh-keygen.exe", - "ssh-keyscan.exe", - "ssh-shellhost.exe", - "ssh.exe", - "sshd.exe" - ) - Invoke-CACL -FileNames $FileNames + Edit-DefaultOpenSSHConfig Set-Service -Name sshd -StartupType Disabled - # ssh-agent is not the same as ssh-agent in *nix openssh Set-Service -Name ssh-agent -StartupType Disabled } function Enable-SSHD { - if ($null -eq (Get-NetFirewallRule | Where-Object { $_.DisplayName -ieq 'SSH' })) - { - "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 -[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 - { - Invoke-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." + # Remove existing OpenSSH firewall rule and recreate with '-Profile Any' option + if (Get-NetFirewallRule -Name "OpenSSH-Server-In-TCP" -ErrorAction SilentlyContinue) { + "Removing firewall rule: 'OpenSSH-Server-In-TCP'" + Remove-NetFirewallRule -Name "OpenSSH-Server-In-TCP" } + Write-Log "Creating firewall rule 'OpenSSH-Server-In-TCP'" + New-NetFirewallRule -Name 'OpenSSH-Server-In-TCP' -DisplayName 'OpenSSH Server (sshd)' -Enabled True -Direction Inbound -Protocol TCP -Action Allow -Profile Any -LocalPort 22 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 @@ -97,48 +24,36 @@ SeAssignPrimaryTokenPrivilege=*S-1-5-19,*S-1-5-20,*S-1-5-80-3847866527-469524349 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" + Write-Log "Removing any existing host keys" Remove-Item -Path "$env:ProgramData\ssh\ssh_host_*" -ErrorAction Ignore - Pop-Location } -function Invoke-CACL +function Edit-DefaultOpenSSHConfig { param ( - [string[]] $FileNames = $( Throw "Files not provided" ) + [string]$ConfigPath = "$env:windir\System32\OpenSSH\sshd_config_default", + [string]$GeneratedConfigPath = "$env:ProgramData\ssh\sshd_config" ) - foreach ($name in $FileNames) - { - $path = Join-Path "$env:PROGRAMFILES\OpenSSH" $name - cacls.exe $Path /E /P "NT AUTHORITY\Authenticated Users:R" - } -} + Copy-Item -Path $ConfigPath -Destination "$ConfigPath.bak" -function Invoke-LGPO -{ - param ( - [string]$LGPOPath = $( Throw "Provide LGPO path" ), - [string]$InfFilePath = $( Throw "Provide Inf file path" ) - ) - & $LGPOPath /s $InfFilePath -} + $OriginalConfig = Get-Content $ConfigPath + Write-Log "Original SSH config at $ConfigPath :" + Write-Log "$OriginalConfig" -function Edit-DefaultOpenSSHConfig -{ - param ( - [string]$ConfigPath = $( Throw "Provide openssh default config path" ) - ) + $ModifiedConfig = $OriginalConfig ` + | ForEach-Object{ $_ -replace ".*Match Group administrators.*", "#$&" } ` + | ForEach-Object{ $_ -replace ".*AllowGroups administrators.*", "#$&" } ` + | ForEach-Object{ $_ -replace ".*AuthorizedKeysFile __PROGRAMDATA__/ssh/administrators_authorized_keys.*", "#$&" } ` + | ForEach-Object{ $_ -replace "#RekeyLimit default none", "$&`r`n# Disable cipher to mitigate CVE-2023-48795`r`nCiphers -chacha20-poly1305@openssh.com`r`n" } + + Write-Log "Modified SSH config at $ConfigPath :" + Write-Log "$ModifiedConfig" - $ModifiedConfig = Get-Content $ConfigPath ` - | ForEach-Object{ $_ -replace ".*Match Group administrators.*", "#$&" } ` - | ForEach-Object{ $_ -replace ".*AuthorizedKeysFile __PROGRAMDATA__/ssh/administrators_authorized_keys.*", "#$&" } ` - | ForEach-Object{ $_ -replace "#RekeyLimit default none", "$&`r`n# Disable cipher to mitigate CVE-2023-48795`r`nCiphers -chacha20-poly1305@openssh.com`r`n" } + Remove-Item -Force $ConfigPath + Out-File -FilePath $ConfigPath -InputObject $ModifiedConfig -Encoding UTF8 - return $ModifiedConfig + # We need to make sure that the generated config is cleared, so our above changes are applied when the config + # is next generated. If this isnt done, then we may have a config from the prior template. + Remove-Item -Path $GeneratedConfigPath -ErrorAction Ignore } diff --git a/modules/BOSH.Utils/BOSH.Utils.Tests.ps1 b/modules/BOSH.Utils/BOSH.Utils.Tests.ps1 index b51fcf3de..642caafd1 100644 --- a/modules/BOSH.Utils/BOSH.Utils.Tests.ps1 +++ b/modules/BOSH.Utils/BOSH.Utils.Tests.ps1 @@ -2,6 +2,8 @@ BeforeAll { Remove-Module -Name BOSH.Utils -ErrorAction Ignore Import-Module ./BOSH.Utils.psm1 + $osVersion = "windows2019" + # As of now, this function only supports DWords and Strings. function Restore-RegistryState { @@ -328,7 +330,7 @@ Describe "BOSH.Utils" { $actualOSVersion = $null { Get-OSVersion | Set-Variable -Name "actualOSVersion" -Scope 1 } | Should -Not -Throw - $actualOsVersion | Should -eq "windows2019" + $actualOsVersion | Should -eq $osVersion Assert-MockCalled Write-Log -Times 1 -Scope It -ParameterFilter { $Message -eq "Found OS version: Windows 2019" } -ModuleName BOSH.Utils Assert-MockCalled Get-OSVersionString -Times 1 -Scope It -ModuleName BOSH.Utils diff --git a/scripts/aws/setup_winrm.txt b/scripts/aws/setup_winrm.txt index 88a9fe4f2..ee4bba5a3 100644 --- a/scripts/aws/setup_winrm.txt +++ b/scripts/aws/setup_winrm.txt @@ -34,5 +34,10 @@ if (-not (Get-Command Enable-WinRM -errorAction SilentlyContinue)) Write-Log "Invoking WinRM" Enable-WinRM +Write-Log "Install OpenSSH.Server" +Write-Log (Get-WindowsCapability -Online -Name "OpenSSH.Server*" | Format-List | Out-String) +Add-WindowsCapability -Online -Name (Get-WindowsCapability -Online -Name "OpenSSH.Server*" | ForEach-Object Name) +Write-Log (Get-WindowsCapability -Online -Name "OpenSSH.Server*" | Format-List | Out-String) + Set-ExecutionPolicy -ExecutionPolicy Bypass -Scope LocalMachine diff --git a/scripts/gcp/setup-winrm.ps1 b/scripts/gcp/setup-winrm.ps1 index 43e7fd82a..370b9342e 100644 --- a/scripts/gcp/setup-winrm.ps1 +++ b/scripts/gcp/setup-winrm.ps1 @@ -32,3 +32,8 @@ if (-not (Get-Command Enable-WinRM -errorAction SilentlyContinue)) Write-Log "Invoking WinRM" Enable-WinRM + +Write-Log "Install OpenSSH.Server" +Write-Log (Get-WindowsCapability -Online -Name "OpenSSH.Server*" | Format-List | Out-String) +Add-WindowsCapability -Online -Name (Get-WindowsCapability -Online -Name "OpenSSH.Server*" | ForEach-Object Name) +Write-Log (Get-WindowsCapability -Online -Name "OpenSSH.Server*" | Format-List | Out-String) diff --git a/spec/packer/config/aws_spec.rb b/spec/packer/config/aws_spec.rb index d86de94e6..5b0e86352 100644 --- a/spec/packer/config/aws_spec.rb +++ b/spec/packer/config/aws_spec.rb @@ -178,8 +178,8 @@ {"type" => "powershell", "inline" => ["$ErrorActionPreference = \"Stop\";", "trap { $host.SetShouldExit(1) }", "Get-HotFix > hotfixes.log"]}, {"type" => "file", "source" => "hotfixes.log", "destination" => "hotfixes.log", "direction" => "download"}, {"type" => "powershell", "inline" => ["$ErrorActionPreference = \"Stop\";", "trap { $host.SetShouldExit(1) }", "Remove-Account -User Provisioner"]}, - {"type" => "file", "source" => "../sshd/OpenSSH-Win64.zip", "destination" => "C:\\provision\\OpenSSH-Win64.zip"}, - {"type" => "powershell", "inline" => ["$ErrorActionPreference = \"Stop\";", "trap { $host.SetShouldExit(1) }", "Install-SSHD -SSHZipFile 'C:\\provision\\OpenSSH-Win64.zip'"]}, + {"type" => "powershell", "inline" => ["$ErrorActionPreference = \"Stop\";", "trap { $host.SetShouldExit(1) }", "Protect-CFCell -IaaS aws"]}, + {"type" => "powershell", "inline" => ["$ErrorActionPreference = \"Stop\";", "trap { $host.SetShouldExit(1) }", "Install-SSHD"]}, {"type" => "powershell", "inline" => ["$ErrorActionPreference = \"Stop\";", "trap { $host.SetShouldExit(1) }", "Enable-SSHD"]}, {"type" => "file", "source" => "build/agent.zip", "destination" => "C:\\provision\\agent.zip"}, {"type" => "powershell", "inline" => ["$ErrorActionPreference = \"Stop\";", "trap { $host.SetShouldExit(1) }", "Install-Agent -IaaS aws -agentZipPath 'C:\\provision\\agent.zip'"]}, diff --git a/spec/packer/config/azure_spec.rb b/spec/packer/config/azure_spec.rb index 32bd57745..4b26fbae1 100644 --- a/spec/packer/config/azure_spec.rb +++ b/spec/packer/config/azure_spec.rb @@ -138,8 +138,8 @@ {"type" => "powershell", "inline" => ["$ErrorActionPreference = \"Stop\";", "trap { $host.SetShouldExit(1) }", "Get-HotFix > hotfixes.log"]}, {"type" => "file", "source" => "hotfixes.log", "destination" => "hotfixes.log", "direction" => "download"}, {"type" => "powershell", "inline" => ["$ErrorActionPreference = \"Stop\";", "trap { $host.SetShouldExit(1) }", "Remove-Account -User Provisioner"]}, - {"type" => "file", "source" => "../sshd/OpenSSH-Win64.zip", "destination" => "C:\\provision\\OpenSSH-Win64.zip"}, - {"type" => "powershell", "inline" => ["$ErrorActionPreference = \"Stop\";", "trap { $host.SetShouldExit(1) }", "Install-SSHD -SSHZipFile 'C:\\provision\\OpenSSH-Win64.zip'"]}, + {"type" => "powershell", "inline" => ["$ErrorActionPreference = \"Stop\";", "trap { $host.SetShouldExit(1) }", "Protect-CFCell -IaaS azure"]}, + {"type" => "powershell", "inline" => ["$ErrorActionPreference = \"Stop\";", "trap { $host.SetShouldExit(1) }", "Install-SSHD"]}, {"type" => "powershell", "inline" => ["$ErrorActionPreference = \"Stop\";", "trap { $host.SetShouldExit(1) }", "Enable-SSHD"]}, {"type" => "file", "source" => "build/agent.zip", "destination" => "C:\\provision\\agent.zip"}, {"type" => "powershell", "inline" => ["$ErrorActionPreference = \"Stop\";", "trap { $host.SetShouldExit(1) }", "Install-Agent -IaaS azure -agentZipPath 'C:\\provision\\agent.zip'"]}, diff --git a/spec/packer/config/gcp_spec.rb b/spec/packer/config/gcp_spec.rb index d0816d702..ec6cc32a6 100644 --- a/spec/packer/config/gcp_spec.rb +++ b/spec/packer/config/gcp_spec.rb @@ -37,14 +37,12 @@ "zone" => "us-west1-c", "disk_size" => 32, "machine_type" => "some-vm-type", - "omit_external_ip" => false, "communicator" => "winrm", "winrm_username" => "winrmuser", - "winrm_use_ssl" => false, "winrm_timeout" => "1h", "state_timeout" => "10m", "metadata" => { - "sysprep-specialize-script-url" => "https://raw.githubusercontent.com/cloudfoundry/bosh-windows-stemcell-builder/master/scripts/gcp/setup-winrm.ps1", + "sysprep-specialize-script-ps1" => File.read(setup_winrm_script), "name" => "some-vm-prefix-#{Time.now.to_i}" } } @@ -120,8 +118,8 @@ {"type" => "powershell", "inline" => ["$ErrorActionPreference = \"Stop\";", "trap { $host.SetShouldExit(1) }", "Get-HotFix > hotfixes.log"]}, {"type" => "file", "source" => "hotfixes.log", "destination" => "hotfixes.log", "direction" => "download"}, {"type" => "powershell", "inline" => ["$ErrorActionPreference = \"Stop\";", "trap { $host.SetShouldExit(1) }", "Remove-Account -User Provisioner"]}, - {"type" => "file", "source" => "../sshd/OpenSSH-Win64.zip", "destination" => "C:\\provision\\OpenSSH-Win64.zip"}, - {"type" => "powershell", "inline" => ["$ErrorActionPreference = \"Stop\";", "trap { $host.SetShouldExit(1) }", "Install-SSHD -SSHZipFile 'C:\\provision\\OpenSSH-Win64.zip'"]}, + {"type" => "powershell", "inline" => ["$ErrorActionPreference = \"Stop\";", "trap { $host.SetShouldExit(1) }", "Protect-CFCell -IaaS gcp"]}, + {"type" => "powershell", "inline" => ["$ErrorActionPreference = \"Stop\";", "trap { $host.SetShouldExit(1) }", "Install-SSHD"]}, {"type" => "powershell", "inline" => ["$ErrorActionPreference = \"Stop\";", "trap { $host.SetShouldExit(1) }", "Enable-SSHD"]}, {"type" => "file", "source" => "build/agent.zip", "destination" => "C:\\provision\\agent.zip"}, {"type" => "powershell", "inline" => ["$ErrorActionPreference = \"Stop\";", "trap { $host.SetShouldExit(1) }", "Install-Agent -IaaS gcp -agentZipPath 'C:\\provision\\agent.zip'"]}, @@ -142,9 +140,9 @@ provisioners.detect { |p| p.has_key?("inline") && p["inline"].include?("New-VersionFile -Version '#{build_version}'") } ).not_to(be_nil, "Expect provisioners to include New-VersionFile") - line_by_line_provisioners = provisioners.delete_if { |x| x["destination"] == "C:\\windows\\LGPO.exe" } - line_by_line_provisioners = - line_by_line_provisioners.delete_if { |p| p.has_key?("inline") && p["inline"].include?("New-VersionFile -Version '#{build_version}'") } + line_by_line_provisioners = provisioners.delete_if { |x| x["destination"] == "C:\\windows\\LGPO.exe" } + line_by_line_provisioners = + line_by_line_provisioners.delete_if { |p| p.has_key?("inline") && p["inline"].include?("New-VersionFile -Version '#{build_version}'") } expect(line_by_line_provisioners).to eq(expected_provisioners_base) end diff --git a/stembuild/README.md b/stembuild/README.md index 63e8f377e..656381897 100644 --- a/stembuild/README.md +++ b/stembuild/README.md @@ -240,7 +240,6 @@ You will need to construct `assets/StemcellAutomation.zip`. This file represents **assets/StemcellAutomation.zip files:** | File | Source / Description | |-|-| -| OpenSSH-Win64.zip | https://github.com/PowerShell/Win32-OpenSSH/releases | | bosh-psmodules.zip | https://github.com/cloudfoundry/bosh-psmodules/tree/master/modules | | agent.zip | A zip constructed using various BOSH executables. See list of necessary files below. | | deps.json | A JSON file with the SHA256 checksums and optionally the version for each component in this zip. See format below. | @@ -261,9 +260,6 @@ You will need to construct `assets/StemcellAutomation.zip`. This file represents **deps.json format:** ```json { - "OpenSSH-Win64.zip": { - "sha": "SOME-SHA256" - }, "bosh-psmodules.zip": { "sha": "SOME-SHA256" }, @@ -279,7 +275,6 @@ You will need to construct `assets/StemcellAutomation.zip`. This file represents Once you have these files, run: ```bash -OPENSSH_ZIP="OpenSSH-Win64.zip" \ BOSH_PSMODULES_ZIP="bosh-psmodules.zip" \ AGENT_ZIP="agent.zip" \ DEPS_JSON="deps.json" \ diff --git a/stembuild/construct/constructfakes/fake_iaas_client.go b/stembuild/construct/constructfakes/fake_iaas_client.go index 833854a2b..a0e90df01 100644 --- a/stembuild/construct/constructfakes/fake_iaas_client.go +++ b/stembuild/construct/constructfakes/fake_iaas_client.go @@ -35,6 +35,20 @@ type FakeIaasClient struct { makeDirectoryReturnsOnCall map[int]struct { result1 error } + RunStub func(string, string, string, []string) error + runMutex sync.RWMutex + runArgsForCall []struct { + arg1 string + arg2 string + arg3 string + arg4 []string + } + runReturns struct { + result1 error + } + runReturnsOnCall map[int]struct { + result1 error + } StartStub func(string, string, string, string, ...string) (string, error) startMutex sync.RWMutex startArgsForCall []struct { @@ -215,6 +229,75 @@ func (fake *FakeIaasClient) MakeDirectoryReturnsOnCall(i int, result1 error) { }{result1} } +func (fake *FakeIaasClient) Run(arg1 string, arg2 string, arg3 string, arg4 []string) error { + var arg4Copy []string + if arg4 != nil { + arg4Copy = make([]string, len(arg4)) + copy(arg4Copy, arg4) + } + fake.runMutex.Lock() + ret, specificReturn := fake.runReturnsOnCall[len(fake.runArgsForCall)] + fake.runArgsForCall = append(fake.runArgsForCall, struct { + arg1 string + arg2 string + arg3 string + arg4 []string + }{arg1, arg2, arg3, arg4Copy}) + stub := fake.RunStub + fakeReturns := fake.runReturns + fake.recordInvocation("Run", []interface{}{arg1, arg2, arg3, arg4Copy}) + fake.runMutex.Unlock() + if stub != nil { + return stub(arg1, arg2, arg3, arg4) + } + if specificReturn { + return ret.result1 + } + return fakeReturns.result1 +} + +func (fake *FakeIaasClient) RunCallCount() int { + fake.runMutex.RLock() + defer fake.runMutex.RUnlock() + return len(fake.runArgsForCall) +} + +func (fake *FakeIaasClient) RunCalls(stub func(string, string, string, []string) error) { + fake.runMutex.Lock() + defer fake.runMutex.Unlock() + fake.RunStub = stub +} + +func (fake *FakeIaasClient) RunArgsForCall(i int) (string, string, string, []string) { + fake.runMutex.RLock() + defer fake.runMutex.RUnlock() + argsForCall := fake.runArgsForCall[i] + return argsForCall.arg1, argsForCall.arg2, argsForCall.arg3, argsForCall.arg4 +} + +func (fake *FakeIaasClient) RunReturns(result1 error) { + fake.runMutex.Lock() + defer fake.runMutex.Unlock() + fake.RunStub = nil + fake.runReturns = struct { + result1 error + }{result1} +} + +func (fake *FakeIaasClient) RunReturnsOnCall(i int, result1 error) { + fake.runMutex.Lock() + defer fake.runMutex.Unlock() + fake.RunStub = nil + if fake.runReturnsOnCall == nil { + fake.runReturnsOnCall = make(map[int]struct { + result1 error + }) + } + fake.runReturnsOnCall[i] = struct { + result1 error + }{result1} +} + func (fake *FakeIaasClient) Start(arg1 string, arg2 string, arg3 string, arg4 string, arg5 ...string) (string, error) { fake.startMutex.Lock() ret, specificReturn := fake.startReturnsOnCall[len(fake.startArgsForCall)] diff --git a/stembuild/construct/vmconstruct.go b/stembuild/construct/vmconstruct.go index dba4cc83c..08530c111 100644 --- a/stembuild/construct/vmconstruct.go +++ b/stembuild/construct/vmconstruct.go @@ -71,7 +71,6 @@ func NewVMConstruct( scriptExecutor ScriptExecutorI, setupFlags []string, ) *VMConstruct { - return &VMConstruct{ ctx: ctx, remoteManager: remoteManager, @@ -114,6 +113,7 @@ type GuestManager interface { type IaasClient interface { UploadArtifact(vmInventoryPath, artifact, destination, username, password string) error MakeDirectory(vmInventoryPath, path, username, password string) error + Run(vmInventoryPath, username, password string, commandArgs []string) error Start(vmInventoryPath, username, password, command string, args ...string) (string, error) WaitForExit(vmInventoryPath, username, password, pid string) (int, error) IsPoweredOff(vmInventoryPath string) (bool, error) @@ -143,6 +143,13 @@ func (c *VMConstruct) PrepareVM() error { } c.messenger.PrintOut("\nAll files have been uploaded.\n") + c.messenger.PrintOut("\nInstalling OpenSSH on target VM...") + err = c.installOpenSSH() + if err != nil { + return err + } + c.messenger.PrintOut("OpenSSH install succeeded.\n") + c.messenger.PrintOut("\nAttempting to enable WinRM on the guest vm...") err = c.winRMEnabler.Enable() if err != nil { @@ -270,6 +277,12 @@ func (c *VMConstruct) isPoweredOff(duration time.Duration) error { return err } +func (c *VMConstruct) installOpenSSH() error { + const installOpenSSHCommand = `"Add-WindowsCapability -Online -Name (Get-WindowsCapability -Online -Name OpenSSH.Server* | ForEach-Object Name)"` + + return c.Client.Run(c.vmInventoryPath, c.vmUsername, c.vmPassword, []string{powershellExePath, installOpenSSHCommand}) +} + func EncodePowershellCommand(command []byte) string { runeCommand := []rune(string(command)) utf16Command := utf16.Encode(runeCommand) diff --git a/stembuild/construct/vmconstruct_test.go b/stembuild/construct/vmconstruct_test.go index f9c77f6fa..19f56bfc6 100644 --- a/stembuild/construct/vmconstruct_test.go +++ b/stembuild/construct/vmconstruct_test.go @@ -32,7 +32,7 @@ func (p *nonWaitingPoller) Poll(_ time.Duration, loopFunc func() (bool, error)) return nil } -var _ = Describe("construct_helpers", func() { +var _ = Describe("VMConstruct", func() { var ( outBuf *Buffer errBuf *Buffer @@ -125,7 +125,7 @@ var _ = Describe("construct_helpers", func() { Expect(vmConstruct.PrepareVM()).NotTo(Succeed()) Expect(fakeVcenterClient.MakeDirectoryCallCount()).To(Equal(1)) - Expect(fakeWinRMEnabler.EnableCallCount()).To(Equal(0)) + Expect(fakeVcenterClient.RunCallCount()).To(Equal(0)) }) It("it logs the attempt", func() { @@ -141,7 +141,7 @@ var _ = Describe("construct_helpers", func() { Expect(vmConstruct.PrepareVM()).To(Succeed()) Expect(fakeVcenterClient.MakeDirectoryCallCount()).To(Equal(1)) - Expect(fakeWinRMEnabler.EnableCallCount()).To(Equal(1)) + Expect(fakeVcenterClient.RunCallCount()).To(Equal(1)) }) It("it logs success", func() { @@ -244,6 +244,7 @@ var _ = Describe("construct_helpers", func() { Expect(fakeVcenterClient.MakeDirectoryCallCount()).To(Equal(1)) Expect(fakeVcenterClient.UploadArtifactCallCount()).To(Equal(2)) + Expect(fakeVcenterClient.RunCallCount()).To(Equal(0)) }) It("it logs the attempt", func() { @@ -261,7 +262,7 @@ var _ = Describe("construct_helpers", func() { Expect(fakeVcenterClient.MakeDirectoryCallCount()).To(Equal(1)) Expect(fakeVcenterClient.UploadArtifactCallCount()).To(Equal(2)) Expect(fakeWinRMEnabler.EnableCallCount()).To(Equal(1)) - Expect(fakeVMConnectionValidator.ValidateCallCount()).To(Equal(1)) + Expect(fakeVcenterClient.RunCallCount()).To(Equal(1)) }) It("it logs success", func() { @@ -272,6 +273,71 @@ var _ = Describe("construct_helpers", func() { }) }) + Describe("it installs Microsoft's OpenSSH", func() { + Context("when it fails", func() { + var installOpenSshErr error + BeforeEach(func() { + installOpenSshErr = errors.New("fake-install-open-ssh-error") + fakeVcenterClient.RunReturns(installOpenSshErr) + }) + + It("returns the error", func() { + err := vmConstruct.PrepareVM() + Expect(err).To(Equal(installOpenSshErr)) + }) + + It("does not execute the next step", func() { + Expect(vmConstruct.PrepareVM()).NotTo(Succeed()) + + Expect(fakeVcenterClient.MakeDirectoryCallCount()).To(Equal(1)) + Expect(fakeVcenterClient.UploadArtifactCallCount()).To(Equal(2)) + Expect(fakeVcenterClient.RunCallCount()).To(Equal(1)) + Expect(fakeWinRMEnabler.EnableCallCount()).To(Equal(0)) + }) + + It("it logs the attempt", func() { + Expect(vmConstruct.PrepareVM()).NotTo(Succeed()) + + Eventually(outBuf).Should(Say("\nInstalling OpenSSH on target VM...")) + Eventually(outBuf).ShouldNot(Say("\nInstalling OpenSSH on target VM...OpenSSH install succeeded.\n")) + }) + }) + + Context("when it succeeds", func() { + It("executes the next step", func() { + Expect(vmConstruct.PrepareVM()).To(Succeed()) + + Expect(fakeVcenterClient.MakeDirectoryCallCount()).To(Equal(1)) + Expect(fakeVcenterClient.UploadArtifactCallCount()).To(Equal(2)) + Expect(fakeVcenterClient.RunCallCount()).To(Equal(1)) + Expect(fakeWinRMEnabler.EnableCallCount()).To(Equal(1)) + }) + + It("invokes VCenterClient.Run() as expected", func() { + Expect(vmConstruct.PrepareVM()).To(Succeed()) + + expectedCommandArgs := []string{ + `C:\Windows\System32\WindowsPowerShell\V1.0\powershell.exe`, + `"Add-WindowsCapability -Online -Name (Get-WindowsCapability -Online -Name OpenSSH.Server* | ForEach-Object Name)"`, + } + + actualVmInventoryPath, actualVmUsername, actualVmPassword, actualCommandArgs := + fakeVcenterClient.RunArgsForCall(0) + + Expect(actualVmInventoryPath).To(Equal(vmInventoryPath)) + Expect(actualVmUsername).To(Equal(vmUsername)) + Expect(actualVmPassword).To(Equal(vmPassword)) + Expect(actualCommandArgs).To(Equal(expectedCommandArgs)) + }) + + It("it logs success", func() { + Expect(vmConstruct.PrepareVM()).To(Succeed()) + + Eventually(outBuf).Should(Say("\nInstalling OpenSSH on target VM...OpenSSH install succeeded.\n")) + }) + }) + }) + Describe("enables WinRM", func() { Context("when it fails", func() { var enableErr error @@ -332,6 +398,7 @@ var _ = Describe("construct_helpers", func() { Expect(fakeVcenterClient.MakeDirectoryCallCount()).To(Equal(1)) Expect(fakeVcenterClient.UploadArtifactCallCount()).To(Equal(2)) + Expect(fakeVcenterClient.RunCallCount()).To(Equal(1)) Expect(fakeWinRMEnabler.EnableCallCount()).To(Equal(1)) Expect(fakeVMConnectionValidator.ValidateCallCount()).To(Equal(1)) Expect(fakeRemoteManager.ExtractArchiveCallCount()).To(Equal(0)) @@ -351,6 +418,7 @@ var _ = Describe("construct_helpers", func() { Expect(fakeVcenterClient.MakeDirectoryCallCount()).To(Equal(1)) Expect(fakeVcenterClient.UploadArtifactCallCount()).To(Equal(2)) + Expect(fakeVcenterClient.RunCallCount()).To(Equal(1)) Expect(fakeWinRMEnabler.EnableCallCount()).To(Equal(1)) Expect(fakeVMConnectionValidator.ValidateCallCount()).To(Equal(1)) Expect(fakeRemoteManager.ExtractArchiveCallCount()).To(Equal(1)) @@ -382,6 +450,7 @@ var _ = Describe("construct_helpers", func() { Expect(fakeVcenterClient.MakeDirectoryCallCount()).To(Equal(1)) Expect(fakeVcenterClient.UploadArtifactCallCount()).To(Equal(2)) + Expect(fakeVcenterClient.RunCallCount()).To(Equal(1)) Expect(fakeWinRMEnabler.EnableCallCount()).To(Equal(1)) Expect(fakeVMConnectionValidator.ValidateCallCount()).To(Equal(1)) Expect(fakeRemoteManager.ExtractArchiveCallCount()).To(Equal(1)) @@ -402,6 +471,7 @@ var _ = Describe("construct_helpers", func() { Expect(fakeVcenterClient.MakeDirectoryCallCount()).To(Equal(1)) Expect(fakeVcenterClient.UploadArtifactCallCount()).To(Equal(2)) + Expect(fakeVcenterClient.RunCallCount()).To(Equal(1)) Expect(fakeWinRMEnabler.EnableCallCount()).To(Equal(1)) Expect(fakeVMConnectionValidator.ValidateCallCount()).To(Equal(1)) Expect(fakeRemoteManager.ExtractArchiveCallCount()).To(Equal(1)) @@ -450,6 +520,7 @@ var _ = Describe("construct_helpers", func() { Expect(fakeVcenterClient.MakeDirectoryCallCount()).To(Equal(1)) Expect(fakeVcenterClient.UploadArtifactCallCount()).To(Equal(2)) + Expect(fakeVcenterClient.RunCallCount()).To(Equal(1)) Expect(fakeWinRMEnabler.EnableCallCount()).To(Equal(1)) Expect(fakeVMConnectionValidator.ValidateCallCount()).To(Equal(1)) Expect(fakeRemoteManager.ExtractArchiveCallCount()).To(Equal(1)) @@ -471,6 +542,7 @@ var _ = Describe("construct_helpers", func() { Expect(fakeVcenterClient.MakeDirectoryCallCount()).To(Equal(1)) Expect(fakeVcenterClient.UploadArtifactCallCount()).To(Equal(2)) + Expect(fakeVcenterClient.RunCallCount()).To(Equal(1)) Expect(fakeWinRMEnabler.EnableCallCount()).To(Equal(1)) Expect(fakeVMConnectionValidator.ValidateCallCount()).To(Equal(1)) Expect(fakeRemoteManager.ExtractArchiveCallCount()).To(Equal(1)) @@ -515,6 +587,7 @@ var _ = Describe("construct_helpers", func() { Expect(fakeVcenterClient.MakeDirectoryCallCount()).To(Equal(1)) Expect(fakeVcenterClient.UploadArtifactCallCount()).To(Equal(2)) + Expect(fakeVcenterClient.RunCallCount()).To(Equal(1)) Expect(fakeWinRMEnabler.EnableCallCount()).To(Equal(1)) Expect(fakeVMConnectionValidator.ValidateCallCount()).To(Equal(1)) Expect(fakeRemoteManager.ExtractArchiveCallCount()).To(Equal(1)) @@ -537,6 +610,7 @@ var _ = Describe("construct_helpers", func() { Expect(fakeVcenterClient.MakeDirectoryCallCount()).To(Equal(1)) Expect(fakeVcenterClient.UploadArtifactCallCount()).To(Equal(2)) + Expect(fakeVcenterClient.RunCallCount()).To(Equal(1)) Expect(fakeWinRMEnabler.EnableCallCount()).To(Equal(1)) Expect(fakeVMConnectionValidator.ValidateCallCount()).To(Equal(1)) Expect(fakeRemoteManager.ExtractArchiveCallCount()).To(Equal(1)) @@ -571,6 +645,7 @@ var _ = Describe("construct_helpers", func() { Expect(fakeVcenterClient.MakeDirectoryCallCount()).To(Equal(1)) Expect(fakeVcenterClient.UploadArtifactCallCount()).To(Equal(2)) + Expect(fakeVcenterClient.RunCallCount()).To(Equal(1)) Expect(fakeWinRMEnabler.EnableCallCount()).To(Equal(1)) Expect(fakeVMConnectionValidator.ValidateCallCount()).To(Equal(1)) Expect(fakeRemoteManager.ExtractArchiveCallCount()).To(Equal(1)) @@ -594,6 +669,7 @@ var _ = Describe("construct_helpers", func() { Expect(fakeVcenterClient.MakeDirectoryCallCount()).To(Equal(1)) Expect(fakeVcenterClient.UploadArtifactCallCount()).To(Equal(2)) + Expect(fakeVcenterClient.RunCallCount()).To(Equal(1)) Expect(fakeWinRMEnabler.EnableCallCount()).To(Equal(1)) Expect(fakeVMConnectionValidator.ValidateCallCount()).To(Equal(1)) Expect(fakeRemoteManager.ExtractArchiveCallCount()).To(Equal(1)) @@ -634,6 +710,7 @@ var _ = Describe("construct_helpers", func() { Expect(fakeVcenterClient.MakeDirectoryCallCount()).To(Equal(1)) Expect(fakeVcenterClient.UploadArtifactCallCount()).To(Equal(2)) + Expect(fakeVcenterClient.RunCallCount()).To(Equal(1)) Expect(fakeWinRMEnabler.EnableCallCount()).To(Equal(1)) Expect(fakeVMConnectionValidator.ValidateCallCount()).To(Equal(1)) Expect(fakeRemoteManager.ExtractArchiveCallCount()).To(Equal(1)) @@ -668,6 +745,7 @@ var _ = Describe("construct_helpers", func() { Expect(fakeVcenterClient.MakeDirectoryCallCount()).To(Equal(1)) Expect(fakeVcenterClient.UploadArtifactCallCount()).To(Equal(2)) + Expect(fakeVcenterClient.RunCallCount()).To(Equal(1)) Expect(fakeWinRMEnabler.EnableCallCount()).To(Equal(1)) Expect(fakeVMConnectionValidator.ValidateCallCount()).To(Equal(1)) Expect(fakeRemoteManager.ExtractArchiveCallCount()).To(Equal(1)) @@ -693,6 +771,7 @@ var _ = Describe("construct_helpers", func() { Expect(fakeVcenterClient.MakeDirectoryCallCount()).To(Equal(1)) Expect(fakeVcenterClient.UploadArtifactCallCount()).To(Equal(2)) + Expect(fakeVcenterClient.RunCallCount()).To(Equal(1)) Expect(fakeWinRMEnabler.EnableCallCount()).To(Equal(1)) Expect(fakeVMConnectionValidator.ValidateCallCount()).To(Equal(1)) Expect(fakeRemoteManager.ExtractArchiveCallCount()).To(Equal(1)) diff --git a/stembuild/iaas_cli/iaas_clients/vcenter_client.go b/stembuild/iaas_cli/iaas_clients/vcenter_client.go index a84b108f7..10511a4f4 100644 --- a/stembuild/iaas_cli/iaas_clients/vcenter_client.go +++ b/stembuild/iaas_cli/iaas_clients/vcenter_client.go @@ -166,6 +166,19 @@ func (c *VcenterClient) IsPoweredOff(vmInventoryPath string) (bool, error) { return false, nil } +func (c *VcenterClient) Run(vmInventoryPath, username, password string, commandAndArgs []string) error { + args := c.buildGovcCommand(append([]string{"guest.run", "-l", vmCredentials(username, password), "-vm", vmInventoryPath}, commandAndArgs...)...) + _, exitCode, err := c.Runner.RunWithOutput(args) + if err != nil { + return fmt.Errorf("vcenter_client - '%v' return an error '%s'", args, err) + } + if exitCode != 0 { + return fmt.Errorf("vcenter_client - '%v' exited with '%d'", args, exitCode) + } + + return nil +} + func (c *VcenterClient) Start(vmInventoryPath, username, password, command string, args ...string) (string, error) { cmdArgs := c.buildGovcCommand(append([]string{"guest.start", "-l", vmCredentials(username, password), "-vm", vmInventoryPath, command}, args...)...) pid, exitCode, err := c.Runner.RunWithOutput(cmdArgs) diff --git a/stembuild/iaas_cli/iaas_clients/vcenter_client_test.go b/stembuild/iaas_cli/iaas_clients/vcenter_client_test.go index 5556fa683..192458d9f 100644 --- a/stembuild/iaas_cli/iaas_clients/vcenter_client_test.go +++ b/stembuild/iaas_cli/iaas_clients/vcenter_client_test.go @@ -372,6 +372,56 @@ ethernet-0 VirtualE1000e internal-network }) }) + Describe("Run", func() { + var baseArgs []string + var commandArgs []string + + BeforeEach(func() { + baseArgs = []string{"guest.run", "-u", vcenterAuthUrl(vcenterUsername, vcenterPassword, vcenterUrl), "-l", "user:pass", "-vm", "validVMPath"} + commandArgs = []string{"fake-command", "fake-arg1", "fake-arg2", "fake-arg3"} + }) + + It("Runs the command provided", func() { + runner.RunWithOutputReturns("fake-open-ssh-install-output", 0, nil) + + err := vcenterClient.Run("validVMPath", "user", "pass", commandArgs) + Expect(err).To(Not(HaveOccurred())) + + expectedArgs := append(baseArgs, commandArgs...) + Expect(runner.RunWithOutputArgsForCall(0)).To(Equal(expectedArgs)) + }) + + Context("when running the command returns a non-zero exit code", func() { + It("returns an error", func() { + exitCode := 42 + runner.RunWithOutputReturns("", exitCode, nil) + + err := vcenterClient.Run("validVMPath", "user", "pass", commandArgs) + Expect(err).To(HaveOccurred()) + + expectedArgs := append(baseArgs, commandArgs...) + Expect(err.Error()).To(Equal(fmt.Sprintf("vcenter_client - '%v' exited with '%d'", expectedArgs, exitCode))) + }) + }) + + Context("when running the command returns an error", func() { + var runWithOutputError error + + BeforeEach(func() { + runWithOutputError = errors.New("fake-run-with-output-error") + }) + + It("returns the error", func() { + runner.RunWithOutputReturns("", 0, runWithOutputError) + + expectedArgs := append(baseArgs, commandArgs...) + + err := vcenterClient.Run("validVMPath", "user", "pass", commandArgs) + Expect(err.Error()).To(Equal(fmt.Sprintf("vcenter_client - '%v' return an error '%s'", expectedArgs, runWithOutputError))) + }) + }) + }) + Describe("Start", func() { It("runs the command on the vm", func() { runner.RunWithOutputReturns("1856\n", 0, nil) // govc add '\n' to the output diff --git a/stembuild/stemcell-automation/AutomationHelpers.Tests.ps1 b/stembuild/stemcell-automation/AutomationHelpers.Tests.ps1 index 18ebe88d6..42c13744d 100644 --- a/stembuild/stemcell-automation/AutomationHelpers.Tests.ps1 +++ b/stembuild/stemcell-automation/AutomationHelpers.Tests.ps1 @@ -1,6 +1,6 @@ BeforeAll { - Import-Module ../../modules/BOSH.SSH Import-Module ../../modules/BOSH.Utils + Import-Module ../../modules/BOSH.SSH Import-Module ../../modules/BOSH.CFCell Import-Module ../../modules/BOSH.Agent Import-Module ../../modules/BOSH.CFCell @@ -150,7 +150,6 @@ Describe "AutomationHelpers" { throw "Something went wrong trying to Install-WUCerts" } - { Setup -FailOnInstallWUCerts } | Should -Throw Should -Invoke -ModuleName AutomationHelpers -CommandName Install-WUCerts -Times 1 @@ -340,9 +339,7 @@ Describe "AutomationHelpers" { { InstallOpenSSH } | Should -Not -Throw - Should -Invoke -ModuleName AutomationHelpers -CommandName Install-SSHD -Times 1 -ParameterFilter { - $SSHZipFile -eq ".\OpenSSH-Win64.zip" - } + Should -Invoke -ModuleName AutomationHelpers -CommandName Install-SSHD -Times 1 Should -Invoke -ModuleName AutomationHelpers -CommandName Write-Log -Times 1 -ParameterFilter { $Message -eq "OpenSSH successfully installed" } @@ -1109,179 +1106,36 @@ Describe "AutomationHelpers" { } } - Describe "Enable-SSHD" { + Describe "EnableOpenSSH" { BeforeAll { - function CreateFakeOpenSSHZip - { - param([string]$dir, [string]$installScriptSpyStatus, [string]$fakeZipPath) - - mkdir "$dir\OpenSSH-Win64" - $installSpyBehavior = "echo installed > $installScriptSpyStatus" - Write-Output $installSpyBehavior > "$dir\OpenSSH-Win64\install-sshd.ps1" - Write-Output "fake sshd" > "$dir\OpenSSH-Win64\sshd.exe" - - Compress-Archive -Force -Path "$dir\OpenSSH-Win64" -DestinationPath $fakeZipPath - } + Mock -ModuleName AutomationHelpers -CommandName Enable-SSHD { } } - BeforeEach { - Mock -ModuleName AutomationHelpers -CommandName Set-Service { } - Mock -ModuleName AutomationHelpers -CommandName Invoke-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" - Write-Output "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" - } + It "executes the Enable-SSHD powershell cmdlet" { + Mock -ModuleName AutomationHelpers -CommandName Enable-SSHD { } - AfterEach { - Remove-Item $TMP_DIR -Recurse -ErrorAction Ignore - $env:WINDIR = $ORIGINAL_WINDIR - $env:PROGRAMDATA = $ORIGINAL_PROGRAMDATA - } + { EnableOpenSSH } | Should -Not -Throw - It "sets the startup type of sshd to automatic" { - Mock -ModuleName AutomationHelpers -CommandName Set-Service { } -Verifiable -ParameterFilter { - $Name -eq "sshd" -and $StartupType -eq "Automatic" + Should -Invoke -ModuleName AutomationHelpers -CommandName Enable-SSHD -Times 1 + Should -Invoke -ModuleName AutomationHelpers -CommandName Write-Log -Times 1 -ParameterFilter { + $Message -eq "OpenSSH successfully installed" } - - { Enable-SSHD -SSHZipFile $FAKE_ZIP } | Should -Not -Throw - - Should -InvokeVerifiable } - It "sets the startup type of ssh-agent to automatic" { - Mock -ModuleName AutomationHelpers -CommandName Set-Service { } -Verifiable -ParameterFilter { - $Name -eq "ssh-agent" -and $StartupType -eq "Automatic" + It "fails gracefully when Enable-SSHD powershell cmdlet fails" { + Mock -ModuleName AutomationHelpers -CommandName Enable-SSHD { + throw "Something terrible happened while attempting to execute Enable-SSHD" } - { Enable-SSHD -SSHZipFile $FAKE_ZIP } | Should -Not -Throw + { EnableOpenSSH } | Should -Throw "Something terrible happened while attempting to execute Enable-SSHD" - Should -InvokeVerifiable - } - - It "sets up firewall when ssh not already set up" { - Mock -ModuleName AutomationHelpers -CommandName 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 -ModuleName AutomationHelpers -CommandName New-NetFirewallRule { } - - { Enable-SSHD -SSHZipFile $FAKE_ZIP } | Should -Not -Throw - - Should -Invoke -ModuleName AutomationHelpers -CommandName New-NetFirewallRule -Times 1 - } - - It "doesn't set up firewall when ssh is already set up " { - Mock -ModuleName AutomationHelpers -CommandName 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 -ModuleName AutomationHelpers -CommandName New-NetFirewallRule { } - - { Enable-SSHD -SSHZipFile $FAKE_ZIP } | Should -Not -Throw - - Should -Invoke -ModuleName AutomationHelpers -CommandName New-NetFirewallRule -Times 0 - } - - It "Generates inf and invokes LGPO if LGPO exists" { - Mock -ModuleName AutomationHelpers -CommandName Invoke-LGPO -Verifiable -ParameterFilter { - $LGPOPath -eq "$TMP_DIR\Windows\LGPO.exe" -and $InfFilePath -eq "$TMP_DIR\Windows\Temp\enable-ssh.inf" + Should -Invoke -ModuleName AutomationHelpers -CommandName Write-Log -Times 1 -ParameterFilter { + $Message -eq "Something terrible happened while attempting to execute Enable-SSHD" } - - { Enable-SSHD -SSHZipFile $FAKE_ZIP } | Should -Not -Throw - - Should -InvokeVerifiable - } - - It "Skips LGPO if LGPO.exe not found" { - rm "$TMP_DIR\Windows\LGPO.exe" - - { Enable-SSHD -SSHZipFile $FAKE_ZIP } | Should -Not -Throw - - Should -Invoke -ModuleName AutomationHelpers -CommandName Invoke-LGPO -Times 0 - } - - Context "When LGPO executable fails" { - It "Throws an appropriate error" { - Mock -ModuleName AutomationHelpers -CommandName Invoke-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*" - - Should -InvokeVerifiable + Should -Invoke -ModuleName AutomationHelpers -CommandName Write-Log -Times 1 -ParameterFilter { + $Message -eq "Failed to execute Enable-SSHD powershell cmdlet. See 'c:\provision\log.log' for more info." } } - - It "removes existing SSH keys" { - New-Item -ItemType Directory -Path "$TMP_DIR\ProgramData\ssh" -ErrorAction Ignore - Write-Output "delete" > "$TMP_DIR\ProgramData\ssh\ssh_host_1" - Write-Output "delete" > "$TMP_DIR\ProgramData\ssh\ssh_host_2" - Write-Output "delete" > "$TMP_DIR\ProgramData\ssh\ssh_host_3" - Write-Output "ignore" > "$TMP_DIR\ProgramData\ssh\not_ssh_host_4" - - { Enable-SSHD -SSHZipFile $FAKE_ZIP } | Should -Not -Throw - - $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 } | Should -Not -Throw - - { Test-Path "$TMP_DIR\ProgramData\ssh" } | Should -eq $True - } } Describe "Extract-LGPO" { @@ -1327,6 +1181,9 @@ Describe "AutomationHelpers" { } Describe "Install-WUCerts" { + BeforeAll { + } + It "executes the Get-WUCerts powershell cmdlet" { Mock -ModuleName AutomationHelpers -CommandName Get-WUCerts { } diff --git a/stembuild/stemcell-automation/AutomationHelpers.psm1 b/stembuild/stemcell-automation/AutomationHelpers.psm1 index 4e02d4fae..a7e47da73 100644 --- a/stembuild/stemcell-automation/AutomationHelpers.psm1 +++ b/stembuild/stemcell-automation/AutomationHelpers.psm1 @@ -34,7 +34,7 @@ function Setup InstallOpenSSH Extract-LGPO Install-SecurityPoliciesAndRegistries - Enable-SSHD + EnableOpenSSH InstallCFFeatures RemoveAvailableWindowsFeatures @@ -161,7 +161,7 @@ function InstallOpenSSH { try { - Install-SSHD -SSHZipFile ".\OpenSSH-Win64.zip" + Install-SSHD Write-Log "OpenSSH successfully installed" } catch [Exception] @@ -392,64 +392,19 @@ function Remove-SSHKeys Pop-Location } -function Invoke-LGPO +function EnableOpenSSH { - 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) + try { - Out-File -FilePath $InfFilePath -Encoding unicode -InputObject $InfFileContents -Force - try - { - Invoke-LGPO -LGPOPath $LGPOPath -InfFilePath $InfFilePath - } - catch - { - throw "LGPO.exe failed with: $_.Exception.Message" - } + Enable-SSHD + Write-Log "OpenSSH successfully installed" } - else + catch [Exception] { - "Did not find $LGPOPath. Assuming existing security policies are sufficient to support ssh." + Write-Log $_.Exception.Message + Write-Log "Failed to execute Enable-SSHD powershell cmdlet. See 'c:\provision\log.log' for more info." + throw $_.Exception } - - 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 } function Install-SecurityPoliciesAndRegistries