hashicorp / packer-plugin-azure

Packer plugin for Azure Virtual Machine Image Builders
https://www.packer.io/docs/builders/azure
Mozilla Public License 2.0
51 stars 79 forks source link

Build.Password is not set when using SSH on windows #267

Closed matt-richardson closed 1 year ago

matt-richardson commented 1 year ago

Overview of the Issue

When using the new SSH on Windows functionality in 1.4.0, it doesn't appear to be setting build.Password (that is recommend here).

I've tracked it down to the setUserNamePassword function in config.go.

If I change

    if c.Comm.Type == "ssh" {
        return nil
    }

to

    if c.Comm.Type == "ssh" {
        c.Comm.SSHPassword = c.Password
        return nil
    }

Then it works, but it fails the TestConfigUserNameOverride test in config_test.go. Unfortunately, the test doesn't say why the password should be empty, so I'm not sure of the ramifications of changing that test.

Reproduction Steps

Use the packer scripts below.

packer init .
packer build .

Output

==> azure-arm.azure-arm: Running builder ...
==> azure-arm.azure-arm: Getting tokens using client secret
==> azure-arm.azure-arm: Getting tokens using client secret
    azure-arm.azure-arm: Creating Azure Resource Manager (ARM) client ...
==> azure-arm.azure-arm: Getting source image id for the deployment ...
==> azure-arm.azure-arm:  -> SourceImageName: '/subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/providers/Microsoft.Compute/locations/Australia East/publishers/MicrosoftSQLServer/ArtifactTypes/vmimage/offers/SQL2017-WS2016/skus/sqldev-gen2/versions/latest'
==> azure-arm.azure-arm: Creating resource group ...
==> azure-arm.azure-arm:  -> ResourceGroupName : 'pkr-Resource-Group-qh4r0mrljp'
==> azure-arm.azure-arm:  -> Location          : 'Australia East'
==> azure-arm.azure-arm:  -> Tags              :
==> azure-arm.azure-arm: Validating deployment template ...
==> azure-arm.azure-arm:  -> ResourceGroupName : 'pkr-Resource-Group-qh4r0mrljp'
==> azure-arm.azure-arm:  -> DeploymentName    : 'kvpkrdpqh4r0mrljp'
==> azure-arm.azure-arm: Deploying deployment template ...
==> azure-arm.azure-arm:  -> ResourceGroupName : 'pkr-Resource-Group-qh4r0mrljp'
==> azure-arm.azure-arm:  -> DeploymentName    : 'kvpkrdpqh4r0mrljp'
==> azure-arm.azure-arm: Getting the certificate's URL ...
==> azure-arm.azure-arm:  -> Key Vault Name        : 'pkrkvqh4r0mrljp'
==> azure-arm.azure-arm:  -> Key Vault Secret Name : 'packerKeyVaultSecret'
==> azure-arm.azure-arm:  -> Certificate URL       : 'https://pkrkvqh4r0mrljp.vault.azure.net/secrets/packerKeyVaultSecret/14d51bd5f39iu88e6e7826c8a74c1ccf'
==> azure-arm.azure-arm: Setting the certificate's URL ...
==> azure-arm.azure-arm: Validating deployment template ...
==> azure-arm.azure-arm:  -> ResourceGroupName : 'pkr-Resource-Group-qh4r0mrljp'
==> azure-arm.azure-arm:  -> DeploymentName    : 'pkrdpqh4r0mrljp'
==> azure-arm.azure-arm: Deploying deployment template ...
==> azure-arm.azure-arm:  -> ResourceGroupName : 'pkr-Resource-Group-qh4r0mrljp'
==> azure-arm.azure-arm:  -> DeploymentName    : 'pkrdpqh4r0mrljp'
==> azure-arm.azure-arm: Getting the VM's IP address ...
==> azure-arm.azure-arm:  -> ResourceGroupName   : 'pkr-Resource-Group-qh4r0mrljp'
==> azure-arm.azure-arm:  -> PublicIPAddressName : 'pkripqh4r0mrljp'
==> azure-arm.azure-arm:  -> NicName             : 'pkrniqh4r0mrljp'
==> azure-arm.azure-arm:  -> Network Connection  : 'PublicEndpoint'
==> azure-arm.azure-arm:  -> IP Address          : '23.314.31.197'
==> azure-arm.azure-arm: Waiting for SSH to become available...
==> azure-arm.azure-arm: Connected to SSH!
==> azure-arm.azure-arm: Running local shell script: /var/folders/0g/v29czy31s783xus0gnz291518ndq4/T/packer-shell1347786305
    azure-arm.azure-arm: build.Password is
==> azure-arm.azure-arm: Querying the machine's properties ...
...

Expected output

