diff --git a/builder/alicloud/ecs/step_config_key_pair.go b/builder/alicloud/ecs/step_config_key_pair.go index 9d502ea68..c4307378e 100644 --- a/builder/alicloud/ecs/step_config_key_pair.go +++ b/builder/alicloud/ecs/step_config_key_pair.go @@ -3,7 +3,6 @@ package ecs import ( "context" "fmt" - "io/ioutil" "os" "runtime" @@ -28,10 +27,9 @@ func (s *stepConfigAlicloudKeyPair) Run(_ context.Context, state multistep.State if s.Comm.SSHPrivateKeyFile != "" { ui.Say("Using existing SSH private key") - privateKeyBytes, err := ioutil.ReadFile(s.Comm.SSHPrivateKeyFile) + privateKeyBytes, err := s.Comm.ReadSSHPrivateKeyFile() if err != nil { - state.Put("error", fmt.Errorf( - "Error loading configured private key file: %s", err)) + state.Put("error", err) return multistep.ActionHalt } diff --git a/builder/amazon/common/step_key_pair.go b/builder/amazon/common/step_key_pair.go index 9b34b64af..ba72b5668 100644 --- a/builder/amazon/common/step_key_pair.go +++ b/builder/amazon/common/step_key_pair.go @@ -3,7 +3,6 @@ package common import ( "context" "fmt" - "io/ioutil" "os" "runtime" @@ -26,10 +25,9 @@ func (s *StepKeyPair) Run(_ context.Context, state multistep.StateBag) multistep if s.Comm.SSHPrivateKeyFile != "" { ui.Say("Using existing SSH private key") - privateKeyBytes, err := ioutil.ReadFile(s.Comm.SSHPrivateKeyFile) + privateKeyBytes, err := s.Comm.ReadSSHPrivateKeyFile() if err != nil { - state.Put("error", fmt.Errorf( - "Error loading configured private key file: %s", err)) + state.Put("error", err) return multistep.ActionHalt } diff --git a/builder/azure/arm/config.go b/builder/azure/arm/config.go index d72d28550..77daa67c0 100644 --- a/builder/azure/arm/config.go +++ b/builder/azure/arm/config.go @@ -326,7 +326,7 @@ func setSshValues(c *Config) error { } if c.Comm.SSHPrivateKeyFile != "" { - privateKeyBytes, err := ioutil.ReadFile(c.Comm.SSHPrivateKeyFile) + privateKeyBytes, err := c.Comm.ReadSSHPrivateKeyFile() if err != nil { return err } diff --git a/builder/cloudstack/step_keypair.go b/builder/cloudstack/step_keypair.go index f08bd706d..bb40534fe 100644 --- a/builder/cloudstack/step_keypair.go +++ b/builder/cloudstack/step_keypair.go @@ -3,7 +3,6 @@ package cloudstack import ( "context" "fmt" - "io/ioutil" "os" "runtime" @@ -23,10 +22,10 @@ func (s *stepKeypair) Run(_ context.Context, state multistep.StateBag) multistep ui := state.Get("ui").(packer.Ui) if s.Comm.SSHPrivateKeyFile != "" { - privateKeyBytes, err := ioutil.ReadFile(s.Comm.SSHPrivateKeyFile) + ui.Say("Using existing SSH private key") + privateKeyBytes, err := s.Comm.ReadSSHPrivateKeyFile() if err != nil { - state.Put("error", fmt.Errorf( - "Error loading configured private key file: %s", err)) + state.Put("error", err) return multistep.ActionHalt } diff --git a/builder/googlecompute/step_create_ssh_key.go b/builder/googlecompute/step_create_ssh_key.go index 889cf1b76..897e0a6a3 100644 --- a/builder/googlecompute/step_create_ssh_key.go +++ b/builder/googlecompute/step_create_ssh_key.go @@ -7,7 +7,6 @@ import ( "crypto/x509" "encoding/pem" "fmt" - "io/ioutil" "os" "github.com/hashicorp/packer/helper/multistep" @@ -29,10 +28,9 @@ func (s *StepCreateSSHKey) Run(_ context.Context, state multistep.StateBag) mult if config.Comm.SSHPrivateKeyFile != "" { ui.Say("Using existing SSH private key") - privateKeyBytes, err := ioutil.ReadFile(config.Comm.SSHPrivateKeyFile) + privateKeyBytes, err := config.Comm.ReadSSHPrivateKeyFile() if err != nil { - state.Put("error", fmt.Errorf( - "Error loading configured private key file: %s", err)) + state.Put("error", err) return multistep.ActionHalt } diff --git a/builder/openstack/step_key_pair.go b/builder/openstack/step_key_pair.go index ccf1eaecf..a850b8056 100644 --- a/builder/openstack/step_key_pair.go +++ b/builder/openstack/step_key_pair.go @@ -28,10 +28,10 @@ func (s *StepKeyPair) Run(_ context.Context, state multistep.StateBag) multistep ui := state.Get("ui").(packer.Ui) if s.Comm.SSHPrivateKeyFile != "" { - privateKeyBytes, err := ioutil.ReadFile(s.Comm.SSHPrivateKeyFile) + ui.Say("Using existing SSH private key") + privateKeyBytes, err := s.Comm.ReadSSHPrivateKeyFile() if err != nil { - state.Put("error", fmt.Errorf( - "Error loading configured private key file: %s", err)) + state.Put("error", err) return multistep.ActionHalt } diff --git a/builder/oracle/common/step_ssh_key_pair.go b/builder/oracle/common/step_ssh_key_pair.go index 04e82b986..49a0fd01b 100644 --- a/builder/oracle/common/step_ssh_key_pair.go +++ b/builder/oracle/common/step_ssh_key_pair.go @@ -7,7 +7,6 @@ import ( "crypto/x509" "encoding/pem" "fmt" - "io/ioutil" "os" "runtime" @@ -27,9 +26,9 @@ func (s *StepKeyPair) Run(_ context.Context, state multistep.StateBag) multistep ui := state.Get("ui").(packer.Ui) if s.Comm.SSHPrivateKeyFile != "" { - privateKeyBytes, err := ioutil.ReadFile(s.Comm.SSHPrivateKeyFile) + ui.Say("Using existing SSH private key") + privateKeyBytes, err := s.Comm.ReadSSHPrivateKeyFile() if err != nil { - err = fmt.Errorf("Error loading configured private key file: %s", err) ui.Error(err.Error()) state.Put("error", err) return multistep.ActionHalt diff --git a/builder/profitbricks/step_create_ssh_key.go b/builder/profitbricks/step_create_ssh_key.go index f9678c751..68c34af33 100644 --- a/builder/profitbricks/step_create_ssh_key.go +++ b/builder/profitbricks/step_create_ssh_key.go @@ -5,7 +5,6 @@ import ( "crypto/x509" "encoding/pem" "fmt" - "io/ioutil" "github.com/hashicorp/packer/helper/multistep" "github.com/hashicorp/packer/packer" @@ -22,9 +21,9 @@ func (s *StepCreateSSHKey) Run(_ context.Context, state multistep.StateBag) mult c := state.Get("config").(*Config) if c.Comm.SSHPrivateKeyFile != "" { - pemBytes, err := ioutil.ReadFile(c.Comm.SSHPrivateKeyFile) - + pemBytes, err := c.Comm.ReadSSHPrivateKeyFile() if err != nil { + state.Put("error", err) ui.Error(err.Error()) return multistep.ActionHalt } diff --git a/builder/scaleway/step_create_ssh_key.go b/builder/scaleway/step_create_ssh_key.go index c58875724..b2d211a9a 100644 --- a/builder/scaleway/step_create_ssh_key.go +++ b/builder/scaleway/step_create_ssh_key.go @@ -7,7 +7,6 @@ import ( "crypto/x509" "encoding/pem" "fmt" - "io/ioutil" "log" "os" "runtime" @@ -28,10 +27,9 @@ func (s *stepCreateSSHKey) Run(_ context.Context, state multistep.StateBag) mult if config.Comm.SSHPrivateKeyFile != "" { ui.Say("Using existing SSH private key") - privateKeyBytes, err := ioutil.ReadFile(config.Comm.SSHPrivateKeyFile) + privateKeyBytes, err := config.Comm.ReadSSHPrivateKeyFile() if err != nil { - state.Put("error", fmt.Errorf( - "Error loading configured private key file: %s", err)) + state.Put("error", err) return multistep.ActionHalt } diff --git a/helper/communicator/config.go b/helper/communicator/config.go index 44738d61b..fa4e053c3 100644 --- a/helper/communicator/config.go +++ b/helper/communicator/config.go @@ -13,6 +13,7 @@ import ( helperssh "github.com/hashicorp/packer/helper/ssh" "github.com/hashicorp/packer/template/interpolate" "github.com/masterzen/winrm" + "github.com/mitchellh/go-homedir" "golang.org/x/crypto/ssh" "golang.org/x/crypto/ssh/agent" ) @@ -27,8 +28,6 @@ type Config struct { SSHPort int `mapstructure:"ssh_port"` SSHUsername string `mapstructure:"ssh_username"` SSHPassword string `mapstructure:"ssh_password"` - SSHPublicKey []byte `mapstructure:"ssh_public_key"` - SSHPrivateKey []byte `mapstructure:"ssh_private_key"` SSHKeyPairName string `mapstructure:"ssh_keypair_name"` SSHTemporaryKeyPairName string `mapstructure:"temporary_key_pair_name"` SSHClearAuthorizedKeys bool `mapstructure:"ssh_clear_authorized_keys"` @@ -53,6 +52,9 @@ type Config struct { SSHProxyPassword string `mapstructure:"ssh_proxy_password"` SSHKeepAliveInterval time.Duration `mapstructure:"ssh_keep_alive_interval"` SSHReadWriteTimeout time.Duration `mapstructure:"ssh_read_write_timeout"` + // SSH Internals + SSHPublicKey []byte + SSHPrivateKey []byte // WinRM WinRMUser string `mapstructure:"winrm_username"` @@ -66,6 +68,23 @@ type Config struct { WinRMTransportDecorator func() winrm.Transporter } +// ReadSSHPrivateKeyFile returns the SSH private key bytes +func (c *Config) ReadSSHPrivateKeyFile() ([]byte, error) { + var privateKey []byte + + if c.SSHPrivateKeyFile != "" { + keyPath, err := homedir.Expand(c.SSHPrivateKeyFile) + if err != nil { + return privateKey, fmt.Errorf("Error expanding path for SSH private key: %s", err) + } + privateKey, err = ioutil.ReadFile(keyPath) + if err != nil { + return privateKey, fmt.Errorf("Error on reading SSH private key: %s", err) + } + } + return privateKey, nil +} + // SSHConfigFunc 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. @@ -92,12 +111,11 @@ func (c *Config) SSHConfigFunc() func(multistep.StateBag) (*ssh.ClientConfig, er var privateKeys [][]byte if c.SSHPrivateKeyFile != "" { - // key based auth - bytes, err := ioutil.ReadFile(c.SSHPrivateKeyFile) + privateKey, err := c.ReadSSHPrivateKeyFile() if err != nil { - return nil, fmt.Errorf("Error setting up SSH config: %s", err) + return nil, err } - privateKeys = append(privateKeys, bytes) + privateKeys = append(privateKeys, privateKey) } // aws,alicloud,cloudstack,digitalOcean,oneAndOne,openstack,oracle & profitbricks key @@ -112,7 +130,7 @@ func (c *Config) SSHConfigFunc() func(multistep.StateBag) (*ssh.ClientConfig, er for _, key := range privateKeys { signer, err := ssh.ParsePrivateKey(key) if err != nil { - return nil, fmt.Errorf("Error setting up SSH config: %s", err) + return nil, fmt.Errorf("Error on parsing SSH private key: %s", err) } sshConfig.Auth = append(sshConfig.Auth, ssh.PublicKeys(signer)) } @@ -243,10 +261,14 @@ func (c *Config) prepareSSH(ctx *interpolate.Context) []error { } if c.SSHPrivateKeyFile != "" { - if _, err := os.Stat(c.SSHPrivateKeyFile); err != nil { + path, err := homedir.Expand(c.SSHPrivateKeyFile) + if err != nil { errs = append(errs, fmt.Errorf( "ssh_private_key_file is invalid: %s", err)) - } else if _, err := helperssh.FileSigner(c.SSHPrivateKeyFile); err != nil { + } else if _, err := os.Stat(path); err != nil { + errs = append(errs, fmt.Errorf( + "ssh_private_key_file is invalid: %s", err)) + } else if _, err := helperssh.FileSigner(path); err != nil { errs = append(errs, fmt.Errorf( "ssh_private_key_file is invalid: %s", err)) } @@ -256,6 +278,18 @@ func (c *Config) prepareSSH(ctx *interpolate.Context) []error { if c.SSHBastionPassword == "" && c.SSHBastionPrivateKeyFile == "" { errs = append(errs, errors.New( "ssh_bastion_password or ssh_bastion_private_key_file must be specified")) + } else if c.SSHBastionPrivateKeyFile != "" { + path, err := homedir.Expand(c.SSHBastionPrivateKeyFile) + if err != nil { + errs = append(errs, fmt.Errorf( + "ssh_bastion_private_key_file is invalid: %s", err)) + } else if _, err := os.Stat(path); err != nil { + errs = append(errs, fmt.Errorf( + "ssh_bastion_private_key_file is invalid: %s", err)) + } else if _, err := helperssh.FileSigner(path); err != nil { + errs = append(errs, fmt.Errorf( + "ssh_bastion_private_key_file is invalid: %s", err)) + } } } diff --git a/helper/communicator/step_connect_ssh.go b/helper/communicator/step_connect_ssh.go index 6bdbcffae..6517a04a7 100644 --- a/helper/communicator/step_connect_ssh.go +++ b/helper/communicator/step_connect_ssh.go @@ -14,6 +14,7 @@ import ( "github.com/hashicorp/packer/helper/multistep" helperssh "github.com/hashicorp/packer/helper/ssh" "github.com/hashicorp/packer/packer" + "github.com/mitchellh/go-homedir" gossh "golang.org/x/crypto/ssh" "golang.org/x/crypto/ssh/agent" "golang.org/x/net/proxy" @@ -226,7 +227,12 @@ func sshBastionConfig(config *Config) (*gossh.ClientConfig, error) { } if config.SSHBastionPrivateKeyFile != "" { - signer, err := helperssh.FileSigner(config.SSHBastionPrivateKeyFile) + path, err := homedir.Expand(config.SSHBastionPrivateKeyFile) + if err != nil { + return nil, fmt.Errorf( + "Error expanding path for SSH bastion private key: %s", err) + } + signer, err := helperssh.FileSigner(path) if err != nil { return nil, err } diff --git a/website/source/docs/templates/communicator.html.md b/website/source/docs/templates/communicator.html.md index 4702c9d1f..8f88e280d 100644 --- a/website/source/docs/templates/communicator.html.md +++ b/website/source/docs/templates/communicator.html.md @@ -75,8 +75,9 @@ The SSH communicator has the following options: - `ssh_bastion_port` (number) - The port of the bastion host. Defaults to `22`. -- `ssh_bastion_private_key_file` (string) - A private key file to use to - authenticate with the bastion host. +- `ssh_bastion_private_key_file` (string) - Path to a PEM encoded private + key file to use to authenticate with the bastion host. The `~` can be used + in path and will be expanded to the home directory of current user. - `ssh_bastion_username` (string) - The username to connect to the bastion host. @@ -111,7 +112,8 @@ The SSH communicator has the following options: - `ssh_port` (number) - The port to connect to SSH. This defaults to `22`. - `ssh_private_key_file` (string) - Path to a PEM encoded private key file to - use to authenticate with SSH. + use to authenticate with SSH. The `~` can be used in path and will be + expanded to the home directory of current user. - `ssh_proxy_host` (string) - A SOCKS proxy host to use for SSH connection