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 0cb35f15e..ed3890374 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 @@ -25,7 +26,7 @@ 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") + ui.Say("Using existing SSH private key") privateKeyBytes, err := ioutil.ReadFile(s.PrivateKeyFile) if err != nil { state.Put("error", fmt.Errorf( @@ -39,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/builder/amazon/ebs/builder.go b/builder/amazon/ebs/builder.go index b98cf19fa..c0ab2e44d 100644 --- a/builder/amazon/ebs/builder.go +++ b/builder/amazon/ebs/builder.go @@ -149,6 +149,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 5672f6dc7..36fdfc85a 100644 --- a/builder/amazon/instance/builder.go +++ b/builder/amazon/instance/builder.go @@ -231,6 +231,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 1780dc2c2..bc3c560ad 100644 --- a/website/source/docs/builders/amazon-ebs.html.md +++ b/website/source/docs/builders/amazon-ebs.html.md @@ -217,11 +217,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. 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 c7a42b2e6..9bc526e52 100644 --- a/website/source/docs/builders/amazon-instance.html.md +++ b/website/source/docs/builders/amazon-instance.html.md @@ -238,8 +238,16 @@ 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. 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.