From c5b8a1069a1192a6dc251945f2ad033b0a4d7245 Mon Sep 17 00:00:00 2001 From: Larry Bordowitz Date: Fri, 10 Mar 2017 08:45:13 -0800 Subject: [PATCH 1/4] Add ssh agent to enable SSH-CA authentication. --- builder/openstack/builder.go | 4 +++- builder/openstack/ssh.go | 23 ++++++++++++++++++- .../source/docs/builders/openstack.html.md | 6 +++++ 3 files changed, 31 insertions(+), 2 deletions(-) diff --git a/builder/openstack/builder.go b/builder/openstack/builder.go index ecf37cf73..f0610c989 100644 --- a/builder/openstack/builder.go +++ b/builder/openstack/builder.go @@ -110,7 +110,9 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe computeClient, b.config.SSHInterface, b.config.SSHIPVersion), - SSHConfig: SSHConfig(b.config.RunConfig.Comm.SSHUsername, + SSHConfig: SSHConfig( + b.config.RunConfig.Comm.SSHAgentAuth, + b.config.RunConfig.Comm.SSHUsername, b.config.RunConfig.Comm.SSHPassword), }, &common.StepProvision{}, diff --git a/builder/openstack/ssh.go b/builder/openstack/ssh.go index a5495eed3..9738c278e 100644 --- a/builder/openstack/ssh.go +++ b/builder/openstack/ssh.go @@ -4,6 +4,8 @@ import ( "errors" "fmt" "log" + "net" + "os" "time" "github.com/gophercloud/gophercloud" @@ -12,6 +14,7 @@ import ( "github.com/mitchellh/multistep" packerssh "github.com/mitchellh/packer/communicator/ssh" "golang.org/x/crypto/ssh" + "golang.org/x/crypto/ssh/agent" ) // CommHost looks up the host for the communicator. @@ -63,8 +66,26 @@ func CommHost( // SSHConfig returns a function that can be used for the SSH communicator // config for connecting to the instance created over SSH using a private key // or a password. -func SSHConfig(username, password string) func(multistep.StateBag) (*ssh.ClientConfig, error) { +func SSHConfig(useAgent bool, username, password string) func(multistep.StateBag) (*ssh.ClientConfig, error) { return func(state multistep.StateBag) (*ssh.ClientConfig, error) { + if useAgent { + authSock := os.Getenv("SSH_AUTH_SOCK") + if authSock == "" { + return nil, fmt.Errorf("SSH_AUTH_SOCK is not set") + } + + sshAgent, err := net.Dial("unix", authSock) + if err != nil { + return nil, fmt.Errorf("Cannot connect to SSH Agent socket %q: %s", authSock, err) + } + + return &ssh.ClientConfig{ + User: username, + Auth: []ssh.AuthMethod{ + ssh.PublicKeysCallback(agent.NewClient(sshAgent).Signers), + }, + }, nil + } privateKey, hasKey := state.GetOk("privateKey") diff --git a/website/source/docs/builders/openstack.html.md b/website/source/docs/builders/openstack.html.md index 486d29e2d..a3920afa5 100644 --- a/website/source/docs/builders/openstack.html.md +++ b/website/source/docs/builders/openstack.html.md @@ -147,6 +147,12 @@ builder. [`ssh_private_key_file`](/docs/templates/communicator.html#ssh_private_key_file) must be specified with this. +- `ssh_agent_auth` (boolean) - If true, the local SSH agent will be used to + authenticate connections to the source instance. No temporary key pair will + be used, and the values of `ssh_password` and `ssh_private_key_file` will + be ignored. Useful for when a temporary keypair can't be used, or when using + a certificate authority for SSH authentication. + - `tenant_id` or `tenant_name` (string) - The tenant ID or name to boot the instance into. Some OpenStack installations require this. If not specified, Packer will use the environment variable `OS_TENANT_NAME`, if set. Tenant From 4c5461102a81628d3012029dde0cd612792cc6f4 Mon Sep 17 00:00:00 2001 From: Rickard von Essen Date: Mon, 13 Mar 2017 07:29:59 +0100 Subject: [PATCH 2/4] amazon: Added (more) support for SSH Agent and temporary keypair names --- builder/openstack/builder.go | 10 +++--- builder/openstack/run_config.go | 19 ++++++++--- builder/openstack/step_key_pair.go | 55 ++++++++++++++++++------------ 3 files changed, 55 insertions(+), 29 deletions(-) diff --git a/builder/openstack/builder.go b/builder/openstack/builder.go index f0610c989..3d4263433 100644 --- a/builder/openstack/builder.go +++ b/builder/openstack/builder.go @@ -75,10 +75,12 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe Flavor: b.config.Flavor, }, &StepKeyPair{ - Debug: b.config.PackerDebug, - DebugKeyPath: fmt.Sprintf("os_%s.pem", b.config.PackerBuildName), - KeyPairName: b.config.SSHKeyPairName, - PrivateKeyFile: b.config.RunConfig.Comm.SSHPrivateKey, + Debug: b.config.PackerDebug, + DebugKeyPath: fmt.Sprintf("os_%s.pem", b.config.PackerBuildName), + KeyPairName: b.config.SSHKeyPairName, + TemporaryKeyPairName: b.config.TemporaryKeyPairName, + PrivateKeyFile: b.config.RunConfig.Comm.SSHPrivateKey, + SSHAgentAuth: b.config.RunConfig.Comm.SSHAgentAuth, }, &StepRunSourceServer{ Name: b.config.ImageName, diff --git a/builder/openstack/run_config.go b/builder/openstack/run_config.go index f0bb4dc7c..06fd0ea73 100644 --- a/builder/openstack/run_config.go +++ b/builder/openstack/run_config.go @@ -4,6 +4,7 @@ import ( "errors" "fmt" + "github.com/mitchellh/packer/common/uuid" "github.com/mitchellh/packer/helper/communicator" "github.com/mitchellh/packer/template/interpolate" ) @@ -11,10 +12,11 @@ import ( // RunConfig contains configuration for running an instance from a source // image and details on how to access that launched image. type RunConfig struct { - Comm communicator.Config `mapstructure:",squash"` - SSHKeyPairName string `mapstructure:"ssh_keypair_name"` - SSHInterface string `mapstructure:"ssh_interface"` - SSHIPVersion string `mapstructure:"ssh_ip_version"` + Comm communicator.Config `mapstructure:",squash"` + SSHKeyPairName string `mapstructure:"ssh_keypair_name"` + TemporaryKeyPairName string `mapstructure:"temporary_key_pair_name"` + SSHInterface string `mapstructure:"ssh_interface"` + SSHIPVersion string `mapstructure:"ssh_ip_version"` SourceImage string `mapstructure:"source_image"` SourceImageName string `mapstructure:"source_image_name"` @@ -38,6 +40,15 @@ type RunConfig struct { } func (c *RunConfig) Prepare(ctx *interpolate.Context) []error { + // If we are not given an explicit ssh_keypair_name or + // ssh_private_key_file, then create a temporary one, but only if the + // temporary_key_pair_name has not been provided and we are not using + // ssh_password. + if c.SSHKeyPairName == "" && c.TemporaryKeyPairName == "" && + c.Comm.SSHPrivateKey == "" && c.Comm.SSHPassword == "" { + + c.TemporaryKeyPairName = fmt.Sprintf("packer_%s", uuid.TimeOrderedUUID()) + } if c.UseFloatingIp && c.FloatingIpPool == "" { c.FloatingIpPool = "public" diff --git a/builder/openstack/step_key_pair.go b/builder/openstack/step_key_pair.go index 94ca5c768..b28994030 100644 --- a/builder/openstack/step_key_pair.go +++ b/builder/openstack/step_key_pair.go @@ -10,21 +10,24 @@ import ( "github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/keypairs" "github.com/mitchellh/multistep" - "github.com/mitchellh/packer/common/uuid" "github.com/mitchellh/packer/packer" "golang.org/x/crypto/ssh" ) type StepKeyPair struct { - Debug bool - DebugKeyPath string - KeyPairName string - PrivateKeyFile string + Debug bool + SSHAgentAuth bool + DebugKeyPath string + TemporaryKeyPairName string + KeyPairName string + PrivateKeyFile string keyName string } func (s *StepKeyPair) Run(state multistep.StateBag) multistep.StepAction { + ui := state.Get("ui").(packer.Ui) + if s.PrivateKeyFile != "" { privateKeyBytes, err := ioutil.ReadFile(s.PrivateKeyFile) if err != nil { @@ -39,14 +42,25 @@ func (s *StepKeyPair) Run(state multistep.StateBag) multistep.StepAction { return multistep.ActionContinue } - config := state.Get("config").(Config) - ui := state.Get("ui").(packer.Ui) - - if config.Comm.Type == "ssh" && config.Comm.SSHPassword != "" { - ui.Say("Not creating temporary keypair when using password.") + if s.SSHAgentAuth && s.KeyPairName == "" { + ui.Say("Using SSH Agent with key pair in Source image") return multistep.ActionContinue } + if s.SSHAgentAuth && s.KeyPairName != "" { + ui.Say(fmt.Sprintf("Using SSH Agent for existing key pair %s", s.KeyPairName)) + state.Put("keyPair", s.KeyPairName) + return multistep.ActionContinue + } + + if s.TemporaryKeyPairName == "" { + ui.Say("Not using temporary keypair") + state.Put("keyPair", "") + return multistep.ActionContinue + } + + config := state.Get("config").(Config) + // We need the v2 compute client computeClient, err := config.computeV2Client() if err != nil { @@ -55,10 +69,9 @@ func (s *StepKeyPair) Run(state multistep.StateBag) multistep.StepAction { return multistep.ActionHalt } - keyName := fmt.Sprintf("packer %s", uuid.TimeOrderedUUID()) - ui.Say(fmt.Sprintf("Creating temporary keypair: %s ...", keyName)) + ui.Say(fmt.Sprintf("Creating temporary keypair: %s ...", s.TemporaryKeyPairName)) keypair, err := keypairs.Create(computeClient, keypairs.CreateOpts{ - Name: keyName, + Name: s.TemporaryKeyPairName, }).Extract() if err != nil { state.Put("error", fmt.Errorf("Error creating temporary keypair: %s", err)) @@ -70,7 +83,7 @@ func (s *StepKeyPair) Run(state multistep.StateBag) multistep.StepAction { return multistep.ActionHalt } - ui.Say(fmt.Sprintf("Created temporary keypair: %s", keyName)) + ui.Say(fmt.Sprintf("Created temporary keypair: %s", s.TemporaryKeyPairName)) keypair.PrivateKey = berToDer(keypair.PrivateKey, ui) @@ -101,10 +114,10 @@ func (s *StepKeyPair) Run(state multistep.StateBag) multistep.StepAction { } // Set the keyname so we know to delete it later - s.keyName = keyName + s.keyName = s.TemporaryKeyPairName // Set some state data for use in future steps - state.Put("keyPair", keyName) + state.Put("keyPair", s.keyName) state.Put("privateKey", keypair.PrivateKey) return multistep.ActionContinue @@ -156,11 +169,11 @@ func berToDer(ber string, ui packer.Ui) string { func (s *StepKeyPair) Cleanup(state multistep.StateBag) { // If we used an SSH private key file, do not go about deleting // keypairs - if s.PrivateKeyFile != "" { + if s.PrivateKeyFile != "" || (s.KeyPairName == "" && s.keyName == "") { return } // If no key name is set, then we never created it, so just return - if s.keyName == "" { + if s.TemporaryKeyPairName == "" { return } @@ -171,14 +184,14 @@ func (s *StepKeyPair) Cleanup(state multistep.StateBag) { computeClient, err := config.computeV2Client() if err != nil { ui.Error(fmt.Sprintf( - "Error cleaning up keypair. Please delete the key manually: %s", s.keyName)) + "Error cleaning up keypair. Please delete the key manually: %s", s.TemporaryKeyPairName)) return } - ui.Say(fmt.Sprintf("Deleting temporary keypair: %s ...", s.keyName)) + ui.Say(fmt.Sprintf("Deleting temporary keypair: %s ...", s.TemporaryKeyPairName)) err = keypairs.Delete(computeClient, s.keyName).ExtractErr() if err != nil { ui.Error(fmt.Sprintf( - "Error cleaning up keypair. Please delete the key manually: %s", s.keyName)) + "Error cleaning up keypair. Please delete the key manually: %s", s.TemporaryKeyPairName)) } } From ad5a41082e06f4cea94a4a8fa671fc82a32c3d38 Mon Sep 17 00:00:00 2001 From: Rickard von Essen Date: Mon, 13 Mar 2017 20:18:05 +0100 Subject: [PATCH 3/4] openstack: Updated docs for ssh_agent_auth and temporary_key_pair_name --- website/source/docs/builders/openstack.html.md | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/website/source/docs/builders/openstack.html.md b/website/source/docs/builders/openstack.html.md index a3920afa5..8ace2652b 100644 --- a/website/source/docs/builders/openstack.html.md +++ b/website/source/docs/builders/openstack.html.md @@ -144,14 +144,21 @@ builder. - `ssh_keypair_name` (string) - If specified, this is the key that will be used for SSH with the machine. By default, this is blank, and Packer will generate a temporary keypair. + [`ssh_password`](/docs/templates/communicator.html#ssh_password) is used. [`ssh_private_key_file`](/docs/templates/communicator.html#ssh_private_key_file) - must be specified with this. + or `ssh_agent_auth` must be specified when `ssh_keypair_name` is utilized. - `ssh_agent_auth` (boolean) - If true, the local SSH agent will be used to - authenticate connections to the source instance. No temporary key pair will - be used, and the values of `ssh_password` and `ssh_private_key_file` will - be ignored. Useful for when a temporary keypair can't be used, or when using - a certificate authority for SSH authentication. + authenticate connections to the source instance. No temporary keypair will + be created, and the values of `ssh_password` and `ssh_private_key_file` will + be ignored. To use this option with a key pair already configured in the source + image, leave the `ssh_keypair_name` blank. To associate an existing key pair + with the source instance, set the `ssh_keypair_name` field to the name + of the key pair. + +- `temporary_key_pair_name` (string) - The name of the temporary key pair + to generate. By default, Packer generates a name that looks like + `packer_`, where \ is a 36 character unique identifier. - `tenant_id` or `tenant_name` (string) - The tenant ID or name to boot the instance into. Some OpenStack installations require this. If not specified, From 6cd3cb18dad94f258c763b7dbf98efd5510aa6b7 Mon Sep 17 00:00:00 2001 From: Rickard von Essen Date: Tue, 14 Mar 2017 20:47:40 +0100 Subject: [PATCH 4/4] builder/openstack: validate ssh key name/file This is a port for OpenStack of PR #4665 --- builder/openstack/run_config.go | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/builder/openstack/run_config.go b/builder/openstack/run_config.go index 06fd0ea73..eddbae1ed 100644 --- a/builder/openstack/run_config.go +++ b/builder/openstack/run_config.go @@ -56,6 +56,15 @@ func (c *RunConfig) Prepare(ctx *interpolate.Context) []error { // Validation errs := c.Comm.Prepare(ctx) + + if c.SSHKeyPairName != "" { + if c.Comm.Type == "winrm" && c.Comm.WinRMPassword == "" && c.Comm.SSHPrivateKey == "" { + errs = append(errs, errors.New("A private_key_file must be provided to retrieve the winrm password when using ssh_keypair_name.")) + } else if c.Comm.SSHPrivateKey == "" && !c.Comm.SSHAgentAuth { + errs = append(errs, errors.New("A private_key_file must be provided or ssh_agent_auth enabled when ssh_keypair_name is specified.")) + } + } + if c.SourceImage == "" && c.SourceImageName == "" { errs = append(errs, errors.New("Either a source_image or a source_image_name must be specified")) } else if len(c.SourceImage) > 0 && len(c.SourceImageName) > 0 {