==> azure-arm.azure-arm: Connected to SSH!
==> azure-arm.azure-arm: Running local shell script: /var/folders/0g/v29czy31s783xus0gnz291518ndq4/T/packer-shell1347786305
    azure-arm.azure-arm: build.Password is <some random string password>
==> azure-arm.azure-arm: Querying the machine's properties ...
...

Plugin and Packer version

From packer version: Packer v1.8.0 Plugin version 1.4.0

Simplified Packer Buildfile

```hcl packer { required_plugins { azure = { version = ">= 1.4.0" source = "github.com/hashicorp/azure" } } } variable "build_version" { type = string default = "0.0.1-local" } source "azure-arm" "azure-arm" { client_id = "XXXXXXXXXXXXXX" client_secret = "XXXXXXXXXXXXXX" communicator = "ssh" image_offer = "SQL2017-WS2016" image_publisher = "MicrosoftSQLServer" image_sku = "sqldev-gen2" # Developer edition location = "Australia East" managed_image_name = "my-packer-built-vm-v${var.build_version}" managed_image_resource_group_name = "PackerBuiltVMS" os_disk_size_gb = 200 os_type = "Windows" subscription_id = "XXXXXXXXXXXXXX" tenant_id = "XXXXXXXXXXXXXX" vm_size = "Standard_D2a_V4" ssh_timeout = "30m" ssh_username = "packer" custom_script = "powershell -ExecutionPolicy Unrestricted -NoProfile -NonInteractive -Command \"$userData = (Invoke-RestMethod -Headers @{Metadata=$true} -Method GET -Uri http://169.254.169.254/metadata/instance/compute/userData?api-version=2021-01-01$([char]38)format=text); $contents = [System.Text.Encoding]::UTF8.GetString([Convert]::FromBase64String($userData)); set-content -path c:\\Windows\\Temp\\userdata.ps1 -value $contents; . c:\\Windows\\Temp\\userdata.ps1;\"" user_data_file = "./scripts/userdata.ps1" } build { sources = [ "source.azure-arm.azure-arm" ] provisioner "shell-local" { inline = ["echo build.Password is '${build.Password}'"] } } ```

scripts/userdata.ps1

