From 7425fef2c7c628fe2e773a8da0cc6dddb79e0c3d Mon Sep 17 00:00:00 2001 From: James Nugent Date: Sun, 23 Oct 2016 21:43:47 -0500 Subject: [PATCH 1/2] builder/amazon: Allow use of local SSH Agent This commit adds an option to use the local SSH Agent to authenticate connections to source instances started by the the EBS and Instance Store builders. This is of use when the source AMI _already_ has configuration for authorized SSH keys - for example if one uses an SSH certificate authority. A further extension (not implemented in this commit) is to allow SSH agent use with a pre-defined key pair, in order to allow keys with passphrases to be used without giving the passphrase to Packer. --- builder/amazon/common/ssh.go | 23 ++++++++++++++++++- builder/amazon/common/step_key_pair.go | 6 +++++ builder/amazon/ebs/builder.go | 1 + builder/amazon/instance/builder.go | 1 + helper/communicator/config.go | 1 + .../source/docs/builders/amazon-ebs.html.md | 6 +++++ .../docs/builders/amazon-instance.html.md | 6 +++++ 7 files changed, 43 insertions(+), 1 deletion(-) diff --git a/builder/amazon/common/ssh.go b/builder/amazon/common/ssh.go index 64479e841..3057bf171 100644 --- a/builder/amazon/common/ssh.go +++ b/builder/amazon/common/ssh.go @@ -3,12 +3,15 @@ package common import ( "errors" "fmt" + "net" + "os" "time" "github.com/aws/aws-sdk-go/service/ec2" "github.com/mitchellh/multistep" packerssh "github.com/mitchellh/packer/communicator/ssh" "golang.org/x/crypto/ssh" + "golang.org/x/crypto/ssh/agent" ) type ec2Describer interface { @@ -67,8 +70,26 @@ 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 private key // or 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") if hasKey { diff --git a/builder/amazon/common/step_key_pair.go b/builder/amazon/common/step_key_pair.go index 0a3cb1cb5..5f3e536de 100644 --- a/builder/amazon/common/step_key_pair.go +++ b/builder/amazon/common/step_key_pair.go @@ -13,6 +13,7 @@ import ( type StepKeyPair struct { Debug bool + SSHAgentAuth bool DebugKeyPath string TemporaryKeyPairName string KeyPairName string @@ -24,6 +25,11 @@ type StepKeyPair struct { func (s *StepKeyPair) Run(state multistep.StateBag) multistep.StepAction { ui := state.Get("ui").(packer.Ui) + if s.SSHAgentAuth { + ui.Say("Using SSH Agent") + return multistep.ActionContinue + } + if s.PrivateKeyFile != "" { ui.Say("Using existing ssh private key") privateKeyBytes, err := ioutil.ReadFile(s.PrivateKeyFile) diff --git a/builder/amazon/ebs/builder.go b/builder/amazon/ebs/builder.go index 5d9cced79..904e004c0 100644 --- a/builder/amazon/ebs/builder.go +++ b/builder/amazon/ebs/builder.go @@ -148,6 +148,7 @@ 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.SSHAgentAuth, b.config.RunConfig.Comm.SSHUsername, b.config.RunConfig.Comm.SSHPassword), }, diff --git a/builder/amazon/instance/builder.go b/builder/amazon/instance/builder.go index c057f9f58..bbf4e4f18 100644 --- a/builder/amazon/instance/builder.go +++ b/builder/amazon/instance/builder.go @@ -230,6 +230,7 @@ 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.SSHAgentAuth, b.config.RunConfig.Comm.SSHUsername, b.config.RunConfig.Comm.SSHPassword), }, diff --git a/helper/communicator/config.go b/helper/communicator/config.go index 7c36e70d9..66e4e96fa 100644 --- a/helper/communicator/config.go +++ b/helper/communicator/config.go @@ -23,6 +23,7 @@ type Config struct { SSHPrivateKey string `mapstructure:"ssh_private_key_file"` SSHPty bool `mapstructure:"ssh_pty"` SSHTimeout time.Duration `mapstructure:"ssh_timeout"` + SSHAgentAuth bool `mapstructure:"ssh_agent_auth"` SSHDisableAgent bool `mapstructure:"ssh_disable_agent"` SSHHandshakeAttempts int `mapstructure:"ssh_handshake_attempts"` SSHBastionHost string `mapstructure:"ssh_bastion_host"` diff --git a/website/source/docs/builders/amazon-ebs.html.md b/website/source/docs/builders/amazon-ebs.html.md index 9667e931f..841f464ef 100644 --- a/website/source/docs/builders/amazon-ebs.html.md +++ b/website/source/docs/builders/amazon-ebs.html.md @@ -194,6 +194,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 keypair will + be created, and the values of `ssh_password` and `ssh_private_key_file` will + be ignored. This is suitable for use if the source AMI already has authorized + keys configured. + - `ssh_private_ip` (boolean) - If true, then SSH will always use the private IP if available. diff --git a/website/source/docs/builders/amazon-instance.html.md b/website/source/docs/builders/amazon-instance.html.md index e80f2a773..474197ea8 100644 --- a/website/source/docs/builders/amazon-instance.html.md +++ b/website/source/docs/builders/amazon-instance.html.md @@ -212,6 +212,12 @@ builder. [`ssh_private_key_file`](/docs/templates/communicator.html#ssh_private_key_file) 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 keypair will + be created, and the values of `ssh_password` and `ssh_private_key_file` will + be ignored. This is suitable for use if the source AMI already has authorized + keys configured. + - `ssh_private_ip` (boolean) - If true, then SSH will always use the private IP if available. From 395d88941c2a0d3ec4a9aa2905c745f20f46617b Mon Sep 17 00:00:00 2001 From: James Nugent Date: Sun, 23 Oct 2016 22:26:14 -0500 Subject: [PATCH 2/2] amazon: Allow SSH Agent auth for existing key pair This commit allows SSH Agent authentication to be used with an existing key pair defined in AWS. --- builder/amazon/common/step_key_pair.go | 18 ++++++++++++------ .../source/docs/builders/amazon-ebs.html.md | 11 +++++++---- .../docs/builders/amazon-instance.html.md | 8 +++++--- 3 files changed, 24 insertions(+), 13 deletions(-) diff --git a/builder/amazon/common/step_key_pair.go b/builder/amazon/common/step_key_pair.go index 5f3e536de..334e4d729 100644 --- a/builder/amazon/common/step_key_pair.go +++ b/builder/amazon/common/step_key_pair.go @@ -25,13 +25,8 @@ type StepKeyPair struct { func (s *StepKeyPair) Run(state multistep.StateBag) multistep.StepAction { ui := state.Get("ui").(packer.Ui) - if s.SSHAgentAuth { - ui.Say("Using SSH Agent") - return multistep.ActionContinue - } - if s.PrivateKeyFile != "" { - ui.Say("Using existing ssh private key") + ui.Say("Using existing SSH private key") privateKeyBytes, err := ioutil.ReadFile(s.PrivateKeyFile) if err != nil { state.Put("error", fmt.Errorf( @@ -45,6 +40,17 @@ func (s *StepKeyPair) Run(state multistep.StateBag) multistep.StepAction { return multistep.ActionContinue } + if s.SSHAgentAuth && s.KeyPairName == "" { + ui.Say("Using SSH Agent with key pair in Source AMI") + 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", "") diff --git a/website/source/docs/builders/amazon-ebs.html.md b/website/source/docs/builders/amazon-ebs.html.md index 841f464ef..ee9d78cfe 100644 --- a/website/source/docs/builders/amazon-ebs.html.md +++ b/website/source/docs/builders/amazon-ebs.html.md @@ -188,17 +188,20 @@ builder. `Linux/UNIX (Amazon VPC)`, `SUSE Linux (Amazon VPC)`, `Windows (Amazon VPC)` - `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 + 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 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. + 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 keypair will be created, and the values of `ssh_password` and `ssh_private_key_file` will - be ignored. This is suitable for use if the source AMI already has authorized - keys configured. + be ignored. To use this option with a key pair already configured in the source + AMI, leave the `ssh_keypair_name` blank. To associate an existing key pair + in AWS with the source instance, set the `ssh_keypair_name` field to the name + of the key pair. - `ssh_private_ip` (boolean) - If true, then SSH will always use the private IP if available. diff --git a/website/source/docs/builders/amazon-instance.html.md b/website/source/docs/builders/amazon-instance.html.md index 474197ea8..5d2e9d8ba 100644 --- a/website/source/docs/builders/amazon-instance.html.md +++ b/website/source/docs/builders/amazon-instance.html.md @@ -210,13 +210,15 @@ builder. 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. + 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 keypair will be created, and the values of `ssh_password` and `ssh_private_key_file` will - be ignored. This is suitable for use if the source AMI already has authorized - keys configured. + be ignored. To use this option with a key pair already configured in the source + AMI, leave the `ssh_keypair_name` blank. To associate an existing key pair + in AWS with the source instance, set the `ssh_keypair_name` field to the name + of the key pair. - `ssh_private_ip` (boolean) - If true, then SSH will always use the private IP if available.