From acc2689b49983d6f188279e0ca144d39af572cd9 Mon Sep 17 00:00:00 2001 From: Rickard von Essen Date: Sun, 2 Oct 2016 22:20:36 +0200 Subject: [PATCH 1/3] builder/amazon: Allow using ssh_private_key_file and ssh_password This adds support for using amazon-ebs/amazon-instance builder without a keypair. If a ssh_private_key_file is supplied without a ssh_keypair_name no temporary ssh keypair is created. If ssh_password is used no temporary ssh keypair is created and the password is used when trying to connect. Closes #2301 Closes #3156 --- builder/amazon/common/run_config.go | 16 +++--- builder/amazon/common/ssh.go | 40 +++++++++----- builder/amazon/common/step_key_pair.go | 12 ++++- .../amazon/common/step_run_source_instance.go | 53 +++++++++++-------- builder/amazon/ebs/builder.go | 3 +- builder/amazon/instance/builder.go | 3 +- 6 files changed, 79 insertions(+), 48 deletions(-) diff --git a/builder/amazon/common/run_config.go b/builder/amazon/common/run_config.go index fa648dc96..2883c9c62 100644 --- a/builder/amazon/common/run_config.go +++ b/builder/amazon/common/run_config.go @@ -44,14 +44,14 @@ type RunConfig struct { } func (c *RunConfig) Prepare(ctx *interpolate.Context) []error { - // If we are not given an explicit ssh_keypair_name, - // then create a temporary one, but only if the - // temporary_key_pair_name has not been provided. - if c.SSHKeyPairName == "" { - if c.TemporaryKeyPairName == "" { - c.TemporaryKeyPairName = fmt.Sprintf( - "packer_%s", uuid.TimeOrderedUUID()) - } + // 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.WindowsPasswordTimeout == 0 { diff --git a/builder/amazon/common/ssh.go b/builder/amazon/common/ssh.go index 6c38ad97c..64479e841 100644 --- a/builder/amazon/common/ssh.go +++ b/builder/amazon/common/ssh.go @@ -7,6 +7,7 @@ import ( "github.com/aws/aws-sdk-go/service/ec2" "github.com/mitchellh/multistep" + packerssh "github.com/mitchellh/packer/communicator/ssh" "golang.org/x/crypto/ssh" ) @@ -64,22 +65,33 @@ func SSHHost(e ec2Describer, private bool) func(multistep.StateBag) (string, err } // SSHConfig returns a function that can be used for the SSH communicator -// config for connecting to the instance created over SSH using the generated -// private key. -func SSHConfig(username string) func(multistep.StateBag) (*ssh.ClientConfig, error) { +// config for connecting to the instance created over SSH using the private key +// or password. +func SSHConfig(username, password string) func(multistep.StateBag) (*ssh.ClientConfig, error) { return func(state multistep.StateBag) (*ssh.ClientConfig, error) { - privateKey := state.Get("privateKey").(string) - signer, err := ssh.ParsePrivateKey([]byte(privateKey)) - if err != nil { - return nil, fmt.Errorf("Error setting up SSH config: %s", err) - } + privateKey, hasKey := state.GetOk("privateKey") + if hasKey { - return &ssh.ClientConfig{ - User: username, - Auth: []ssh.AuthMethod{ - ssh.PublicKeys(signer), - }, - }, nil + signer, err := ssh.ParsePrivateKey([]byte(privateKey.(string))) + if err != nil { + return nil, fmt.Errorf("Error setting up SSH config: %s", err) + } + return &ssh.ClientConfig{ + User: username, + Auth: []ssh.AuthMethod{ + ssh.PublicKeys(signer), + }, + }, nil + + } else { + return &ssh.ClientConfig{ + User: username, + Auth: []ssh.AuthMethod{ + ssh.Password(password), + ssh.KeyboardInteractive( + packerssh.PasswordKeyboardInteractive(password)), + }}, nil + } } } diff --git a/builder/amazon/common/step_key_pair.go b/builder/amazon/common/step_key_pair.go index 6e7f2bb2b..0a3cb1cb5 100644 --- a/builder/amazon/common/step_key_pair.go +++ b/builder/amazon/common/step_key_pair.go @@ -22,7 +22,10 @@ type StepKeyPair struct { } func (s *StepKeyPair) Run(state multistep.StateBag) multistep.StepAction { + ui := state.Get("ui").(packer.Ui) + if s.PrivateKeyFile != "" { + ui.Say("Using existing ssh private key") privateKeyBytes, err := ioutil.ReadFile(s.PrivateKeyFile) if err != nil { state.Put("error", fmt.Errorf( @@ -36,8 +39,13 @@ func (s *StepKeyPair) Run(state multistep.StateBag) multistep.StepAction { return multistep.ActionContinue } + if s.TemporaryKeyPairName == "" { + ui.Say("Not using temporary keypair") + state.Put("keyPair", "") + return multistep.ActionContinue + } + ec2conn := state.Get("ec2").(*ec2.EC2) - ui := state.Get("ui").(packer.Ui) ui.Say(fmt.Sprintf("Creating temporary keypair: %s", s.TemporaryKeyPairName)) keyResp, err := ec2conn.CreateKeyPair(&ec2.CreateKeyPairInput{ @@ -87,7 +95,7 @@ func (s *StepKeyPair) Cleanup(state multistep.StateBag) { // If no key name is set, then we never created it, so just return // If we used an SSH private key file, do not go about deleting // keypairs - if s.PrivateKeyFile != "" { + if s.PrivateKeyFile != "" || s.KeyPairName == "" { return } diff --git a/builder/amazon/common/step_run_source_instance.go b/builder/amazon/common/step_run_source_instance.go index 48c88db4a..3dcc8a20c 100644 --- a/builder/amazon/common/step_run_source_instance.go +++ b/builder/amazon/common/step_run_source_instance.go @@ -160,7 +160,6 @@ func (s *StepRunSourceInstance) Run(state multistep.StateBag) multistep.StepActi if spotPrice == "" || spotPrice == "0" { runOpts := &ec2.RunInstancesInput{ - KeyName: &keyName, ImageId: &s.SourceAMI, InstanceType: &s.InstanceType, UserData: &userData, @@ -172,6 +171,10 @@ func (s *StepRunSourceInstance) Run(state multistep.StateBag) multistep.StepActi EbsOptimized: &s.EbsOptimized, } + if keyName != "" { + runOpts.KeyName = &keyName + } + if s.SubnetId != "" && s.AssociatePublicIpAddress { runOpts.NetworkInterfaces = []*ec2.InstanceNetworkInterfaceSpecification{ &ec2.InstanceNetworkInterfaceSpecification{ @@ -203,29 +206,35 @@ func (s *StepRunSourceInstance) Run(state multistep.StateBag) multistep.StepActi ui.Message(fmt.Sprintf( "Requesting spot instance '%s' for: %s", s.InstanceType, spotPrice)) - runSpotResp, err := ec2conn.RequestSpotInstances(&ec2.RequestSpotInstancesInput{ - SpotPrice: &spotPrice, - LaunchSpecification: &ec2.RequestSpotLaunchSpecification{ - KeyName: &keyName, - ImageId: &s.SourceAMI, - InstanceType: &s.InstanceType, - UserData: &userData, - IamInstanceProfile: &ec2.IamInstanceProfileSpecification{Name: &s.IamInstanceProfile}, - NetworkInterfaces: []*ec2.InstanceNetworkInterfaceSpecification{ - &ec2.InstanceNetworkInterfaceSpecification{ - DeviceIndex: aws.Int64(0), - AssociatePublicIpAddress: &s.AssociatePublicIpAddress, - SubnetId: &s.SubnetId, - Groups: securityGroupIds, - DeleteOnTermination: aws.Bool(true), - }, - }, - Placement: &ec2.SpotPlacement{ - AvailabilityZone: &availabilityZone, + + runOpts := &ec2.RequestSpotLaunchSpecification{ + ImageId: &s.SourceAMI, + InstanceType: &s.InstanceType, + UserData: &userData, + IamInstanceProfile: &ec2.IamInstanceProfileSpecification{Name: &s.IamInstanceProfile}, + NetworkInterfaces: []*ec2.InstanceNetworkInterfaceSpecification{ + &ec2.InstanceNetworkInterfaceSpecification{ + DeviceIndex: aws.Int64(0), + AssociatePublicIpAddress: &s.AssociatePublicIpAddress, + SubnetId: &s.SubnetId, + Groups: securityGroupIds, + DeleteOnTermination: aws.Bool(true), }, - BlockDeviceMappings: s.BlockDevices.BuildLaunchDevices(), - EbsOptimized: &s.EbsOptimized, }, + Placement: &ec2.SpotPlacement{ + AvailabilityZone: &availabilityZone, + }, + BlockDeviceMappings: s.BlockDevices.BuildLaunchDevices(), + EbsOptimized: &s.EbsOptimized, + } + + if keyName != "" { + runOpts.KeyName = &keyName + } + + runSpotResp, err := ec2conn.RequestSpotInstances(&ec2.RequestSpotInstancesInput{ + SpotPrice: &spotPrice, + LaunchSpecification: runOpts, }) if err != nil { err := fmt.Errorf("Error launching source spot instance: %s", err) diff --git a/builder/amazon/ebs/builder.go b/builder/amazon/ebs/builder.go index 9916d821d..2d7f0e2b1 100644 --- a/builder/amazon/ebs/builder.go +++ b/builder/amazon/ebs/builder.go @@ -148,7 +148,8 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe ec2conn, b.config.SSHPrivateIp), SSHConfig: awscommon.SSHConfig( - b.config.RunConfig.Comm.SSHUsername), + b.config.RunConfig.Comm.SSHUsername, + b.config.RunConfig.Comm.SSHPassword), }, &common.StepProvision{}, &stepStopInstance{ diff --git a/builder/amazon/instance/builder.go b/builder/amazon/instance/builder.go index a900aef9b..c057f9f58 100644 --- a/builder/amazon/instance/builder.go +++ b/builder/amazon/instance/builder.go @@ -230,7 +230,8 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe ec2conn, b.config.SSHPrivateIp), SSHConfig: awscommon.SSHConfig( - b.config.RunConfig.Comm.SSHUsername), + b.config.RunConfig.Comm.SSHUsername, + b.config.RunConfig.Comm.SSHPassword), }, &common.StepProvision{}, &StepUploadX509Cert{}, From a38e16bdd8734f4ac05d363c62fe6fe91aecb66d Mon Sep 17 00:00:00 2001 From: Rickard von Essen Date: Mon, 3 Oct 2016 09:54:08 +0200 Subject: [PATCH 2/3] Delete ssh_username from the builder/amazon docs --- website/source/docs/builders/amazon-ebs.html.md | 3 --- website/source/docs/builders/amazon-instance.html.md | 3 --- 2 files changed, 6 deletions(-) diff --git a/website/source/docs/builders/amazon-ebs.html.md b/website/source/docs/builders/amazon-ebs.html.md index cdef61fc1..d84c40d44 100644 --- a/website/source/docs/builders/amazon-ebs.html.md +++ b/website/source/docs/builders/amazon-ebs.html.md @@ -60,9 +60,6 @@ builder. - `source_ami` (string) - The initial AMI used as a base for the newly created machine. -- `ssh_username` (string) - The username to use in order to communicate over - SSH to the running machine. - ### Optional: - `ami_block_device_mappings` (array of block device mappings) - Add the block diff --git a/website/source/docs/builders/amazon-instance.html.md b/website/source/docs/builders/amazon-instance.html.md index 846f0f556..31360ebb5 100644 --- a/website/source/docs/builders/amazon-instance.html.md +++ b/website/source/docs/builders/amazon-instance.html.md @@ -74,9 +74,6 @@ builder. - `source_ami` (string) - The initial AMI used as a base for the newly created machine. -- `ssh_username` (string) - The username to use in order to communicate over - SSH to the running machine. - - `x509_cert_path` (string) - The local path to a valid X509 certificate for your AWS account. This is used for bundling the AMI. This X509 certificate must be registered with your account from the security credentials page in From a04988a59752850f954431b95a9c2beb052a7c53 Mon Sep 17 00:00:00 2001 From: Rickard von Essen Date: Mon, 3 Oct 2016 10:03:45 +0200 Subject: [PATCH 3/3] Updated docs to reflect support for ssh_password --- website/source/docs/builders/amazon-ebs.html.md | 3 ++- website/source/docs/builders/amazon-instance.html.md | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/website/source/docs/builders/amazon-ebs.html.md b/website/source/docs/builders/amazon-ebs.html.md index d84c40d44..3d0634f38 100644 --- a/website/source/docs/builders/amazon-ebs.html.md +++ b/website/source/docs/builders/amazon-ebs.html.md @@ -185,7 +185,8 @@ 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. + generate a temporary keypair unless + [`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. diff --git a/website/source/docs/builders/amazon-instance.html.md b/website/source/docs/builders/amazon-instance.html.md index 31360ebb5..e80f2a773 100644 --- a/website/source/docs/builders/amazon-instance.html.md +++ b/website/source/docs/builders/amazon-instance.html.md @@ -207,7 +207,8 @@ builder. - `ssh_keypair_name` (string) - If specified, this is the key that will be used for SSH with the machine. The key must match a key pair name loaded up into Amazon EC2. By default, this is blank, and Packer will - generate a temporary keypair. + generate a temporary keypair unless + [`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 when `ssh_keypair_name` is utilized.