```powershell Start-Transcript -Path "$env:SystemRoot\Temp\PowerShell_transcript.$($env:COMPUTERNAME).$(Get-Date ((Get-Date).ToUniversalTime()) -f yyyyMMddHHmmss).txt" -IncludeInvocationHeader Set-PSDebug -Trace 2 # adapted from https://github.com/jimmy58663/X509ToOpenSSH/blob/master/X509ToOpenSSH.psm1 Function ConvertHex-ToBase64($HexString) { $ByteArray = [System.Byte[]]::new($HexString.Length / 2) For ($i = 0; $i -lt $ByteArray.Length; $i++) { $ByteArray[$i] = [System.Convert]::ToByte($HexString.Substring($i * 2, 2), 16) } return ([System.Convert]::ToBase64String($ByteArray)) } Function ConvertX509Cert2-ToOpenSshPubKey { <# .EXAMPLE $Cert = Get-ChildItem Cert:\CurrentUser\My\A4321234CAF8EFACF74A8567D61F04ACA4321234 ConvertX509Cert2-ToOpenSshPubKey -Certificate $Cert ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDCurn...ccwdiJ1 CAPI:A4321234CAF8EFACF74A8567D61F04ACA4321234 CN=SMITH.BOB.JONES, OU=PKI, OU=Office, O=Company, C=US #> [CmdletBinding()] Param ( [Parameter(Mandatory = $true, Position = 1)] [System.Security.Cryptography.X509Certificates.X509Certificate2]$Certificate ) #RSA Specifics If ($Certificate.PublicKey.Oid.FriendlyName -eq 'RSA') { $OpenSshPubKey = 'ssh-rsa ' $Algorithm = '00-00-00-07-73-73-68-2d-72-73-61' #ssh-rsa $RSAPubKey = [System.Security.Cryptography.X509Certificates.RSACertificateExtensions]::GetRSAPublicKey($Certificate) [xml]$XmlKey = $RSAPubKey.ToXmlString($false) $ExponentBytes = [System.Convert]::FromBase64String($XmlKey.RSAKeyValue.Exponent) $Modulus = [System.BitConverter]::ToString([System.Convert]::FromBase64String($XmlKey.RsaKeyValue.Modulus)) } #DSA Specifics ElseIf ($Certificate.PublicKey.Oid.FriendlyName -eq 'DSA') { $OpenSshPubKey = 'ssh-dss ' $Algorithm = '00-00-00-07-73-73-68-2d-64-73-73' #ssh-dss $DSAPubKey = [System.Security.Cryptography.X509Certificates.DSACertificateExtensions]::GetDSAPublicKey($Certificate) [xml]$XmlKey = $DSAPubKey.ToXmlString($false) $ExponentBytes = [System.Convert]::FromBase64String($XmlKey.DSAKeyValue.Exponent) $Modulus = [System.BitConverter]::ToString([System.Convert]::FromBase64String($XmlKey.DsaKeyValue.Modulus)) } #ECDsa and Ed25519 do not have the proper types or ToXmlString() in the X509Certificate library Else { Throw 'Unsupported key type.' } #Key $ExponentLengthHex = $ExponentBytes.Length.ToString("x8") $ExponentHex = [System.BitConverter]::ToString($ExponentBytes) $Exponent = $ExponentLengthHex + $ExponentHex $KeySize = $Certificate.PublicKey.Key.KeySize $KeyBytesHex = (($KeySize / 8) + 1).ToString("x8") $ModBegin = "$KeyBytesHex-00" $HexKey = ($Algorithm, $Exponent, $ModBegin, $Modulus -join '') -replace '-', '' $Base64Key = ConvertHex-ToBase64 -HexString $HexKey $OpenSshPubKey += $Base64Key #Comment $OpenSshPubKey += " CAPI:$($Certificate.Thumbprint.ToLower()) $($Certificate.Subject)" return $OpenSshPubKey } # adapted from https://jen20.dev/post/windows-amis-with-even-fewer-tears/ # Version and download URL $openSSHVersion = "9.1.0.0p1-Beta" $openSSHURL = "https://github.com/PowerShell/Win32-OpenSSH/releases/download/v$openSSHVersion/OpenSSH-Win64.zip" Set-ExecutionPolicy Unrestricted [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.SecurityProtocolType]::Tls12; # Function to unzip an archive to a given destination Add-Type -AssemblyName System.IO.Compression.FileSystem function Unzip { [CmdletBinding()] param( [Parameter(Mandatory=$true, Position=0)] [string] $ZipFile, [Parameter(Mandatory=$true, Position=1)] [string] $OutPath ) [System.IO.Compression.ZipFile]::ExtractToDirectory($zipFile, $outPath) } # Set various known paths $openSSHZip = Join-Path $env:TEMP 'OpenSSH.zip' $openSSHInstallDir = Join-Path $env:ProgramFiles 'OpenSSH' $openSSHInstallScript = Join-Path $openSSHInstallDir 'install-sshd.ps1' $openSSHDaemon = Join-Path $openSSHInstallDir 'sshd.exe' $openSSHDaemonConfig = [io.path]::combine($env:ProgramData, 'ssh', 'sshd_config') Write-Output "Downloading OpenSSH from $openSSHURL" # Download and unpack the binary distribution of OpenSSH Invoke-WebRequest -Uri $openSSHURL -OutFile $openSSHZip -ErrorAction Stop Unzip -ZipFile $openSSHZip -OutPath "$env:TEMP" -ErrorAction Stop Remove-Item $openSSHZip -ErrorAction SilentlyContinue # Move into Program Files Move-Item -Path (Join-Path $env:TEMP 'OpenSSH-Win64') -Destination $openSSHInstallDir -ErrorAction Stop $installScriptContent = Get-Content $openSSHInstallScript $installScriptContent = $installScriptContent -replace "Set-StrictMode -Version 2.0", "Set-StrictMode -Version 2.0`nSet-PSDebug -Trace 2" Set-Content -Path $openSSHInstallScript -value $installScriptContent Write-Output "Installing OpenSSH via $openSSHInstallScript" # Run the install script, terminate if it fails & Powershell.exe -ExecutionPolicy Bypass -File $openSSHInstallScript if ($LASTEXITCODE -ne 0) { exit 1 } Write-Output "Opening firewall port" # Add a firewall rule to allow inbound SSH connections to sshd.exe New-NetFirewallRule -Name sshd ` -DisplayName "OpenSSH Server (sshd)" ` -Group "Remote Access" ` -Description "Allow access via TCP port 22 to the OpenSSH Daemon" ` -Enabled True ` -Direction Inbound ` -Protocol TCP ` -LocalPort 22 ` -Program "$openSSHDaemon" ` -Action Allow ` -ErrorAction Stop Write-Output "Setting sshd service to start automatically on boot" # Ensure sshd automatically starts on boot Set-Service sshd -StartupType Automatic -ErrorAction Stop Write-Output "Setting default login shell to powershell" # Set the default login shell for SSH connections to Powershell New-Item -Path HKLM:\SOFTWARE\OpenSSH -Force New-ItemProperty -Path HKLM:\SOFTWARE\OpenSSH ` -Name DefaultShell ` -Value "C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe" ` -ErrorAction Stop Write-Output "Setting authorized_key" # Download the instance key pair and authorize Administrator logins using it $openSSHAdminUser = 'c:\ProgramData\ssh' $openSSHAuthorizedKeys = Join-Path $openSSHAdminUser 'authorized_keys' If (-Not (Test-Path $openSSHAdminUser)) { New-Item -Path $openSSHAdminUser -Type Directory } $cert = (Get-ChildItem cert:\localmachine\my | where-object { $_.subject.EndsWith(".cloudapp.net") })[0] $keyMaterial = convertX509Cert2-ToOpenSshPubKey -Certificate $Cert $keyMaterial | Out-File -Append -FilePath $openSSHAuthorizedKeys -Encoding ASCII Write-Output "Setting access control on $openSSHAuthorizedKeys" # Ensure access control on authorized_keys meets the requirements $acl = Get-ACL -Path $openSSHAuthorizedKeys $acl.SetAccessRuleProtection($True, $True) Set-Acl -Path $openSSHAuthorizedKeys -AclObject $acl $acl = Get-ACL -Path $openSSHAuthorizedKeys $ar = New-Object System.Security.AccessControl.FileSystemAccessRule("NT Authority\Authenticated Users", "ReadAndExecute", "Allow") $acl.RemoveAccessRule($ar) | out-null $ar = New-Object System.Security.AccessControl.FileSystemAccessRule("BUILTIN\Administrators", "FullControl", "Allow") $acl.RemoveAccessRule($ar) | out-null $ar = New-Object System.Security.AccessControl.FileSystemAccessRule("BUILTIN\Users", "FullControl", "Allow") $acl.RemoveAccessRule($ar) | out-null Set-Acl -Path $openSSHAuthorizedKeys -AclObject $acl Write-Output "Setting access control on $openSSHInstallDir" # Ensure access control on authorized_keys meets the requirements # https://github.com/PowerShell/Win32-OpenSSH/issues/1035#issuecomment-360261681 $acl = Get-ACL -Path $openSSHInstallDir $acl.SetAccessRuleProtection($False, $False) Set-Acl -Path $openSSHInstallDir -AclObject $acl $ar = New-Object System.Security.AccessControl.FileSystemAccessRule("BUILTIN\Users", "ReadAndExecute", "Allow") $acl.AddAccessRule($ar) | out-null Set-Acl -Path $openSSHInstallDir -AclObject $acl Write-Output "Setting sshd daemon config" $sshdConfigContent = @" # Modified sshd_config, created by Packer provisioner PasswordAuthentication yes PubKeyAuthentication yes PidFile __PROGRAMDATA__/ssh/logs/sshd.pid AuthorizedKeysFile __PROGRAMDATA__/ssh/authorized_keys AllowUsers packer # for debugging - lots will end up at %programdata%\ssh\logs. SyslogFacility LOCAL0 LogLevel Debug3 Subsystem sftp sftp-server.exe "@ Set-Content -Path $openSSHDaemonConfig -Value $sshdConfigContent Write-Output "Adding $openSSHInstallDir to PATH" $oldpath = (Get-ItemProperty -Path 'Registry::HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\Session Manager\Environment' -Name PATH).path $newpath = "$oldpath;$openSSHInstallDir" Set-ItemProperty -Path 'Registry::HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\Session Manager\Environment' -Name PATH -Value $newpath Write-Output "Starting sshd " Start-Service sshd write-output "userdatafile completed" ```

