From f9a49bb0ed871d2a75c2ad1442ab48e44c3ff992 Mon Sep 17 00:00:00 2001 From: Sam Hentschel Date: Tue, 3 Dec 2024 01:53:58 -0500 Subject: [PATCH] [feat] add temp SSH key ability to iso builder The documentation for proxmox-iso states that a temporary SSH key will be created if no SSH credentials are passed. This was not the case and this commit remedies that. --- builder/proxmox/clone/builder.go | 4 - builder/proxmox/common/builder.go | 16 +++- .../{clone => common}/step_ssh_key_pair.go | 5 +- .../common/step_update_cloud_init_ssh.go | 95 +++++++++++++++++++ 4 files changed, 109 insertions(+), 11 deletions(-) rename builder/proxmox/{clone => common}/step_ssh_key_pair.go (97%) create mode 100644 builder/proxmox/common/step_update_cloud_init_ssh.go diff --git a/builder/proxmox/clone/builder.go b/builder/proxmox/clone/builder.go index 9b6d512e..5ac9859f 100644 --- a/builder/proxmox/clone/builder.go +++ b/builder/proxmox/clone/builder.go @@ -39,10 +39,6 @@ func (b *Builder) Run(ctx context.Context, ui packersdk.Ui, hook packersdk.Hook) state.Put("clone-config", &b.config) preSteps := []multistep.Step{ - &StepSshKeyPair{ - Debug: b.config.PackerDebug, - DebugKeyPath: fmt.Sprintf("%s.pem", b.config.PackerBuildName), - }, &StepMapSourceDisks{}, } postSteps := []multistep.Step{} diff --git a/builder/proxmox/common/builder.go b/builder/proxmox/common/builder.go index a10fd691..14bb98a7 100644 --- a/builder/proxmox/common/builder.go +++ b/builder/proxmox/common/builder.go @@ -52,9 +52,11 @@ func (b *Builder) Run(ctx context.Context, ui packersdk.Ui, hook packersdk.Hook, // Build the steps coreSteps := []multistep.Step{ + // Since the ISOs get added to the VM before/during this line we need to update the cloud-init data before this line. It probably needs to be before as CD creation is done in presteps &stepStartVM{ vmCreator: b.vmCreator, }, + // Also need to update the cloud-init data before this line commonsteps.HTTPServerFromHTTPConfig(&b.config.HTTPConfig), &stepTypeBootCommand{ BootConfig: b.config.BootConfig, @@ -67,14 +69,21 @@ func (b *Builder) Run(ctx context.Context, ui packersdk.Ui, hook packersdk.Hook, }, &commonsteps.StepProvision{}, &commonsteps.StepCleanupTempKeys{ - Comm: &b.config.Comm, + Comm: comm, }, &stepRemoveCloudInitDrive{}, &stepConvertToTemplate{}, &stepFinalizeTemplateConfig{}, &stepSuccess{}, } - preSteps := b.preSteps + + preSteps := []multistep.Step{ + &StepSshKeyPair{ + Debug: b.config.PackerDebug, + DebugKeyPath: fmt.Sprintf("%s.pem", b.config.PackerBuildName), + }, + &StepUpdateCloudInitSSH{}, + } for idx := range b.config.ISOs { if b.config.ISOs[idx].ISODownloadPVE { preSteps = append(preSteps, @@ -104,8 +113,7 @@ func (b *Builder) Run(ctx context.Context, ui packersdk.Ui, hook packersdk.Hook, } } - steps := append(preSteps, coreSteps...) - steps = append(steps, b.postSteps...) + steps := append(preSteps, b.preSteps..., coreSteps..., b.postSteps...) // Run the steps b.runner = commonsteps.NewRunner(steps, b.config.PackerConfig, ui) b.runner.Run(ctx, state) diff --git a/builder/proxmox/clone/step_ssh_key_pair.go b/builder/proxmox/common/step_ssh_key_pair.go similarity index 97% rename from builder/proxmox/clone/step_ssh_key_pair.go rename to builder/proxmox/common/step_ssh_key_pair.go index f7d2cfa5..b2433768 100644 --- a/builder/proxmox/clone/step_ssh_key_pair.go +++ b/builder/proxmox/common/step_ssh_key_pair.go @@ -1,7 +1,7 @@ // Copyright (c) HashiCorp, Inc. // SPDX-License-Identifier: MPL-2.0 -package proxmoxclone +package proxmox import ( "context" @@ -47,9 +47,8 @@ func (s *StepSshKeyPair) Run(ctx context.Context, state multistep.StateBag) mult return multistep.ActionHalt } - c.Comm.SSHPrivateKey = privateKeyBytes c.Comm.SSHKeyPairName = kp.Comment - c.Comm.SSHTemporaryKeyPairName = kp.Comment + c.Comm.SSHPrivateKey = privateKeyBytes c.Comm.SSHPublicKey = kp.PublicKeyAuthorizedKeysLine return multistep.ActionContinue diff --git a/builder/proxmox/common/step_update_cloud_init_ssh.go b/builder/proxmox/common/step_update_cloud_init_ssh.go new file mode 100644 index 00000000..d93537e8 --- /dev/null +++ b/builder/proxmox/common/step_update_cloud_init_ssh.go @@ -0,0 +1,95 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package proxmox + +import ( + "context" + "os" + "slices" + "strings" + "fmt" + + packersdk "github.com/hashicorp/packer-plugin-sdk/packer" + common "github.com/hashicorp/packer-plugin-proxmox/builder/proxmox/common" + "github.com/hashicorp/packer-plugin-sdk/multistep" +) + +type StepUpdateCloudInitSSH struct{} + +func (s *StepUpdateCloudInitSSH) Run(ctx context.Context, state multiste.StateBag) multistep.StepAction { + // NOTE: Can pass Cloud-Init data via CD or HTTP Server + + ui := state.Get("ui").(packersdk.Ui) + c := state.Get("config").(*common.Config) + + if c.Comm.SSHTemporaryKeyPairName == "" || + (len(c.ISOs) <= 1 && + c.HTTPConfig.HTTPDir == "" && + len(c.HTTPConfig.HTTPContent) == 0) { + return multistep.ActionContinue + } + + temp_ssh_public_key := string(c.Comm.SSHPublicKey) + + if len(c.ISOs) > 1 { + // Skip first ISO as it should be the BootISO + for idx := range c.ISOs[1:] { + if c.ISOs[idx].CDConfig.CDLabel != "cidata" { + continue + } + + if value, ok := c.ISOs[idx].CDConfig.CDContent["user-data"]; ok { + c.ISOs[idx].CDConfig.CDContent["user-data"] = strings.ReplaceAll(value, "${temporary_ssh_public_key}", temp_ssh_public_key) + ui.Say("Updated 'user-data' CD Content to use temporary SSH public key") + // CDContent will take precedence over CDFiles + break + } + + // TODO: This needs to be able to handle when directories and globs + // are passed to CDFiles + for jdx, value := range c.ISOs[idx].CDConfig.CDFiles { + if slices.Contains(value, "user-data") { + dat, err := os.ReadFile(c.ISOs[idx].CDConfig.CDFiles[jdx]) + if err != nil { + // It is ok if file does not exist + break + } + // Choosing to write CDContent to avoid overwriting original + // file or creating a new file with the temporary public key + c.ISOs[idx].CDConfig.CDContent["user-data"] = strings.ReplaceAll(dat, "${temporary_ssh_public_key}", temp_ssh_public_key) + ui.Say("Created 'user-data' CD Content to override CD File and use temporary SSH public key") + // There can only be one user-data file + break + } + } + } + } + + if c.HTTPConfig.HTTPDir != "" { + user_data_file := fmt.Sprintf("%s/user-data", c.HTTPConfig.HTTPDir) + + dat, err := os.ReadFile(user_data_file) + if err != nil { + // It is ok if file does not exist + return multistep.ActionContinue + } + + // Can't just write to "HTTPContent". Since it directly conflicts with + // "HTTPDir", we would have to walk and load every file from "HTTPDir" + // into "HTTPContent" + err := os.WriteFile(user_data_file, strings.ReplaceAll(dat, "${temporary_ssh_public_key}", temp_ssh_public_key), 0600) + if err == nil { + ui.Say(fmt.Sprintf("Rewrote '%s' in HTTPDir to use temporary SSH public key", user_data_file)) + } else { + ui.Say(fmt.Sprintf("Failed to rewrite '%s' in HTTPDir to use temporary SSH public key", user_data_file)) + } + } else if len(c.HTTPConfig.HTTPContent) > 0 { + if value, ok := c.HTTPConfig.HTTPContent["user-data"]; ok { + c.HTTPConfig.HTTPContent["user-data"] = strings.ReplaceAll(value, "%{temporary_ssh_public_key}", temp_ssh_public_key) + ui.Say("Updated 'user-data' HTTP Content to use temporary SSH public key") + } + } + + return multistep.ActionContinue +} \ No newline at end of file