diff --git a/builder/vmware/common/step_vnc_boot_command.go b/builder/vmware/common/step_vnc_boot_command.go index a5f2b70a..2681c186 100644 --- a/builder/vmware/common/step_vnc_boot_command.go +++ b/builder/vmware/common/step_vnc_boot_command.go @@ -10,6 +10,7 @@ import ( "time" "github.com/hashicorp/packer-plugin-sdk/bootcommand" + "github.com/hashicorp/packer-plugin-sdk/communicator" "github.com/hashicorp/packer-plugin-sdk/multistep" packersdk "github.com/hashicorp/packer-plugin-sdk/packer" "github.com/hashicorp/packer-plugin-sdk/template/interpolate" @@ -21,12 +22,14 @@ type StepVNCBootCommand struct { Config bootcommand.VNCConfig VMName string Ctx interpolate.Context + Comm *communicator.Config } type VNCBootCommandTemplateData struct { - HTTPIP string - HTTPPort int - Name string + HTTPIP string + HTTPPort int + Name string + SSHPublicKey string } func (s *StepVNCBootCommand) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction { @@ -58,9 +61,10 @@ func (s *StepVNCBootCommand) Run(ctx context.Context, state multistep.StateBag) hostIP := state.Get("http_ip").(string) s.Ctx.Data = &VNCBootCommandTemplateData{ - HTTPIP: hostIP, - HTTPPort: httpPort, - Name: s.VMName, + HTTPIP: hostIP, + HTTPPort: httpPort, + Name: s.VMName, + SSHPublicKey: string(s.Comm.SSHPublicKey), } d := bootcommand.NewVNCDriver(conn, s.Config.BootKeyInterval) diff --git a/builder/vmware/iso/builder.go b/builder/vmware/iso/builder.go index be0fc991..e377c7d7 100644 --- a/builder/vmware/iso/builder.go +++ b/builder/vmware/iso/builder.go @@ -123,6 +123,10 @@ func (b *Builder) Run(ctx context.Context, ui packersdk.Ui, hook packersdk.Hook) &vmwcommon.StepSuppressMessages{}, &vmwcommon.StepHTTPIPDiscover{}, commonsteps.HTTPServerFromHTTPConfig(&b.config.HTTPConfig), + multistep.If(b.config.Comm.Type == "ssh", &communicator.StepSSHKeyGen{ + CommConf: &b.config.Comm, + SSHTemporaryKeyPair: b.config.Comm.SSHTemporaryKeyPair, + }), &vmwcommon.StepConfigureVNC{ Enabled: !b.config.DisableVNC && !b.config.VNCOverWebsocket, VNCBindAddress: b.config.VNCBindAddress, @@ -149,6 +153,7 @@ func (b *Builder) Run(ctx context.Context, ui packersdk.Ui, hook packersdk.Hook) Config: b.config.VNCConfig, VMName: b.config.VMName, Ctx: b.config.ctx, + Comm: &b.config.Comm, }, &communicator.StepConnect{ Config: &b.config.SSHConfig.Comm, diff --git a/builder/vmware/vmx/builder.go b/builder/vmware/vmx/builder.go index f0b85253..599f4ea4 100644 --- a/builder/vmware/vmx/builder.go +++ b/builder/vmware/vmx/builder.go @@ -114,6 +114,10 @@ func (b *Builder) Run(ctx context.Context, ui packersdk.Ui, hook packersdk.Hook) &vmwcommon.StepSuppressMessages{}, &vmwcommon.StepHTTPIPDiscover{}, commonsteps.HTTPServerFromHTTPConfig(&b.config.HTTPConfig), + multistep.If(b.config.Comm.Type == "ssh", &communicator.StepSSHKeyGen{ + CommConf: &b.config.Comm, + SSHTemporaryKeyPair: b.config.Comm.SSHTemporaryKeyPair, + }), &vmwcommon.StepUploadVMX{ RemoteType: b.config.RemoteType, }, @@ -143,6 +147,7 @@ func (b *Builder) Run(ctx context.Context, ui packersdk.Ui, hook packersdk.Hook) Config: b.config.VNCConfig, VMName: b.config.VMName, Ctx: b.config.ctx, + Comm: &b.config.Comm, }, &communicator.StepConnect{ Config: &b.config.SSHConfig.Comm, diff --git a/docs/builders/iso.mdx b/docs/builders/iso.mdx index 149aeb30..e53dea4c 100644 --- a/docs/builders/iso.mdx +++ b/docs/builders/iso.mdx @@ -191,6 +191,8 @@ necessary for this build to succeed and can be found further down the page. @include 'packer-plugin-sdk/communicator/SSH-not-required.mdx' +@include 'packer-plugin-sdk/communicator/SSHTemporaryKeyPair-not-required.mdx' + #### Optional WinRM fields: @include 'packer-plugin-sdk/communicator/WinRM-not-required.mdx' @@ -355,3 +357,74 @@ a different syntax to source a kickstart file from a mounted floppy image. ] } ``` + +### SSH key pair automation + +The VMware builders can inject the current SSH key pair's public key into +the template using the `SSHPublicKey` template engine. This is the SSH public +key as a line in OpenSSH authorized_keys format. + +When a private key is provided using `ssh_private_key_file`, the key's +corresponding public key can be accessed using the above engine. + +@include 'packer-plugin-sdk/communicator/SSH-Private-Key-File-not-required.mdx' + +If `ssh_password` and `ssh_private_key_file` are not specified, Packer will +automatically generate en ephemeral key pair. The key pair's public key can +be accessed using the template engine. + +For example, the public key can be provided in the boot command as a URL +encoded string by appending `| urlquery` to the variable: + +In JSON: + +```json +"boot_command": [ + " text ks=http://{{ .HTTPIP }}:{{ .HTTPPort }}/ks.cfg PACKER_USER={{ user `username` }} PACKER_AUTHORIZED_KEY={{ .SSHPublicKey | urlquery }}" +] +``` + +In HCL2: + +```hcl +boot_command = [ + " text ks=http://{{ .HTTPIP }}:{{ .HTTPPort }}/ks.cfg PACKER_USER={{ user `username` }} PACKER_AUTHORIZED_KEY={{ .SSHPublicKey | urlquery }}" +] +``` + +A kickstart could then leverage those fields from the kernel command line by +decoding the URL-encoded public key: + +```shell +%post + +# Newly created users need the file/folder framework for SSH key authentication. +umask 0077 +mkdir /etc/skel/.ssh +touch /etc/skel/.ssh/authorized_keys + +# Loop over the command line. Set interesting variables. +for x in $(cat /proc/cmdline) +do + case $x in + PACKER_USER=*) + PACKER_USER="${x#*=}" + ;; + PACKER_AUTHORIZED_KEY=*) + # URL decode $encoded into $PACKER_AUTHORIZED_KEY + encoded=$(echo "${x#*=}" | tr '+' ' ') + printf -v PACKER_AUTHORIZED_KEY '%b' "${encoded//%/\\x}" + ;; + esac +done + +# Create/configure packer user, if any. +if [ -n "$PACKER_USER" ] +then + useradd $PACKER_USER + echo "%$PACKER_USER ALL=(ALL) NOPASSWD: ALL" >> /etc/sudoers.d/$PACKER_USER + [ -n "$PACKER_AUTHORIZED_KEY" ] && echo $PACKER_AUTHORIZED_KEY >> $(eval echo ~"$PACKER_USER")/.ssh/authorized_keys +fi + +%end +``` diff --git a/docs/builders/vmx.mdx b/docs/builders/vmx.mdx index 5dd09192..ea02a4b7 100644 --- a/docs/builders/vmx.mdx +++ b/docs/builders/vmx.mdx @@ -172,6 +172,8 @@ necessary for this build to succeed and can be found further down the page. @include 'packer-plugin-sdk/communicator/SSH-not-required.mdx' +@include 'packer-plugin-sdk/communicator/SSHTemporaryKeyPair-not-required.mdx' + #### Optional WinRM fields: @include 'packer-plugin-sdk/communicator/WinRM-not-required.mdx' @@ -300,3 +302,73 @@ Since ovftool is only capable of password based authentication - `vnc_disable_password` - This must be set to "true" when using VNC with ESXi 6.5 or 6.7. +### SSH key pair automation + +The VMware builders can inject the current SSH key pair's public key into +the template using the `SSHPublicKey` template engine. This is the SSH public +key as a line in OpenSSH authorized_keys format. + +When a private key is provided using `ssh_private_key_file`, the key's +corresponding public key can be accessed using the above engine. + +@include 'packer-plugin-sdk/communicator/SSH-Private-Key-File-not-required.mdx' + +If `ssh_password` and `ssh_private_key_file` are not specified, Packer will +automatically generate en ephemeral key pair. The key pair's public key can +be accessed using the template engine. + +For example, the public key can be provided in the boot command as a URL +encoded string by appending `| urlquery` to the variable: + +In JSON: + +```json +"boot_command": [ + " text ks=http://{{ .HTTPIP }}:{{ .HTTPPort }}/ks.cfg PACKER_USER={{ user `username` }} PACKER_AUTHORIZED_KEY={{ .SSHPublicKey | urlquery }}" +] +``` + +In HCL2: + +```hcl +boot_command = [ + " text ks=http://{{ .HTTPIP }}:{{ .HTTPPort }}/ks.cfg PACKER_USER={{ user `username` }} PACKER_AUTHORIZED_KEY={{ .SSHPublicKey | urlquery }}" +] +``` + +A kickstart could then leverage those fields from the kernel command line by +decoding the URL-encoded public key: + +```shell +%post + +# Newly created users need the file/folder framework for SSH key authentication. +umask 0077 +mkdir /etc/skel/.ssh +touch /etc/skel/.ssh/authorized_keys + +# Loop over the command line. Set interesting variables. +for x in $(cat /proc/cmdline) +do + case $x in + PACKER_USER=*) + PACKER_USER="${x#*=}" + ;; + PACKER_AUTHORIZED_KEY=*) + # URL decode $encoded into $PACKER_AUTHORIZED_KEY + encoded=$(echo "${x#*=}" | tr '+' ' ') + printf -v PACKER_AUTHORIZED_KEY '%b' "${encoded//%/\\x}" + ;; + esac +done + +# Create/configure packer user, if any. +if [ -n "$PACKER_USER" ] +then + useradd $PACKER_USER + echo "%$PACKER_USER ALL=(ALL) NOPASSWD: ALL" >> /etc/sudoers.d/$PACKER_USER + [ -n "$PACKER_AUTHORIZED_KEY" ] && echo $PACKER_AUTHORIZED_KEY >> $(eval echo ~"$PACKER_USER")/.ssh/authorized_keys +fi + +%end +```