Operating system and Environment details

OS, Architecture, and any other information you can provide about the environment.

Host: macOS 13.1, arm64. Guest: Windows 2016, x86.

Log Fragments and crash.log files

Include appropriate log fragments. If the log is longer than a few dozen lines, please include the URL to the gist of the log or use the Github detailed format instead of posting it directly in the issue.

Set the env var PACKER_LOG=1 for maximum log detail.

matt-richardson commented 1 year ago

Ping @JenGoldstrich, as you worked with me on the PR to get the SSH support in.

JenGoldstrich commented 1 year ago

Hey @matt-richardson thanks for reporting this issue,

I would change this slightly as just above your suggest change we set c.Password to c.Comm.SSHPassword if the user passed one in.

I would do something like

    if c.Comm.Type == "ssh" {
                if c.Comm.SSHPassword == "" {
                 c.Comm.SSHPassword = c.Password
                }
        return nil
    }

so that we don't overwrite a pre-set SSH Password.

One important caveat is I'm curious how this currently works on Linux, the code you linked appears to be called on Linux and Windows builds, so I don't think this issue is exclusive to Windows, it'll be worth testing this on Linux VMs to see if they set a default password when not setting an ssh password

with regards to the unit test I think we just didn't set an ssh password if users didn't provide one before, so its just checking if you don't set one it doesn't get set, I think the assertion makes sense with the current code but changing it would be fine.

If you wanna open a PR for this I can try and do some before/after tests with Linux builds to confirm this doesn't break anything.

matt-richardson commented 1 year ago

@JenGoldstrich - PR 268 raised :smile:

github-actions[bot] commented 1 year ago

This issue has been synced to JIRA for planning.

JIRA ID: HPR-997