From f233e54992af8618b41d3af87848a1b7d8155f18 Mon Sep 17 00:00:00 2001 From: Stephen Fox Date: Wed, 30 Jan 2019 21:59:56 -0500 Subject: [PATCH 01/49] Initial SSH key pair helper implementation. --- builder/virtualbox/common/sshkeypair.go | 221 +++++++++++++++++++ builder/virtualbox/common/sshkeypair_test.go | 93 ++++++++ 2 files changed, 314 insertions(+) create mode 100644 builder/virtualbox/common/sshkeypair.go create mode 100644 builder/virtualbox/common/sshkeypair_test.go diff --git a/builder/virtualbox/common/sshkeypair.go b/builder/virtualbox/common/sshkeypair.go new file mode 100644 index 000000000..dd926b9bc --- /dev/null +++ b/builder/virtualbox/common/sshkeypair.go @@ -0,0 +1,221 @@ +package common + +// TODO: Make this available to other packer APIs. +// Perhaps through 'helper/ssh'? + +import ( + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rand" + "crypto/rsa" + "crypto/x509" + "encoding/pem" + "errors" + "strconv" + + "golang.org/x/crypto/ssh" +) + +const ( + // That's a lot of bits. + defaultRsaBits = 4096 + + // rsaSsh is a SSH key pair of RSA type. + rsaSsh sshKeyPairType = "rsa" + + // ecdsaSsh is a SSH key pair of ECDSA type. + ecdsaSsh sshKeyPairType = "ecdsa" +) + +// sshKeyPairType represents different types of SSH key pairs. +// For example, RSA. +type sshKeyPairType string + +func (o sshKeyPairType) String() string { + return string(o) +} + +// sshKeyPairBuilder builds SSH key pairs. +type sshKeyPairBuilder interface { + // SetType sets the key pair type. + SetType(sshKeyPairType) sshKeyPairBuilder + + // SetBits sets the key pair's bits of entropy. + SetBits(int) sshKeyPairBuilder + + // Build returns a SSH key pair. + // + // The following defaults are used if not specified: + // Default type: ECDSA + // Default bits of entropy: + // - RSA: 4096 + // - ECDSA: 521 + Build() (sshKeyPair, error) +} + +type defaultSshKeyPairBuilder struct { + // kind describes the resulting key pair's type. + kind sshKeyPairType + + // bits is the resulting key pair's bits of entropy. + bits int +} + +func (o *defaultSshKeyPairBuilder) SetType(kind sshKeyPairType) sshKeyPairBuilder { + o.kind = kind + return o +} + +func (o *defaultSshKeyPairBuilder) SetBits(bits int) sshKeyPairBuilder { + o.bits = bits + return o +} + +func (o *defaultSshKeyPairBuilder) Build() (sshKeyPair, error) { + switch o.kind { + case rsaSsh: + return newRsaSshKeyPair(o.bits) + case ecdsaSsh: + // Default case. + } + + return newEcdsaSshKeyPair(o.bits) +} + +// sshKeyPair represents a SSH key pair. +type sshKeyPair interface { + // Type returns the key pair's type. + Type() sshKeyPairType + + // Bits returns the bits of entropy. + Bits() int + + // PrivateKeyPemBlock returns a slice of bytes representing + // the private key in ASN.1, DER format in a PEM block. + PrivateKeyPemBlock() []byte + + // PublicKeyAuthorizedKeysFormat returns a slice of bytes + // representing the public key in OpenSSH authorized_keys + // format with a trailing new line. + PublicKeyAuthorizedKeysFormat() []byte +} + +type defaultSshKeyPair struct { + // kind is the key pair's type. + kind sshKeyPairType + + // bits is the key pair's bits of entropy. + bits int + + // privateKeyDerBytes is the private key's bytes + // in ASN.1 DER format + privateKeyDerBytes []byte + + // publicKey is the key pair's public key. + publicKey ssh.PublicKey +} + +func (o defaultSshKeyPair) Type() sshKeyPairType { + return o.kind +} + +func (o defaultSshKeyPair) Bits() int { + return o.bits +} + +func (o defaultSshKeyPair) PrivateKeyPemBlock() []byte { + t := "UNKNOWN PRIVATE KEY" + + switch o.kind { + case ecdsaSsh: + t = "EC PRIVATE KEY" + case rsaSsh: + t = "RSA PRIVATE KEY" + } + + return pem.EncodeToMemory(&pem.Block{ + Type: t, + Headers: nil, + Bytes: o.privateKeyDerBytes, + }) +} + +func (o defaultSshKeyPair) PublicKeyAuthorizedKeysFormat() []byte { + return ssh.MarshalAuthorizedKey(o.publicKey) +} + +// newEcdsaSshKeyPair returns a new ECDSA SSH key pair for the given bits +// of entropy. +func newEcdsaSshKeyPair(bits int) (sshKeyPair, error) { + var curve elliptic.Curve + + switch bits { + case 0: + bits = 521 + fallthrough + case 521: + curve = elliptic.P521() + case 384: + elliptic.P384() + case 256: + elliptic.P256() + case 224: + // Not supported by "golang.org/x/crypto/ssh". + return &defaultSshKeyPair{}, errors.New("golang.org/x/crypto/ssh does not support " + + strconv.Itoa(bits) + " bits") + default: + return &defaultSshKeyPair{}, errors.New("crypto/elliptic does not support " + + strconv.Itoa(bits) + " bits") + } + + privateKey, err := ecdsa.GenerateKey(curve, rand.Reader) + if err != nil { + return &defaultSshKeyPair{}, err + } + + sshPublicKey, err := ssh.NewPublicKey(&privateKey.PublicKey) + if err != nil { + return &defaultSshKeyPair{}, err + } + + raw, err := x509.MarshalECPrivateKey(privateKey) + if err != nil { + return &defaultSshKeyPair{}, err + } + + return &defaultSshKeyPair{ + kind: ecdsaSsh, + bits: bits, + privateKeyDerBytes: raw, + publicKey: sshPublicKey, + }, nil +} + +// newRsaSshKeyPair returns a new RSA SSH key pair for the given bits +// of entropy. +func newRsaSshKeyPair(bits int) (sshKeyPair, error) { + if bits == 0 { + bits = defaultRsaBits + } + + privateKey, err := rsa.GenerateKey(rand.Reader, bits) + if err != nil { + return &defaultSshKeyPair{}, err + } + + sshPublicKey, err := ssh.NewPublicKey(&privateKey.PublicKey) + if err != nil { + return &defaultSshKeyPair{}, err + } + + return &defaultSshKeyPair{ + kind: rsaSsh, + bits: bits, + privateKeyDerBytes: x509.MarshalPKCS1PrivateKey(privateKey), + publicKey: sshPublicKey, + }, nil +} + +func newSshKeyPairBuilder() sshKeyPairBuilder { + return &defaultSshKeyPairBuilder{} +} diff --git a/builder/virtualbox/common/sshkeypair_test.go b/builder/virtualbox/common/sshkeypair_test.go new file mode 100644 index 000000000..82cae75b8 --- /dev/null +++ b/builder/virtualbox/common/sshkeypair_test.go @@ -0,0 +1,93 @@ +package common + +import ( + "crypto/rand" + "errors" + "testing" + + "golang.org/x/crypto/ssh" +) + +func TestDefaultSshKeyPairBuilder_Build_Default(t *testing.T) { + kp, err := newSshKeyPairBuilder().Build() + if err != nil { + t.Fatal(err.Error()) + } + + if kp.Type() != ecdsaSsh { + t.Fatal("Expected key pair type to be", + ecdsaSsh.String(), "- got", kp.Type()) + } + + if kp.Bits() != 521 { + t.Fatal("Expected key pair to be 521 bits - got", kp.Bits()) + } + + err = verifySshKeyPair(kp) + if err != nil { + t.Fatal(err.Error()) + } +} + +func TestDefaultSshKeyPairBuilder_Build_EcdsaDefault(t *testing.T) { + kp, err := newSshKeyPairBuilder().SetType(ecdsaSsh).Build() + if err != nil { + t.Fatal(err.Error()) + } + + if kp.Type() != ecdsaSsh { + t.Fatal("Expected key pair type to be", + ecdsaSsh.String(), "- got", kp.Type()) + } + + if kp.Bits() != 521 { + t.Fatal("Expected key pair to be 521 bits - got", kp.Bits()) + } + + err = verifySshKeyPair(kp) + if err != nil { + t.Fatal(err.Error()) + } +} + +func TestDefaultSshKeyPairBuilder_Build_RsaDefault(t *testing.T) { + kp, err := newSshKeyPairBuilder().SetType(rsaSsh).Build() + if err != nil { + t.Fatal(err.Error()) + } + + if kp.Type() != rsaSsh { + t.Fatal("Expected default key pair type to be", + rsaSsh.String(), "- got", kp.Type()) + } + + if kp.Bits() != 4096 { + t.Fatal("Expected key pair to be", 4096, "bits - got", kp.Bits()) + } + + err = verifySshKeyPair(kp) + if err != nil { + t.Fatal(err.Error()) + } +} + +func verifySshKeyPair(kp sshKeyPair) error { + signer, err := ssh.ParsePrivateKey(kp.PrivateKeyPemBlock()) + if err != nil { + return errors.New("failed to parse private key during verification - " + err.Error()) + } + + data := []byte{'b', 'r', '4', 'n', '3'} + + signature, err := signer.Sign(rand.Reader, data) + if err != nil { + return errors.New("failed to sign test data during verification - " + err.Error()) + } + + err = signer.PublicKey().Verify(data, signature) + if err != nil { + return errors.New("failed to verify test data - " + err.Error()) + } + + return nil +} From ad075ffac39b69373aa3e756a0c68ded7c9cec70 Mon Sep 17 00:00:00 2001 From: Stephen Fox Date: Sun, 3 Feb 2019 09:17:18 -0500 Subject: [PATCH 02/49] Added 'Description()' to 'sshKeyPair'. Also cleaned up tests. --- builder/virtualbox/common/sshkeypair.go | 8 ++ builder/virtualbox/common/sshkeypair_test.go | 88 +++++++++++++------- 2 files changed, 67 insertions(+), 29 deletions(-) diff --git a/builder/virtualbox/common/sshkeypair.go b/builder/virtualbox/common/sshkeypair.go index dd926b9bc..8e323aee9 100644 --- a/builder/virtualbox/common/sshkeypair.go +++ b/builder/virtualbox/common/sshkeypair.go @@ -90,6 +90,10 @@ type sshKeyPair interface { // Bits returns the bits of entropy. Bits() int + // Description returns a brief description of the key pair that + // is suitable for log messages or printing. + Description() string + // PrivateKeyPemBlock returns a slice of bytes representing // the private key in ASN.1, DER format in a PEM block. PrivateKeyPemBlock() []byte @@ -123,6 +127,10 @@ func (o defaultSshKeyPair) Bits() int { return o.bits } +func (o defaultSshKeyPair) Description() string { + return o.kind.String() + " " + strconv.Itoa(o.bits) +} + func (o defaultSshKeyPair) PrivateKeyPemBlock() []byte { t := "UNKNOWN PRIVATE KEY" diff --git a/builder/virtualbox/common/sshkeypair_test.go b/builder/virtualbox/common/sshkeypair_test.go index 82cae75b8..b92fa385f 100644 --- a/builder/virtualbox/common/sshkeypair_test.go +++ b/builder/virtualbox/common/sshkeypair_test.go @@ -3,48 +3,83 @@ package common import ( "crypto/rand" "errors" + "strconv" "testing" "golang.org/x/crypto/ssh" ) -func TestDefaultSshKeyPairBuilder_Build_Default(t *testing.T) { - kp, err := newSshKeyPairBuilder().Build() - if err != nil { - t.Fatal(err.Error()) +// expected contains the data that the key pair should contain. +type expected struct { + kind sshKeyPairType + bits int + desc string +} + +func (o expected) matches(kp sshKeyPair) error { + if o.kind.String() == "" { + return errors.New("expected kind's value cannot be empty") + } + + if o.bits <= 0 { + return errors.New("expected bits' value cannot be less than or equal to 0") } - if kp.Type() != ecdsaSsh { - t.Fatal("Expected key pair type to be", - ecdsaSsh.String(), "- got", kp.Type()) + if o.desc == "" { + return errors.New("expected description's value cannot be empty") } - if kp.Bits() != 521 { - t.Fatal("Expected key pair to be 521 bits - got", kp.Bits()) + if kp.Type() != o.kind { + return errors.New("expected key pair type to be " + + o.kind.String() + " - got '" + kp.Type().String() + "'") } - err = verifySshKeyPair(kp) + if kp.Bits() != o.bits { + return errors.New("expected key pair to be " + + strconv.Itoa(o.bits) + " bits - got " + strconv.Itoa(kp.Bits())) + } + + expDescription := kp.Type().String() + " " + strconv.Itoa(o.bits) + if kp.Description() != expDescription { + return errors.New("expected key pair description to be '" + + expDescription + "' - got '" + kp.Description() + "'") + } + + err := verifySshKeyPair(kp) if err != nil { - t.Fatal(err.Error()) + return err } + + return nil } -func TestDefaultSshKeyPairBuilder_Build_EcdsaDefault(t *testing.T) { - kp, err := newSshKeyPairBuilder().SetType(ecdsaSsh).Build() +func TestDefaultSshKeyPairBuilder_Build_Default(t *testing.T) { + kp, err := newSshKeyPairBuilder().Build() if err != nil { t.Fatal(err.Error()) } - if kp.Type() != ecdsaSsh { - t.Fatal("Expected key pair type to be", - ecdsaSsh.String(), "- got", kp.Type()) + err = expected{ + kind: ecdsaSsh, + bits: 521, + desc: "ecdsa 521", + }.matches(kp) + if err != nil { + t.Fatal(err.Error()) } +} - if kp.Bits() != 521 { - t.Fatal("Expected key pair to be 521 bits - got", kp.Bits()) +func TestDefaultSshKeyPairBuilder_Build_EcdsaDefault(t *testing.T) { + kp, err := newSshKeyPairBuilder().SetType(ecdsaSsh).Build() + if err != nil { + t.Fatal(err.Error()) } - err = verifySshKeyPair(kp) + err = expected{ + kind: ecdsaSsh, + bits: 521, + desc: "ecdsa 521", + }.matches(kp) if err != nil { t.Fatal(err.Error()) } @@ -56,16 +91,11 @@ func TestDefaultSshKeyPairBuilder_Build_RsaDefault(t *testing.T) { t.Fatal(err.Error()) } - if kp.Type() != rsaSsh { - t.Fatal("Expected default key pair type to be", - rsaSsh.String(), "- got", kp.Type()) - } - - if kp.Bits() != 4096 { - t.Fatal("Expected key pair to be", 4096, "bits - got", kp.Bits()) - } - - err = verifySshKeyPair(kp) + err = expected{ + kind: rsaSsh, + bits: 4096, + desc: "rsa 4096", + }.matches(kp) if err != nil { t.Fatal(err.Error()) } From 6824806e6f422cad597373a6a3f14bf75d7b78bd Mon Sep 17 00:00:00 2001 From: Stephen Fox Date: Sun, 3 Feb 2019 10:36:06 -0500 Subject: [PATCH 03/49] Allow caller to specify new line for SSH public key. The default behavior of the various builders that create SSH key pairs appears to be to add a trailing new line. This will be the default behavior, but at least it can be customized if desired. --- builder/virtualbox/common/sshkeypair.go | 52 ++++++++++++++++++-- builder/virtualbox/common/sshkeypair_test.go | 43 +++++++++++++++- 2 files changed, 89 insertions(+), 6 deletions(-) diff --git a/builder/virtualbox/common/sshkeypair.go b/builder/virtualbox/common/sshkeypair.go index 8e323aee9..dfaa4208a 100644 --- a/builder/virtualbox/common/sshkeypair.go +++ b/builder/virtualbox/common/sshkeypair.go @@ -4,6 +4,7 @@ package common // Perhaps through 'helper/ssh'? import ( + "bytes" "crypto/ecdsa" "crypto/elliptic" "crypto/rand" @@ -35,6 +36,29 @@ func (o sshKeyPairType) String() string { return string(o) } +const ( + // unixNewLine is a unix new line. + unixNewLine newLineOption = "\n" + + // windowsNewLine is a Windows new line. + windowsNewLine newLineOption = "\r\n" + + // noNewLine will not append a new line. + noNewLine newLineOption = "" +) + +// newLineOption specifies the type of new line to append to a string. +// See the 'const' block for choices. +type newLineOption string + +func (o newLineOption) String() string { + return string(o) +} + +func (o newLineOption) Bytes() []byte { + return []byte(o) +} + // sshKeyPairBuilder builds SSH key pairs. type sshKeyPairBuilder interface { // SetType sets the key pair type. @@ -99,9 +123,9 @@ type sshKeyPair interface { PrivateKeyPemBlock() []byte // PublicKeyAuthorizedKeysFormat returns a slice of bytes - // representing the public key in OpenSSH authorized_keys - // format with a trailing new line. - PublicKeyAuthorizedKeysFormat() []byte + // representing the public key in OpenSSH authorized_keys format + // with the specified new line. + PublicKeyAuthorizedKeysFormat(newLineOption) []byte } type defaultSshKeyPair struct { @@ -148,8 +172,26 @@ func (o defaultSshKeyPair) PrivateKeyPemBlock() []byte { }) } -func (o defaultSshKeyPair) PublicKeyAuthorizedKeysFormat() []byte { - return ssh.MarshalAuthorizedKey(o.publicKey) +func (o defaultSshKeyPair) PublicKeyAuthorizedKeysFormat(nl newLineOption) []byte { + result := ssh.MarshalAuthorizedKey(o.publicKey) + + switch nl { + case noNewLine: + result = bytes.TrimSuffix(result, unixNewLine.Bytes()) + case windowsNewLine: + result = bytes.TrimSuffix(result, unixNewLine.Bytes()) + result = append(result, nl.Bytes()...) + case unixNewLine: + fallthrough + default: + // This is how all the other "SSH key pair" code works in + // the different builders. + if !bytes.HasSuffix(result, unixNewLine.Bytes()) { + result = append(result, unixNewLine.Bytes()...) + } + } + + return result } // newEcdsaSshKeyPair returns a new ECDSA SSH key pair for the given bits diff --git a/builder/virtualbox/common/sshkeypair_test.go b/builder/virtualbox/common/sshkeypair_test.go index b92fa385f..e976b655e 100644 --- a/builder/virtualbox/common/sshkeypair_test.go +++ b/builder/virtualbox/common/sshkeypair_test.go @@ -45,7 +45,12 @@ func (o expected) matches(kp sshKeyPair) error { expDescription + "' - got '" + kp.Description() + "'") } - err := verifySshKeyPair(kp) + err := verifyPublickeyAuthorizedKeysFormat(kp) + if err != nil { + return err + } + + err = verifySshKeyPair(kp) if err != nil { return err } @@ -121,3 +126,39 @@ func verifySshKeyPair(kp sshKeyPair) error { return nil } + +func verifyPublickeyAuthorizedKeysFormat(kp sshKeyPair) error { + newLines := []newLineOption{ + unixNewLine, + noNewLine, + windowsNewLine, + } + + for _, nl := range newLines { + publicKeyAk := kp.PublicKeyAuthorizedKeysFormat(nl) + + if len(publicKeyAk) < 2 { + return errors.New("expected public key in authorized keys format to be at least 2 bytes") + } + + switch nl { + case noNewLine: + if publicKeyAk[len(publicKeyAk) - 1] == '\n' { + return errors.New("public key in authorized keys format has trailing new line when none was specified") + } + case unixNewLine: + if publicKeyAk[len(publicKeyAk) - 1] != '\n' { + return errors.New("public key in authorized keys format does not have unix new line when unix was specified") + } + if string(publicKeyAk[len(publicKeyAk) - 2:]) == windowsNewLine.String() { + return errors.New("public key in authorized keys format has windows new line when unix was specified") + } + case windowsNewLine: + if string(publicKeyAk[len(publicKeyAk) - 2:]) != windowsNewLine.String() { + return errors.New("public key in authorized keys format does not have windows new line when windows was specified") + } + } + } + + return nil +} From 459bd1ea7ac115e9de2c3a9feb796b0b8ead44ae Mon Sep 17 00:00:00 2001 From: Stephen Fox Date: Sun, 3 Feb 2019 12:07:57 -0500 Subject: [PATCH 04/49] Added vbox step for SSH key pair business logic. --- .../virtualbox/common/step_ssh_key_pair.go | 103 ++++++++++++++++++ 1 file changed, 103 insertions(+) create mode 100644 builder/virtualbox/common/step_ssh_key_pair.go diff --git a/builder/virtualbox/common/step_ssh_key_pair.go b/builder/virtualbox/common/step_ssh_key_pair.go new file mode 100644 index 000000000..afb4f9563 --- /dev/null +++ b/builder/virtualbox/common/step_ssh_key_pair.go @@ -0,0 +1,103 @@ +package common + +import ( + "context" + "fmt" + "os" + "runtime" + + "github.com/hashicorp/packer/common/uuid" + "github.com/hashicorp/packer/helper/communicator" + "github.com/hashicorp/packer/helper/multistep" + "github.com/hashicorp/packer/packer" +) + +// StepSshKeyPair executes the business logic for setting the SSH key pair in +// the specified communicator.Config. +type StepSshKeyPair struct { + Debug bool + DebugKeyPath string + Comm *communicator.Config +} + +func (s *StepSshKeyPair) Run(_ context.Context, state multistep.StateBag) multistep.StepAction { + if s.Comm.SSHPassword != "" { + return multistep.ActionContinue + } + + ui := state.Get("ui").(packer.Ui) + + if s.Comm.SSHPrivateKeyFile != "" { + ui.Say("Using existing SSH private key for the communicator...") + privateKeyBytes, err := s.Comm.ReadSSHPrivateKeyFile() + if err != nil { + state.Put("error", err) + return multistep.ActionHalt + } + + s.Comm.SSHPrivateKey = privateKeyBytes + + return multistep.ActionContinue + } + + if s.Comm.SSHAgentAuth { + ui.Say("Using local SSH Agent to authenticate connections for the communicator...") + return multistep.ActionContinue + } + + ui.Say("Creating ephemeral key pair for SSH communicator...") + + // TODO: Should we respect 's.Comm.SSHTemporaryKeyPairName'? + // It appears to be specific to certain other builders, but it is not + // mentioned in the virtualbox builders' documentation. + s.Comm.SSHKeyPairName = fmt.Sprintf("packer_%s", uuid.TimeOrderedUUID()) + + kp, err := newSshKeyPairBuilder().Build() + if err != nil { + state.Put("error", fmt.Errorf("Error creating temporary keypair: %s", err)) + return multistep.ActionHalt + } + + s.Comm.SSHPrivateKey = kp.PrivateKeyPemBlock() + s.Comm.SSHPublicKey = kp.PublicKeyAuthorizedKeysFormat(unixNewLine) + + ui.Say(fmt.Sprintf("Created ephemeral SSH key pair of type %s", kp.Description())) + + // If we're in debug mode, output the private key to the working + // directory. + if s.Debug { + ui.Message(fmt.Sprintf("Saving communicator private key for debug purposes: %s", s.DebugKeyPath)) + f, err := os.Create(s.DebugKeyPath) + if err != nil { + state.Put("error", fmt.Errorf("Error saving debug key: %s", err)) + return multistep.ActionHalt + } + defer f.Close() + + // Write the key out + if _, err := f.Write(kp.PrivateKeyPemBlock()); err != nil { + state.Put("error", fmt.Errorf("Error saving debug key: %s", err)) + return multistep.ActionHalt + } + + // Chmod it so that it is SSH ready + if runtime.GOOS != "windows" { + if err := f.Chmod(0600); err != nil { + state.Put("error", fmt.Errorf("Error setting permissions of debug key: %s", err)) + return multistep.ActionHalt + } + } + } + + return multistep.ActionContinue +} + +func (s *StepSshKeyPair) Cleanup(state multistep.StateBag) { + if s.Debug { + if err := os.Remove(s.DebugKeyPath); err != nil { + ui := state.Get("ui").(packer.Ui) + ui.Error(fmt.Sprintf( + "Error removing debug key '%s': %s", s.DebugKeyPath, err)) + } + } +} From 7b857929abbf7ff51398fa7b6e850dbf936576bd Mon Sep 17 00:00:00 2001 From: Stephen Fox Date: Sun, 3 Feb 2019 12:10:27 -0500 Subject: [PATCH 05/49] Added 'SSHPublicKeyUrlEncoded()' to comm.Config. This allows us to get a URL encoded string representing the SSH public key. This is needed because the key will have spaces when it is in authorized_keys format. --- helper/communicator/config.go | 7 +++++++ helper/communicator/config_test.go | 18 ++++++++++++++++++ 2 files changed, 25 insertions(+) diff --git a/helper/communicator/config.go b/helper/communicator/config.go index 7d66254b5..75ac3b2b6 100644 --- a/helper/communicator/config.go +++ b/helper/communicator/config.go @@ -5,6 +5,7 @@ import ( "fmt" "io/ioutil" "net" + "net/url" "os" "time" @@ -329,3 +330,9 @@ func (c *Config) prepareWinRM(ctx *interpolate.Context) []error { return errs } + +// SSHPublicKeyUrlEncoded returns a string representing the SSH public key +// encoded in URL format. +func (c *Config) SSHPublicKeyUrlEncoded() string { + return url.PathEscape(string(c.SSHPublicKey)) +} diff --git a/helper/communicator/config_test.go b/helper/communicator/config_test.go index c5af24114..9f28c2be5 100644 --- a/helper/communicator/config_test.go +++ b/helper/communicator/config_test.go @@ -1,6 +1,7 @@ package communicator import ( + "net/url" "reflect" "testing" @@ -136,6 +137,23 @@ func TestConfig_winrm(t *testing.T) { } } +func TestConfig_SSHPublicKeyUrlEncoded(t *testing.T) { + c := &Config{ + SSHPublicKey: []byte("ecdsa-sha2-nistp521 AAAAE2VjZHNhLXNoYTItbmlzdHA1MjEAAAAIbmlzdHA1MjEAAACFBADulbdHCjXhsH8wGtyLhhi3qVvX6M0tGgtousr/DzArwf2KX0L2Zm1OZfqMWFCrSVD743OFY60YL5CGsN9/PVQP7gApll5yTWyaQJu8lReptR5TMnUDn0u3mJN/QRT5Zs8qS5J5Q3WhXwaMF96kSuu+MwXrBnl8sK+bwxOKQtlKJXowcw==\n"), + } + + encoded := c.SSHPublicKeyUrlEncoded() + + decoded, err := url.PathUnescape(encoded) + if err != nil { + t.Fatal(err.Error()) + } + + if decoded != string(c.SSHPublicKey) { + t.Fatal("resulting public key does not match original public key") + } +} + func testContext(t *testing.T) *interpolate.Context { return nil } From 72d15015513150f2009f8c3612909841fa7bfd89 Mon Sep 17 00:00:00 2001 From: Stephen Fox Date: Sun, 3 Feb 2019 12:16:43 -0500 Subject: [PATCH 06/49] Made SSH public key available in vbox boot command. The SSH public key (in authorized_keys format) can be retreived in the build template by specifying '{{ .SSHPublicKey }}'. The key is URL encoded to escape spaces. --- .../common/step_type_boot_command.go | 24 +++++++++++++++---- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/builder/virtualbox/common/step_type_boot_command.go b/builder/virtualbox/common/step_type_boot_command.go index 859e425ff..0cb433903 100644 --- a/builder/virtualbox/common/step_type_boot_command.go +++ b/builder/virtualbox/common/step_type_boot_command.go @@ -7,6 +7,7 @@ import ( "github.com/hashicorp/packer/common" "github.com/hashicorp/packer/common/bootcommand" + "github.com/hashicorp/packer/helper/communicator" "github.com/hashicorp/packer/helper/multistep" "github.com/hashicorp/packer/packer" "github.com/hashicorp/packer/template/interpolate" @@ -14,10 +15,21 @@ import ( const KeyLeftShift uint32 = 0xFFE1 +// TODO: Should this be made available for other builders? +// It is copy pasted in the VMWare builder as well. type bootCommandTemplateData struct { - HTTPIP string + // HTTPIP is the HTTP server's IP address. + HTTPIP string + + // HTTPPort is the HTTP server port. HTTPPort uint - Name string + + // Name is the VM's name. + Name string + + // SSHPublicKey is the URL encoded public key in + // authorized_keys format. + SSHPublicKey string } type StepTypeBootCommand struct { @@ -26,6 +38,7 @@ type StepTypeBootCommand struct { VMName string Ctx interpolate.Context GroupInterval time.Duration + Comm *communicator.Config } func (s *StepTypeBootCommand) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction { @@ -54,9 +67,10 @@ func (s *StepTypeBootCommand) Run(ctx context.Context, state multistep.StateBag) hostIP := "10.0.2.2" common.SetHTTPIP(hostIP) s.Ctx.Data = &bootCommandTemplateData{ - hostIP, - httpPort, - s.VMName, + HTTPIP: hostIP, + HTTPPort: httpPort, + Name: s.VMName, + SSHPublicKey: s.Comm.SSHPublicKeyUrlEncoded(), } sendCodes := func(codes []string) error { From cd4ae0332465aa75a2e669907023977c3f193ed3 Mon Sep 17 00:00:00 2001 From: Stephen Fox Date: Sun, 3 Feb 2019 12:20:52 -0500 Subject: [PATCH 07/49] Create / use SSH key pairs in vbox builders. This follows the business logic for creating / using SSH key pairs in the cloud-type builders (e.g., Amazon EC2). --- builder/virtualbox/iso/builder.go | 6 ++++++ builder/virtualbox/ovf/builder.go | 6 ++++++ 2 files changed, 12 insertions(+) diff --git a/builder/virtualbox/iso/builder.go b/builder/virtualbox/iso/builder.go index 55aa602a9..ab6111a08 100644 --- a/builder/virtualbox/iso/builder.go +++ b/builder/virtualbox/iso/builder.go @@ -222,6 +222,11 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe HTTPPortMin: b.config.HTTPPortMin, HTTPPortMax: b.config.HTTPPortMax, }, + &vboxcommon.StepSshKeyPair{ + Debug: b.config.PackerDebug, + DebugKeyPath: fmt.Sprintf("virtualbox_%s.pem", b.config.PackerBuildName), + Comm: &b.config.Comm, + }, new(vboxcommon.StepSuppressMessages), new(stepCreateVM), new(stepCreateDisk), @@ -254,6 +259,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe VMName: b.config.VMName, Ctx: b.config.ctx, GroupInterval: b.config.BootConfig.BootGroupInterval, + Comm: &b.config.Comm, }, &communicator.StepConnect{ Config: &b.config.SSHConfig.Comm, diff --git a/builder/virtualbox/ovf/builder.go b/builder/virtualbox/ovf/builder.go index 60142728f..020a58b87 100644 --- a/builder/virtualbox/ovf/builder.go +++ b/builder/virtualbox/ovf/builder.go @@ -64,6 +64,11 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe HTTPPortMin: b.config.HTTPPortMin, HTTPPortMax: b.config.HTTPPortMax, }, + &vboxcommon.StepSshKeyPair{ + Debug: b.config.PackerDebug, + DebugKeyPath: fmt.Sprintf("virtualbox_%s.pem", b.config.PackerBuildName), + Comm: &b.config.Comm, + }, &vboxcommon.StepDownloadGuestAdditions{ GuestAdditionsMode: b.config.GuestAdditionsMode, GuestAdditionsURL: b.config.GuestAdditionsURL, @@ -111,6 +116,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe VMName: b.config.VMName, Ctx: b.config.ctx, GroupInterval: b.config.BootConfig.BootGroupInterval, + Comm: &b.config.Comm, }, &communicator.StepConnect{ Config: &b.config.SSHConfig.Comm, From 5893134c6106cfebfec1ebb9484f45fc5672f602 Mon Sep 17 00:00:00 2001 From: Stephen Fox Date: Mon, 4 Feb 2019 10:02:07 -0500 Subject: [PATCH 08/49] Updated comment for 'PrivateKeyPemBlock()'. --- builder/virtualbox/common/sshkeypair.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/builder/virtualbox/common/sshkeypair.go b/builder/virtualbox/common/sshkeypair.go index dfaa4208a..fe5ba449c 100644 --- a/builder/virtualbox/common/sshkeypair.go +++ b/builder/virtualbox/common/sshkeypair.go @@ -119,7 +119,8 @@ type sshKeyPair interface { Description() string // PrivateKeyPemBlock returns a slice of bytes representing - // the private key in ASN.1, DER format in a PEM block. + // the private key in ASN.1 Distinguished Encoding Rules (DER) + // format in a Privacy-Enhanced Mail (PEM) block. PrivateKeyPemBlock() []byte // PublicKeyAuthorizedKeysFormat returns a slice of bytes From b1b67ecffa999326b52828e4e36df64ddfe5d286 Mon Sep 17 00:00:00 2001 From: Stephen Fox Date: Mon, 4 Feb 2019 12:07:32 -0500 Subject: [PATCH 09/49] Added support for setting a name in SSH key pair. Also refactored how new SSH key pairs are created, and how the tests are structured. --- builder/virtualbox/common/sshkeypair.go | 61 ++++-- builder/virtualbox/common/sshkeypair_test.go | 184 +++++++++++++------ 2 files changed, 179 insertions(+), 66 deletions(-) diff --git a/builder/virtualbox/common/sshkeypair.go b/builder/virtualbox/common/sshkeypair.go index fe5ba449c..be4f26d30 100644 --- a/builder/virtualbox/common/sshkeypair.go +++ b/builder/virtualbox/common/sshkeypair.go @@ -13,6 +13,7 @@ import ( "encoding/pem" "errors" "strconv" + "strings" "golang.org/x/crypto/ssh" ) @@ -67,6 +68,10 @@ type sshKeyPairBuilder interface { // SetBits sets the key pair's bits of entropy. SetBits(int) sshKeyPairBuilder + // SetName sets the name of the key pair. This is primarily used + // to identify the public key in the authorized_keys file. + SetName(string) sshKeyPairBuilder + // Build returns a SSH key pair. // // The following defaults are used if not specified: @@ -74,6 +79,7 @@ type sshKeyPairBuilder interface { // Default bits of entropy: // - RSA: 4096 // - ECDSA: 521 + // Default name: (empty string) Build() (sshKeyPair, error) } @@ -83,6 +89,9 @@ type defaultSshKeyPairBuilder struct { // bits is the resulting key pair's bits of entropy. bits int + + // name is the resulting key pair's name. + name string } func (o *defaultSshKeyPairBuilder) SetType(kind sshKeyPairType) sshKeyPairBuilder { @@ -95,15 +104,20 @@ func (o *defaultSshKeyPairBuilder) SetBits(bits int) sshKeyPairBuilder { return o } +func (o *defaultSshKeyPairBuilder) SetName(name string) sshKeyPairBuilder { + o.name = name + return o +} + func (o *defaultSshKeyPairBuilder) Build() (sshKeyPair, error) { switch o.kind { case rsaSsh: - return newRsaSshKeyPair(o.bits) + return o.newRsaSshKeyPair() case ecdsaSsh: // Default case. } - return newEcdsaSshKeyPair(o.bits) + return o.newEcdsaSshKeyPair() } // sshKeyPair represents a SSH key pair. @@ -114,6 +128,10 @@ type sshKeyPair interface { // Bits returns the bits of entropy. Bits() int + // Name returns the key pair's name. An empty string is + // returned is no name was specified. + Name() string + // Description returns a brief description of the key pair that // is suitable for log messages or printing. Description() string @@ -136,6 +154,9 @@ type defaultSshKeyPair struct { // bits is the key pair's bits of entropy. bits int + // name is the key pair's name. + name string + // privateKeyDerBytes is the private key's bytes // in ASN.1 DER format privateKeyDerBytes []byte @@ -152,6 +173,10 @@ func (o defaultSshKeyPair) Bits() int { return o.bits } +func (o defaultSshKeyPair) Name() string { + return o.name +} + func (o defaultSshKeyPair) Description() string { return o.kind.String() + " " + strconv.Itoa(o.bits) } @@ -176,6 +201,14 @@ func (o defaultSshKeyPair) PrivateKeyPemBlock() []byte { func (o defaultSshKeyPair) PublicKeyAuthorizedKeysFormat(nl newLineOption) []byte { result := ssh.MarshalAuthorizedKey(o.publicKey) + if len(strings.TrimSpace(o.name)) > 0 { + // Awful, but the go ssh library automatically appends + // a unix new line. + result = bytes.TrimSuffix(result, unixNewLine.Bytes()) + result = append(result, ' ') + result = append(result, o.name...) + } + switch nl { case noNewLine: result = bytes.TrimSuffix(result, unixNewLine.Bytes()) @@ -197,12 +230,12 @@ func (o defaultSshKeyPair) PublicKeyAuthorizedKeysFormat(nl newLineOption) []byt // newEcdsaSshKeyPair returns a new ECDSA SSH key pair for the given bits // of entropy. -func newEcdsaSshKeyPair(bits int) (sshKeyPair, error) { +func (o *defaultSshKeyPairBuilder) newEcdsaSshKeyPair() (sshKeyPair, error) { var curve elliptic.Curve - switch bits { + switch o.bits { case 0: - bits = 521 + o.bits = 521 fallthrough case 521: curve = elliptic.P521() @@ -213,10 +246,10 @@ func newEcdsaSshKeyPair(bits int) (sshKeyPair, error) { case 224: // Not supported by "golang.org/x/crypto/ssh". return &defaultSshKeyPair{}, errors.New("golang.org/x/crypto/ssh does not support " + - strconv.Itoa(bits) + " bits") + strconv.Itoa(o.bits) + " bits") default: return &defaultSshKeyPair{}, errors.New("crypto/elliptic does not support " + - strconv.Itoa(bits) + " bits") + strconv.Itoa(o.bits) + " bits") } privateKey, err := ecdsa.GenerateKey(curve, rand.Reader) @@ -236,7 +269,8 @@ func newEcdsaSshKeyPair(bits int) (sshKeyPair, error) { return &defaultSshKeyPair{ kind: ecdsaSsh, - bits: bits, + bits: o.bits, + name: o.name, privateKeyDerBytes: raw, publicKey: sshPublicKey, }, nil @@ -244,12 +278,12 @@ func newEcdsaSshKeyPair(bits int) (sshKeyPair, error) { // newRsaSshKeyPair returns a new RSA SSH key pair for the given bits // of entropy. -func newRsaSshKeyPair(bits int) (sshKeyPair, error) { - if bits == 0 { - bits = defaultRsaBits +func (o *defaultSshKeyPairBuilder) newRsaSshKeyPair() (sshKeyPair, error) { + if o.bits == 0 { + o.bits = defaultRsaBits } - privateKey, err := rsa.GenerateKey(rand.Reader, bits) + privateKey, err := rsa.GenerateKey(rand.Reader, o.bits) if err != nil { return &defaultSshKeyPair{}, err } @@ -261,7 +295,8 @@ func newRsaSshKeyPair(bits int) (sshKeyPair, error) { return &defaultSshKeyPair{ kind: rsaSsh, - bits: bits, + bits: o.bits, + name: o.name, privateKeyDerBytes: x509.MarshalPKCS1PrivateKey(privateKey), publicKey: sshPublicKey, }, nil diff --git a/builder/virtualbox/common/sshkeypair_test.go b/builder/virtualbox/common/sshkeypair_test.go index e976b655e..a9f7c8de7 100644 --- a/builder/virtualbox/common/sshkeypair_test.go +++ b/builder/virtualbox/common/sshkeypair_test.go @@ -1,11 +1,13 @@ package common import ( + "bytes" "crypto/rand" "errors" "strconv" "testing" + "github.com/hashicorp/packer/common/uuid" "golang.org/x/crypto/ssh" ) @@ -14,6 +16,8 @@ type expected struct { kind sshKeyPairType bits int desc string + name string + data []byte } func (o expected) matches(kp sshKeyPair) error { @@ -29,28 +33,37 @@ func (o expected) matches(kp sshKeyPair) error { return errors.New("expected description's value cannot be empty") } + if len(o.data) == 0 { + return errors.New("expected random data value cannot be nothing") + } + if kp.Type() != o.kind { - return errors.New("expected key pair type to be " + - o.kind.String() + " - got '" + kp.Type().String() + "'") + return errors.New("key pair type should be " + o.kind.String() + + " - got '" + kp.Type().String() + "'") } if kp.Bits() != o.bits { - return errors.New("expected key pair to be " + - strconv.Itoa(o.bits) + " bits - got " + strconv.Itoa(kp.Bits())) + return errors.New("key pair bits should be " + strconv.Itoa(o.bits) + + " - got " + strconv.Itoa(kp.Bits())) + } + + if len(o.name) > 0 && kp.Name() != o.name { + return errors.New("key pair name should be '" + o.name + + "' - got '" + kp.Name() + "'") } expDescription := kp.Type().String() + " " + strconv.Itoa(o.bits) if kp.Description() != expDescription { - return errors.New("expected key pair description to be '" + + return errors.New("key pair description should be '" + expDescription + "' - got '" + kp.Description() + "'") } - err := verifyPublickeyAuthorizedKeysFormat(kp) + err := o.verifyPublicKeyAuthorizedKeysFormat(kp) if err != nil { return err } - err = verifySshKeyPair(kp) + err = o.verifySshKeyPair(kp) if err != nil { return err } @@ -58,6 +71,75 @@ func (o expected) matches(kp sshKeyPair) error { return nil } +func (o expected) verifyPublicKeyAuthorizedKeysFormat(kp sshKeyPair) error { + newLines := []newLineOption{ + unixNewLine, + noNewLine, + windowsNewLine, + } + + for _, nl := range newLines { + publicKeyAk := kp.PublicKeyAuthorizedKeysFormat(nl) + + if len(publicKeyAk) < 2 { + return errors.New("expected public key in authorized keys format to be at least 2 bytes") + } + + switch nl { + case noNewLine: + if publicKeyAk[len(publicKeyAk) - 1] == '\n' { + return errors.New("public key in authorized keys format has trailing new line when none was specified") + } + case unixNewLine: + if publicKeyAk[len(publicKeyAk) - 1] != '\n' { + return errors.New("public key in authorized keys format does not have unix new line when unix was specified") + } + if string(publicKeyAk[len(publicKeyAk) - 2:]) == windowsNewLine.String() { + return errors.New("public key in authorized keys format has windows new line when unix was specified") + } + case windowsNewLine: + if string(publicKeyAk[len(publicKeyAk) - 2:]) != windowsNewLine.String() { + return errors.New("public key in authorized keys format does not have windows new line when windows was specified") + } + } + + if len(o.name) > 0 { + if len(publicKeyAk) < len(o.name) { + return errors.New("public key in authorized keys format is shorter than the key pair's name") + } + + suffix := []byte{' '} + suffix = append(suffix, o.name...) + suffix = append(suffix, nl.Bytes()...) + if !bytes.HasSuffix(publicKeyAk, suffix) { + return errors.New("public key in authorized keys format with name does not have name in suffix - got '" + + string(publicKeyAk) + "'") + } + } + } + + return nil +} + +func (o expected) verifySshKeyPair(kp sshKeyPair) error { + signer, err := ssh.ParsePrivateKey(kp.PrivateKeyPemBlock()) + if err != nil { + return errors.New("failed to parse private key during verification - " + err.Error()) + } + + signature, err := signer.Sign(rand.Reader, o.data) + if err != nil { + return errors.New("failed to sign test data during verification - " + err.Error()) + } + + err = signer.PublicKey().Verify(o.data, signature) + if err != nil { + return errors.New("failed to verify test data - " + err.Error()) + } + + return nil +} + func TestDefaultSshKeyPairBuilder_Build_Default(t *testing.T) { kp, err := newSshKeyPairBuilder().Build() if err != nil { @@ -68,6 +150,7 @@ func TestDefaultSshKeyPairBuilder_Build_Default(t *testing.T) { kind: ecdsaSsh, bits: 521, desc: "ecdsa 521", + data: []byte(uuid.TimeOrderedUUID()), }.matches(kp) if err != nil { t.Fatal(err.Error()) @@ -75,7 +158,9 @@ func TestDefaultSshKeyPairBuilder_Build_Default(t *testing.T) { } func TestDefaultSshKeyPairBuilder_Build_EcdsaDefault(t *testing.T) { - kp, err := newSshKeyPairBuilder().SetType(ecdsaSsh).Build() + kp, err := newSshKeyPairBuilder(). + SetType(ecdsaSsh). + Build() if err != nil { t.Fatal(err.Error()) } @@ -84,6 +169,7 @@ func TestDefaultSshKeyPairBuilder_Build_EcdsaDefault(t *testing.T) { kind: ecdsaSsh, bits: 521, desc: "ecdsa 521", + data: []byte(uuid.TimeOrderedUUID()), }.matches(kp) if err != nil { t.Fatal(err.Error()) @@ -91,7 +177,9 @@ func TestDefaultSshKeyPairBuilder_Build_EcdsaDefault(t *testing.T) { } func TestDefaultSshKeyPairBuilder_Build_RsaDefault(t *testing.T) { - kp, err := newSshKeyPairBuilder().SetType(rsaSsh).Build() + kp, err := newSshKeyPairBuilder(). + SetType(rsaSsh). + Build() if err != nil { t.Fatal(err.Error()) } @@ -100,65 +188,55 @@ func TestDefaultSshKeyPairBuilder_Build_RsaDefault(t *testing.T) { kind: rsaSsh, bits: 4096, desc: "rsa 4096", + data: []byte(uuid.TimeOrderedUUID()), }.matches(kp) if err != nil { t.Fatal(err.Error()) } } -func verifySshKeyPair(kp sshKeyPair) error { - signer, err := ssh.ParsePrivateKey(kp.PrivateKeyPemBlock()) - if err != nil { - return errors.New("failed to parse private key during verification - " + err.Error()) - } - - data := []byte{'b', 'r', '4', 'n', '3'} +func TestDefaultSshKeyPairBuilder_Build_NamedEcdsa(t *testing.T) { + name := uuid.TimeOrderedUUID() - signature, err := signer.Sign(rand.Reader, data) + kp, err := newSshKeyPairBuilder(). + SetType(ecdsaSsh). + SetName(name). + Build() if err != nil { - return errors.New("failed to sign test data during verification - " + err.Error()) + t.Fatal(err.Error()) } - err = signer.PublicKey().Verify(data, signature) + err = expected{ + kind: ecdsaSsh, + bits: 521, + desc: "ecdsa 521", + data: []byte(uuid.TimeOrderedUUID()), + name: name, + }.matches(kp) if err != nil { - return errors.New("failed to verify test data - " + err.Error()) + t.Fatal(err.Error()) } - - return nil } -func verifyPublickeyAuthorizedKeysFormat(kp sshKeyPair) error { - newLines := []newLineOption{ - unixNewLine, - noNewLine, - windowsNewLine, - } - - for _, nl := range newLines { - publicKeyAk := kp.PublicKeyAuthorizedKeysFormat(nl) - - if len(publicKeyAk) < 2 { - return errors.New("expected public key in authorized keys format to be at least 2 bytes") - } +func TestDefaultSshKeyPairBuilder_Build_NamedRsa(t *testing.T) { + name := uuid.TimeOrderedUUID() - switch nl { - case noNewLine: - if publicKeyAk[len(publicKeyAk) - 1] == '\n' { - return errors.New("public key in authorized keys format has trailing new line when none was specified") - } - case unixNewLine: - if publicKeyAk[len(publicKeyAk) - 1] != '\n' { - return errors.New("public key in authorized keys format does not have unix new line when unix was specified") - } - if string(publicKeyAk[len(publicKeyAk) - 2:]) == windowsNewLine.String() { - return errors.New("public key in authorized keys format has windows new line when unix was specified") - } - case windowsNewLine: - if string(publicKeyAk[len(publicKeyAk) - 2:]) != windowsNewLine.String() { - return errors.New("public key in authorized keys format does not have windows new line when windows was specified") - } - } + kp, err := newSshKeyPairBuilder(). + SetType(rsaSsh). + SetName(name). + Build() + if err != nil { + t.Fatal(err.Error()) } - return nil + err = expected{ + kind: rsaSsh, + bits: 4096, + desc: "rsa 4096", + data: []byte(uuid.TimeOrderedUUID()), + name: name, + }.matches(kp) + if err != nil { + t.Fatal(err.Error()) + } } From be081944b18900904d46378d07f685100317fc5e Mon Sep 17 00:00:00 2001 From: Stephen Fox Date: Mon, 4 Feb 2019 12:09:23 -0500 Subject: [PATCH 10/49] Moved new key pair funcs closer to builder funcs. --- builder/virtualbox/common/sshkeypair.go | 148 ++++++++++++------------ 1 file changed, 74 insertions(+), 74 deletions(-) diff --git a/builder/virtualbox/common/sshkeypair.go b/builder/virtualbox/common/sshkeypair.go index be4f26d30..7b45b9381 100644 --- a/builder/virtualbox/common/sshkeypair.go +++ b/builder/virtualbox/common/sshkeypair.go @@ -120,6 +120,80 @@ func (o *defaultSshKeyPairBuilder) Build() (sshKeyPair, error) { return o.newEcdsaSshKeyPair() } +// newEcdsaSshKeyPair returns a new ECDSA SSH key pair for the given bits +// of entropy. +func (o *defaultSshKeyPairBuilder) newEcdsaSshKeyPair() (sshKeyPair, error) { + var curve elliptic.Curve + + switch o.bits { + case 0: + o.bits = 521 + fallthrough + case 521: + curve = elliptic.P521() + case 384: + elliptic.P384() + case 256: + elliptic.P256() + case 224: + // Not supported by "golang.org/x/crypto/ssh". + return &defaultSshKeyPair{}, errors.New("golang.org/x/crypto/ssh does not support " + + strconv.Itoa(o.bits) + " bits") + default: + return &defaultSshKeyPair{}, errors.New("crypto/elliptic does not support " + + strconv.Itoa(o.bits) + " bits") + } + + privateKey, err := ecdsa.GenerateKey(curve, rand.Reader) + if err != nil { + return &defaultSshKeyPair{}, err + } + + sshPublicKey, err := ssh.NewPublicKey(&privateKey.PublicKey) + if err != nil { + return &defaultSshKeyPair{}, err + } + + raw, err := x509.MarshalECPrivateKey(privateKey) + if err != nil { + return &defaultSshKeyPair{}, err + } + + return &defaultSshKeyPair{ + kind: ecdsaSsh, + bits: o.bits, + name: o.name, + privateKeyDerBytes: raw, + publicKey: sshPublicKey, + }, nil +} + +// newRsaSshKeyPair returns a new RSA SSH key pair for the given bits +// of entropy. +func (o *defaultSshKeyPairBuilder) newRsaSshKeyPair() (sshKeyPair, error) { + if o.bits == 0 { + o.bits = defaultRsaBits + } + + privateKey, err := rsa.GenerateKey(rand.Reader, o.bits) + if err != nil { + return &defaultSshKeyPair{}, err + } + + sshPublicKey, err := ssh.NewPublicKey(&privateKey.PublicKey) + if err != nil { + return &defaultSshKeyPair{}, err + } + + return &defaultSshKeyPair{ + kind: rsaSsh, + bits: o.bits, + name: o.name, + privateKeyDerBytes: x509.MarshalPKCS1PrivateKey(privateKey), + publicKey: sshPublicKey, + }, nil +} + // sshKeyPair represents a SSH key pair. type sshKeyPair interface { // Type returns the key pair's type. @@ -228,80 +302,6 @@ func (o defaultSshKeyPair) PublicKeyAuthorizedKeysFormat(nl newLineOption) []byt return result } -// newEcdsaSshKeyPair returns a new ECDSA SSH key pair for the given bits -// of entropy. -func (o *defaultSshKeyPairBuilder) newEcdsaSshKeyPair() (sshKeyPair, error) { - var curve elliptic.Curve - - switch o.bits { - case 0: - o.bits = 521 - fallthrough - case 521: - curve = elliptic.P521() - case 384: - elliptic.P384() - case 256: - elliptic.P256() - case 224: - // Not supported by "golang.org/x/crypto/ssh". - return &defaultSshKeyPair{}, errors.New("golang.org/x/crypto/ssh does not support " + - strconv.Itoa(o.bits) + " bits") - default: - return &defaultSshKeyPair{}, errors.New("crypto/elliptic does not support " + - strconv.Itoa(o.bits) + " bits") - } - - privateKey, err := ecdsa.GenerateKey(curve, rand.Reader) - if err != nil { - return &defaultSshKeyPair{}, err - } - - sshPublicKey, err := ssh.NewPublicKey(&privateKey.PublicKey) - if err != nil { - return &defaultSshKeyPair{}, err - } - - raw, err := x509.MarshalECPrivateKey(privateKey) - if err != nil { - return &defaultSshKeyPair{}, err - } - - return &defaultSshKeyPair{ - kind: ecdsaSsh, - bits: o.bits, - name: o.name, - privateKeyDerBytes: raw, - publicKey: sshPublicKey, - }, nil -} - -// newRsaSshKeyPair returns a new RSA SSH key pair for the given bits -// of entropy. -func (o *defaultSshKeyPairBuilder) newRsaSshKeyPair() (sshKeyPair, error) { - if o.bits == 0 { - o.bits = defaultRsaBits - } - - privateKey, err := rsa.GenerateKey(rand.Reader, o.bits) - if err != nil { - return &defaultSshKeyPair{}, err - } - - sshPublicKey, err := ssh.NewPublicKey(&privateKey.PublicKey) - if err != nil { - return &defaultSshKeyPair{}, err - } - - return &defaultSshKeyPair{ - kind: rsaSsh, - bits: o.bits, - name: o.name, - privateKeyDerBytes: x509.MarshalPKCS1PrivateKey(privateKey), - publicKey: sshPublicKey, - }, nil -} - func newSshKeyPairBuilder() sshKeyPairBuilder { return &defaultSshKeyPairBuilder{} } From 4c2851013ef1cecc2d07cffd3254f58dfae4ad36 Mon Sep 17 00:00:00 2001 From: Stephen Fox Date: Mon, 4 Feb 2019 12:10:17 -0500 Subject: [PATCH 11/49] Fixed 'sed' regex to remove public key by magic string. --- common/step_cleanup_temp_keys.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/common/step_cleanup_temp_keys.go b/common/step_cleanup_temp_keys.go index 1d5fec1e5..c44a5c723 100644 --- a/common/step_cleanup_temp_keys.go +++ b/common/step_cleanup_temp_keys.go @@ -38,12 +38,12 @@ func (s *StepCleanupTempKeys) Run(_ context.Context, state multistep.StateBag) m ui.Say("Trying to remove ephemeral keys from authorized_keys files") - cmd.Command = fmt.Sprintf("sed -i.bak '/ssh-rsa.*%s$/d' ~/.ssh/authorized_keys; rm ~/.ssh/authorized_keys.bak", s.Comm.SSHTemporaryKeyPairName) + cmd.Command = fmt.Sprintf("sed -i.bak '/ %s$/d' ~/.ssh/authorized_keys; rm ~/.ssh/authorized_keys.bak", s.Comm.SSHTemporaryKeyPairName) if err := cmd.StartWithUi(comm, ui); err != nil { log.Printf("Error cleaning up ~/.ssh/authorized_keys; please clean up keys manually: %s", err) } cmd = new(packer.RemoteCmd) - cmd.Command = fmt.Sprintf("sudo sed -i.bak '/ssh-rsa.*%s$/d' /root/.ssh/authorized_keys; sudo rm /root/.ssh/authorized_keys.bak", s.Comm.SSHTemporaryKeyPairName) + cmd.Command = fmt.Sprintf("sudo sed -i.bak '/ %s$/d' /root/.ssh/authorized_keys; sudo rm /root/.ssh/authorized_keys.bak", s.Comm.SSHTemporaryKeyPairName) if err := cmd.StartWithUi(comm, ui); err != nil { log.Printf("Error cleaning up /root/.ssh/authorized_keys; please clean up keys manually: %s", err) From 25775886a8da6214f35537ab36f6b716c5691fc0 Mon Sep 17 00:00:00 2001 From: Stephen Fox Date: Mon, 4 Feb 2019 12:12:18 -0500 Subject: [PATCH 12/49] Properly set SSH key pair name. --- builder/virtualbox/common/step_ssh_key_pair.go | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/builder/virtualbox/common/step_ssh_key_pair.go b/builder/virtualbox/common/step_ssh_key_pair.go index afb4f9563..77e00b1b3 100644 --- a/builder/virtualbox/common/step_ssh_key_pair.go +++ b/builder/virtualbox/common/step_ssh_key_pair.go @@ -47,19 +47,20 @@ func (s *StepSshKeyPair) Run(_ context.Context, state multistep.StateBag) multis ui.Say("Creating ephemeral key pair for SSH communicator...") - // TODO: Should we respect 's.Comm.SSHTemporaryKeyPairName'? - // It appears to be specific to certain other builders, but it is not - // mentioned in the virtualbox builders' documentation. - s.Comm.SSHKeyPairName = fmt.Sprintf("packer_%s", uuid.TimeOrderedUUID()) - kp, err := newSshKeyPairBuilder().Build() + kp, err := newSshKeyPairBuilder(). + SetName(fmt.Sprintf("packer_%s", uuid.TimeOrderedUUID())). + Build() if err != nil { state.Put("error", fmt.Errorf("Error creating temporary keypair: %s", err)) return multistep.ActionHalt } + s.Comm.SSHKeyPairName = kp.Name() + s.Comm.SSHTemporaryKeyPairName = kp.Name() s.Comm.SSHPrivateKey = kp.PrivateKeyPemBlock() s.Comm.SSHPublicKey = kp.PublicKeyAuthorizedKeysFormat(unixNewLine) + s.Comm.SSHClearAuthorizedKeys = true ui.Say(fmt.Sprintf("Created ephemeral SSH key pair of type %s", kp.Description())) From c6ae8654d9f3d3b4fde5114465a5196388552d47 Mon Sep 17 00:00:00 2001 From: Stephen Fox Date: Mon, 4 Feb 2019 12:21:58 -0500 Subject: [PATCH 13/49] Moved SSH key pair code into 'helper/ssh'. --- .../virtualbox/common/step_ssh_key_pair.go | 5 +- .../common => helper/ssh}/sshkeypair.go | 147 +++++++++--------- .../common => helper/ssh}/sshkeypair_test.go | 68 ++++---- 3 files changed, 108 insertions(+), 112 deletions(-) rename {builder/virtualbox/common => helper/ssh}/sshkeypair.go (56%) rename {builder/virtualbox/common => helper/ssh}/sshkeypair_test.go (78%) diff --git a/builder/virtualbox/common/step_ssh_key_pair.go b/builder/virtualbox/common/step_ssh_key_pair.go index 77e00b1b3..89cd58ea9 100644 --- a/builder/virtualbox/common/step_ssh_key_pair.go +++ b/builder/virtualbox/common/step_ssh_key_pair.go @@ -9,6 +9,7 @@ import ( "github.com/hashicorp/packer/common/uuid" "github.com/hashicorp/packer/helper/communicator" "github.com/hashicorp/packer/helper/multistep" + "github.com/hashicorp/packer/helper/ssh" "github.com/hashicorp/packer/packer" ) @@ -48,7 +49,7 @@ func (s *StepSshKeyPair) Run(_ context.Context, state multistep.StateBag) multis ui.Say("Creating ephemeral key pair for SSH communicator...") - kp, err := newSshKeyPairBuilder(). + kp, err := ssh.NewKeyPairBuilder(). SetName(fmt.Sprintf("packer_%s", uuid.TimeOrderedUUID())). Build() if err != nil { @@ -59,7 +60,7 @@ func (s *StepSshKeyPair) Run(_ context.Context, state multistep.StateBag) multis s.Comm.SSHKeyPairName = kp.Name() s.Comm.SSHTemporaryKeyPairName = kp.Name() s.Comm.SSHPrivateKey = kp.PrivateKeyPemBlock() - s.Comm.SSHPublicKey = kp.PublicKeyAuthorizedKeysFormat(unixNewLine) + s.Comm.SSHPublicKey = kp.PublicKeyAuthorizedKeysFormat(ssh.UnixNewLine) s.Comm.SSHClearAuthorizedKeys = true ui.Say(fmt.Sprintf("Created ephemeral SSH key pair of type %s", kp.Description())) diff --git a/builder/virtualbox/common/sshkeypair.go b/helper/ssh/sshkeypair.go similarity index 56% rename from builder/virtualbox/common/sshkeypair.go rename to helper/ssh/sshkeypair.go index 7b45b9381..d65b33641 100644 --- a/builder/virtualbox/common/sshkeypair.go +++ b/helper/ssh/sshkeypair.go @@ -1,7 +1,4 @@ -package common - -// TODO: Make this available to other packer APIs. -// Perhaps through 'helper/ssh'? +package ssh import ( "bytes" @@ -22,55 +19,55 @@ const ( // That's a lot of bits. defaultRsaBits = 4096 - // rsaSsh is a SSH key pair of RSA type. - rsaSsh sshKeyPairType = "rsa" + // RsaSsh is a SSH key pair of RSA type. + RsaSsh KeyPairType = "rsa" - // ecdsaSsh is a SSH key pair of ECDSA type. - ecdsaSsh sshKeyPairType = "ecdsa" + // EcdsaSsh is a SSH key pair of ECDSA type. + EcdsaSsh KeyPairType = "ecdsa" ) -// sshKeyPairType represents different types of SSH key pairs. +// KeyPairType represents different types of SSH key pairs. // For example, RSA. -type sshKeyPairType string +type KeyPairType string -func (o sshKeyPairType) String() string { +func (o KeyPairType) String() string { return string(o) } const ( - // unixNewLine is a unix new line. - unixNewLine newLineOption = "\n" + // UnixNewLine is a unix new line. + UnixNewLine NewLineOption = "\n" - // windowsNewLine is a Windows new line. - windowsNewLine newLineOption = "\r\n" + // WindowsNewLine is a Windows new line. + WindowsNewLine NewLineOption = "\r\n" - // noNewLine will not append a new line. - noNewLine newLineOption = "" + // NoNewLine will not append a new line. + NoNewLine NewLineOption = "" ) -// newLineOption specifies the type of new line to append to a string. +// NewLineOption specifies the type of new line to append to a string. // See the 'const' block for choices. -type newLineOption string +type NewLineOption string -func (o newLineOption) String() string { +func (o NewLineOption) String() string { return string(o) } -func (o newLineOption) Bytes() []byte { +func (o NewLineOption) Bytes() []byte { return []byte(o) } -// sshKeyPairBuilder builds SSH key pairs. -type sshKeyPairBuilder interface { +// KeyPairBuilder builds SSH key pairs. +type KeyPairBuilder interface { // SetType sets the key pair type. - SetType(sshKeyPairType) sshKeyPairBuilder + SetType(KeyPairType) KeyPairBuilder // SetBits sets the key pair's bits of entropy. - SetBits(int) sshKeyPairBuilder + SetBits(int) KeyPairBuilder // SetName sets the name of the key pair. This is primarily used // to identify the public key in the authorized_keys file. - SetName(string) sshKeyPairBuilder + SetName(string) KeyPairBuilder // Build returns a SSH key pair. // @@ -80,12 +77,12 @@ type sshKeyPairBuilder interface { // - RSA: 4096 // - ECDSA: 521 // Default name: (empty string) - Build() (sshKeyPair, error) + Build() (KeyPair, error) } -type defaultSshKeyPairBuilder struct { +type defaultKeyPairBuilder struct { // kind describes the resulting key pair's type. - kind sshKeyPairType + kind KeyPairType // bits is the resulting key pair's bits of entropy. bits int @@ -94,35 +91,34 @@ type defaultSshKeyPairBuilder struct { name string } -func (o *defaultSshKeyPairBuilder) SetType(kind sshKeyPairType) sshKeyPairBuilder { +func (o *defaultKeyPairBuilder) SetType(kind KeyPairType) KeyPairBuilder { o.kind = kind return o } -func (o *defaultSshKeyPairBuilder) SetBits(bits int) sshKeyPairBuilder { +func (o *defaultKeyPairBuilder) SetBits(bits int) KeyPairBuilder { o.bits = bits return o } -func (o *defaultSshKeyPairBuilder) SetName(name string) sshKeyPairBuilder { +func (o *defaultKeyPairBuilder) SetName(name string) KeyPairBuilder { o.name = name return o } -func (o *defaultSshKeyPairBuilder) Build() (sshKeyPair, error) { +func (o *defaultKeyPairBuilder) Build() (KeyPair, error) { switch o.kind { - case rsaSsh: + case RsaSsh: return o.newRsaSshKeyPair() - case ecdsaSsh: + case EcdsaSsh: // Default case. } return o.newEcdsaSshKeyPair() } -// newEcdsaSshKeyPair returns a new ECDSA SSH key pair for the given bits -// of entropy. -func (o *defaultSshKeyPairBuilder) newEcdsaSshKeyPair() (sshKeyPair, error) { +// newEcdsaSshKeyPair returns a new ECDSA SSH key pair. +func (o *defaultKeyPairBuilder) newEcdsaSshKeyPair() (KeyPair, error) { var curve elliptic.Curve switch o.bits { @@ -137,30 +133,30 @@ func (o *defaultSshKeyPairBuilder) newEcdsaSshKeyPair() (sshKeyPair, error) { elliptic.P256() case 224: // Not supported by "golang.org/x/crypto/ssh". - return &defaultSshKeyPair{}, errors.New("golang.org/x/crypto/ssh does not support " + + return &defaultKeyPair{}, errors.New("golang.org/x/crypto/ssh does not support " + strconv.Itoa(o.bits) + " bits") default: - return &defaultSshKeyPair{}, errors.New("crypto/elliptic does not support " + + return &defaultKeyPair{}, errors.New("crypto/elliptic does not support " + strconv.Itoa(o.bits) + " bits") } privateKey, err := ecdsa.GenerateKey(curve, rand.Reader) if err != nil { - return &defaultSshKeyPair{}, err + return &defaultKeyPair{}, err } sshPublicKey, err := ssh.NewPublicKey(&privateKey.PublicKey) if err != nil { - return &defaultSshKeyPair{}, err + return &defaultKeyPair{}, err } raw, err := x509.MarshalECPrivateKey(privateKey) if err != nil { - return &defaultSshKeyPair{}, err + return &defaultKeyPair{}, err } - return &defaultSshKeyPair{ - kind: ecdsaSsh, + return &defaultKeyPair{ + kind: EcdsaSsh, bits: o.bits, name: o.name, privateKeyDerBytes: raw, @@ -168,25 +164,24 @@ func (o *defaultSshKeyPairBuilder) newEcdsaSshKeyPair() (sshKeyPair, error) { }, nil } -// newRsaSshKeyPair returns a new RSA SSH key pair for the given bits -// of entropy. -func (o *defaultSshKeyPairBuilder) newRsaSshKeyPair() (sshKeyPair, error) { +// newRsaSshKeyPair returns a new RSA SSH key pair. +func (o *defaultKeyPairBuilder) newRsaSshKeyPair() (KeyPair, error) { if o.bits == 0 { o.bits = defaultRsaBits } privateKey, err := rsa.GenerateKey(rand.Reader, o.bits) if err != nil { - return &defaultSshKeyPair{}, err + return &defaultKeyPair{}, err } sshPublicKey, err := ssh.NewPublicKey(&privateKey.PublicKey) if err != nil { - return &defaultSshKeyPair{}, err + return &defaultKeyPair{}, err } - return &defaultSshKeyPair{ - kind: rsaSsh, + return &defaultKeyPair{ + kind: RsaSsh, bits: o.bits, name: o.name, privateKeyDerBytes: x509.MarshalPKCS1PrivateKey(privateKey), @@ -194,10 +189,10 @@ func (o *defaultSshKeyPairBuilder) newRsaSshKeyPair() (sshKeyPair, error) { }, nil } -// sshKeyPair represents a SSH key pair. -type sshKeyPair interface { +// KeyPair represents a SSH key pair. +type KeyPair interface { // Type returns the key pair's type. - Type() sshKeyPairType + Type() KeyPairType // Bits returns the bits of entropy. Bits() int @@ -218,12 +213,12 @@ type sshKeyPair interface { // PublicKeyAuthorizedKeysFormat returns a slice of bytes // representing the public key in OpenSSH authorized_keys format // with the specified new line. - PublicKeyAuthorizedKeysFormat(newLineOption) []byte + PublicKeyAuthorizedKeysFormat(NewLineOption) []byte } -type defaultSshKeyPair struct { +type defaultKeyPair struct { // kind is the key pair's type. - kind sshKeyPairType + kind KeyPairType // bits is the key pair's bits of entropy. bits int @@ -239,29 +234,29 @@ type defaultSshKeyPair struct { publicKey ssh.PublicKey } -func (o defaultSshKeyPair) Type() sshKeyPairType { +func (o defaultKeyPair) Type() KeyPairType { return o.kind } -func (o defaultSshKeyPair) Bits() int { +func (o defaultKeyPair) Bits() int { return o.bits } -func (o defaultSshKeyPair) Name() string { +func (o defaultKeyPair) Name() string { return o.name } -func (o defaultSshKeyPair) Description() string { +func (o defaultKeyPair) Description() string { return o.kind.String() + " " + strconv.Itoa(o.bits) } -func (o defaultSshKeyPair) PrivateKeyPemBlock() []byte { +func (o defaultKeyPair) PrivateKeyPemBlock() []byte { t := "UNKNOWN PRIVATE KEY" switch o.kind { - case ecdsaSsh: + case EcdsaSsh: t = "EC PRIVATE KEY" - case rsaSsh: + case RsaSsh: t = "RSA PRIVATE KEY" } @@ -272,36 +267,36 @@ func (o defaultSshKeyPair) PrivateKeyPemBlock() []byte { }) } -func (o defaultSshKeyPair) PublicKeyAuthorizedKeysFormat(nl newLineOption) []byte { +func (o defaultKeyPair) PublicKeyAuthorizedKeysFormat(nl NewLineOption) []byte { result := ssh.MarshalAuthorizedKey(o.publicKey) if len(strings.TrimSpace(o.name)) > 0 { // Awful, but the go ssh library automatically appends // a unix new line. - result = bytes.TrimSuffix(result, unixNewLine.Bytes()) + result = bytes.TrimSuffix(result, UnixNewLine.Bytes()) result = append(result, ' ') result = append(result, o.name...) } switch nl { - case noNewLine: - result = bytes.TrimSuffix(result, unixNewLine.Bytes()) - case windowsNewLine: - result = bytes.TrimSuffix(result, unixNewLine.Bytes()) + case NoNewLine: + result = bytes.TrimSuffix(result, UnixNewLine.Bytes()) + case WindowsNewLine: + result = bytes.TrimSuffix(result, UnixNewLine.Bytes()) result = append(result, nl.Bytes()...) - case unixNewLine: + case UnixNewLine: fallthrough default: // This is how all the other "SSH key pair" code works in // the different builders. - if !bytes.HasSuffix(result, unixNewLine.Bytes()) { - result = append(result, unixNewLine.Bytes()...) + if !bytes.HasSuffix(result, UnixNewLine.Bytes()) { + result = append(result, UnixNewLine.Bytes()...) } } return result } -func newSshKeyPairBuilder() sshKeyPairBuilder { - return &defaultSshKeyPairBuilder{} +func NewKeyPairBuilder() KeyPairBuilder { + return &defaultKeyPairBuilder{} } diff --git a/builder/virtualbox/common/sshkeypair_test.go b/helper/ssh/sshkeypair_test.go similarity index 78% rename from builder/virtualbox/common/sshkeypair_test.go rename to helper/ssh/sshkeypair_test.go index a9f7c8de7..83049a265 100644 --- a/builder/virtualbox/common/sshkeypair_test.go +++ b/helper/ssh/sshkeypair_test.go @@ -1,4 +1,4 @@ -package common +package ssh import ( "bytes" @@ -13,14 +13,14 @@ import ( // expected contains the data that the key pair should contain. type expected struct { - kind sshKeyPairType + kind KeyPairType bits int desc string name string data []byte } -func (o expected) matches(kp sshKeyPair) error { +func (o expected) matches(kp KeyPair) error { if o.kind.String() == "" { return errors.New("expected kind's value cannot be empty") } @@ -63,7 +63,7 @@ func (o expected) matches(kp sshKeyPair) error { return err } - err = o.verifySshKeyPair(kp) + err = o.verifyKeyPair(kp) if err != nil { return err } @@ -71,11 +71,11 @@ func (o expected) matches(kp sshKeyPair) error { return nil } -func (o expected) verifyPublicKeyAuthorizedKeysFormat(kp sshKeyPair) error { - newLines := []newLineOption{ - unixNewLine, - noNewLine, - windowsNewLine, +func (o expected) verifyPublicKeyAuthorizedKeysFormat(kp KeyPair) error { + newLines := []NewLineOption{ + UnixNewLine, + NoNewLine, + WindowsNewLine, } for _, nl := range newLines { @@ -86,19 +86,19 @@ func (o expected) verifyPublicKeyAuthorizedKeysFormat(kp sshKeyPair) error { } switch nl { - case noNewLine: + case NoNewLine: if publicKeyAk[len(publicKeyAk) - 1] == '\n' { return errors.New("public key in authorized keys format has trailing new line when none was specified") } - case unixNewLine: + case UnixNewLine: if publicKeyAk[len(publicKeyAk) - 1] != '\n' { return errors.New("public key in authorized keys format does not have unix new line when unix was specified") } - if string(publicKeyAk[len(publicKeyAk) - 2:]) == windowsNewLine.String() { + if string(publicKeyAk[len(publicKeyAk) - 2:]) == WindowsNewLine.String() { return errors.New("public key in authorized keys format has windows new line when unix was specified") } - case windowsNewLine: - if string(publicKeyAk[len(publicKeyAk) - 2:]) != windowsNewLine.String() { + case WindowsNewLine: + if string(publicKeyAk[len(publicKeyAk) - 2:]) != WindowsNewLine.String() { return errors.New("public key in authorized keys format does not have windows new line when windows was specified") } } @@ -121,7 +121,7 @@ func (o expected) verifyPublicKeyAuthorizedKeysFormat(kp sshKeyPair) error { return nil } -func (o expected) verifySshKeyPair(kp sshKeyPair) error { +func (o expected) verifyKeyPair(kp KeyPair) error { signer, err := ssh.ParsePrivateKey(kp.PrivateKeyPemBlock()) if err != nil { return errors.New("failed to parse private key during verification - " + err.Error()) @@ -140,14 +140,14 @@ func (o expected) verifySshKeyPair(kp sshKeyPair) error { return nil } -func TestDefaultSshKeyPairBuilder_Build_Default(t *testing.T) { - kp, err := newSshKeyPairBuilder().Build() +func TestDefaultKeyPairBuilder_Build_Default(t *testing.T) { + kp, err := NewKeyPairBuilder().Build() if err != nil { t.Fatal(err.Error()) } err = expected{ - kind: ecdsaSsh, + kind: EcdsaSsh, bits: 521, desc: "ecdsa 521", data: []byte(uuid.TimeOrderedUUID()), @@ -157,16 +157,16 @@ func TestDefaultSshKeyPairBuilder_Build_Default(t *testing.T) { } } -func TestDefaultSshKeyPairBuilder_Build_EcdsaDefault(t *testing.T) { - kp, err := newSshKeyPairBuilder(). - SetType(ecdsaSsh). +func TestDefaultKeyPairBuilder_Build_EcdsaDefault(t *testing.T) { + kp, err := NewKeyPairBuilder(). + SetType(EcdsaSsh). Build() if err != nil { t.Fatal(err.Error()) } err = expected{ - kind: ecdsaSsh, + kind: EcdsaSsh, bits: 521, desc: "ecdsa 521", data: []byte(uuid.TimeOrderedUUID()), @@ -176,16 +176,16 @@ func TestDefaultSshKeyPairBuilder_Build_EcdsaDefault(t *testing.T) { } } -func TestDefaultSshKeyPairBuilder_Build_RsaDefault(t *testing.T) { - kp, err := newSshKeyPairBuilder(). - SetType(rsaSsh). +func TestDefaultKeyPairBuilder_Build_RsaDefault(t *testing.T) { + kp, err := NewKeyPairBuilder(). + SetType(RsaSsh). Build() if err != nil { t.Fatal(err.Error()) } err = expected{ - kind: rsaSsh, + kind: RsaSsh, bits: 4096, desc: "rsa 4096", data: []byte(uuid.TimeOrderedUUID()), @@ -195,11 +195,11 @@ func TestDefaultSshKeyPairBuilder_Build_RsaDefault(t *testing.T) { } } -func TestDefaultSshKeyPairBuilder_Build_NamedEcdsa(t *testing.T) { +func TestDefaultKeyPairBuilder_Build_NamedEcdsa(t *testing.T) { name := uuid.TimeOrderedUUID() - kp, err := newSshKeyPairBuilder(). - SetType(ecdsaSsh). + kp, err := NewKeyPairBuilder(). + SetType(EcdsaSsh). SetName(name). Build() if err != nil { @@ -207,7 +207,7 @@ func TestDefaultSshKeyPairBuilder_Build_NamedEcdsa(t *testing.T) { } err = expected{ - kind: ecdsaSsh, + kind: EcdsaSsh, bits: 521, desc: "ecdsa 521", data: []byte(uuid.TimeOrderedUUID()), @@ -218,11 +218,11 @@ func TestDefaultSshKeyPairBuilder_Build_NamedEcdsa(t *testing.T) { } } -func TestDefaultSshKeyPairBuilder_Build_NamedRsa(t *testing.T) { +func TestDefaultKeyPairBuilder_Build_NamedRsa(t *testing.T) { name := uuid.TimeOrderedUUID() - kp, err := newSshKeyPairBuilder(). - SetType(rsaSsh). + kp, err := NewKeyPairBuilder(). + SetType(RsaSsh). SetName(name). Build() if err != nil { @@ -230,7 +230,7 @@ func TestDefaultSshKeyPairBuilder_Build_NamedRsa(t *testing.T) { } err = expected{ - kind: rsaSsh, + kind: RsaSsh, bits: 4096, desc: "rsa 4096", data: []byte(uuid.TimeOrderedUUID()), From 7f6b307dd65c294124a25b9d957f59b4720d330c Mon Sep 17 00:00:00 2001 From: Stephen Fox Date: Mon, 4 Feb 2019 12:23:42 -0500 Subject: [PATCH 14/49] Remove 'Ssh' suffix from 'KeyPairType' constants. --- helper/ssh/sshkeypair.go | 22 +++++++++++----------- helper/ssh/sshkeypair_test.go | 18 +++++++++--------- 2 files changed, 20 insertions(+), 20 deletions(-) diff --git a/helper/ssh/sshkeypair.go b/helper/ssh/sshkeypair.go index d65b33641..f4f2ed4ac 100644 --- a/helper/ssh/sshkeypair.go +++ b/helper/ssh/sshkeypair.go @@ -19,15 +19,15 @@ const ( // That's a lot of bits. defaultRsaBits = 4096 - // RsaSsh is a SSH key pair of RSA type. - RsaSsh KeyPairType = "rsa" + // Rsa is a SSH key pair of RSA type. + Rsa KeyPairType = "rsa" - // EcdsaSsh is a SSH key pair of ECDSA type. - EcdsaSsh KeyPairType = "ecdsa" + // Ecdsa is a SSH key pair of ECDSA type. + Ecdsa KeyPairType = "ecdsa" ) // KeyPairType represents different types of SSH key pairs. -// For example, RSA. +// See the 'const' block for details. type KeyPairType string func (o KeyPairType) String() string { @@ -108,9 +108,9 @@ func (o *defaultKeyPairBuilder) SetName(name string) KeyPairBuilder { func (o *defaultKeyPairBuilder) Build() (KeyPair, error) { switch o.kind { - case RsaSsh: + case Rsa: return o.newRsaSshKeyPair() - case EcdsaSsh: + case Ecdsa: // Default case. } @@ -156,7 +156,7 @@ func (o *defaultKeyPairBuilder) newEcdsaSshKeyPair() (KeyPair, error) { } return &defaultKeyPair{ - kind: EcdsaSsh, + kind: Ecdsa, bits: o.bits, name: o.name, privateKeyDerBytes: raw, @@ -181,7 +181,7 @@ func (o *defaultKeyPairBuilder) newRsaSshKeyPair() (KeyPair, error) { } return &defaultKeyPair{ - kind: RsaSsh, + kind: Rsa, bits: o.bits, name: o.name, privateKeyDerBytes: x509.MarshalPKCS1PrivateKey(privateKey), @@ -254,9 +254,9 @@ func (o defaultKeyPair) PrivateKeyPemBlock() []byte { t := "UNKNOWN PRIVATE KEY" switch o.kind { - case EcdsaSsh: + case Ecdsa: t = "EC PRIVATE KEY" - case RsaSsh: + case Rsa: t = "RSA PRIVATE KEY" } diff --git a/helper/ssh/sshkeypair_test.go b/helper/ssh/sshkeypair_test.go index 83049a265..48ac9f726 100644 --- a/helper/ssh/sshkeypair_test.go +++ b/helper/ssh/sshkeypair_test.go @@ -147,7 +147,7 @@ func TestDefaultKeyPairBuilder_Build_Default(t *testing.T) { } err = expected{ - kind: EcdsaSsh, + kind: Ecdsa, bits: 521, desc: "ecdsa 521", data: []byte(uuid.TimeOrderedUUID()), @@ -159,14 +159,14 @@ func TestDefaultKeyPairBuilder_Build_Default(t *testing.T) { func TestDefaultKeyPairBuilder_Build_EcdsaDefault(t *testing.T) { kp, err := NewKeyPairBuilder(). - SetType(EcdsaSsh). + SetType(Ecdsa). Build() if err != nil { t.Fatal(err.Error()) } err = expected{ - kind: EcdsaSsh, + kind: Ecdsa, bits: 521, desc: "ecdsa 521", data: []byte(uuid.TimeOrderedUUID()), @@ -178,14 +178,14 @@ func TestDefaultKeyPairBuilder_Build_EcdsaDefault(t *testing.T) { func TestDefaultKeyPairBuilder_Build_RsaDefault(t *testing.T) { kp, err := NewKeyPairBuilder(). - SetType(RsaSsh). + SetType(Rsa). Build() if err != nil { t.Fatal(err.Error()) } err = expected{ - kind: RsaSsh, + kind: Rsa, bits: 4096, desc: "rsa 4096", data: []byte(uuid.TimeOrderedUUID()), @@ -199,7 +199,7 @@ func TestDefaultKeyPairBuilder_Build_NamedEcdsa(t *testing.T) { name := uuid.TimeOrderedUUID() kp, err := NewKeyPairBuilder(). - SetType(EcdsaSsh). + SetType(Ecdsa). SetName(name). Build() if err != nil { @@ -207,7 +207,7 @@ func TestDefaultKeyPairBuilder_Build_NamedEcdsa(t *testing.T) { } err = expected{ - kind: EcdsaSsh, + kind: Ecdsa, bits: 521, desc: "ecdsa 521", data: []byte(uuid.TimeOrderedUUID()), @@ -222,7 +222,7 @@ func TestDefaultKeyPairBuilder_Build_NamedRsa(t *testing.T) { name := uuid.TimeOrderedUUID() kp, err := NewKeyPairBuilder(). - SetType(RsaSsh). + SetType(Rsa). SetName(name). Build() if err != nil { @@ -230,7 +230,7 @@ func TestDefaultKeyPairBuilder_Build_NamedRsa(t *testing.T) { } err = expected{ - kind: RsaSsh, + kind: Rsa, bits: 4096, desc: "rsa 4096", data: []byte(uuid.TimeOrderedUUID()), From 9a9bc18fab7508d13fd1f62dc1badf50904eed70 Mon Sep 17 00:00:00 2001 From: Stephen Fox Date: Mon, 4 Feb 2019 12:25:00 -0500 Subject: [PATCH 15/49] Renamed SSH key pair source files per pattern. There appears to be a pattern of naming SSH key pair related source files "*_key_pair*". --- helper/ssh/{sshkeypair.go => key_pair.go} | 0 helper/ssh/{sshkeypair_test.go => key_pair_test.go} | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename helper/ssh/{sshkeypair.go => key_pair.go} (100%) rename helper/ssh/{sshkeypair_test.go => key_pair_test.go} (100%) diff --git a/helper/ssh/sshkeypair.go b/helper/ssh/key_pair.go similarity index 100% rename from helper/ssh/sshkeypair.go rename to helper/ssh/key_pair.go diff --git a/helper/ssh/sshkeypair_test.go b/helper/ssh/key_pair_test.go similarity index 100% rename from helper/ssh/sshkeypair_test.go rename to helper/ssh/key_pair_test.go From f8db84334dc9956b1a79237ded8aa53c31249d43 Mon Sep 17 00:00:00 2001 From: Stephen Fox Date: Mon, 4 Feb 2019 12:29:47 -0500 Subject: [PATCH 16/49] Alias 'golang.org/x/crypto/ssh' as 'gossh'. --- helper/ssh/key_pair.go | 10 +++++----- helper/ssh/key_pair_test.go | 4 ++-- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/helper/ssh/key_pair.go b/helper/ssh/key_pair.go index f4f2ed4ac..74b3871f7 100644 --- a/helper/ssh/key_pair.go +++ b/helper/ssh/key_pair.go @@ -12,7 +12,7 @@ import ( "strconv" "strings" - "golang.org/x/crypto/ssh" + gossh "golang.org/x/crypto/ssh" ) const ( @@ -145,7 +145,7 @@ func (o *defaultKeyPairBuilder) newEcdsaSshKeyPair() (KeyPair, error) { return &defaultKeyPair{}, err } - sshPublicKey, err := ssh.NewPublicKey(&privateKey.PublicKey) + sshPublicKey, err := gossh.NewPublicKey(&privateKey.PublicKey) if err != nil { return &defaultKeyPair{}, err } @@ -175,7 +175,7 @@ func (o *defaultKeyPairBuilder) newRsaSshKeyPair() (KeyPair, error) { return &defaultKeyPair{}, err } - sshPublicKey, err := ssh.NewPublicKey(&privateKey.PublicKey) + sshPublicKey, err := gossh.NewPublicKey(&privateKey.PublicKey) if err != nil { return &defaultKeyPair{}, err } @@ -231,7 +231,7 @@ type defaultKeyPair struct { privateKeyDerBytes []byte // publicKey is the key pair's public key. - publicKey ssh.PublicKey + publicKey gossh.PublicKey } func (o defaultKeyPair) Type() KeyPairType { @@ -268,7 +268,7 @@ func (o defaultKeyPair) PrivateKeyPemBlock() []byte { } func (o defaultKeyPair) PublicKeyAuthorizedKeysFormat(nl NewLineOption) []byte { - result := ssh.MarshalAuthorizedKey(o.publicKey) + result := gossh.MarshalAuthorizedKey(o.publicKey) if len(strings.TrimSpace(o.name)) > 0 { // Awful, but the go ssh library automatically appends diff --git a/helper/ssh/key_pair_test.go b/helper/ssh/key_pair_test.go index 48ac9f726..783fab735 100644 --- a/helper/ssh/key_pair_test.go +++ b/helper/ssh/key_pair_test.go @@ -8,7 +8,7 @@ import ( "testing" "github.com/hashicorp/packer/common/uuid" - "golang.org/x/crypto/ssh" + gossh "golang.org/x/crypto/ssh" ) // expected contains the data that the key pair should contain. @@ -122,7 +122,7 @@ func (o expected) verifyPublicKeyAuthorizedKeysFormat(kp KeyPair) error { } func (o expected) verifyKeyPair(kp KeyPair) error { - signer, err := ssh.ParsePrivateKey(kp.PrivateKeyPemBlock()) + signer, err := gossh.ParsePrivateKey(kp.PrivateKeyPemBlock()) if err != nil { return errors.New("failed to parse private key during verification - " + err.Error()) } From 01c98f3d82dcde54ca8a40102cd40581cdde0329 Mon Sep 17 00:00:00 2001 From: Stephen Fox Date: Mon, 4 Feb 2019 12:47:18 -0500 Subject: [PATCH 17/49] Tweaked debug SSH private key file name for vbox. --- builder/virtualbox/common/step_ssh_key_pair.go | 1 - builder/virtualbox/iso/builder.go | 2 +- builder/virtualbox/ovf/builder.go | 2 +- 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/builder/virtualbox/common/step_ssh_key_pair.go b/builder/virtualbox/common/step_ssh_key_pair.go index 89cd58ea9..8bdd18842 100644 --- a/builder/virtualbox/common/step_ssh_key_pair.go +++ b/builder/virtualbox/common/step_ssh_key_pair.go @@ -48,7 +48,6 @@ func (s *StepSshKeyPair) Run(_ context.Context, state multistep.StateBag) multis ui.Say("Creating ephemeral key pair for SSH communicator...") - kp, err := ssh.NewKeyPairBuilder(). SetName(fmt.Sprintf("packer_%s", uuid.TimeOrderedUUID())). Build() diff --git a/builder/virtualbox/iso/builder.go b/builder/virtualbox/iso/builder.go index ab6111a08..18320bb9f 100644 --- a/builder/virtualbox/iso/builder.go +++ b/builder/virtualbox/iso/builder.go @@ -224,7 +224,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe }, &vboxcommon.StepSshKeyPair{ Debug: b.config.PackerDebug, - DebugKeyPath: fmt.Sprintf("virtualbox_%s.pem", b.config.PackerBuildName), + DebugKeyPath: fmt.Sprintf("%s.pem", b.config.PackerBuildName), Comm: &b.config.Comm, }, new(vboxcommon.StepSuppressMessages), diff --git a/builder/virtualbox/ovf/builder.go b/builder/virtualbox/ovf/builder.go index 020a58b87..ffc5a2f14 100644 --- a/builder/virtualbox/ovf/builder.go +++ b/builder/virtualbox/ovf/builder.go @@ -66,7 +66,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe }, &vboxcommon.StepSshKeyPair{ Debug: b.config.PackerDebug, - DebugKeyPath: fmt.Sprintf("virtualbox_%s.pem", b.config.PackerBuildName), + DebugKeyPath: fmt.Sprintf("%s.pem", b.config.PackerBuildName), Comm: &b.config.Comm, }, &vboxcommon.StepDownloadGuestAdditions{ From 9328c9f9e09ead7e6b582a9b321ee386c50538e7 Mon Sep 17 00:00:00 2001 From: Stephen Fox Date: Mon, 4 Feb 2019 14:12:18 -0500 Subject: [PATCH 18/49] Renamed "new*" SSH key pair methods. --- helper/ssh/key_pair.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/helper/ssh/key_pair.go b/helper/ssh/key_pair.go index 74b3871f7..4a4f12e00 100644 --- a/helper/ssh/key_pair.go +++ b/helper/ssh/key_pair.go @@ -109,16 +109,16 @@ func (o *defaultKeyPairBuilder) SetName(name string) KeyPairBuilder { func (o *defaultKeyPairBuilder) Build() (KeyPair, error) { switch o.kind { case Rsa: - return o.newRsaSshKeyPair() + return o.newRsaKeyPair() case Ecdsa: // Default case. } - return o.newEcdsaSshKeyPair() + return o.newEcdsaKeyPair() } -// newEcdsaSshKeyPair returns a new ECDSA SSH key pair. -func (o *defaultKeyPairBuilder) newEcdsaSshKeyPair() (KeyPair, error) { +// newEcdsaKeyPair returns a new ECDSA SSH key pair. +func (o *defaultKeyPairBuilder) newEcdsaKeyPair() (KeyPair, error) { var curve elliptic.Curve switch o.bits { @@ -164,8 +164,8 @@ func (o *defaultKeyPairBuilder) newEcdsaSshKeyPair() (KeyPair, error) { }, nil } -// newRsaSshKeyPair returns a new RSA SSH key pair. -func (o *defaultKeyPairBuilder) newRsaSshKeyPair() (KeyPair, error) { +// newRsaKeyPair returns a new RSA SSH key pair. +func (o *defaultKeyPairBuilder) newRsaKeyPair() (KeyPair, error) { if o.bits == 0 { o.bits = defaultRsaBits } From d465231e633d087d254f0bb47ff53190c8aef06c Mon Sep 17 00:00:00 2001 From: Stephen Fox Date: Mon, 4 Feb 2019 14:25:37 -0500 Subject: [PATCH 19/49] Fixed bad curve ecdsa curve bug. --- helper/ssh/key_pair.go | 4 ++-- helper/ssh/key_pair_test.go | 28 ++++++++++++++++++++++++++++ 2 files changed, 30 insertions(+), 2 deletions(-) diff --git a/helper/ssh/key_pair.go b/helper/ssh/key_pair.go index 4a4f12e00..830d374cd 100644 --- a/helper/ssh/key_pair.go +++ b/helper/ssh/key_pair.go @@ -128,9 +128,9 @@ func (o *defaultKeyPairBuilder) newEcdsaKeyPair() (KeyPair, error) { case 521: curve = elliptic.P521() case 384: - elliptic.P384() + curve = elliptic.P384() case 256: - elliptic.P256() + curve = elliptic.P256() case 224: // Not supported by "golang.org/x/crypto/ssh". return &defaultKeyPair{}, errors.New("golang.org/x/crypto/ssh does not support " + diff --git a/helper/ssh/key_pair_test.go b/helper/ssh/key_pair_test.go index 783fab735..808e98e02 100644 --- a/helper/ssh/key_pair_test.go +++ b/helper/ssh/key_pair_test.go @@ -176,6 +176,34 @@ func TestDefaultKeyPairBuilder_Build_EcdsaDefault(t *testing.T) { } } +func TestDefaultKeyPairBuilder_Build_EcdsaSupportedCurves(t *testing.T) { + supportedBits := []int{ + 521, + 384, + 256, + } + + for _, bits := range supportedBits { + kp, err := NewKeyPairBuilder(). + SetType(Ecdsa). + SetBits(bits). + Build() + if err != nil { + t.Fatal(err.Error()) + } + + err = expected{ + kind: Ecdsa, + bits: bits, + desc: "ecdsa " + strconv.Itoa(bits), + data: []byte(uuid.TimeOrderedUUID()), + }.matches(kp) + if err != nil { + t.Fatal(err.Error()) + } + } +} + func TestDefaultKeyPairBuilder_Build_RsaDefault(t *testing.T) { kp, err := NewKeyPairBuilder(). SetType(Rsa). From f2c11b55be5cb365bdc0863421d3cca442e2871c Mon Sep 17 00:00:00 2001 From: Stephen Fox Date: Mon, 4 Feb 2019 14:27:14 -0500 Subject: [PATCH 20/49] Renamed authorized_keys public key function. --- builder/virtualbox/common/step_ssh_key_pair.go | 2 +- helper/ssh/key_pair.go | 6 +++--- helper/ssh/key_pair_test.go | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/builder/virtualbox/common/step_ssh_key_pair.go b/builder/virtualbox/common/step_ssh_key_pair.go index 8bdd18842..2e157940f 100644 --- a/builder/virtualbox/common/step_ssh_key_pair.go +++ b/builder/virtualbox/common/step_ssh_key_pair.go @@ -59,7 +59,7 @@ func (s *StepSshKeyPair) Run(_ context.Context, state multistep.StateBag) multis s.Comm.SSHKeyPairName = kp.Name() s.Comm.SSHTemporaryKeyPairName = kp.Name() s.Comm.SSHPrivateKey = kp.PrivateKeyPemBlock() - s.Comm.SSHPublicKey = kp.PublicKeyAuthorizedKeysFormat(ssh.UnixNewLine) + s.Comm.SSHPublicKey = kp.PublicKeyAuthorizedKeysLine(ssh.UnixNewLine) s.Comm.SSHClearAuthorizedKeys = true ui.Say(fmt.Sprintf("Created ephemeral SSH key pair of type %s", kp.Description())) diff --git a/helper/ssh/key_pair.go b/helper/ssh/key_pair.go index 830d374cd..cd7a2f4a8 100644 --- a/helper/ssh/key_pair.go +++ b/helper/ssh/key_pair.go @@ -210,10 +210,10 @@ type KeyPair interface { // format in a Privacy-Enhanced Mail (PEM) block. PrivateKeyPemBlock() []byte - // PublicKeyAuthorizedKeysFormat returns a slice of bytes + // PublicKeyAuthorizedKeysLine returns a slice of bytes // representing the public key in OpenSSH authorized_keys format // with the specified new line. - PublicKeyAuthorizedKeysFormat(NewLineOption) []byte + PublicKeyAuthorizedKeysLine(NewLineOption) []byte } type defaultKeyPair struct { @@ -267,7 +267,7 @@ func (o defaultKeyPair) PrivateKeyPemBlock() []byte { }) } -func (o defaultKeyPair) PublicKeyAuthorizedKeysFormat(nl NewLineOption) []byte { +func (o defaultKeyPair) PublicKeyAuthorizedKeysLine(nl NewLineOption) []byte { result := gossh.MarshalAuthorizedKey(o.publicKey) if len(strings.TrimSpace(o.name)) > 0 { diff --git a/helper/ssh/key_pair_test.go b/helper/ssh/key_pair_test.go index 808e98e02..406c3b6bd 100644 --- a/helper/ssh/key_pair_test.go +++ b/helper/ssh/key_pair_test.go @@ -79,7 +79,7 @@ func (o expected) verifyPublicKeyAuthorizedKeysFormat(kp KeyPair) error { } for _, nl := range newLines { - publicKeyAk := kp.PublicKeyAuthorizedKeysFormat(nl) + publicKeyAk := kp.PublicKeyAuthorizedKeysLine(nl) if len(publicKeyAk) < 2 { return errors.New("expected public key in authorized keys format to be at least 2 bytes") From f3128143fa915f7961629873ced5c2d8cfb5b012 Mon Sep 17 00:00:00 2001 From: Stephen Fox Date: Mon, 4 Feb 2019 14:27:59 -0500 Subject: [PATCH 21/49] Simplified building of authorized_keys public key. --- helper/ssh/key_pair.go | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/helper/ssh/key_pair.go b/helper/ssh/key_pair.go index cd7a2f4a8..600f258e4 100644 --- a/helper/ssh/key_pair.go +++ b/helper/ssh/key_pair.go @@ -270,28 +270,23 @@ func (o defaultKeyPair) PrivateKeyPemBlock() []byte { func (o defaultKeyPair) PublicKeyAuthorizedKeysLine(nl NewLineOption) []byte { result := gossh.MarshalAuthorizedKey(o.publicKey) + // Remove the mandatory unix new line. + // Awful, but the go ssh library automatically appends + // a unix new line. + result = bytes.TrimSuffix(result, UnixNewLine.Bytes()) + if len(strings.TrimSpace(o.name)) > 0 { - // Awful, but the go ssh library automatically appends - // a unix new line. - result = bytes.TrimSuffix(result, UnixNewLine.Bytes()) result = append(result, ' ') result = append(result, o.name...) } switch nl { - case NoNewLine: - result = bytes.TrimSuffix(result, UnixNewLine.Bytes()) case WindowsNewLine: - result = bytes.TrimSuffix(result, UnixNewLine.Bytes()) result = append(result, nl.Bytes()...) case UnixNewLine: - fallthrough - default: // This is how all the other "SSH key pair" code works in // the different builders. - if !bytes.HasSuffix(result, UnixNewLine.Bytes()) { - result = append(result, UnixNewLine.Bytes()...) - } + result = append(result, UnixNewLine.Bytes()...) } return result From 58c692a58728b46bcc32aee425412fc36cdc5a75 Mon Sep 17 00:00:00 2001 From: Stephen Fox Date: Tue, 5 Feb 2019 09:52:46 -0500 Subject: [PATCH 22/49] Get bits from private key rather than user input. --- helper/ssh/key_pair.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/helper/ssh/key_pair.go b/helper/ssh/key_pair.go index 600f258e4..c18d7b8ad 100644 --- a/helper/ssh/key_pair.go +++ b/helper/ssh/key_pair.go @@ -157,7 +157,7 @@ func (o *defaultKeyPairBuilder) newEcdsaKeyPair() (KeyPair, error) { return &defaultKeyPair{ kind: Ecdsa, - bits: o.bits, + bits: privateKey.Curve.Params().BitSize, name: o.name, privateKeyDerBytes: raw, publicKey: sshPublicKey, @@ -182,7 +182,7 @@ func (o *defaultKeyPairBuilder) newRsaKeyPair() (KeyPair, error) { return &defaultKeyPair{ kind: Rsa, - bits: o.bits, + bits: privateKey.N.BitLen(), name: o.name, privateKeyDerBytes: x509.MarshalPKCS1PrivateKey(privateKey), publicKey: sshPublicKey, From d7510ecdf738c384929a2f62b911432b9b3b185e Mon Sep 17 00:00:00 2001 From: Stephen Fox Date: Tue, 5 Feb 2019 09:54:18 -0500 Subject: [PATCH 23/49] Tweaked 'PublicKeyAuthorizedKeysLine()' comment. --- helper/ssh/key_pair.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/helper/ssh/key_pair.go b/helper/ssh/key_pair.go index c18d7b8ad..51a21e786 100644 --- a/helper/ssh/key_pair.go +++ b/helper/ssh/key_pair.go @@ -211,8 +211,8 @@ type KeyPair interface { PrivateKeyPemBlock() []byte // PublicKeyAuthorizedKeysLine returns a slice of bytes - // representing the public key in OpenSSH authorized_keys format - // with the specified new line. + // representing the public key as a line in OpenSSH authorized_keys + // format with the specified new line. PublicKeyAuthorizedKeysLine(NewLineOption) []byte } From 4b649f7ce40c3ce5878b8aea6118b352db6b2dc2 Mon Sep 17 00:00:00 2001 From: Stephen Fox Date: Tue, 5 Feb 2019 11:53:12 -0500 Subject: [PATCH 24/49] Use individual key pair implementations. This allows us to store more information about the key pair. In particular, we can query the private key for its bits of entropy - avoiding the possibility of hardcoding the wrong value. --- helper/ssh/ecdsa_key_pair.go | 38 +++++++++++ helper/ssh/key_pair.go | 129 ++++++++++++++++------------------- helper/ssh/rsa_key_pair.go | 38 +++++++++++ 3 files changed, 133 insertions(+), 72 deletions(-) create mode 100644 helper/ssh/ecdsa_key_pair.go create mode 100644 helper/ssh/rsa_key_pair.go diff --git a/helper/ssh/ecdsa_key_pair.go b/helper/ssh/ecdsa_key_pair.go new file mode 100644 index 000000000..9f3dde341 --- /dev/null +++ b/helper/ssh/ecdsa_key_pair.go @@ -0,0 +1,38 @@ +package ssh + +import ( + "crypto/ecdsa" + + gossh "golang.org/x/crypto/ssh" +) + +type ecdsaKeyPair struct { + privateKey *ecdsa.PrivateKey + publicKey gossh.PublicKey + name string + privatePemBlock []byte +} + +func (o ecdsaKeyPair) Type() KeyPairType { + return Ecdsa +} + +func (o ecdsaKeyPair) Bits() int { + return o.privateKey.Curve.Params().BitSize +} + +func (o ecdsaKeyPair) Name() string { + return o.name +} + +func (o ecdsaKeyPair) Description() string { + return description(o) +} + +func (o ecdsaKeyPair) PrivateKeyPemBlock() []byte { + return o.privatePemBlock +} + +func (o ecdsaKeyPair) PublicKeyAuthorizedKeysLine(nl NewLineOption) []byte { + return publicKeyAuthorizedKeysLine(o.publicKey, o.name, nl) +} diff --git a/helper/ssh/key_pair.go b/helper/ssh/key_pair.go index 51a21e786..708ecd3a7 100644 --- a/helper/ssh/key_pair.go +++ b/helper/ssh/key_pair.go @@ -133,34 +133,42 @@ func (o *defaultKeyPairBuilder) newEcdsaKeyPair() (KeyPair, error) { curve = elliptic.P256() case 224: // Not supported by "golang.org/x/crypto/ssh". - return &defaultKeyPair{}, errors.New("golang.org/x/crypto/ssh does not support " + + return &ecdsaKeyPair{}, errors.New("golang.org/x/crypto/ssh does not support " + strconv.Itoa(o.bits) + " bits") default: - return &defaultKeyPair{}, errors.New("crypto/elliptic does not support " + + return &ecdsaKeyPair{}, errors.New("crypto/elliptic does not support " + strconv.Itoa(o.bits) + " bits") } privateKey, err := ecdsa.GenerateKey(curve, rand.Reader) if err != nil { - return &defaultKeyPair{}, err + return &ecdsaKeyPair{}, err } sshPublicKey, err := gossh.NewPublicKey(&privateKey.PublicKey) if err != nil { - return &defaultKeyPair{}, err + return &ecdsaKeyPair{}, err } - raw, err := x509.MarshalECPrivateKey(privateKey) + privateRaw, err := x509.MarshalECPrivateKey(privateKey) if err != nil { - return &defaultKeyPair{}, err + return &ecdsaKeyPair{}, err } - return &defaultKeyPair{ - kind: Ecdsa, - bits: privateKey.Curve.Params().BitSize, - name: o.name, - privateKeyDerBytes: raw, - publicKey: sshPublicKey, + privatePem, err := rawPemBlock(&pem.Block{ + Type: "EC PRIVATE KEY", + Headers: nil, + Bytes: privateRaw, + }) + if err != nil { + return &ecdsaKeyPair{}, err + } + + return &ecdsaKeyPair{ + privateKey: privateKey, + publicKey: sshPublicKey, + name: o.name, + privatePemBlock: privatePem, }, nil } @@ -172,20 +180,28 @@ func (o *defaultKeyPairBuilder) newRsaKeyPair() (KeyPair, error) { privateKey, err := rsa.GenerateKey(rand.Reader, o.bits) if err != nil { - return &defaultKeyPair{}, err + return &rsaKeyPair{}, err } sshPublicKey, err := gossh.NewPublicKey(&privateKey.PublicKey) if err != nil { - return &defaultKeyPair{}, err + return &rsaKeyPair{}, err + } + + privatePemBlock, err := rawPemBlock(&pem.Block{ + Type: "RSA PRIVATE KEY", + Headers: nil, + Bytes: x509.MarshalPKCS1PrivateKey(privateKey), + }) + if err != nil { + return &rsaKeyPair{}, err } - return &defaultKeyPair{ - kind: Rsa, - bits: privateKey.N.BitLen(), - name: o.name, - privateKeyDerBytes: x509.MarshalPKCS1PrivateKey(privateKey), - publicKey: sshPublicKey, + return &rsaKeyPair{ + privateKey: privateKey, + publicKey: sshPublicKey, + name: o.name, + privatePemBlock: privatePemBlock, }, nil } @@ -216,68 +232,41 @@ type KeyPair interface { PublicKeyAuthorizedKeysLine(NewLineOption) []byte } -type defaultKeyPair struct { - // kind is the key pair's type. - kind KeyPairType - - // bits is the key pair's bits of entropy. - bits int - - // name is the key pair's name. - name string - - // privateKeyDerBytes is the private key's bytes - // in ASN.1 DER format - privateKeyDerBytes []byte - - // publicKey is the key pair's public key. - publicKey gossh.PublicKey -} - -func (o defaultKeyPair) Type() KeyPairType { - return o.kind +func NewKeyPairBuilder() KeyPairBuilder { + return &defaultKeyPairBuilder{} } -func (o defaultKeyPair) Bits() int { - return o.bits -} +// rawPemBlock encodes a pem.Block to a slice of bytes. +func rawPemBlock(block *pem.Block) ([]byte, error) { + buffer := bytes.NewBuffer(nil) -func (o defaultKeyPair) Name() string { - return o.name -} + err := pem.Encode(buffer, block) + if err != nil { + return []byte{}, err + } -func (o defaultKeyPair) Description() string { - return o.kind.String() + " " + strconv.Itoa(o.bits) + return buffer.Bytes(), nil } -func (o defaultKeyPair) PrivateKeyPemBlock() []byte { - t := "UNKNOWN PRIVATE KEY" - - switch o.kind { - case Ecdsa: - t = "EC PRIVATE KEY" - case Rsa: - t = "RSA PRIVATE KEY" - } - - return pem.EncodeToMemory(&pem.Block{ - Type: t, - Headers: nil, - Bytes: o.privateKeyDerBytes, - }) +// TODO: Key pair name. +// description returns a string describing a key pair. +func description(kp KeyPair) string { + return kp.Type().String() + " " + strconv.Itoa(kp.Bits()) } -func (o defaultKeyPair) PublicKeyAuthorizedKeysLine(nl NewLineOption) []byte { - result := gossh.MarshalAuthorizedKey(o.publicKey) +// publicKeyAuthorizedKeysLine returns a slice of bytes representing a SSH +// public key as a line in OpenSSH authorized_keys format. +func publicKeyAuthorizedKeysLine(publicKey gossh.PublicKey, name string, nl NewLineOption) []byte { + result := gossh.MarshalAuthorizedKey(publicKey) // Remove the mandatory unix new line. // Awful, but the go ssh library automatically appends // a unix new line. - result = bytes.TrimSuffix(result, UnixNewLine.Bytes()) + result = bytes.TrimSpace(result) - if len(strings.TrimSpace(o.name)) > 0 { + if len(strings.TrimSpace(name)) > 0 { result = append(result, ' ') - result = append(result, o.name...) + result = append(result, name...) } switch nl { @@ -291,7 +280,3 @@ func (o defaultKeyPair) PublicKeyAuthorizedKeysLine(nl NewLineOption) []byte { return result } - -func NewKeyPairBuilder() KeyPairBuilder { - return &defaultKeyPairBuilder{} -} diff --git a/helper/ssh/rsa_key_pair.go b/helper/ssh/rsa_key_pair.go new file mode 100644 index 000000000..9a83f193b --- /dev/null +++ b/helper/ssh/rsa_key_pair.go @@ -0,0 +1,38 @@ +package ssh + +import ( + "crypto/rsa" + + gossh "golang.org/x/crypto/ssh" +) + +type rsaKeyPair struct { + privateKey *rsa.PrivateKey + publicKey gossh.PublicKey + name string + privatePemBlock []byte +} + +func (o rsaKeyPair) Type() KeyPairType { + return Rsa +} + +func (o rsaKeyPair) Bits() int { + return o.privateKey.N.BitLen() +} + +func (o rsaKeyPair) Name() string { + return o.name +} + +func (o rsaKeyPair) Description() string { + return description(o) +} + +func (o rsaKeyPair) PrivateKeyPemBlock() []byte { + return o.privatePemBlock +} + +func (o rsaKeyPair) PublicKeyAuthorizedKeysLine(nl NewLineOption) []byte { + return publicKeyAuthorizedKeysLine(o.publicKey, o.name, nl) +} From f9d24ccda0bcfff561a9971b39221a67900a3519 Mon Sep 17 00:00:00 2001 From: Stephen Fox Date: Tue, 5 Feb 2019 11:59:13 -0500 Subject: [PATCH 25/49] Allow user to get public key straight up, or URL encoded. --- builder/virtualbox/common/step_ssh_key_pair.go | 2 ++ .../common/step_type_boot_command.go | 18 ++++++++++++------ 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/builder/virtualbox/common/step_ssh_key_pair.go b/builder/virtualbox/common/step_ssh_key_pair.go index 2e157940f..40c728e2f 100644 --- a/builder/virtualbox/common/step_ssh_key_pair.go +++ b/builder/virtualbox/common/step_ssh_key_pair.go @@ -66,6 +66,8 @@ func (s *StepSshKeyPair) Run(_ context.Context, state multistep.StateBag) multis // If we're in debug mode, output the private key to the working // directory. + // TODO: It would be better if the file was 'chmod' before writing + // the key to the disk - or if umask was set before creating the file. if s.Debug { ui.Message(fmt.Sprintf("Saving communicator private key for debug purposes: %s", s.DebugKeyPath)) f, err := os.Create(s.DebugKeyPath) diff --git a/builder/virtualbox/common/step_type_boot_command.go b/builder/virtualbox/common/step_type_boot_command.go index 0cb433903..6095505af 100644 --- a/builder/virtualbox/common/step_type_boot_command.go +++ b/builder/virtualbox/common/step_type_boot_command.go @@ -27,8 +27,13 @@ type bootCommandTemplateData struct { // Name is the VM's name. Name string - // SSHPublicKey is the URL encoded public key in - // authorized_keys format. + // EncodedSSHPublicKey is the URL encoded SSH public key in + // OpenSSH authorized_keys format. This is safe for usage + // on the the kernel command line, or other places that split + // on whitespace. + EncodedSSHPublicKey string + + // SSHPublicKey is the SSH public key in OpenSSH authorized_keys format. SSHPublicKey string } @@ -67,10 +72,11 @@ func (s *StepTypeBootCommand) Run(ctx context.Context, state multistep.StateBag) hostIP := "10.0.2.2" common.SetHTTPIP(hostIP) s.Ctx.Data = &bootCommandTemplateData{ - HTTPIP: hostIP, - HTTPPort: httpPort, - Name: s.VMName, - SSHPublicKey: s.Comm.SSHPublicKeyUrlEncoded(), + HTTPIP: hostIP, + HTTPPort: httpPort, + Name: s.VMName, + EncodedSSHPublicKey: s.Comm.SSHPublicKeyUrlEncoded(), + SSHPublicKey: string(s.Comm.SSHPublicKey), } sendCodes := func(codes []string) error { From e40aa515f24708a71fd25f97c8aea59c93b5a798 Mon Sep 17 00:00:00 2001 From: Stephen Fox Date: Tue, 5 Feb 2019 12:00:01 -0500 Subject: [PATCH 26/49] Added TODO about public key removal logic. --- common/step_cleanup_temp_keys.go | 1 + 1 file changed, 1 insertion(+) diff --git a/common/step_cleanup_temp_keys.go b/common/step_cleanup_temp_keys.go index c44a5c723..74b528a80 100644 --- a/common/step_cleanup_temp_keys.go +++ b/common/step_cleanup_temp_keys.go @@ -38,6 +38,7 @@ func (s *StepCleanupTempKeys) Run(_ context.Context, state multistep.StateBag) m ui.Say("Trying to remove ephemeral keys from authorized_keys files") + // TODO: Why create a backup file if you are going to remove it? cmd.Command = fmt.Sprintf("sed -i.bak '/ %s$/d' ~/.ssh/authorized_keys; rm ~/.ssh/authorized_keys.bak", s.Comm.SSHTemporaryKeyPairName) if err := cmd.StartWithUi(comm, ui); err != nil { log.Printf("Error cleaning up ~/.ssh/authorized_keys; please clean up keys manually: %s", err) From 72b7d63a6a90b558304f03368071e0dbb65dec81 Mon Sep 17 00:00:00 2001 From: Stephen Fox Date: Tue, 5 Feb 2019 13:37:09 -0500 Subject: [PATCH 27/49] Restructured the SSH key pair's description. Now include the key pair name (if any), and make it more "readable". --- helper/ssh/key_pair.go | 18 ++++++++++++++---- helper/ssh/key_pair_test.go | 17 ++++++++--------- 2 files changed, 22 insertions(+), 13 deletions(-) diff --git a/helper/ssh/key_pair.go b/helper/ssh/key_pair.go index 708ecd3a7..ae9cb2c23 100644 --- a/helper/ssh/key_pair.go +++ b/helper/ssh/key_pair.go @@ -20,10 +20,10 @@ const ( defaultRsaBits = 4096 // Rsa is a SSH key pair of RSA type. - Rsa KeyPairType = "rsa" + Rsa KeyPairType = "RSA" // Ecdsa is a SSH key pair of ECDSA type. - Ecdsa KeyPairType = "ecdsa" + Ecdsa KeyPairType = "ECDSA" ) // KeyPairType represents different types of SSH key pairs. @@ -248,10 +248,20 @@ func rawPemBlock(block *pem.Block) ([]byte, error) { return buffer.Bytes(), nil } -// TODO: Key pair name. // description returns a string describing a key pair. func description(kp KeyPair) string { - return kp.Type().String() + " " + strconv.Itoa(kp.Bits()) + buffer := bytes.NewBuffer(nil) + + buffer.WriteString(strconv.Itoa(kp.Bits())) + buffer.WriteString(" bit ") + buffer.WriteString(kp.Type().String()) + + if len(kp.Name()) > 0 { + buffer.WriteString(" named ") + buffer.WriteString(kp.Name()) + } + + return buffer.String() } // publicKeyAuthorizedKeysLine returns a slice of bytes representing a SSH diff --git a/helper/ssh/key_pair_test.go b/helper/ssh/key_pair_test.go index 406c3b6bd..c4a4075eb 100644 --- a/helper/ssh/key_pair_test.go +++ b/helper/ssh/key_pair_test.go @@ -52,10 +52,9 @@ func (o expected) matches(kp KeyPair) error { "' - got '" + kp.Name() + "'") } - expDescription := kp.Type().String() + " " + strconv.Itoa(o.bits) - if kp.Description() != expDescription { + if kp.Description() != o.desc { return errors.New("key pair description should be '" + - expDescription + "' - got '" + kp.Description() + "'") + o.desc + "' - got '" + kp.Description() + "'") } err := o.verifyPublicKeyAuthorizedKeysFormat(kp) @@ -149,7 +148,7 @@ func TestDefaultKeyPairBuilder_Build_Default(t *testing.T) { err = expected{ kind: Ecdsa, bits: 521, - desc: "ecdsa 521", + desc: "521 bit ECDSA", data: []byte(uuid.TimeOrderedUUID()), }.matches(kp) if err != nil { @@ -168,7 +167,7 @@ func TestDefaultKeyPairBuilder_Build_EcdsaDefault(t *testing.T) { err = expected{ kind: Ecdsa, bits: 521, - desc: "ecdsa 521", + desc: "521 bit ECDSA", data: []byte(uuid.TimeOrderedUUID()), }.matches(kp) if err != nil { @@ -195,7 +194,7 @@ func TestDefaultKeyPairBuilder_Build_EcdsaSupportedCurves(t *testing.T) { err = expected{ kind: Ecdsa, bits: bits, - desc: "ecdsa " + strconv.Itoa(bits), + desc: strconv.Itoa(bits) + " bit ECDSA", data: []byte(uuid.TimeOrderedUUID()), }.matches(kp) if err != nil { @@ -215,7 +214,7 @@ func TestDefaultKeyPairBuilder_Build_RsaDefault(t *testing.T) { err = expected{ kind: Rsa, bits: 4096, - desc: "rsa 4096", + desc: "4096 bit RSA", data: []byte(uuid.TimeOrderedUUID()), }.matches(kp) if err != nil { @@ -237,7 +236,7 @@ func TestDefaultKeyPairBuilder_Build_NamedEcdsa(t *testing.T) { err = expected{ kind: Ecdsa, bits: 521, - desc: "ecdsa 521", + desc: "521 bit ECDSA named " + name, data: []byte(uuid.TimeOrderedUUID()), name: name, }.matches(kp) @@ -260,7 +259,7 @@ func TestDefaultKeyPairBuilder_Build_NamedRsa(t *testing.T) { err = expected{ kind: Rsa, bits: 4096, - desc: "rsa 4096", + desc: "4096 bit RSA named " + name, data: []byte(uuid.TimeOrderedUUID()), name: name, }.matches(kp) From 13cc73d600712bc6b31dc28cc5911a932e717cd9 Mon Sep 17 00:00:00 2001 From: chris marget Date: Wed, 6 Feb 2019 13:46:55 -0500 Subject: [PATCH 28/49] keypair parsing and testing --- .../virtualbox/common/step_ssh_key_pair.go | 13 + helper/ssh/default_key_pair.go | 28 ++ helper/ssh/dsa_key_pair.go | 37 +++ helper/ssh/ed25519_key_pair.go | 38 +++ helper/ssh/key_pair.go | 107 ++++++- helper/ssh/key_pair_test.go | 295 +++++++++++++++++- 6 files changed, 501 insertions(+), 17 deletions(-) create mode 100644 helper/ssh/default_key_pair.go create mode 100644 helper/ssh/dsa_key_pair.go create mode 100644 helper/ssh/ed25519_key_pair.go diff --git a/builder/virtualbox/common/step_ssh_key_pair.go b/builder/virtualbox/common/step_ssh_key_pair.go index 40c728e2f..4e04d9603 100644 --- a/builder/virtualbox/common/step_ssh_key_pair.go +++ b/builder/virtualbox/common/step_ssh_key_pair.go @@ -35,8 +35,21 @@ func (s *StepSshKeyPair) Run(_ context.Context, state multistep.StateBag) multis state.Put("error", err) return multistep.ActionHalt } + ui.Say(string(privateKeyBytes)) + + kp, err := ssh.NewKeyPairBuilder(). + SetPrivateKey(privateKeyBytes). + SetName(fmt.Sprintf("packer_%s", uuid.TimeOrderedUUID())). + Build() + if err != nil { + state.Put("error", err) + return multistep.ActionHalt + } s.Comm.SSHPrivateKey = privateKeyBytes + s.Comm.SSHKeyPairName = kp.Name() + s.Comm.SSHTemporaryKeyPairName = kp.Name() + s.Comm.SSHPublicKey = kp.PublicKeyAuthorizedKeysLine(ssh.UnixNewLine) return multistep.ActionContinue } diff --git a/helper/ssh/default_key_pair.go b/helper/ssh/default_key_pair.go new file mode 100644 index 000000000..5ea1859c4 --- /dev/null +++ b/helper/ssh/default_key_pair.go @@ -0,0 +1,28 @@ +package ssh + +type defaultKeyPair struct { +} + +func (o defaultKeyPair) Type() KeyPairType { + return Default +} + +func (o defaultKeyPair) Bits() int { + return 0 +} + +func (o defaultKeyPair) Name() string { + return "" +} + +func (o defaultKeyPair) Description() string { + return "" +} + +func (o defaultKeyPair) PrivateKeyPemBlock() []byte { + return []byte{} +} + +func (o defaultKeyPair) PublicKeyAuthorizedKeysLine(nl NewLineOption) []byte { + return []byte{} +} diff --git a/helper/ssh/dsa_key_pair.go b/helper/ssh/dsa_key_pair.go new file mode 100644 index 000000000..9d20693f5 --- /dev/null +++ b/helper/ssh/dsa_key_pair.go @@ -0,0 +1,37 @@ +package ssh + +import ( + "crypto/dsa" + gossh "golang.org/x/crypto/ssh" +) + +type dsaKeyPair struct { + privateKey *dsa.PrivateKey + publicKey gossh.PublicKey + name string + privatePemBlock []byte +} + +func (o dsaKeyPair) Type() KeyPairType { + return Dsa +} + +func (o dsaKeyPair) Bits() int { + return 1024 +} + +func (o dsaKeyPair) Name() string { + return o.name +} + +func (o dsaKeyPair) Description() string { + return description(o) +} + +func (o dsaKeyPair) PrivateKeyPemBlock() []byte { + return o.privatePemBlock +} + +func (o dsaKeyPair) PublicKeyAuthorizedKeysLine(nl NewLineOption) []byte { + return publicKeyAuthorizedKeysLine(o.publicKey, o.name, nl) +} diff --git a/helper/ssh/ed25519_key_pair.go b/helper/ssh/ed25519_key_pair.go new file mode 100644 index 000000000..91a0a3654 --- /dev/null +++ b/helper/ssh/ed25519_key_pair.go @@ -0,0 +1,38 @@ +package ssh + +import ( + "golang.org/x/crypto/ed25519" + + gossh "golang.org/x/crypto/ssh" +) + +type ed25519KeyPair struct { + privateKey *ed25519.PrivateKey + publicKey gossh.PublicKey + name string + privatePemBlock []byte +} + +func (o ed25519KeyPair) Type() KeyPairType { + return Ed25519 +} + +func (o ed25519KeyPair) Bits() int { + return 256 +} + +func (o ed25519KeyPair) Name() string { + return o.name +} + +func (o ed25519KeyPair) Description() string { + return description(o) +} + +func (o ed25519KeyPair) PrivateKeyPemBlock() []byte { + return o.privatePemBlock +} + +func (o ed25519KeyPair) PublicKeyAuthorizedKeysLine(nl NewLineOption) []byte { + return publicKeyAuthorizedKeysLine(o.publicKey, o.name, nl) +} diff --git a/helper/ssh/key_pair.go b/helper/ssh/key_pair.go index ae9cb2c23..f1f2c9ea0 100644 --- a/helper/ssh/key_pair.go +++ b/helper/ssh/key_pair.go @@ -2,6 +2,7 @@ package ssh import ( "bytes" + "crypto/dsa" "crypto/ecdsa" "crypto/elliptic" "crypto/rand" @@ -9,9 +10,11 @@ import ( "crypto/x509" "encoding/pem" "errors" + "fmt" "strconv" "strings" + "golang.org/x/crypto/ed25519" gossh "golang.org/x/crypto/ssh" ) @@ -19,17 +22,22 @@ const ( // That's a lot of bits. defaultRsaBits = 4096 - // Rsa is a SSH key pair of RSA type. - Rsa KeyPairType = "RSA" - - // Ecdsa is a SSH key pair of ECDSA type. - Ecdsa KeyPairType = "ECDSA" + // Markers for various SSH key pair types + Default KeyPairType = "" + Rsa KeyPairType = "RSA" + Ecdsa KeyPairType = "ECDSA" + Dsa KeyPairType = "DSA" + Ed25519 KeyPairType = "ED25519" ) // KeyPairType represents different types of SSH key pairs. // See the 'const' block for details. type KeyPairType string +// PrivateKeyHeader represents header text in PEM encoded private key files. +// See the 'const' block for details. +type PrivateKeyHeader string + func (o KeyPairType) String() string { return string(o) } @@ -58,6 +66,9 @@ func (o NewLineOption) Bytes() []byte { } // KeyPairBuilder builds SSH key pairs. +// It can generate new keys of type RSA and ECDSA. +// It can parse user supplied keys of type DSA, RSA, ECDSA, +// and ED25519. type KeyPairBuilder interface { // SetType sets the key pair type. SetType(KeyPairType) KeyPairBuilder @@ -65,10 +76,15 @@ type KeyPairBuilder interface { // SetBits sets the key pair's bits of entropy. SetBits(int) KeyPairBuilder - // SetName sets the name of the key pair. This is primarily used - // to identify the public key in the authorized_keys file. + // SetName sets the name of the key pair. This is primarily + // used to identify the public key in the authorized_keys file. SetName(string) KeyPairBuilder + // SetPrivateKey takes an existing private key in PEM format. + // It overrides key generation details specified by SetType() + // and SetBits(). + SetPrivateKey([]byte) KeyPairBuilder + // Build returns a SSH key pair. // // The following defaults are used if not specified: @@ -89,6 +105,10 @@ type defaultKeyPairBuilder struct { // name is the resulting key pair's name. name string + + // privatePemBytes is the supplied key data when the builder + // is working from a preallocated key. + privatePemBytes []byte } func (o *defaultKeyPairBuilder) SetType(kind KeyPairType) KeyPairBuilder { @@ -106,15 +126,82 @@ func (o *defaultKeyPairBuilder) SetName(name string) KeyPairBuilder { return o } +func (o *defaultKeyPairBuilder) SetPrivateKey(privateBytes []byte) KeyPairBuilder { + o.privatePemBytes = privateBytes + return o +} + func (o *defaultKeyPairBuilder) Build() (KeyPair, error) { + if o.privatePemBytes != nil { + return o.preallocatedKeyPair() + } + switch o.kind { case Rsa: return o.newRsaKeyPair() - case Ecdsa: - // Default case. + case Ecdsa, Default: + return o.newEcdsaKeyPair() + } + + return nil, fmt.Errorf("Unsupported keypair type: %s", o.kind.String()) +} + +// preallocatedKeyPair returns an SSH key pair based on user +// supplied PEM data. +func (o *defaultKeyPairBuilder) preallocatedKeyPair() (KeyPair, error) { + privateKey, err := gossh.ParseRawPrivateKey(o.privatePemBytes) + if err != nil { + return nil, err + } + + switch pk := privateKey.(type) { + case *rsa.PrivateKey: + publicKey, err := gossh.NewPublicKey(&pk.PublicKey) + if err != nil { + return nil, err + } + return &rsaKeyPair{ + privateKey: pk, + publicKey: publicKey, + name: o.name, + privatePemBlock: o.privatePemBytes, + }, nil + case *ecdsa.PrivateKey: + publicKey, err := gossh.NewPublicKey(&pk.PublicKey) + if err != nil { + return nil, err + } + return &ecdsaKeyPair{ + privateKey: pk, + publicKey: publicKey, + name: o.name, + privatePemBlock: o.privatePemBytes, + }, nil + case *dsa.PrivateKey: + publicKey, err := gossh.NewPublicKey(&pk.PublicKey) + if err != nil { + return nil, err + } + return &dsaKeyPair{ + privateKey: pk, + publicKey: publicKey, + name: o.name, + privatePemBlock: o.privatePemBytes, + }, nil + case *ed25519.PrivateKey: + publicKey, err := gossh.NewPublicKey(pk.Public()) + if err != nil { + return nil, err + } + return &ed25519KeyPair{ + privateKey: pk, + publicKey: publicKey, + name: o.name, + privatePemBlock: o.privatePemBytes, + }, nil } - return o.newEcdsaKeyPair() + return &defaultKeyPair{}, fmt.Errorf("Unknown ssh key pair type") } // newEcdsaKeyPair returns a new ECDSA SSH key pair. diff --git a/helper/ssh/key_pair_test.go b/helper/ssh/key_pair_test.go index c4a4075eb..58560dc3d 100644 --- a/helper/ssh/key_pair_test.go +++ b/helper/ssh/key_pair_test.go @@ -4,11 +4,10 @@ import ( "bytes" "crypto/rand" "errors" - "strconv" - "testing" - "github.com/hashicorp/packer/common/uuid" gossh "golang.org/x/crypto/ssh" + "strconv" + "testing" ) // expected contains the data that the key pair should contain. @@ -20,6 +19,137 @@ type expected struct { data []byte } +const ( + PemRsa1024 = `-----BEGIN RSA PRIVATE KEY----- +MIICWwIBAAKBgQDJEMFPpTBiWNDb3qEIPTSeEnIP8FZdBpG8njOrclcMoQQNhzZ+ +4uz37tqtHMp36Z7LB4/+85NN6epNXO+ekyZIHswiyBcJC2sT3KuH7nG1BESOooPY +DfeCSM+CJT9GDIhy9nUXSsJjrceEyh/B5DjEtIbS0XfcRelrNTJodCmPJwIDAQAB +AoGAK66GMOV0c4lUJtBhL8cMTWM4gJn4SVGKC+5az16R5t58YOwFPN/UF7E+tOlS +W2bX5sgH0p3cXMr66j/Mlyjk4deLg7trDavulIP93MyVO2SUJ0cstQ0ZmRz2oGwx +Gow+hD75Cet7uvepdmG4DKHJe8D/I72rtP1WKuZyd0vP6WECQQDua6wWlyEdIimx +XoGWUvmywACWPnQmBnyHG7x5hxMjijQoQZu60zRxSU9I5q08BerTsvbTc+xLnDVv +mFzlcjT/AkEA1+P7lcvViZeNKoDB1Qt+VV+pkcqL5aoRwdnLA51SyFJ9tXkxeZwA +LOof3xtoRGhCld7ixi3kF5aZsafAJOZd2QJAH8rFyMFgTgU3MAqdFxF7cGV/7ojn +bgahZlbBfCcR20Rbjh6piHEPZifTZbI02XMkjBQqK6oikTaEPZxAjuv6uwJANczu +yWm+kUdfOpRTuY/fr87jJx3etyEmw7ROz1vJYXqNMUg+eBvUP10pDCR8W2/QCCE/ +Sjvtd6NkMc2oKInwIQJAFZ1xJte0EaQsXaCIoZwHrQJbK1dd5l1xTAzz51voAcKH +2K23xgx4I+/eam2enjFa7wXLZFoW0xg/51xsaIjnrA== +-----END RSA PRIVATE KEY----- +` + PemRsa2048 = `-----BEGIN RSA PRIVATE KEY----- +MIIEowIBAAKCAQEA/ZPazeRmBapF01gzHXtJGpu0S936xHY+pOrIyIk6lEE06paf +q5gh6BCuiN/60Keed5Nz+Es4dPGc73mql9pd7N0HOoEc1IQjZzJVqWOy3E55oWbz +rXr1qbmMjw8bGHalZsVBov1UhyB6f2bKi88fGkThJi9HZ+Dc3Jr87eW+whS4D0bI +JJe5dkY0VhDqB0YVEk299TxlAiDkeXD1EcMZrD/yHsusapwlXL2WHWmCgbPpbeYW +YJhD1bScChYmf41iiInBwFymG7kz4bPsup7wCBXpcLJplY1iuXdtVVujNLDbJwlb +Xi2oBm3WizPjYcUthvMlqOieuy6Z4KzyJd7EnQIDAQABAoIBAByZ8LQIbvl0myub +ZyiMH1LA/TURdJd0PtybHsp/r/vI3w8WrivMnQZv2z/VA5VFUrpqB0qaMWP/XJQm +RPebybxNseMHbRkLTnL1WnQgqqvurglmc1W96LecFh6MtaGswDs3RI/9wur63tY/ +4dijI/7yhfKoooU097RqRt0ObNW3BxGwNKUraMLKEZjtohv1cZBeRqzGZuui351E +YsG1jt23/3OP3Acfd1xpzoi+daadxl9JTr02kE7lMjfq32quhTdzuNZP84sQsaV+ +RXLNEoiSufjzy3nHTEpG6QaEWQc4gszCIBVRabxr7LtIOqJn2KmXxtOyFE52AJJj +ls3ifAECgYEA/9K+5oHdZBizWWUvNzQWXrdHUtVavCwjj+0v+yRFZSoAhKtVmLYl +8R4NeG6NCIOoJsqmGVpgtCyPndR4PQ6yr4Jt1FJorjsNw21eYrjOVG+y9Z0DkCwJ +uCRVUeqB42jLu7v9r1V3OBQdKLN6VxO4np05KEZyv1LOGGt0XC8NCykCgYEA/cC2 +NR7Y4Z5OjCc2GHMQNrVZ2HTDDir71RjcIIEmsIQ5/AMAELFLCSqzD73tJ87T5jPi +aWeOpIcnK78jMvIIsbV0BXaDsjtlvCdQui2AoX63FuK4q4E+vwe5Q/TqY2nDh2io +mGHfeXECyUx4gxIede2XEO9zYQ0lP8gxnjmLkFUCgYBO8LolqQcm/xRAzp9eOn14 +prekkN+Z10j1/avjpFKhn+9fAPu9zt8wYySm9/4fFXlK1xegFSpoDqQWgNzFgoaS +7/1yGifhM6nQlywb7IkGtx0S+2uBDoXFQ7jsOR/xi4HqoVzrwMS0EkjZKWDkA9rh +XwSnL+3yqduc33OdiotM2QKBgCgNCrVHsSOrQOqOJdOmFaEM7qljhIXv8t+nlNbs +i5bAyAYm0xPPZ/CCdNC/QXdPBdMHzWylk7YUPvKAsKWR3h1ubmmOUysGhQA1lGBO +XkcfIPbTwiIPvD+akHtRZM1cHCh7NGEY0ZTxaWcsUrkdWwFyBq39nVBsKrzudCZt +HsIhAoGBAMv3erZQawzIgX9nOUHB2UJl8pS+LZSNMlYvFoMHKI2hLq0g/VxUlnsh +Jzw9+fTLMVFdY+F3ydO6qQFd8wlfov7deyscdoSj8R5gjGKJsarBs+YVdFde2oLG +gkFsXmbmc2boyqGg51CbAX34VJOhGQKhWgKCWqDGmoYXafmyiZc+ +-----END RSA PRIVATE KEY----- +` + PemOpenSshRsa1024 = `-----BEGIN OPENSSH PRIVATE KEY----- +b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAlwAAAAdzc2gtcn +NhAAAAAwEAAQAAAIEAzzknaHV741775aJOPacDpd2SiDpIDYmm7/w2sgY8lrinSakfLIVk +1qn0IBRLNOzMxoF/pvIgGQXS51xvE1vB3QK8L+8vJwH06DuOXPP1WgVoDTU03gGvBJ7MNF +5HcQYvBiIaU5XxG8l0OZO88B9RFhPP9r0XrYxAlSjuk9KKlEcAAAIYLQ46zy0OOs8AAAAH +c3NoLXJzYQAAAIEAzzknaHV741775aJOPacDpd2SiDpIDYmm7/w2sgY8lrinSakfLIVk1q +n0IBRLNOzMxoF/pvIgGQXS51xvE1vB3QK8L+8vJwH06DuOXPP1WgVoDTU03gGvBJ7MNF5H +cQYvBiIaU5XxG8l0OZO88B9RFhPP9r0XrYxAlSjuk9KKlEcAAAADAQABAAAAgQDJ9Jq6jF +08P/LhXug/38iHW0UW7S4Ru4jttHGd2MQt5DJtcJzIKA0ZxLL+nKibIPmFsOm2y5yKpolg +IE7EoBVzTeg0LedbRayc0Kc5tY7PEz0Shi9ABIMYbNo2L2pNmsq9ns0xA8ur3OugfKHsH8 +XjJ1rdHsyLjoMx2ADfLY0xkQAAAEAvyrgW4jswENdErbF0rOdP+Y73B/8rxBaY/QBE2qtG +oUp7bpOtUAH2Ip7RjXOX4xTAt4n2QeHBSfX7gfXRjmY6AAAAQQDmYlgSWtTYLV9VZSScLU +OG+GkhQxYqkKN/N9LSpTP4Pwh81KpMp40yvIlufmKLgGihWVxUDzRap3aoR7PqIvHPAAAA +QQDmQ47VwclxiVn5tVAht/Lk2ZVa7rSjeFlXAkAWZkUAiHboaH8IfW9W4gYV7o2BqJO11L +0vi+vCq+le45F416wJAAAAImNocmlzQHBvZXRhc3Rlci5jb3JwLm11dHVhbGluay5uZXQ= + +-----END OPENSSH PRIVATE KEY----- +` + PemOpenSshRsa2048 = `-----BEGIN OPENSSH PRIVATE KEY----- +b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABFwAAAAdzc2gtcn +NhAAAAAwEAAQAAAQEAxWfWNu0i8sbmwPqTUfKSeXOSt/fLMuqucn9KYU7rJ+83trznRhAn +AHQzKgcSU8PBgkax+PDEUexYUB9gZApNI6K/2twVDYh3Hgwx7EjXf05rji7bQk6TFyKEp4 +n348CWAdK8iFmNUutSpJLy7GciyMPLu3BK+EsXpsnuPpIm184hEFOiADZyHTGeUgvsKOAc +G7u5hBS3kty8LRZmL+pihbktFwGC4D5bapCcTaF2++zkUy4JKcVE5/2JfK1Ya6D0ATczjz +1b6+r7j2RUg1mXfK6AwMHEcamzhgeuM9RdrPtMdhZI09LCJzjmXc9pzlGu1HCZzh3rJ3hd +8PVmlAd3VQAAA+A9hesQPYXrEAAAAAdzc2gtcnNhAAABAQDFZ9Y27SLyxubA+pNR8pJ5c5 +K398sy6q5yf0phTusn7ze2vOdGECcAdDMqBxJTw8GCRrH48MRR7FhQH2BkCk0jor/a3BUN +iHceDDHsSNd/TmuOLttCTpMXIoSniffjwJYB0ryIWY1S61KkkvLsZyLIw8u7cEr4Sxemye +4+kibXziEQU6IANnIdMZ5SC+wo4Bwbu7mEFLeS3LwtFmYv6mKFuS0XAYLgPltqkJxNoXb7 +7ORTLgkpxUTn/Yl8rVhroPQBNzOPPVvr6vuPZFSDWZd8roDAwcRxqbOGB64z1F2s+0x2Fk +jT0sInOOZdz2nOUa7UcJnOHesneF3w9WaUB3dVAAAAAwEAAQAAAQEAvA8Z8iWjX6nA9yM/ +6ZevluhVY9E60XzlR8qgL2ehet/YMcxwfzywCyyn+WfXO9mHpfZ3YfLs9Ca2U04w4900c7 +h+EaAMpmHVKNjxTmpucadhq4hT9S0pz6ZgvcMgVuaHgaEjXroBencYuhQMPM5cQurUUfK+ +WSAgnhJNV2qgeoEGgfDZoL1HkItckEZwIzmx4lfMVAuaeqVq5tJNcdv5ukNHpnIYl6fgDp +WGUn/9F8sSHO7P7kGl67IZIsAz+1wW+6pFaVgxbZJ3baPiURtRp+nRSaKLYZSMph6MAiTu +YC8EEVqi3X4m/ZHy+BkphfzR24ouwpt1Vv9QOAPzXXsPwQAAAIEAvmA+yiBdzsJplCifTA +KljE+KpSuvLPRPTb7MGsBO0LIvxrkXOMCXZF4I2VP1zSUH+SDPPc6JeR1Q8liMqPC3Md6c +CIkHfVFBAZL709d0ZtTiir1BipG/l5vIpBnepNX/bWIszIOMzPF2at0WF1lFe6THWujuE8 +Xjp2AJSFZlUjAAAACBAOMxr6FN38VwRC1nrDcZyo4TjWVhAdk4p3AkdNJhFSiKS/x5/yo2 +K1majzcKbrR8+fEPTVWGszAg+AXQdsOq17q+DMenfrBckQ9ZHr3upSZAaGN+keNwge/Kaj +yOvYiKdYFXmAulQZCPQsDNp7e7Z1dTqxi5IlhUgDPzzO0vRGjNAAAAgQDeb0Ulv7fkYAav +tZ+D0LohGjlGFwTeLdwErcVnq9wGyupdeNhlTXZXxRvF+yEt4FCV9UEUnRX75CAnpk2hT2 +D5uYMyixAEfSeIo59Ln27MmAy0alR3UnT7JnLEZRh4dnvFbSSMJ1rHxf8Eg6YFJmpH65fX +exrJE+p69wgRVndoqQAAACJjaHJpc0Bwb2V0YXN0ZXIuY29ycC5tdXR1YWxpbmsubmV0AQ +IDBAUGBw== +-----END OPENSSH PRIVATE KEY----- +` + PemDsa = `-----BEGIN DSA PRIVATE KEY----- +MIIBuwIBAAKBgQDH/T+IkpbdA9nUM7O4MMRoeS0bn7iXWs63Amo2fsIyJPxDvjjF +5HZBH5Rq045TFCCWHjymwiYof+wvwUMZIUH++ABTrKzes/r5qG5jXp42pFWf6nTI +zHwttdjvNiXr+AgreXOrJKhjv6Ga3hq8MNcXMa9xFsIB83EZNMBPxbj0nwIVAJQW +1eR4Uf8/8haQb4HkTsoH+R5/AoGBAK9FV5LIZxY1TeNsD5eXoqpTqCy1WROMggSG +VZ4yN0rrKCtLd8am61m/L8VCMUWiO3IJQdq3yWBTEBbsShL/toau9beUdTl6rdB8 +wcEcNgtZnhypQR58HlmgUFWC45rW37hW4AUJuMDgLxgqSVuoF1pDcHrHSi/fZwgp +7t0MKH2SAoGAJfUcLrXg5ZR8jbpZs4L/ubibUn+y35y+33aos07auMW1MesuNTcZ +Ch42nbH2wKnbjk8eDxHdHLHzzOLGgYVMpUuBeuc7G5Q94rM/Z0I8HGQ6mvIkuFyp +58Unu5yu33GsNUgGEHmriiMGezXNXGNH/72PmTXuyxEMSrad23c6NZoCFAtIqbal +4tGCfnnmWU514A7ZzEKj +-----END DSA PRIVATE KEY----- +` + PemEcdsa384 = `-----BEGIN EC PRIVATE KEY----- +MIGkAgEBBDAjuEIlmFyhGjFtJoAwD420FuPAjIknN3YwDZL4cfMFpB4YAK+7QVLs +coAJ/ADuT7OgBwYFK4EEACKhZANiAASeXKyBr2prr4f4aOsM4dtVikYOUIL3yYnb +GFOy7yHmauCnkIB48paXpvRE5m53Q8zgu7vkz/z9tcMBcC0GzpY3Sef37fmgTUuZ +AJuJp36DMBdQel+j51TcQ79sizxCayg= +-----END EC PRIVATE KEY----- +` + PemEcdsa521 = `-----BEGIN EC PRIVATE KEY----- +MIHcAgEBBEIBVCiwcf/did2vCIu3aMe7OeTD35PULm0hqmfkAK9OKIosi/DjOFfA +8h99rVNPaf+Cx/JNmEzR4bZNnYDyilSRCr+gBwYFK4EEACOhgYkDgYYABABHBMLP +XbQoRF31ZGIeUj9jt9GqKES1dLBtGDEQSiiZFouL4tEIW7NfIZDpOIkA0khNcO8N +xH6eylg0XOgcr01GRwCjY5VOapOahtn63SpajPGeKk+46F2dULIwrov9tWQuYNa3 +P50N8j3rx6fAdgyDENOcCJlfNdNcySvkH4bgL1xcsw== +-----END EC PRIVATE KEY----- +` + PemOpenSshEd25519 = `-----BEGIN OPENSSH PRIVATE KEY----- +b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW +QyNTUxOQAAACAUftPhZQN17kAlThiiWJEgJvddm/pUhHvgrHUtpuYFOQAAAKjN+UhDzflI +QwAAAAtzc2gtZWQyNTUxOQAAACAUftPhZQN17kAlThiiWJEgJvddm/pUhHvgrHUtpuYFOQ +AAAEANXlEZdNU03RMmj77O2ojWh06Hbj8/qQ++H5wkt688NBR+0+FlA3XuQCVOGKJYkSAm +912b+lSEe+CsdS2m5gU5AAAAImNocmlzQHBvZXRhc3Rlci5jb3JwLm11dHVhbGluay5uZX +QBAgM= +-----END OPENSSH PRIVATE KEY----- +` +) + func (o expected) matches(kp KeyPair) error { if o.kind.String() == "" { return errors.New("expected kind's value cannot be empty") @@ -86,18 +216,18 @@ func (o expected) verifyPublicKeyAuthorizedKeysFormat(kp KeyPair) error { switch nl { case NoNewLine: - if publicKeyAk[len(publicKeyAk) - 1] == '\n' { + if publicKeyAk[len(publicKeyAk)-1] == '\n' { return errors.New("public key in authorized keys format has trailing new line when none was specified") } case UnixNewLine: - if publicKeyAk[len(publicKeyAk) - 1] != '\n' { + if publicKeyAk[len(publicKeyAk)-1] != '\n' { return errors.New("public key in authorized keys format does not have unix new line when unix was specified") } - if string(publicKeyAk[len(publicKeyAk) - 2:]) == WindowsNewLine.String() { + if string(publicKeyAk[len(publicKeyAk)-2:]) == WindowsNewLine.String() { return errors.New("public key in authorized keys format has windows new line when unix was specified") } case WindowsNewLine: - if string(publicKeyAk[len(publicKeyAk) - 2:]) != WindowsNewLine.String() { + if string(publicKeyAk[len(publicKeyAk)-2:]) != WindowsNewLine.String() { return errors.New("public key in authorized keys format does not have windows new line when windows was specified") } } @@ -267,3 +397,154 @@ func TestDefaultKeyPairBuilder_Build_NamedRsa(t *testing.T) { t.Fatal(err.Error()) } } + +func TestDefaultKeyPairBuilder_SetPrivateKey(t *testing.T) { + name := uuid.TimeOrderedUUID() + pemData := make(map[string]expected) + pemData[PemRsa1024] = expected{ + bits: 1024, + kind: Rsa, + name: name, + desc: "1024 bit RSA named " + name, + data: []byte(uuid.TimeOrderedUUID()), + } + pemData[PemRsa2048] = expected{ + bits: 2048, + kind: Rsa, + name: name, + desc: "2048 bit RSA named " + name, + data: []byte(uuid.TimeOrderedUUID()), + } + pemData[PemOpenSshRsa1024] = expected{ + bits: 1024, + kind: Rsa, + name: name, + desc: "1024 bit RSA named " + name, + data: []byte(uuid.TimeOrderedUUID()), + } + pemData[PemOpenSshRsa2048] = expected{ + bits: 2048, + kind: Rsa, + name: name, + desc: "2048 bit RSA named " + name, + data: []byte(uuid.TimeOrderedUUID()), + } + pemData[PemDsa] = expected{ + bits: 1024, + kind: Dsa, + name: name, + desc: "1024 bit DSA named " + name, + data: []byte(uuid.TimeOrderedUUID()), + } + pemData[PemEcdsa384] = expected{ + bits: 384, + kind: Ecdsa, + name: name, + desc: "384 bit ECDSA named " + name, + data: []byte(uuid.TimeOrderedUUID()), + } + pemData[PemEcdsa521] = expected{ + bits: 521, + kind: Ecdsa, + name: name, + desc: "521 bit ECDSA named " + name, + data: []byte(uuid.TimeOrderedUUID()), + } + pemData[PemOpenSshEd25519] = expected{ + bits: 256, + kind: Ed25519, + name: name, + desc: "256 bit ED25519 named " + name, + data: []byte(uuid.TimeOrderedUUID()), + } + + for s, l := range pemData { + kp, err := NewKeyPairBuilder().SetPrivateKey([]byte(s)).SetName(name).Build() + if err != nil { + t.Fatal(err) + } + err = l.matches(kp) + if err != nil { + t.Fatal(err) + } + } +} + +func TestDefaultKeyPairBuilder_SetPrivateKey_Override(t *testing.T) { + name := uuid.TimeOrderedUUID() + pemData := make(map[string]expected) + pemData[PemRsa1024] = expected{ + bits: 1024, + kind: Rsa, + name: name, + desc: "1024 bit RSA named " + name, + data: []byte(uuid.TimeOrderedUUID()), + } + pemData[PemRsa2048] = expected{ + bits: 2048, + kind: Rsa, + name: name, + desc: "2048 bit RSA named " + name, + data: []byte(uuid.TimeOrderedUUID()), + } + pemData[PemOpenSshRsa1024] = expected{ + bits: 1024, + kind: Rsa, + name: name, + desc: "1024 bit RSA named " + name, + data: []byte(uuid.TimeOrderedUUID()), + } + pemData[PemOpenSshRsa2048] = expected{ + bits: 2048, + kind: Rsa, + name: name, + desc: "2048 bit RSA named " + name, + data: []byte(uuid.TimeOrderedUUID()), + } + pemData[PemDsa] = expected{ + bits: 1024, + kind: Dsa, + name: name, + desc: "1024 bit DSA named " + name, + data: []byte(uuid.TimeOrderedUUID()), + } + pemData[PemEcdsa384] = expected{ + bits: 384, + kind: Ecdsa, + name: name, + desc: "384 bit ECDSA named " + name, + data: []byte(uuid.TimeOrderedUUID()), + } + pemData[PemEcdsa521] = expected{ + bits: 521, + kind: Ecdsa, + name: name, + desc: "521 bit ECDSA named " + name, + data: []byte(uuid.TimeOrderedUUID()), + } + pemData[PemOpenSshEd25519] = expected{ + bits: 256, + kind: Ed25519, + name: name, + desc: "256 bit ED25519 named " + name, + data: []byte(uuid.TimeOrderedUUID()), + } + + supportedKeyTypes := []KeyPairType{Rsa, Dsa} + for _, kt := range supportedKeyTypes { + for s, l := range pemData { + kp, err := NewKeyPairBuilder(). + SetPrivateKey([]byte(s)). + SetName(name). + SetType(kt). + Build() + if err != nil { + t.Fatal(err) + } + err = l.matches(kp) + if err != nil { + t.Fatal(err) + } + } + } +} From 53c0376dfd4747831950f1757db0f3f3cf6fbe9c Mon Sep 17 00:00:00 2001 From: chris marget Date: Wed, 6 Feb 2019 13:54:05 -0500 Subject: [PATCH 29/49] don't print keypair to the user's screen --- builder/virtualbox/common/step_ssh_key_pair.go | 1 - 1 file changed, 1 deletion(-) diff --git a/builder/virtualbox/common/step_ssh_key_pair.go b/builder/virtualbox/common/step_ssh_key_pair.go index 4e04d9603..167030deb 100644 --- a/builder/virtualbox/common/step_ssh_key_pair.go +++ b/builder/virtualbox/common/step_ssh_key_pair.go @@ -35,7 +35,6 @@ func (s *StepSshKeyPair) Run(_ context.Context, state multistep.StateBag) multis state.Put("error", err) return multistep.ActionHalt } - ui.Say(string(privateKeyBytes)) kp, err := ssh.NewKeyPairBuilder(). SetPrivateKey(privateKeyBytes). From 484aa4768f8830afd49a7f7e0a7f5dec0a663005 Mon Sep 17 00:00:00 2001 From: chris marget Date: Wed, 6 Feb 2019 13:58:26 -0500 Subject: [PATCH 30/49] Remove unused keyheader data type --- helper/ssh/key_pair.go | 4 ---- 1 file changed, 4 deletions(-) diff --git a/helper/ssh/key_pair.go b/helper/ssh/key_pair.go index f1f2c9ea0..f85d17dde 100644 --- a/helper/ssh/key_pair.go +++ b/helper/ssh/key_pair.go @@ -34,10 +34,6 @@ const ( // See the 'const' block for details. type KeyPairType string -// PrivateKeyHeader represents header text in PEM encoded private key files. -// See the 'const' block for details. -type PrivateKeyHeader string - func (o KeyPairType) String() string { return string(o) } From be01ca70f4eeab036f0109eec19a4d2e1d584dc7 Mon Sep 17 00:00:00 2001 From: chris marget Date: Wed, 6 Feb 2019 14:02:26 -0500 Subject: [PATCH 31/49] on error return empty defaultKeyPair{} rather than nil --- helper/ssh/key_pair.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/helper/ssh/key_pair.go b/helper/ssh/key_pair.go index f85d17dde..5c2e388af 100644 --- a/helper/ssh/key_pair.go +++ b/helper/ssh/key_pair.go @@ -139,7 +139,7 @@ func (o *defaultKeyPairBuilder) Build() (KeyPair, error) { return o.newEcdsaKeyPair() } - return nil, fmt.Errorf("Unsupported keypair type: %s", o.kind.String()) + return defaultKeyPair{}, fmt.Errorf("Unsupported keypair type: %s", o.kind.String()) } // preallocatedKeyPair returns an SSH key pair based on user From b476e54db11b84354662d5b509110c6a2a129d26 Mon Sep 17 00:00:00 2001 From: chris marget Date: Wed, 6 Feb 2019 14:34:09 -0500 Subject: [PATCH 32/49] whitespace --- helper/ssh/default_key_pair.go | 2 +- helper/ssh/dsa_key_pair.go | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/helper/ssh/default_key_pair.go b/helper/ssh/default_key_pair.go index 5ea1859c4..e5c68a36d 100644 --- a/helper/ssh/default_key_pair.go +++ b/helper/ssh/default_key_pair.go @@ -4,7 +4,7 @@ type defaultKeyPair struct { } func (o defaultKeyPair) Type() KeyPairType { - return Default + return Default } func (o defaultKeyPair) Bits() int { diff --git a/helper/ssh/dsa_key_pair.go b/helper/ssh/dsa_key_pair.go index 9d20693f5..01f0c7007 100644 --- a/helper/ssh/dsa_key_pair.go +++ b/helper/ssh/dsa_key_pair.go @@ -2,6 +2,7 @@ package ssh import ( "crypto/dsa" + gossh "golang.org/x/crypto/ssh" ) From 75bd3c6951f21bec6811ba7bfa6df775ee804559 Mon Sep 17 00:00:00 2001 From: chris marget Date: Wed, 6 Feb 2019 14:36:38 -0500 Subject: [PATCH 33/49] constants don't need to be [P]ublic --- helper/ssh/key_pair_test.go | 53 +++++++++++++++++++------------------ 1 file changed, 27 insertions(+), 26 deletions(-) diff --git a/helper/ssh/key_pair_test.go b/helper/ssh/key_pair_test.go index 58560dc3d..c9d361ea9 100644 --- a/helper/ssh/key_pair_test.go +++ b/helper/ssh/key_pair_test.go @@ -4,10 +4,11 @@ import ( "bytes" "crypto/rand" "errors" - "github.com/hashicorp/packer/common/uuid" - gossh "golang.org/x/crypto/ssh" "strconv" "testing" + + "github.com/hashicorp/packer/common/uuid" + gossh "golang.org/x/crypto/ssh" ) // expected contains the data that the key pair should contain. @@ -20,7 +21,7 @@ type expected struct { } const ( - PemRsa1024 = `-----BEGIN RSA PRIVATE KEY----- + pemRsa1024 = `-----BEGIN RSA PRIVATE KEY----- MIICWwIBAAKBgQDJEMFPpTBiWNDb3qEIPTSeEnIP8FZdBpG8njOrclcMoQQNhzZ+ 4uz37tqtHMp36Z7LB4/+85NN6epNXO+ekyZIHswiyBcJC2sT3KuH7nG1BESOooPY DfeCSM+CJT9GDIhy9nUXSsJjrceEyh/B5DjEtIbS0XfcRelrNTJodCmPJwIDAQAB @@ -36,7 +37,7 @@ Sjvtd6NkMc2oKInwIQJAFZ1xJte0EaQsXaCIoZwHrQJbK1dd5l1xTAzz51voAcKH 2K23xgx4I+/eam2enjFa7wXLZFoW0xg/51xsaIjnrA== -----END RSA PRIVATE KEY----- ` - PemRsa2048 = `-----BEGIN RSA PRIVATE KEY----- + pemRsa2048 = `-----BEGIN RSA PRIVATE KEY----- MIIEowIBAAKCAQEA/ZPazeRmBapF01gzHXtJGpu0S936xHY+pOrIyIk6lEE06paf q5gh6BCuiN/60Keed5Nz+Es4dPGc73mql9pd7N0HOoEc1IQjZzJVqWOy3E55oWbz rXr1qbmMjw8bGHalZsVBov1UhyB6f2bKi88fGkThJi9HZ+Dc3Jr87eW+whS4D0bI @@ -64,7 +65,7 @@ Jzw9+fTLMVFdY+F3ydO6qQFd8wlfov7deyscdoSj8R5gjGKJsarBs+YVdFde2oLG gkFsXmbmc2boyqGg51CbAX34VJOhGQKhWgKCWqDGmoYXafmyiZc+ -----END RSA PRIVATE KEY----- ` - PemOpenSshRsa1024 = `-----BEGIN OPENSSH PRIVATE KEY----- + pemOpenSshRsa1024 = `-----BEGIN OPENSSH PRIVATE KEY----- b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAlwAAAAdzc2gtcn NhAAAAAwEAAQAAAIEAzzknaHV741775aJOPacDpd2SiDpIDYmm7/w2sgY8lrinSakfLIVk 1qn0IBRLNOzMxoF/pvIgGQXS51xvE1vB3QK8L+8vJwH06DuOXPP1WgVoDTU03gGvBJ7MNF @@ -82,7 +83,7 @@ QQDmQ47VwclxiVn5tVAht/Lk2ZVa7rSjeFlXAkAWZkUAiHboaH8IfW9W4gYV7o2BqJO11L -----END OPENSSH PRIVATE KEY----- ` - PemOpenSshRsa2048 = `-----BEGIN OPENSSH PRIVATE KEY----- + pemOpenSshRsa2048 = `-----BEGIN OPENSSH PRIVATE KEY----- b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABFwAAAAdzc2gtcn NhAAAAAwEAAQAAAQEAxWfWNu0i8sbmwPqTUfKSeXOSt/fLMuqucn9KYU7rJ+83trznRhAn AHQzKgcSU8PBgkax+PDEUexYUB9gZApNI6K/2twVDYh3Hgwx7EjXf05rji7bQk6TFyKEp4 @@ -111,7 +112,7 @@ exrJE+p69wgRVndoqQAAACJjaHJpc0Bwb2V0YXN0ZXIuY29ycC5tdXR1YWxpbmsubmV0AQ IDBAUGBw== -----END OPENSSH PRIVATE KEY----- ` - PemDsa = `-----BEGIN DSA PRIVATE KEY----- + pemDsa = `-----BEGIN DSA PRIVATE KEY----- MIIBuwIBAAKBgQDH/T+IkpbdA9nUM7O4MMRoeS0bn7iXWs63Amo2fsIyJPxDvjjF 5HZBH5Rq045TFCCWHjymwiYof+wvwUMZIUH++ABTrKzes/r5qG5jXp42pFWf6nTI zHwttdjvNiXr+AgreXOrJKhjv6Ga3hq8MNcXMa9xFsIB83EZNMBPxbj0nwIVAJQW @@ -124,14 +125,14 @@ Ch42nbH2wKnbjk8eDxHdHLHzzOLGgYVMpUuBeuc7G5Q94rM/Z0I8HGQ6mvIkuFyp 4tGCfnnmWU514A7ZzEKj -----END DSA PRIVATE KEY----- ` - PemEcdsa384 = `-----BEGIN EC PRIVATE KEY----- + pemEcdsa384 = `-----BEGIN EC PRIVATE KEY----- MIGkAgEBBDAjuEIlmFyhGjFtJoAwD420FuPAjIknN3YwDZL4cfMFpB4YAK+7QVLs coAJ/ADuT7OgBwYFK4EEACKhZANiAASeXKyBr2prr4f4aOsM4dtVikYOUIL3yYnb GFOy7yHmauCnkIB48paXpvRE5m53Q8zgu7vkz/z9tcMBcC0GzpY3Sef37fmgTUuZ AJuJp36DMBdQel+j51TcQ79sizxCayg= -----END EC PRIVATE KEY----- ` - PemEcdsa521 = `-----BEGIN EC PRIVATE KEY----- + pemEcdsa521 = `-----BEGIN EC PRIVATE KEY----- MIHcAgEBBEIBVCiwcf/did2vCIu3aMe7OeTD35PULm0hqmfkAK9OKIosi/DjOFfA 8h99rVNPaf+Cx/JNmEzR4bZNnYDyilSRCr+gBwYFK4EEACOhgYkDgYYABABHBMLP XbQoRF31ZGIeUj9jt9GqKES1dLBtGDEQSiiZFouL4tEIW7NfIZDpOIkA0khNcO8N @@ -139,7 +140,7 @@ xH6eylg0XOgcr01GRwCjY5VOapOahtn63SpajPGeKk+46F2dULIwrov9tWQuYNa3 P50N8j3rx6fAdgyDENOcCJlfNdNcySvkH4bgL1xcsw== -----END EC PRIVATE KEY----- ` - PemOpenSshEd25519 = `-----BEGIN OPENSSH PRIVATE KEY----- + pemOpenSshEd25519 = `-----BEGIN OPENSSH PRIVATE KEY----- b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW QyNTUxOQAAACAUftPhZQN17kAlThiiWJEgJvddm/pUhHvgrHUtpuYFOQAAAKjN+UhDzflI QwAAAAtzc2gtZWQyNTUxOQAAACAUftPhZQN17kAlThiiWJEgJvddm/pUhHvgrHUtpuYFOQ @@ -401,56 +402,56 @@ func TestDefaultKeyPairBuilder_Build_NamedRsa(t *testing.T) { func TestDefaultKeyPairBuilder_SetPrivateKey(t *testing.T) { name := uuid.TimeOrderedUUID() pemData := make(map[string]expected) - pemData[PemRsa1024] = expected{ + pemData[pemRsa1024] = expected{ bits: 1024, kind: Rsa, name: name, desc: "1024 bit RSA named " + name, data: []byte(uuid.TimeOrderedUUID()), } - pemData[PemRsa2048] = expected{ + pemData[pemRsa2048] = expected{ bits: 2048, kind: Rsa, name: name, desc: "2048 bit RSA named " + name, data: []byte(uuid.TimeOrderedUUID()), } - pemData[PemOpenSshRsa1024] = expected{ + pemData[pemOpenSshRsa1024] = expected{ bits: 1024, kind: Rsa, name: name, desc: "1024 bit RSA named " + name, data: []byte(uuid.TimeOrderedUUID()), } - pemData[PemOpenSshRsa2048] = expected{ + pemData[pemOpenSshRsa2048] = expected{ bits: 2048, kind: Rsa, name: name, desc: "2048 bit RSA named " + name, data: []byte(uuid.TimeOrderedUUID()), } - pemData[PemDsa] = expected{ + pemData[pemDsa] = expected{ bits: 1024, kind: Dsa, name: name, desc: "1024 bit DSA named " + name, data: []byte(uuid.TimeOrderedUUID()), } - pemData[PemEcdsa384] = expected{ + pemData[pemEcdsa384] = expected{ bits: 384, kind: Ecdsa, name: name, desc: "384 bit ECDSA named " + name, data: []byte(uuid.TimeOrderedUUID()), } - pemData[PemEcdsa521] = expected{ + pemData[pemEcdsa521] = expected{ bits: 521, kind: Ecdsa, name: name, desc: "521 bit ECDSA named " + name, data: []byte(uuid.TimeOrderedUUID()), } - pemData[PemOpenSshEd25519] = expected{ + pemData[pemOpenSshEd25519] = expected{ bits: 256, kind: Ed25519, name: name, @@ -473,56 +474,56 @@ func TestDefaultKeyPairBuilder_SetPrivateKey(t *testing.T) { func TestDefaultKeyPairBuilder_SetPrivateKey_Override(t *testing.T) { name := uuid.TimeOrderedUUID() pemData := make(map[string]expected) - pemData[PemRsa1024] = expected{ + pemData[pemRsa1024] = expected{ bits: 1024, kind: Rsa, name: name, desc: "1024 bit RSA named " + name, data: []byte(uuid.TimeOrderedUUID()), } - pemData[PemRsa2048] = expected{ + pemData[pemRsa2048] = expected{ bits: 2048, kind: Rsa, name: name, desc: "2048 bit RSA named " + name, data: []byte(uuid.TimeOrderedUUID()), } - pemData[PemOpenSshRsa1024] = expected{ + pemData[pemOpenSshRsa1024] = expected{ bits: 1024, kind: Rsa, name: name, desc: "1024 bit RSA named " + name, data: []byte(uuid.TimeOrderedUUID()), } - pemData[PemOpenSshRsa2048] = expected{ + pemData[pemOpenSshRsa2048] = expected{ bits: 2048, kind: Rsa, name: name, desc: "2048 bit RSA named " + name, data: []byte(uuid.TimeOrderedUUID()), } - pemData[PemDsa] = expected{ + pemData[pemDsa] = expected{ bits: 1024, kind: Dsa, name: name, desc: "1024 bit DSA named " + name, data: []byte(uuid.TimeOrderedUUID()), } - pemData[PemEcdsa384] = expected{ + pemData[pemEcdsa384] = expected{ bits: 384, kind: Ecdsa, name: name, desc: "384 bit ECDSA named " + name, data: []byte(uuid.TimeOrderedUUID()), } - pemData[PemEcdsa521] = expected{ + pemData[pemEcdsa521] = expected{ bits: 521, kind: Ecdsa, name: name, desc: "521 bit ECDSA named " + name, data: []byte(uuid.TimeOrderedUUID()), } - pemData[PemOpenSshEd25519] = expected{ + pemData[pemOpenSshEd25519] = expected{ bits: 256, kind: Ed25519, name: name, From 3515afa994cb01ebcc2b857be76dc4088837df19 Mon Sep 17 00:00:00 2001 From: chris marget Date: Wed, 6 Feb 2019 14:39:39 -0500 Subject: [PATCH 34/49] better variable names in test function --- helper/ssh/key_pair_test.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/helper/ssh/key_pair_test.go b/helper/ssh/key_pair_test.go index c9d361ea9..5a6e23b6c 100644 --- a/helper/ssh/key_pair_test.go +++ b/helper/ssh/key_pair_test.go @@ -532,17 +532,17 @@ func TestDefaultKeyPairBuilder_SetPrivateKey_Override(t *testing.T) { } supportedKeyTypes := []KeyPairType{Rsa, Dsa} - for _, kt := range supportedKeyTypes { - for s, l := range pemData { + for _, keyType := range supportedKeyTypes { + for pemString, expectedResult := range pemData { kp, err := NewKeyPairBuilder(). - SetPrivateKey([]byte(s)). + SetPrivateKey([]byte(pemString)). SetName(name). - SetType(kt). + SetType(keyType). Build() if err != nil { t.Fatal(err) } - err = l.matches(kp) + err = expectedResult.matches(kp) if err != nil { t.Fatal(err) } From 97268b30416e4c63594135ada815d857bdc30be8 Mon Sep 17 00:00:00 2001 From: chris marget Date: Wed, 6 Feb 2019 15:05:56 -0500 Subject: [PATCH 35/49] . --- helper/ssh/key_pair.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/helper/ssh/key_pair.go b/helper/ssh/key_pair.go index 5c2e388af..3dd0b5fdf 100644 --- a/helper/ssh/key_pair.go +++ b/helper/ssh/key_pair.go @@ -22,7 +22,7 @@ const ( // That's a lot of bits. defaultRsaBits = 4096 - // Markers for various SSH key pair types + // Markers for various SSH key pair types. Default KeyPairType = "" Rsa KeyPairType = "RSA" Ecdsa KeyPairType = "ECDSA" From 0378f3ad354f7baacd404c96299b4f4cdd1de825 Mon Sep 17 00:00:00 2001 From: Stephen Fox Date: Wed, 6 Feb 2019 16:03:40 -0500 Subject: [PATCH 36/49] Initial documentation for VirtualBox SSH key pair vars. --- .../docs/builders/virtualbox-iso.html.md.erb | 2 + .../docs/builders/virtualbox-ovf.html.md.erb | 2 + .../builders/_virtualbox-ssh-key-pair.html.md | 62 +++++++++++++++++++ 3 files changed, 66 insertions(+) create mode 100644 website/source/partials/builders/_virtualbox-ssh-key-pair.html.md diff --git a/website/source/docs/builders/virtualbox-iso.html.md.erb b/website/source/docs/builders/virtualbox-iso.html.md.erb index 4337cb7c8..61bf0cdee 100644 --- a/website/source/docs/builders/virtualbox-iso.html.md.erb +++ b/website/source/docs/builders/virtualbox-iso.html.md.erb @@ -375,6 +375,8 @@ contention. If you notice missing keys, you can tune this delay by specifying <%= partial "partials/builders/boot-command" %> +<%= partial "partials/builders/virtualbox-ssh-key-pair" %> + Example boot command. This is actually a working boot command used to start an Ubuntu 12.04 installer: diff --git a/website/source/docs/builders/virtualbox-ovf.html.md.erb b/website/source/docs/builders/virtualbox-ovf.html.md.erb index d3ca4d7ae..e22f677d8 100644 --- a/website/source/docs/builders/virtualbox-ovf.html.md.erb +++ b/website/source/docs/builders/virtualbox-ovf.html.md.erb @@ -317,6 +317,8 @@ contention. If you notice missing keys, you can tune this delay by specifying <%= partial "partials/builders/boot-command" %> +<%= partial "partials/builders/virtualbox-ssh-key-pair" %> + Example boot command. This is actually a working boot command used to start an Ubuntu 12.04 installer: diff --git a/website/source/partials/builders/_virtualbox-ssh-key-pair.html.md b/website/source/partials/builders/_virtualbox-ssh-key-pair.html.md new file mode 100644 index 000000000..f1f8c189c --- /dev/null +++ b/website/source/partials/builders/_virtualbox-ssh-key-pair.html.md @@ -0,0 +1,62 @@ +### SSH key pair automation + +The VirtualBox builders can inject the current SSH key pair's public key into +the template using the following variables: + +- `SSHPublicKey` (*VirtualBox builders only*) - The SSH public key as a line + in OpenSSH authorized_keys format. +- `EncodedSSHPublicKey` (*VirtualBox builders only*) - The same as + `SSHPublicKey`, except it is URL encoded for usage in places + like the kernel command line. + +When a private key is provided using `ssh_private_key_file`, the key's +corresponding public key can be accessed using the above variables. + +If `ssh_password` and `ssh_private_key_file` are not specified, Packer will +automatically generate en ephemeral key pair. The key pair's public key can +be accessed using the template variables. + +For example, the public key can be provided in the boot command: +```json +{ + "type": "virtualbox-iso", + "boot_command": [ + " text ks=http://{{ .HTTPIP }}:{{ .HTTPPort }}/ks.cfg PACKER_USER={{ user `username` }} PACKER_AUTHORIZED_KEY={{ .EncodedSSHPublicKey }}" + ] +} +``` + +The kickstart can then leverage those fields from the kernel command line: +``` +%post + +# Newly created users need the file/folder framework for SSH key authentication. +umask 0077 +mkdir /etc/skel/.ssh +touch /etc/skel/.ssh/authorized_keys + +# Loop over the command line. Set interesting variables. +for x in $(cat /proc/cmdline) +do + case $x in + PACKER_USER=*) + PACKER_USER="${x#*=}" + ;; + PACKER_AUTHORIZED_KEY=*) + encoded="${x#*=}" + # URL decode $encoded into $PACKER_AUTHORIZED_KEY + printf -v PACKER_AUTHORIZED_KEY '%b' "${encoded//%/\\x}" + ;; + esac +done + +# Create/configure packer user, if any. +if [ -n "$PACKER_USER" ] +then + useradd $PACKER_USER + echo "%$PACKER_USER ALL=(ALL) NOPASSWD: ALL" >> /etc/sudoers.d/$PACKER_USER + [ -n "$PACKER_AUTHORIZED_KEY" ] && echo $PACKER_AUTHORIZED_KEY >> $(eval echo ~"$PACKER_USER")/.ssh/authorized_keys +fi + +%end +``` From ed1d224b973be76607aaf53d31b213010df5e6e9 Mon Sep 17 00:00:00 2001 From: Stephen Fox Date: Thu, 7 Feb 2019 11:09:53 -0500 Subject: [PATCH 37/49] Remove defaultKeyPair type. Be more specific in errors. --- helper/ssh/default_key_pair.go | 28 ---------------------------- helper/ssh/key_pair.go | 4 ++-- 2 files changed, 2 insertions(+), 30 deletions(-) delete mode 100644 helper/ssh/default_key_pair.go diff --git a/helper/ssh/default_key_pair.go b/helper/ssh/default_key_pair.go deleted file mode 100644 index e5c68a36d..000000000 --- a/helper/ssh/default_key_pair.go +++ /dev/null @@ -1,28 +0,0 @@ -package ssh - -type defaultKeyPair struct { -} - -func (o defaultKeyPair) Type() KeyPairType { - return Default -} - -func (o defaultKeyPair) Bits() int { - return 0 -} - -func (o defaultKeyPair) Name() string { - return "" -} - -func (o defaultKeyPair) Description() string { - return "" -} - -func (o defaultKeyPair) PrivateKeyPemBlock() []byte { - return []byte{} -} - -func (o defaultKeyPair) PublicKeyAuthorizedKeysLine(nl NewLineOption) []byte { - return []byte{} -} diff --git a/helper/ssh/key_pair.go b/helper/ssh/key_pair.go index 3dd0b5fdf..f46455ebf 100644 --- a/helper/ssh/key_pair.go +++ b/helper/ssh/key_pair.go @@ -139,7 +139,7 @@ func (o *defaultKeyPairBuilder) Build() (KeyPair, error) { return o.newEcdsaKeyPair() } - return defaultKeyPair{}, fmt.Errorf("Unsupported keypair type: %s", o.kind.String()) + return nil, fmt.Errorf("Cannot generate SSH key pair - unsupported key pair type: %s", o.kind.String()) } // preallocatedKeyPair returns an SSH key pair based on user @@ -197,7 +197,7 @@ func (o *defaultKeyPairBuilder) preallocatedKeyPair() (KeyPair, error) { }, nil } - return &defaultKeyPair{}, fmt.Errorf("Unknown ssh key pair type") + return nil, fmt.Errorf("Cannot parse preallocated key pair - unknown ssh key pair type") } // newEcdsaKeyPair returns a new ECDSA SSH key pair. From 2ab2ea6ef131d3533f94a09759e8d5289a35c3e0 Mon Sep 17 00:00:00 2001 From: Stephen Fox Date: Tue, 12 Feb 2019 10:29:43 -0500 Subject: [PATCH 38/49] Fixed interface documentation typo. --- helper/ssh/key_pair.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/helper/ssh/key_pair.go b/helper/ssh/key_pair.go index f46455ebf..60c2c22e5 100644 --- a/helper/ssh/key_pair.go +++ b/helper/ssh/key_pair.go @@ -297,7 +297,7 @@ type KeyPair interface { Bits() int // Name returns the key pair's name. An empty string is - // returned is no name was specified. + // returned if no name was specified. Name() string // Description returns a brief description of the key pair that From 2ef6b9247ddfcd17762fd9eac0e52bbedb2981ab Mon Sep 17 00:00:00 2001 From: Stephen Fox Date: Mon, 25 Feb 2019 17:16:24 -0500 Subject: [PATCH 39/49] Initial take on code review feedback from @azr. Do not use builder pattern or interfaces; stick to structs and some basic functions. --- .../virtualbox/common/step_ssh_key_pair.go | 32 +- helper/ssh/dsa_key_pair.go | 38 -- helper/ssh/ecdsa_key_pair.go | 38 -- helper/ssh/ed25519_key_pair.go | 38 -- helper/ssh/key_pair.go | 323 ++++------ helper/ssh/key_pair_test.go | 568 +++++++----------- helper/ssh/rsa_key_pair.go | 38 -- 7 files changed, 357 insertions(+), 718 deletions(-) delete mode 100644 helper/ssh/dsa_key_pair.go delete mode 100644 helper/ssh/ecdsa_key_pair.go delete mode 100644 helper/ssh/ed25519_key_pair.go delete mode 100644 helper/ssh/rsa_key_pair.go diff --git a/builder/virtualbox/common/step_ssh_key_pair.go b/builder/virtualbox/common/step_ssh_key_pair.go index 167030deb..20de4655b 100644 --- a/builder/virtualbox/common/step_ssh_key_pair.go +++ b/builder/virtualbox/common/step_ssh_key_pair.go @@ -36,19 +36,19 @@ func (s *StepSshKeyPair) Run(_ context.Context, state multistep.StateBag) multis return multistep.ActionHalt } - kp, err := ssh.NewKeyPairBuilder(). - SetPrivateKey(privateKeyBytes). - SetName(fmt.Sprintf("packer_%s", uuid.TimeOrderedUUID())). - Build() + kp, err := ssh.KeyPairFromPrivateKey(ssh.FromPrivateKeyConfig{ + RawPrivateKeyPemBlock: privateKeyBytes, + Name: fmt.Sprintf("packer_%s", uuid.TimeOrderedUUID()), + }) if err != nil { state.Put("error", err) return multistep.ActionHalt } s.Comm.SSHPrivateKey = privateKeyBytes - s.Comm.SSHKeyPairName = kp.Name() - s.Comm.SSHTemporaryKeyPairName = kp.Name() - s.Comm.SSHPublicKey = kp.PublicKeyAuthorizedKeysLine(ssh.UnixNewLine) + s.Comm.SSHKeyPairName = kp.Name + s.Comm.SSHTemporaryKeyPairName = kp.Name + s.Comm.SSHPublicKey = kp.PublicKeyAuthorizedKeysLine return multistep.ActionContinue } @@ -60,21 +60,21 @@ func (s *StepSshKeyPair) Run(_ context.Context, state multistep.StateBag) multis ui.Say("Creating ephemeral key pair for SSH communicator...") - kp, err := ssh.NewKeyPairBuilder(). - SetName(fmt.Sprintf("packer_%s", uuid.TimeOrderedUUID())). - Build() + kp, err := ssh.NewKeyPair(ssh.CreateKeyPairConfig{ + Name: fmt.Sprintf("packer_%s", uuid.TimeOrderedUUID()), + }) if err != nil { state.Put("error", fmt.Errorf("Error creating temporary keypair: %s", err)) return multistep.ActionHalt } - s.Comm.SSHKeyPairName = kp.Name() - s.Comm.SSHTemporaryKeyPairName = kp.Name() - s.Comm.SSHPrivateKey = kp.PrivateKeyPemBlock() - s.Comm.SSHPublicKey = kp.PublicKeyAuthorizedKeysLine(ssh.UnixNewLine) + s.Comm.SSHKeyPairName = kp.Name + s.Comm.SSHTemporaryKeyPairName = kp.Name + s.Comm.SSHPrivateKey = kp.PrivateKeyPemBlock + s.Comm.SSHPublicKey = kp.PublicKeyAuthorizedKeysLine s.Comm.SSHClearAuthorizedKeys = true - ui.Say(fmt.Sprintf("Created ephemeral SSH key pair of type %s", kp.Description())) + ui.Say("Created ephemeral SSH key pair for communicator") // If we're in debug mode, output the private key to the working // directory. @@ -90,7 +90,7 @@ func (s *StepSshKeyPair) Run(_ context.Context, state multistep.StateBag) multis defer f.Close() // Write the key out - if _, err := f.Write(kp.PrivateKeyPemBlock()); err != nil { + if _, err := f.Write(kp.PrivateKeyPemBlock); err != nil { state.Put("error", fmt.Errorf("Error saving debug key: %s", err)) return multistep.ActionHalt } diff --git a/helper/ssh/dsa_key_pair.go b/helper/ssh/dsa_key_pair.go deleted file mode 100644 index 01f0c7007..000000000 --- a/helper/ssh/dsa_key_pair.go +++ /dev/null @@ -1,38 +0,0 @@ -package ssh - -import ( - "crypto/dsa" - - gossh "golang.org/x/crypto/ssh" -) - -type dsaKeyPair struct { - privateKey *dsa.PrivateKey - publicKey gossh.PublicKey - name string - privatePemBlock []byte -} - -func (o dsaKeyPair) Type() KeyPairType { - return Dsa -} - -func (o dsaKeyPair) Bits() int { - return 1024 -} - -func (o dsaKeyPair) Name() string { - return o.name -} - -func (o dsaKeyPair) Description() string { - return description(o) -} - -func (o dsaKeyPair) PrivateKeyPemBlock() []byte { - return o.privatePemBlock -} - -func (o dsaKeyPair) PublicKeyAuthorizedKeysLine(nl NewLineOption) []byte { - return publicKeyAuthorizedKeysLine(o.publicKey, o.name, nl) -} diff --git a/helper/ssh/ecdsa_key_pair.go b/helper/ssh/ecdsa_key_pair.go deleted file mode 100644 index 9f3dde341..000000000 --- a/helper/ssh/ecdsa_key_pair.go +++ /dev/null @@ -1,38 +0,0 @@ -package ssh - -import ( - "crypto/ecdsa" - - gossh "golang.org/x/crypto/ssh" -) - -type ecdsaKeyPair struct { - privateKey *ecdsa.PrivateKey - publicKey gossh.PublicKey - name string - privatePemBlock []byte -} - -func (o ecdsaKeyPair) Type() KeyPairType { - return Ecdsa -} - -func (o ecdsaKeyPair) Bits() int { - return o.privateKey.Curve.Params().BitSize -} - -func (o ecdsaKeyPair) Name() string { - return o.name -} - -func (o ecdsaKeyPair) Description() string { - return description(o) -} - -func (o ecdsaKeyPair) PrivateKeyPemBlock() []byte { - return o.privatePemBlock -} - -func (o ecdsaKeyPair) PublicKeyAuthorizedKeysLine(nl NewLineOption) []byte { - return publicKeyAuthorizedKeysLine(o.publicKey, o.name, nl) -} diff --git a/helper/ssh/ed25519_key_pair.go b/helper/ssh/ed25519_key_pair.go deleted file mode 100644 index 91a0a3654..000000000 --- a/helper/ssh/ed25519_key_pair.go +++ /dev/null @@ -1,38 +0,0 @@ -package ssh - -import ( - "golang.org/x/crypto/ed25519" - - gossh "golang.org/x/crypto/ssh" -) - -type ed25519KeyPair struct { - privateKey *ed25519.PrivateKey - publicKey gossh.PublicKey - name string - privatePemBlock []byte -} - -func (o ed25519KeyPair) Type() KeyPairType { - return Ed25519 -} - -func (o ed25519KeyPair) Bits() int { - return 256 -} - -func (o ed25519KeyPair) Name() string { - return o.name -} - -func (o ed25519KeyPair) Description() string { - return description(o) -} - -func (o ed25519KeyPair) PrivateKeyPemBlock() []byte { - return o.privatePemBlock -} - -func (o ed25519KeyPair) PublicKeyAuthorizedKeysLine(nl NewLineOption) []byte { - return publicKeyAuthorizedKeysLine(o.publicKey, o.name, nl) -} diff --git a/helper/ssh/key_pair.go b/helper/ssh/key_pair.go index 60c2c22e5..11f6b6386 100644 --- a/helper/ssh/key_pair.go +++ b/helper/ssh/key_pair.go @@ -9,9 +9,7 @@ import ( "crypto/rsa" "crypto/x509" "encoding/pem" - "errors" "fmt" - "strconv" "strings" "golang.org/x/crypto/ed25519" @@ -38,175 +36,130 @@ func (o KeyPairType) String() string { return string(o) } -const ( - // UnixNewLine is a unix new line. - UnixNewLine NewLineOption = "\n" - - // WindowsNewLine is a Windows new line. - WindowsNewLine NewLineOption = "\r\n" - - // NoNewLine will not append a new line. - NoNewLine NewLineOption = "" -) +// CreateKeyPairConfig describes how an SSH key pair should be created. +type CreateKeyPairConfig struct { + // Type describes the key pair's type. + Type KeyPairType -// NewLineOption specifies the type of new line to append to a string. -// See the 'const' block for choices. -type NewLineOption string - -func (o NewLineOption) String() string { - return string(o) -} + // Bits represents the key pair's bits of entropy. E.g., 4096 for + // a 4096 bit RSA key pair, or 521 for a ECDSA key pair with a + // 521-bit curve. + Bits int -func (o NewLineOption) Bytes() []byte { - return []byte(o) + // Name is the resulting key pair's name. This is used to identify + // the key pair in the SSH server's 'authorized_keys'. + Name string } -// KeyPairBuilder builds SSH key pairs. -// It can generate new keys of type RSA and ECDSA. -// It can parse user supplied keys of type DSA, RSA, ECDSA, -// and ED25519. -type KeyPairBuilder interface { - // SetType sets the key pair type. - SetType(KeyPairType) KeyPairBuilder - - // SetBits sets the key pair's bits of entropy. - SetBits(int) KeyPairBuilder - - // SetName sets the name of the key pair. This is primarily - // used to identify the public key in the authorized_keys file. - SetName(string) KeyPairBuilder - - // SetPrivateKey takes an existing private key in PEM format. - // It overrides key generation details specified by SetType() - // and SetBits(). - SetPrivateKey([]byte) KeyPairBuilder - - // Build returns a SSH key pair. - // - // The following defaults are used if not specified: - // Default type: ECDSA - // Default bits of entropy: - // - RSA: 4096 - // - ECDSA: 521 - // Default name: (empty string) - Build() (KeyPair, error) -} - -type defaultKeyPairBuilder struct { - // kind describes the resulting key pair's type. - kind KeyPairType - - // bits is the resulting key pair's bits of entropy. - bits int - - // name is the resulting key pair's name. - name string - - // privatePemBytes is the supplied key data when the builder - // is working from a preallocated key. - privatePemBytes []byte -} +// FromPrivateKeyConfig describes how an SSH key pair should be loaded from an +// existing private key. +type FromPrivateKeyConfig struct { + // RawPrivateKeyPemBlock is the raw private key that the key pair + // should be loaded from. + RawPrivateKeyPemBlock []byte -func (o *defaultKeyPairBuilder) SetType(kind KeyPairType) KeyPairBuilder { - o.kind = kind - return o + // Name is the resulting key pair's name. This is used to identify + // the key pair in the SSH server's 'authorized_keys'. + Name string } -func (o *defaultKeyPairBuilder) SetBits(bits int) KeyPairBuilder { - o.bits = bits - return o +// KeyPair represents an SSH key pair. +// TODO: Maybe a field for a description? Maybe save the type? +type KeyPair struct { + // PrivateKeyPemBlock represents the key pair's private key in + // ASN.1 Distinguished Encoding Rules (DER) format in a + // Privacy-Enhanced Mail (PEM) block. + PrivateKeyPemBlock []byte + + // PublicKeyAuthorizedKeysLine represents the key pair's public key + // as a line in OpenSSH authorized_keys. + PublicKeyAuthorizedKeysLine []byte + + // Name is the key pair's name. This is used to identify + // the key pair in the SSH server's 'authorized_keys'. + Name string } -func (o *defaultKeyPairBuilder) SetName(name string) KeyPairBuilder { - o.name = name - return o -} - -func (o *defaultKeyPairBuilder) SetPrivateKey(privateBytes []byte) KeyPairBuilder { - o.privatePemBytes = privateBytes - return o -} - -func (o *defaultKeyPairBuilder) Build() (KeyPair, error) { - if o.privatePemBytes != nil { - return o.preallocatedKeyPair() - } - - switch o.kind { - case Rsa: - return o.newRsaKeyPair() - case Ecdsa, Default: - return o.newEcdsaKeyPair() - } - - return nil, fmt.Errorf("Cannot generate SSH key pair - unsupported key pair type: %s", o.kind.String()) -} - -// preallocatedKeyPair returns an SSH key pair based on user -// supplied PEM data. -func (o *defaultKeyPairBuilder) preallocatedKeyPair() (KeyPair, error) { - privateKey, err := gossh.ParseRawPrivateKey(o.privatePemBytes) +// KeyPairFromPrivateKey returns a KeyPair loaded from an existing private key. +// +// Supported key pair types include: +// - DSA +// - ECDSA +// - ED25519 +// - RSA +func KeyPairFromPrivateKey(config FromPrivateKeyConfig) (KeyPair, error) { + privateKey, err := gossh.ParseRawPrivateKey(config.RawPrivateKeyPemBlock) if err != nil { - return nil, err + return KeyPair{}, err } switch pk := privateKey.(type) { case *rsa.PrivateKey: publicKey, err := gossh.NewPublicKey(&pk.PublicKey) if err != nil { - return nil, err + return KeyPair{}, err } - return &rsaKeyPair{ - privateKey: pk, - publicKey: publicKey, - name: o.name, - privatePemBlock: o.privatePemBytes, + return KeyPair{ + PrivateKeyPemBlock: config.RawPrivateKeyPemBlock, + PublicKeyAuthorizedKeysLine: authorizedKeysLine(publicKey, config.Name), }, nil case *ecdsa.PrivateKey: publicKey, err := gossh.NewPublicKey(&pk.PublicKey) if err != nil { - return nil, err + return KeyPair{}, err } - return &ecdsaKeyPair{ - privateKey: pk, - publicKey: publicKey, - name: o.name, - privatePemBlock: o.privatePemBytes, + return KeyPair{ + PrivateKeyPemBlock: config.RawPrivateKeyPemBlock, + PublicKeyAuthorizedKeysLine: authorizedKeysLine(publicKey, config.Name), }, nil case *dsa.PrivateKey: publicKey, err := gossh.NewPublicKey(&pk.PublicKey) if err != nil { - return nil, err + return KeyPair{}, err } - return &dsaKeyPair{ - privateKey: pk, - publicKey: publicKey, - name: o.name, - privatePemBlock: o.privatePemBytes, + return KeyPair{ + PrivateKeyPemBlock: config.RawPrivateKeyPemBlock, + PublicKeyAuthorizedKeysLine: authorizedKeysLine(publicKey, config.Name), }, nil case *ed25519.PrivateKey: publicKey, err := gossh.NewPublicKey(pk.Public()) if err != nil { - return nil, err + return KeyPair{}, err } - return &ed25519KeyPair{ - privateKey: pk, - publicKey: publicKey, - name: o.name, - privatePemBlock: o.privatePemBytes, + return KeyPair{ + PrivateKeyPemBlock: config.RawPrivateKeyPemBlock, + PublicKeyAuthorizedKeysLine: authorizedKeysLine(publicKey, config.Name), }, nil } - return nil, fmt.Errorf("Cannot parse preallocated key pair - unknown ssh key pair type") + return KeyPair{}, fmt.Errorf("Cannot parse existing SSH key pair - unknown key pair type") +} + +// NewKeyPair generates a new SSH key pair using the specified +// CreateKeyPairConfig. +func NewKeyPair(config CreateKeyPairConfig) (KeyPair, error) { + if config.Type == Default { + config.Type = Ecdsa + } + + switch config.Type { + case Ecdsa: + return newEcdsaKeyPair(config) + case Rsa: + return newRsaKeyPair(config) + } + + return KeyPair{}, fmt.Errorf("Unable to generate new key pair, type %s is not supported", + config.Type.String()) } // newEcdsaKeyPair returns a new ECDSA SSH key pair. -func (o *defaultKeyPairBuilder) newEcdsaKeyPair() (KeyPair, error) { +func newEcdsaKeyPair(config CreateKeyPairConfig) (KeyPair, error) { var curve elliptic.Curve - switch o.bits { + switch config.Bits { case 0: - o.bits = 521 + config.Bits = 521 fallthrough case 521: curve = elliptic.P521() @@ -216,26 +169,24 @@ func (o *defaultKeyPairBuilder) newEcdsaKeyPair() (KeyPair, error) { curve = elliptic.P256() case 224: // Not supported by "golang.org/x/crypto/ssh". - return &ecdsaKeyPair{}, errors.New("golang.org/x/crypto/ssh does not support " + - strconv.Itoa(o.bits) + " bits") + return KeyPair{}, fmt.Errorf("golang.org/x/crypto/ssh does not support %d bits", config.Bits) default: - return &ecdsaKeyPair{}, errors.New("crypto/elliptic does not support " + - strconv.Itoa(o.bits) + " bits") + return KeyPair{}, fmt.Errorf("crypto/elliptic does not support %d bits", config.Bits) } privateKey, err := ecdsa.GenerateKey(curve, rand.Reader) if err != nil { - return &ecdsaKeyPair{}, err + return KeyPair{}, err } sshPublicKey, err := gossh.NewPublicKey(&privateKey.PublicKey) if err != nil { - return &ecdsaKeyPair{}, err + return KeyPair{}, err } privateRaw, err := x509.MarshalECPrivateKey(privateKey) if err != nil { - return &ecdsaKeyPair{}, err + return KeyPair{}, err } privatePem, err := rawPemBlock(&pem.Block{ @@ -244,31 +195,30 @@ func (o *defaultKeyPairBuilder) newEcdsaKeyPair() (KeyPair, error) { Bytes: privateRaw, }) if err != nil { - return &ecdsaKeyPair{}, err + return KeyPair{}, err } - return &ecdsaKeyPair{ - privateKey: privateKey, - publicKey: sshPublicKey, - name: o.name, - privatePemBlock: privatePem, + return KeyPair{ + PrivateKeyPemBlock: privatePem, + PublicKeyAuthorizedKeysLine: authorizedKeysLine(sshPublicKey, config.Name), + Name: config.Name, }, nil } // newRsaKeyPair returns a new RSA SSH key pair. -func (o *defaultKeyPairBuilder) newRsaKeyPair() (KeyPair, error) { - if o.bits == 0 { - o.bits = defaultRsaBits +func newRsaKeyPair(config CreateKeyPairConfig) (KeyPair, error) { + if config.Bits == 0 { + config.Bits = defaultRsaBits } - privateKey, err := rsa.GenerateKey(rand.Reader, o.bits) + privateKey, err := rsa.GenerateKey(rand.Reader, config.Bits) if err != nil { - return &rsaKeyPair{}, err + return KeyPair{}, err } sshPublicKey, err := gossh.NewPublicKey(&privateKey.PublicKey) if err != nil { - return &rsaKeyPair{}, err + return KeyPair{}, err } privatePemBlock, err := rawPemBlock(&pem.Block{ @@ -277,48 +227,16 @@ func (o *defaultKeyPairBuilder) newRsaKeyPair() (KeyPair, error) { Bytes: x509.MarshalPKCS1PrivateKey(privateKey), }) if err != nil { - return &rsaKeyPair{}, err + return KeyPair{}, err } - return &rsaKeyPair{ - privateKey: privateKey, - publicKey: sshPublicKey, - name: o.name, - privatePemBlock: privatePemBlock, + return KeyPair{ + PrivateKeyPemBlock: privatePemBlock, + PublicKeyAuthorizedKeysLine: authorizedKeysLine(sshPublicKey, config.Name), + Name: config.Name, }, nil } -// KeyPair represents a SSH key pair. -type KeyPair interface { - // Type returns the key pair's type. - Type() KeyPairType - - // Bits returns the bits of entropy. - Bits() int - - // Name returns the key pair's name. An empty string is - // returned if no name was specified. - Name() string - - // Description returns a brief description of the key pair that - // is suitable for log messages or printing. - Description() string - - // PrivateKeyPemBlock returns a slice of bytes representing - // the private key in ASN.1 Distinguished Encoding Rules (DER) - // format in a Privacy-Enhanced Mail (PEM) block. - PrivateKeyPemBlock() []byte - - // PublicKeyAuthorizedKeysLine returns a slice of bytes - // representing the public key as a line in OpenSSH authorized_keys - // format with the specified new line. - PublicKeyAuthorizedKeysLine(NewLineOption) []byte -} - -func NewKeyPairBuilder() KeyPairBuilder { - return &defaultKeyPairBuilder{} -} - // rawPemBlock encodes a pem.Block to a slice of bytes. func rawPemBlock(block *pem.Block) ([]byte, error) { buffer := bytes.NewBuffer(nil) @@ -331,26 +249,10 @@ func rawPemBlock(block *pem.Block) ([]byte, error) { return buffer.Bytes(), nil } -// description returns a string describing a key pair. -func description(kp KeyPair) string { - buffer := bytes.NewBuffer(nil) - - buffer.WriteString(strconv.Itoa(kp.Bits())) - buffer.WriteString(" bit ") - buffer.WriteString(kp.Type().String()) - - if len(kp.Name()) > 0 { - buffer.WriteString(" named ") - buffer.WriteString(kp.Name()) - } - - return buffer.String() -} - -// publicKeyAuthorizedKeysLine returns a slice of bytes representing a SSH -// public key as a line in OpenSSH authorized_keys format. -func publicKeyAuthorizedKeysLine(publicKey gossh.PublicKey, name string, nl NewLineOption) []byte { - result := gossh.MarshalAuthorizedKey(publicKey) +// authorizedKeysLine returns a slice of bytes representing an SSH public key +// as a line in OpenSSH authorized_keys format. No line break is appended. +func authorizedKeysLine(sshPublicKey gossh.PublicKey, name string) []byte { + result := gossh.MarshalAuthorizedKey(sshPublicKey) // Remove the mandatory unix new line. // Awful, but the go ssh library automatically appends @@ -362,14 +264,5 @@ func publicKeyAuthorizedKeysLine(publicKey gossh.PublicKey, name string, nl NewL result = append(result, name...) } - switch nl { - case WindowsNewLine: - result = append(result, nl.Bytes()...) - case UnixNewLine: - // This is how all the other "SSH key pair" code works in - // the different builders. - result = append(result, UnixNewLine.Bytes()...) - } - return result } diff --git a/helper/ssh/key_pair_test.go b/helper/ssh/key_pair_test.go index 5a6e23b6c..eddb9a3cb 100644 --- a/helper/ssh/key_pair_test.go +++ b/helper/ssh/key_pair_test.go @@ -2,24 +2,17 @@ package ssh import ( "bytes" - "crypto/rand" - "errors" - "strconv" + "crypto/dsa" + "crypto/ecdsa" + "crypto/rsa" + "fmt" "testing" "github.com/hashicorp/packer/common/uuid" + "golang.org/x/crypto/ed25519" gossh "golang.org/x/crypto/ssh" ) -// expected contains the data that the key pair should contain. -type expected struct { - kind KeyPairType - bits int - desc string - name string - data []byte -} - const ( pemRsa1024 = `-----BEGIN RSA PRIVATE KEY----- MIICWwIBAAKBgQDJEMFPpTBiWNDb3qEIPTSeEnIP8FZdBpG8njOrclcMoQQNhzZ+ @@ -151,401 +144,306 @@ QBAgM= ` ) -func (o expected) matches(kp KeyPair) error { - if o.kind.String() == "" { - return errors.New("expected kind's value cannot be empty") - } - - if o.bits <= 0 { - return errors.New("expected bits' value cannot be less than or equal to 0") - } - - if o.desc == "" { - return errors.New("expected description's value cannot be empty") +func TestNewKeyPair_Default(t *testing.T) { + kp, err := NewKeyPair(CreateKeyPairConfig{}) + if err != nil { + t.Fatal(err.Error()) } - if len(o.data) == 0 { - return errors.New("expected random data value cannot be nothing") + err = verifyEcdsaKeyPair(kp, expectedData{ + bits: 521, + }) + if err != nil { + t.Fatal(err.Error()) } +} - if kp.Type() != o.kind { - return errors.New("key pair type should be " + o.kind.String() + - " - got '" + kp.Type().String() + "'") +func TestNewKeyPair_ECDSA_Default(t *testing.T) { + kp, err := NewKeyPair(CreateKeyPairConfig{ + Type: Ecdsa, + }) + if err != nil { + t.Fatal(err.Error()) } - if kp.Bits() != o.bits { - return errors.New("key pair bits should be " + strconv.Itoa(o.bits) + - " - got " + strconv.Itoa(kp.Bits())) + err = verifyEcdsaKeyPair(kp, expectedData{ + bits: 521, + }) + if err != nil { + t.Fatal(err.Error()) } +} - if len(o.name) > 0 && kp.Name() != o.name { - return errors.New("key pair name should be '" + o.name + - "' - got '" + kp.Name() + "'") - } +func TestNewKeyPair_ECDSA_Positive(t *testing.T) { + for _, bits := range []int{521, 384, 256} { + config := CreateKeyPairConfig{ + Type: Ecdsa, + Bits: bits, + Name: uuid.TimeOrderedUUID(), + } - if kp.Description() != o.desc { - return errors.New("key pair description should be '" + - o.desc + "' - got '" + kp.Description() + "'") - } + kp, err := NewKeyPair(config) + if err != nil { + t.Fatal(err.Error()) + } - err := o.verifyPublicKeyAuthorizedKeysFormat(kp) - if err != nil { - return err + err = verifyEcdsaKeyPair(kp, expectedData{ + bits: bits, + name: config.Name, + }) + if err != nil { + t.Fatal(err.Error()) + } } +} - err = o.verifyKeyPair(kp) - if err != nil { - return err +func TestNewKeyPair_ECDSA_Negative(t *testing.T) { + for _, bits := range []int{224, 1, 2, 3} { + _, err := NewKeyPair(CreateKeyPairConfig{ + Type: Ecdsa, + Bits: bits, + }) + if err == nil { + t.Fatalf("expected key pair generation to fail for %d bits", bits) + } } - - return nil } -func (o expected) verifyPublicKeyAuthorizedKeysFormat(kp KeyPair) error { - newLines := []NewLineOption{ - UnixNewLine, - NoNewLine, - WindowsNewLine, - } +func TestNewKeyPair_RSA_Positive(t *testing.T) { + for _, bits := range []int{4096, 2048} { + config := CreateKeyPairConfig{ + Type: Rsa, + Bits: bits, + Name: uuid.TimeOrderedUUID(), + } - for _, nl := range newLines { - publicKeyAk := kp.PublicKeyAuthorizedKeysLine(nl) + kp, err := NewKeyPair(config) + if err != nil { + t.Fatal(err.Error()) + } - if len(publicKeyAk) < 2 { - return errors.New("expected public key in authorized keys format to be at least 2 bytes") + err = verifyRsaKeyPair(kp, expectedData{ + bits: config.Bits, + name: config.Name, + }) + if err != nil { + t.Fatal(err.Error()) } + } +} - switch nl { - case NoNewLine: - if publicKeyAk[len(publicKeyAk)-1] == '\n' { - return errors.New("public key in authorized keys format has trailing new line when none was specified") - } - case UnixNewLine: - if publicKeyAk[len(publicKeyAk)-1] != '\n' { - return errors.New("public key in authorized keys format does not have unix new line when unix was specified") - } - if string(publicKeyAk[len(publicKeyAk)-2:]) == WindowsNewLine.String() { - return errors.New("public key in authorized keys format has windows new line when unix was specified") - } - case WindowsNewLine: - if string(publicKeyAk[len(publicKeyAk)-2:]) != WindowsNewLine.String() { - return errors.New("public key in authorized keys format does not have windows new line when windows was specified") - } +func TestKeyPairFromPrivateKey(t *testing.T) { + m := map[string]fromPrivateExpectedData{ + pemRsa1024: { + t: Rsa, + d: expectedData{ + bits: 1024, + }, + }, + pemRsa2048: { + t: Rsa, + d: expectedData{ + bits: 2048, + }, + }, + pemOpenSshRsa1024: { + t: Rsa, + d: expectedData{ + bits: 1024, + }, + }, + pemOpenSshRsa2048: { + t: Rsa, + d: expectedData{ + bits: 2048, + }, + }, + pemDsa: { + t: Dsa, + d: expectedData{ + bits: 1024, + }, + }, + pemEcdsa384: { + t: Ecdsa, + d: expectedData{ + bits: 384, + }, + }, + pemEcdsa521: { + t: Ecdsa, + d: expectedData{ + bits: 521, + }, + }, + pemOpenSshEd25519: { + t: Ed25519, + d: expectedData{ + bits: 256, + }, + }, + } + + for rawPrivateKey, expected := range m { + kp, err := KeyPairFromPrivateKey(FromPrivateKeyConfig{ + RawPrivateKeyPemBlock: []byte(rawPrivateKey), + }) + if err != nil { + t.Fatal(err.Error()) } - if len(o.name) > 0 { - if len(publicKeyAk) < len(o.name) { - return errors.New("public key in authorized keys format is shorter than the key pair's name") - } - - suffix := []byte{' '} - suffix = append(suffix, o.name...) - suffix = append(suffix, nl.Bytes()...) - if !bytes.HasSuffix(publicKeyAk, suffix) { - return errors.New("public key in authorized keys format with name does not have name in suffix - got '" + - string(publicKeyAk) + "'") - } + switch expected.t { + case Dsa: + err = verifyDsaKeyPair(kp, expected) + case Ecdsa: + err = verifyEcdsaKeyPair(kp, expected.d) + case Ed25519: + err = verifyEd25519KeyPair(kp, expected) + case Rsa: + err = verifyRsaKeyPair(kp, expected.d) + default: + err = fmt.Errorf("unexected SSH key pair type %s", expected.t.String()) + } + if err != nil { + t.Fatal(err.Error()) } } +} - return nil +type fromPrivateExpectedData struct { + t KeyPairType + d expectedData } -func (o expected) verifyKeyPair(kp KeyPair) error { - signer, err := gossh.ParsePrivateKey(kp.PrivateKeyPemBlock()) +type expectedData struct { + bits int + name string +} + +func verifyEcdsaKeyPair(kp KeyPair, e expectedData) error { + privateKey, err := gossh.ParseRawPrivateKey(kp.PrivateKeyPemBlock) if err != nil { - return errors.New("failed to parse private key during verification - " + err.Error()) + return err } - signature, err := signer.Sign(rand.Reader, o.data) - if err != nil { - return errors.New("failed to sign test data during verification - " + err.Error()) + pk, ok := privateKey.(*ecdsa.PrivateKey) + if !ok { + return fmt.Errorf("private key should be *ecdsa.PrivateKey") } - err = signer.PublicKey().Verify(o.data, signature) + if pk.Curve.Params().BitSize != e.bits { + return fmt.Errorf("bit size should be %d - got %d", e.bits, pk.Curve.Params().BitSize) + } + + publicKey, err := gossh.NewPublicKey(&pk.PublicKey) if err != nil { - return errors.New("failed to verify test data - " + err.Error()) + return err + } + + expectedBytes := bytes.TrimSuffix(gossh.MarshalAuthorizedKey(publicKey), []byte("\n")) + if len(e.name) > 0 { + expectedBytes = append(expectedBytes, ' ') + expectedBytes = append(expectedBytes, e.name...) + } + + if !bytes.Equal(expectedBytes, kp.PublicKeyAuthorizedKeysLine) { + return fmt.Errorf("authorized keys line should be:\n'%s'\nGot:\n'%s'", + string(expectedBytes), string(kp.PublicKeyAuthorizedKeysLine)) } return nil } -func TestDefaultKeyPairBuilder_Build_Default(t *testing.T) { - kp, err := NewKeyPairBuilder().Build() +func verifyRsaKeyPair(kp KeyPair, e expectedData) error { + privateKey, err := gossh.ParseRawPrivateKey(kp.PrivateKeyPemBlock) if err != nil { - t.Fatal(err.Error()) + return err } - err = expected{ - kind: Ecdsa, - bits: 521, - desc: "521 bit ECDSA", - data: []byte(uuid.TimeOrderedUUID()), - }.matches(kp) - if err != nil { - t.Fatal(err.Error()) + pk, ok := privateKey.(*rsa.PrivateKey) + if !ok { + return fmt.Errorf("private key should be *rsa.PrivateKey") } -} -func TestDefaultKeyPairBuilder_Build_EcdsaDefault(t *testing.T) { - kp, err := NewKeyPairBuilder(). - SetType(Ecdsa). - Build() - if err != nil { - t.Fatal(err.Error()) + if pk.N.BitLen() != e.bits { + return fmt.Errorf("bit size should be %d - got %d", e.bits, pk.N.BitLen()) } - err = expected{ - kind: Ecdsa, - bits: 521, - desc: "521 bit ECDSA", - data: []byte(uuid.TimeOrderedUUID()), - }.matches(kp) + publicKey, err := gossh.NewPublicKey(&pk.PublicKey) if err != nil { - t.Fatal(err.Error()) + return err } -} -func TestDefaultKeyPairBuilder_Build_EcdsaSupportedCurves(t *testing.T) { - supportedBits := []int{ - 521, - 384, - 256, + expectedBytes := bytes.TrimSuffix(gossh.MarshalAuthorizedKey(publicKey), []byte("\n")) + if len(e.name) > 0 { + expectedBytes = append(expectedBytes, ' ') + expectedBytes = append(expectedBytes, e.name...) } - for _, bits := range supportedBits { - kp, err := NewKeyPairBuilder(). - SetType(Ecdsa). - SetBits(bits). - Build() - if err != nil { - t.Fatal(err.Error()) - } - - err = expected{ - kind: Ecdsa, - bits: bits, - desc: strconv.Itoa(bits) + " bit ECDSA", - data: []byte(uuid.TimeOrderedUUID()), - }.matches(kp) - if err != nil { - t.Fatal(err.Error()) - } + if !bytes.Equal(expectedBytes, kp.PublicKeyAuthorizedKeysLine) { + return fmt.Errorf("authorized keys line should be:\n'%s'\nGot:\n'%s'", + string(expectedBytes), string(kp.PublicKeyAuthorizedKeysLine)) } + + return nil } -func TestDefaultKeyPairBuilder_Build_RsaDefault(t *testing.T) { - kp, err := NewKeyPairBuilder(). - SetType(Rsa). - Build() +func verifyDsaKeyPair(kp KeyPair, e fromPrivateExpectedData) error { + privateKey, err := gossh.ParseRawPrivateKey(kp.PrivateKeyPemBlock) if err != nil { - t.Fatal(err.Error()) + return err } - err = expected{ - kind: Rsa, - bits: 4096, - desc: "4096 bit RSA", - data: []byte(uuid.TimeOrderedUUID()), - }.matches(kp) - if err != nil { - t.Fatal(err.Error()) + pk, ok := privateKey.(*dsa.PrivateKey) + if !ok { + return fmt.Errorf("private key should be *rsa.PrivateKey") } -} -func TestDefaultKeyPairBuilder_Build_NamedEcdsa(t *testing.T) { - name := uuid.TimeOrderedUUID() - - kp, err := NewKeyPairBuilder(). - SetType(Ecdsa). - SetName(name). - Build() + publicKey, err := gossh.NewPublicKey(&pk.PublicKey) if err != nil { - t.Fatal(err.Error()) + return err } - err = expected{ - kind: Ecdsa, - bits: 521, - desc: "521 bit ECDSA named " + name, - data: []byte(uuid.TimeOrderedUUID()), - name: name, - }.matches(kp) - if err != nil { - t.Fatal(err.Error()) + expectedBytes := bytes.TrimSuffix(gossh.MarshalAuthorizedKey(publicKey), []byte("\n")) + if len(e.d.name) > 0 { + expectedBytes = append(expectedBytes, ' ') + expectedBytes = append(expectedBytes, e.d.name...) } -} - -func TestDefaultKeyPairBuilder_Build_NamedRsa(t *testing.T) { - name := uuid.TimeOrderedUUID() - kp, err := NewKeyPairBuilder(). - SetType(Rsa). - SetName(name). - Build() - if err != nil { - t.Fatal(err.Error()) + if !bytes.Equal(expectedBytes, kp.PublicKeyAuthorizedKeysLine) { + return fmt.Errorf("authorized keys line should be:\n'%s'\nGot:\n'%s'", + string(expectedBytes), string(kp.PublicKeyAuthorizedKeysLine)) } - err = expected{ - kind: Rsa, - bits: 4096, - desc: "4096 bit RSA named " + name, - data: []byte(uuid.TimeOrderedUUID()), - name: name, - }.matches(kp) - if err != nil { - t.Fatal(err.Error()) - } + return nil } -func TestDefaultKeyPairBuilder_SetPrivateKey(t *testing.T) { - name := uuid.TimeOrderedUUID() - pemData := make(map[string]expected) - pemData[pemRsa1024] = expected{ - bits: 1024, - kind: Rsa, - name: name, - desc: "1024 bit RSA named " + name, - data: []byte(uuid.TimeOrderedUUID()), - } - pemData[pemRsa2048] = expected{ - bits: 2048, - kind: Rsa, - name: name, - desc: "2048 bit RSA named " + name, - data: []byte(uuid.TimeOrderedUUID()), - } - pemData[pemOpenSshRsa1024] = expected{ - bits: 1024, - kind: Rsa, - name: name, - desc: "1024 bit RSA named " + name, - data: []byte(uuid.TimeOrderedUUID()), - } - pemData[pemOpenSshRsa2048] = expected{ - bits: 2048, - kind: Rsa, - name: name, - desc: "2048 bit RSA named " + name, - data: []byte(uuid.TimeOrderedUUID()), - } - pemData[pemDsa] = expected{ - bits: 1024, - kind: Dsa, - name: name, - desc: "1024 bit DSA named " + name, - data: []byte(uuid.TimeOrderedUUID()), - } - pemData[pemEcdsa384] = expected{ - bits: 384, - kind: Ecdsa, - name: name, - desc: "384 bit ECDSA named " + name, - data: []byte(uuid.TimeOrderedUUID()), - } - pemData[pemEcdsa521] = expected{ - bits: 521, - kind: Ecdsa, - name: name, - desc: "521 bit ECDSA named " + name, - data: []byte(uuid.TimeOrderedUUID()), - } - pemData[pemOpenSshEd25519] = expected{ - bits: 256, - kind: Ed25519, - name: name, - desc: "256 bit ED25519 named " + name, - data: []byte(uuid.TimeOrderedUUID()), +func verifyEd25519KeyPair(kp KeyPair, e fromPrivateExpectedData) error { + privateKey, err := gossh.ParseRawPrivateKey(kp.PrivateKeyPemBlock) + if err != nil { + return err } - for s, l := range pemData { - kp, err := NewKeyPairBuilder().SetPrivateKey([]byte(s)).SetName(name).Build() - if err != nil { - t.Fatal(err) - } - err = l.matches(kp) - if err != nil { - t.Fatal(err) - } + pk, ok := privateKey.(*ed25519.PrivateKey) + if !ok { + return fmt.Errorf("private key should be *rsa.PrivateKey") } -} -func TestDefaultKeyPairBuilder_SetPrivateKey_Override(t *testing.T) { - name := uuid.TimeOrderedUUID() - pemData := make(map[string]expected) - pemData[pemRsa1024] = expected{ - bits: 1024, - kind: Rsa, - name: name, - desc: "1024 bit RSA named " + name, - data: []byte(uuid.TimeOrderedUUID()), - } - pemData[pemRsa2048] = expected{ - bits: 2048, - kind: Rsa, - name: name, - desc: "2048 bit RSA named " + name, - data: []byte(uuid.TimeOrderedUUID()), - } - pemData[pemOpenSshRsa1024] = expected{ - bits: 1024, - kind: Rsa, - name: name, - desc: "1024 bit RSA named " + name, - data: []byte(uuid.TimeOrderedUUID()), - } - pemData[pemOpenSshRsa2048] = expected{ - bits: 2048, - kind: Rsa, - name: name, - desc: "2048 bit RSA named " + name, - data: []byte(uuid.TimeOrderedUUID()), - } - pemData[pemDsa] = expected{ - bits: 1024, - kind: Dsa, - name: name, - desc: "1024 bit DSA named " + name, - data: []byte(uuid.TimeOrderedUUID()), - } - pemData[pemEcdsa384] = expected{ - bits: 384, - kind: Ecdsa, - name: name, - desc: "384 bit ECDSA named " + name, - data: []byte(uuid.TimeOrderedUUID()), - } - pemData[pemEcdsa521] = expected{ - bits: 521, - kind: Ecdsa, - name: name, - desc: "521 bit ECDSA named " + name, - data: []byte(uuid.TimeOrderedUUID()), + publicKey, err := gossh.NewPublicKey(pk.Public()) + if err != nil { + return err } - pemData[pemOpenSshEd25519] = expected{ - bits: 256, - kind: Ed25519, - name: name, - desc: "256 bit ED25519 named " + name, - data: []byte(uuid.TimeOrderedUUID()), + + expectedBytes := bytes.TrimSuffix(gossh.MarshalAuthorizedKey(publicKey), []byte("\n")) + if len(e.d.name) > 0 { + expectedBytes = append(expectedBytes, ' ') + expectedBytes = append(expectedBytes, e.d.name...) } - supportedKeyTypes := []KeyPairType{Rsa, Dsa} - for _, keyType := range supportedKeyTypes { - for pemString, expectedResult := range pemData { - kp, err := NewKeyPairBuilder(). - SetPrivateKey([]byte(pemString)). - SetName(name). - SetType(keyType). - Build() - if err != nil { - t.Fatal(err) - } - err = expectedResult.matches(kp) - if err != nil { - t.Fatal(err) - } - } + if !bytes.Equal(expectedBytes, kp.PublicKeyAuthorizedKeysLine) { + return fmt.Errorf("authorized keys line should be:\n'%s'\nGot:\n'%s'", + string(expectedBytes), string(kp.PublicKeyAuthorizedKeysLine)) } + + return nil } diff --git a/helper/ssh/rsa_key_pair.go b/helper/ssh/rsa_key_pair.go deleted file mode 100644 index 9a83f193b..000000000 --- a/helper/ssh/rsa_key_pair.go +++ /dev/null @@ -1,38 +0,0 @@ -package ssh - -import ( - "crypto/rsa" - - gossh "golang.org/x/crypto/ssh" -) - -type rsaKeyPair struct { - privateKey *rsa.PrivateKey - publicKey gossh.PublicKey - name string - privatePemBlock []byte -} - -func (o rsaKeyPair) Type() KeyPairType { - return Rsa -} - -func (o rsaKeyPair) Bits() int { - return o.privateKey.N.BitLen() -} - -func (o rsaKeyPair) Name() string { - return o.name -} - -func (o rsaKeyPair) Description() string { - return description(o) -} - -func (o rsaKeyPair) PrivateKeyPemBlock() []byte { - return o.privatePemBlock -} - -func (o rsaKeyPair) PublicKeyAuthorizedKeysLine(nl NewLineOption) []byte { - return publicKeyAuthorizedKeysLine(o.publicKey, o.name, nl) -} From 415ef26768a7c7365b775447072fba073db0035e Mon Sep 17 00:00:00 2001 From: Stephen Fox Date: Thu, 28 Feb 2019 09:30:30 -0500 Subject: [PATCH 40/49] Removed TODO about key pair description per review feedback. --- helper/ssh/key_pair.go | 1 - 1 file changed, 1 deletion(-) diff --git a/helper/ssh/key_pair.go b/helper/ssh/key_pair.go index 11f6b6386..442b85b51 100644 --- a/helper/ssh/key_pair.go +++ b/helper/ssh/key_pair.go @@ -64,7 +64,6 @@ type FromPrivateKeyConfig struct { } // KeyPair represents an SSH key pair. -// TODO: Maybe a field for a description? Maybe save the type? type KeyPair struct { // PrivateKeyPemBlock represents the key pair's private key in // ASN.1 Distinguished Encoding Rules (DER) format in a From ed06656613b69d78ff1fbf1c6d4d53eac39a9aeb Mon Sep 17 00:00:00 2001 From: Stephen Fox Date: Thu, 28 Feb 2019 09:40:00 -0500 Subject: [PATCH 41/49] Update 'authorizedKeysLine' doc per review feedback. --- helper/ssh/key_pair.go | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/helper/ssh/key_pair.go b/helper/ssh/key_pair.go index 442b85b51..906fde465 100644 --- a/helper/ssh/key_pair.go +++ b/helper/ssh/key_pair.go @@ -248,20 +248,22 @@ func rawPemBlock(block *pem.Block) ([]byte, error) { return buffer.Bytes(), nil } -// authorizedKeysLine returns a slice of bytes representing an SSH public key -// as a line in OpenSSH authorized_keys format. No line break is appended. -func authorizedKeysLine(sshPublicKey gossh.PublicKey, name string) []byte { - result := gossh.MarshalAuthorizedKey(sshPublicKey) - - // Remove the mandatory unix new line. - // Awful, but the go ssh library automatically appends - // a unix new line. - result = bytes.TrimSpace(result) +// authorizedKeysLine serializes key for inclusion in an OpenSSH +// authorized_keys file. The return value ends without newline so +// a key name can be appended to the end. +func authorizedKeysLine(key gossh.PublicKey, name string) []byte { + marshaledPublicKey := gossh.MarshalAuthorizedKey(key) + + // Remove the mandatory unix new line. Awful, but the go + // ssh library automatically appends a unix new line. + // We remove it so a key name can be safely appended to the + // end of the string. + marshaledPublicKey = bytes.TrimSpace(marshaledPublicKey) if len(strings.TrimSpace(name)) > 0 { - result = append(result, ' ') - result = append(result, name...) + marshaledPublicKey = append(marshaledPublicKey, ' ') + marshaledPublicKey = append(marshaledPublicKey, name...) } - return result + return marshaledPublicKey } From 302828905d3d9e7cf544675e0b6804c39f375717 Mon Sep 17 00:00:00 2001 From: Stephen Fox Date: Thu, 28 Feb 2019 09:42:20 -0500 Subject: [PATCH 42/49] Move 'FromPrivateKeyConfig' closer to caller. Per review feedback. --- helper/ssh/key_pair.go | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/helper/ssh/key_pair.go b/helper/ssh/key_pair.go index 906fde465..0922d3dfa 100644 --- a/helper/ssh/key_pair.go +++ b/helper/ssh/key_pair.go @@ -51,18 +51,6 @@ type CreateKeyPairConfig struct { Name string } -// FromPrivateKeyConfig describes how an SSH key pair should be loaded from an -// existing private key. -type FromPrivateKeyConfig struct { - // RawPrivateKeyPemBlock is the raw private key that the key pair - // should be loaded from. - RawPrivateKeyPemBlock []byte - - // Name is the resulting key pair's name. This is used to identify - // the key pair in the SSH server's 'authorized_keys'. - Name string -} - // KeyPair represents an SSH key pair. type KeyPair struct { // PrivateKeyPemBlock represents the key pair's private key in @@ -134,6 +122,18 @@ func KeyPairFromPrivateKey(config FromPrivateKeyConfig) (KeyPair, error) { return KeyPair{}, fmt.Errorf("Cannot parse existing SSH key pair - unknown key pair type") } +// FromPrivateKeyConfig describes how an SSH key pair should be loaded from an +// existing private key. +type FromPrivateKeyConfig struct { + // RawPrivateKeyPemBlock is the raw private key that the key pair + // should be loaded from. + RawPrivateKeyPemBlock []byte + + // Name is the resulting key pair's name. This is used to identify + // the key pair in the SSH server's 'authorized_keys'. + Name string +} + // NewKeyPair generates a new SSH key pair using the specified // CreateKeyPairConfig. func NewKeyPair(config CreateKeyPairConfig) (KeyPair, error) { From 672b22bd13225276235daca4c04874828b599f96 Mon Sep 17 00:00:00 2001 From: Stephen Fox Date: Thu, 28 Feb 2019 09:44:25 -0500 Subject: [PATCH 43/49] Move 'CreateKeyPairConfig' closer to callers. Per review feedback. --- helper/ssh/key_pair.go | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/helper/ssh/key_pair.go b/helper/ssh/key_pair.go index 0922d3dfa..55071cf63 100644 --- a/helper/ssh/key_pair.go +++ b/helper/ssh/key_pair.go @@ -36,21 +36,6 @@ func (o KeyPairType) String() string { return string(o) } -// CreateKeyPairConfig describes how an SSH key pair should be created. -type CreateKeyPairConfig struct { - // Type describes the key pair's type. - Type KeyPairType - - // Bits represents the key pair's bits of entropy. E.g., 4096 for - // a 4096 bit RSA key pair, or 521 for a ECDSA key pair with a - // 521-bit curve. - Bits int - - // Name is the resulting key pair's name. This is used to identify - // the key pair in the SSH server's 'authorized_keys'. - Name string -} - // KeyPair represents an SSH key pair. type KeyPair struct { // PrivateKeyPemBlock represents the key pair's private key in @@ -236,6 +221,21 @@ func newRsaKeyPair(config CreateKeyPairConfig) (KeyPair, error) { }, nil } +// CreateKeyPairConfig describes how an SSH key pair should be created. +type CreateKeyPairConfig struct { + // Type describes the key pair's type. + Type KeyPairType + + // Bits represents the key pair's bits of entropy. E.g., 4096 for + // a 4096 bit RSA key pair, or 521 for a ECDSA key pair with a + // 521-bit curve. + Bits int + + // Name is the resulting key pair's name. This is used to identify + // the key pair in the SSH server's 'authorized_keys'. + Name string +} + // rawPemBlock encodes a pem.Block to a slice of bytes. func rawPemBlock(block *pem.Block) ([]byte, error) { buffer := bytes.NewBuffer(nil) From 856810e82e21bcf3181077219daf909cf89bdc16 Mon Sep 17 00:00:00 2001 From: Stephen Fox Date: Thu, 28 Feb 2019 09:56:11 -0500 Subject: [PATCH 44/49] Coalesce private key type switch cases per review feedback. --- helper/ssh/key_pair.go | 27 ++++++--------------------- 1 file changed, 6 insertions(+), 21 deletions(-) diff --git a/helper/ssh/key_pair.go b/helper/ssh/key_pair.go index 55071cf63..f6852080d 100644 --- a/helper/ssh/key_pair.go +++ b/helper/ssh/key_pair.go @@ -2,6 +2,7 @@ package ssh import ( "bytes" + "crypto" "crypto/dsa" "crypto/ecdsa" "crypto/elliptic" @@ -12,7 +13,6 @@ import ( "fmt" "strings" - "golang.org/x/crypto/ed25519" gossh "golang.org/x/crypto/ssh" ) @@ -66,17 +66,11 @@ func KeyPairFromPrivateKey(config FromPrivateKeyConfig) (KeyPair, error) { } switch pk := privateKey.(type) { - case *rsa.PrivateKey: - publicKey, err := gossh.NewPublicKey(&pk.PublicKey) - if err != nil { - return KeyPair{}, err - } - return KeyPair{ - PrivateKeyPemBlock: config.RawPrivateKeyPemBlock, - PublicKeyAuthorizedKeysLine: authorizedKeysLine(publicKey, config.Name), - }, nil - case *ecdsa.PrivateKey: - publicKey, err := gossh.NewPublicKey(&pk.PublicKey) + case crypto.Signer: + // crypto.Signer is implemented by ecdsa.PrivateKey, + // ed25519.PrivateKey, and rsa.PrivateKey - separate cases + // for each PrivateKey type would be redundant. + publicKey, err := gossh.NewPublicKey(pk.Public()) if err != nil { return KeyPair{}, err } @@ -93,15 +87,6 @@ func KeyPairFromPrivateKey(config FromPrivateKeyConfig) (KeyPair, error) { PrivateKeyPemBlock: config.RawPrivateKeyPemBlock, PublicKeyAuthorizedKeysLine: authorizedKeysLine(publicKey, config.Name), }, nil - case *ed25519.PrivateKey: - publicKey, err := gossh.NewPublicKey(pk.Public()) - if err != nil { - return KeyPair{}, err - } - return KeyPair{ - PrivateKeyPemBlock: config.RawPrivateKeyPemBlock, - PublicKeyAuthorizedKeysLine: authorizedKeysLine(publicKey, config.Name), - }, nil } return KeyPair{}, fmt.Errorf("Cannot parse existing SSH key pair - unknown key pair type") From 56c17941fecdd92875b1b3377a65a32a3be6617c Mon Sep 17 00:00:00 2001 From: Stephen Fox Date: Thu, 28 Feb 2019 10:54:14 -0500 Subject: [PATCH 45/49] Added comment to authorized_keys 'sed' calls per review. --- common/step_cleanup_temp_keys.go | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/common/step_cleanup_temp_keys.go b/common/step_cleanup_temp_keys.go index 74b528a80..56ca700aa 100644 --- a/common/step_cleanup_temp_keys.go +++ b/common/step_cleanup_temp_keys.go @@ -38,6 +38,22 @@ func (s *StepCleanupTempKeys) Run(_ context.Context, state multistep.StateBag) m ui.Say("Trying to remove ephemeral keys from authorized_keys files") + // Per the OpenSSH manual (https://man.openbsd.org/sshd.8), a typical + // line in the 'authorized_keys' file contains several fields that + // are delimited by spaces. Here is an (abbreviated) example of a line: + // ssh-rsa AAAAB3Nza...LiPk== user@example.net + // + // In the above example, 'ssh-rsa' is the key pair type, + // 'AAAAB3Nza...LiPk==' is the base64 encoded public key, + // and 'user@example.net' is a comment (in this case, describing + // who the key belongs to). + // + // In the following 'sed' calls, the comment field will be equal to + // the value of communicator.Config.SSHTemporaryKeyPairName. + // We can remove an authorized public key using 'sed' by looking + // for a line ending in ' packer-key-pair-comment' (note the + // leading space). + // // TODO: Why create a backup file if you are going to remove it? cmd.Command = fmt.Sprintf("sed -i.bak '/ %s$/d' ~/.ssh/authorized_keys; rm ~/.ssh/authorized_keys.bak", s.Comm.SSHTemporaryKeyPairName) if err := cmd.StartWithUi(comm, ui); err != nil { @@ -45,7 +61,6 @@ func (s *StepCleanupTempKeys) Run(_ context.Context, state multistep.StateBag) m } cmd = new(packer.RemoteCmd) cmd.Command = fmt.Sprintf("sudo sed -i.bak '/ %s$/d' /root/.ssh/authorized_keys; sudo rm /root/.ssh/authorized_keys.bak", s.Comm.SSHTemporaryKeyPairName) - if err := cmd.StartWithUi(comm, ui); err != nil { log.Printf("Error cleaning up /root/.ssh/authorized_keys; please clean up keys manually: %s", err) } From 094c87e39585ceda07559882e9973070373a8d44 Mon Sep 17 00:00:00 2001 From: Stephen Fox Date: Thu, 28 Feb 2019 17:06:51 -0500 Subject: [PATCH 46/49] Remove 'SSHPublicKeyUrlEncoded', use 'urlquery' instead. Per code review feedback, the Go template library supports encoding variables in URL query format. Instead of exposing two different public key formats (unmodified string and a URL encoded string), just have the user apply the 'urlquery' modifier to their template. --- .../common/step_type_boot_command.go | 15 ++++----------- helper/communicator/config.go | 7 ------- helper/communicator/config_test.go | 18 ------------------ .../builders/_virtualbox-ssh-key-pair.html.md | 19 +++++++++---------- 4 files changed, 13 insertions(+), 46 deletions(-) diff --git a/builder/virtualbox/common/step_type_boot_command.go b/builder/virtualbox/common/step_type_boot_command.go index 6095505af..fb596cc82 100644 --- a/builder/virtualbox/common/step_type_boot_command.go +++ b/builder/virtualbox/common/step_type_boot_command.go @@ -27,12 +27,6 @@ type bootCommandTemplateData struct { // Name is the VM's name. Name string - // EncodedSSHPublicKey is the URL encoded SSH public key in - // OpenSSH authorized_keys format. This is safe for usage - // on the the kernel command line, or other places that split - // on whitespace. - EncodedSSHPublicKey string - // SSHPublicKey is the SSH public key in OpenSSH authorized_keys format. SSHPublicKey string } @@ -72,11 +66,10 @@ func (s *StepTypeBootCommand) Run(ctx context.Context, state multistep.StateBag) hostIP := "10.0.2.2" common.SetHTTPIP(hostIP) s.Ctx.Data = &bootCommandTemplateData{ - HTTPIP: hostIP, - HTTPPort: httpPort, - Name: s.VMName, - EncodedSSHPublicKey: s.Comm.SSHPublicKeyUrlEncoded(), - SSHPublicKey: string(s.Comm.SSHPublicKey), + HTTPIP: hostIP, + HTTPPort: httpPort, + Name: s.VMName, + SSHPublicKey: string(s.Comm.SSHPublicKey), } sendCodes := func(codes []string) error { diff --git a/helper/communicator/config.go b/helper/communicator/config.go index 75ac3b2b6..7d66254b5 100644 --- a/helper/communicator/config.go +++ b/helper/communicator/config.go @@ -5,7 +5,6 @@ import ( "fmt" "io/ioutil" "net" - "net/url" "os" "time" @@ -330,9 +329,3 @@ func (c *Config) prepareWinRM(ctx *interpolate.Context) []error { return errs } - -// SSHPublicKeyUrlEncoded returns a string representing the SSH public key -// encoded in URL format. -func (c *Config) SSHPublicKeyUrlEncoded() string { - return url.PathEscape(string(c.SSHPublicKey)) -} diff --git a/helper/communicator/config_test.go b/helper/communicator/config_test.go index 9f28c2be5..c5af24114 100644 --- a/helper/communicator/config_test.go +++ b/helper/communicator/config_test.go @@ -1,7 +1,6 @@ package communicator import ( - "net/url" "reflect" "testing" @@ -137,23 +136,6 @@ func TestConfig_winrm(t *testing.T) { } } -func TestConfig_SSHPublicKeyUrlEncoded(t *testing.T) { - c := &Config{ - SSHPublicKey: []byte("ecdsa-sha2-nistp521 AAAAE2VjZHNhLXNoYTItbmlzdHA1MjEAAAAIbmlzdHA1MjEAAACFBADulbdHCjXhsH8wGtyLhhi3qVvX6M0tGgtousr/DzArwf2KX0L2Zm1OZfqMWFCrSVD743OFY60YL5CGsN9/PVQP7gApll5yTWyaQJu8lReptR5TMnUDn0u3mJN/QRT5Zs8qS5J5Q3WhXwaMF96kSuu+MwXrBnl8sK+bwxOKQtlKJXowcw==\n"), - } - - encoded := c.SSHPublicKeyUrlEncoded() - - decoded, err := url.PathUnescape(encoded) - if err != nil { - t.Fatal(err.Error()) - } - - if decoded != string(c.SSHPublicKey) { - t.Fatal("resulting public key does not match original public key") - } -} - func testContext(t *testing.T) *interpolate.Context { return nil } diff --git a/website/source/partials/builders/_virtualbox-ssh-key-pair.html.md b/website/source/partials/builders/_virtualbox-ssh-key-pair.html.md index f1f8c189c..62372174c 100644 --- a/website/source/partials/builders/_virtualbox-ssh-key-pair.html.md +++ b/website/source/partials/builders/_virtualbox-ssh-key-pair.html.md @@ -1,13 +1,10 @@ ### SSH key pair automation The VirtualBox builders can inject the current SSH key pair's public key into -the template using the following variables: +the template using the following variable: -- `SSHPublicKey` (*VirtualBox builders only*) - The SSH public key as a line - in OpenSSH authorized_keys format. -- `EncodedSSHPublicKey` (*VirtualBox builders only*) - The same as - `SSHPublicKey`, except it is URL encoded for usage in places - like the kernel command line. +- `SSHPublicKey` (*VirtualBox builders only*) - This is the SSH public key + as a line in OpenSSH authorized_keys format. When a private key is provided using `ssh_private_key_file`, the key's corresponding public key can be accessed using the above variables. @@ -16,17 +13,19 @@ If `ssh_password` and `ssh_private_key_file` are not specified, Packer will automatically generate en ephemeral key pair. The key pair's public key can be accessed using the template variables. -For example, the public key can be provided in the boot command: +For example, the public key can be provided in the boot command as a URL +encoded string by appending `| urlquery` to the variable: ```json { "type": "virtualbox-iso", "boot_command": [ - " text ks=http://{{ .HTTPIP }}:{{ .HTTPPort }}/ks.cfg PACKER_USER={{ user `username` }} PACKER_AUTHORIZED_KEY={{ .EncodedSSHPublicKey }}" + " text ks=http://{{ .HTTPIP }}:{{ .HTTPPort }}/ks.cfg PACKER_USER={{ user `username` }} PACKER_AUTHORIZED_KEY={{ .SSHPublicKey | urlquery }}" ] } ``` -The kickstart can then leverage those fields from the kernel command line: +A kickstart could then leverage those fields from the kernel command line by +decoding the URL-encoded public key: ``` %post @@ -43,8 +42,8 @@ do PACKER_USER="${x#*=}" ;; PACKER_AUTHORIZED_KEY=*) - encoded="${x#*=}" # URL decode $encoded into $PACKER_AUTHORIZED_KEY + encoded=$(echo "${x#*=}" | tr '+' ' ') printf -v PACKER_AUTHORIZED_KEY '%b' "${encoded//%/\\x}" ;; esac From 61a8968b2279dc469b5edda7471139b895d9c83d Mon Sep 17 00:00:00 2001 From: Stephen Fox Date: Thu, 28 Feb 2019 17:23:33 -0500 Subject: [PATCH 47/49] Use 'os.OpenFile' to atomically create debug key file. Per code review feedback. --- builder/virtualbox/common/step_ssh_key_pair.go | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/builder/virtualbox/common/step_ssh_key_pair.go b/builder/virtualbox/common/step_ssh_key_pair.go index 20de4655b..66577bab1 100644 --- a/builder/virtualbox/common/step_ssh_key_pair.go +++ b/builder/virtualbox/common/step_ssh_key_pair.go @@ -4,7 +4,6 @@ import ( "context" "fmt" "os" - "runtime" "github.com/hashicorp/packer/common/uuid" "github.com/hashicorp/packer/helper/communicator" @@ -78,11 +77,9 @@ func (s *StepSshKeyPair) Run(_ context.Context, state multistep.StateBag) multis // If we're in debug mode, output the private key to the working // directory. - // TODO: It would be better if the file was 'chmod' before writing - // the key to the disk - or if umask was set before creating the file. if s.Debug { ui.Message(fmt.Sprintf("Saving communicator private key for debug purposes: %s", s.DebugKeyPath)) - f, err := os.Create(s.DebugKeyPath) + f, err := os.OpenFile(s.DebugKeyPath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600) if err != nil { state.Put("error", fmt.Errorf("Error saving debug key: %s", err)) return multistep.ActionHalt @@ -94,14 +91,6 @@ func (s *StepSshKeyPair) Run(_ context.Context, state multistep.StateBag) multis state.Put("error", fmt.Errorf("Error saving debug key: %s", err)) return multistep.ActionHalt } - - // Chmod it so that it is SSH ready - if runtime.GOOS != "windows" { - if err := f.Chmod(0600); err != nil { - state.Put("error", fmt.Errorf("Error setting permissions of debug key: %s", err)) - return multistep.ActionHalt - } - } } return multistep.ActionContinue From 7e1cbc6d89962d3b908e775c24f210848d4cf8b8 Mon Sep 17 00:00:00 2001 From: Stephen Fox Date: Fri, 1 Mar 2019 10:00:29 -0500 Subject: [PATCH 48/49] Rename references to key pair name to comment. Per the OpenSSH manual, the field is for a comment. --- .../virtualbox/common/step_ssh_key_pair.go | 12 +++--- helper/ssh/key_pair.go | 43 ++++++++++--------- helper/ssh/key_pair_test.go | 40 ++++++++--------- 3 files changed, 49 insertions(+), 46 deletions(-) diff --git a/builder/virtualbox/common/step_ssh_key_pair.go b/builder/virtualbox/common/step_ssh_key_pair.go index 66577bab1..929bc9526 100644 --- a/builder/virtualbox/common/step_ssh_key_pair.go +++ b/builder/virtualbox/common/step_ssh_key_pair.go @@ -37,7 +37,7 @@ func (s *StepSshKeyPair) Run(_ context.Context, state multistep.StateBag) multis kp, err := ssh.KeyPairFromPrivateKey(ssh.FromPrivateKeyConfig{ RawPrivateKeyPemBlock: privateKeyBytes, - Name: fmt.Sprintf("packer_%s", uuid.TimeOrderedUUID()), + Comment: fmt.Sprintf("packer_%s", uuid.TimeOrderedUUID()), }) if err != nil { state.Put("error", err) @@ -45,8 +45,8 @@ func (s *StepSshKeyPair) Run(_ context.Context, state multistep.StateBag) multis } s.Comm.SSHPrivateKey = privateKeyBytes - s.Comm.SSHKeyPairName = kp.Name - s.Comm.SSHTemporaryKeyPairName = kp.Name + s.Comm.SSHKeyPairName = kp.Comment + s.Comm.SSHTemporaryKeyPairName = kp.Comment s.Comm.SSHPublicKey = kp.PublicKeyAuthorizedKeysLine return multistep.ActionContinue @@ -60,15 +60,15 @@ func (s *StepSshKeyPair) Run(_ context.Context, state multistep.StateBag) multis ui.Say("Creating ephemeral key pair for SSH communicator...") kp, err := ssh.NewKeyPair(ssh.CreateKeyPairConfig{ - Name: fmt.Sprintf("packer_%s", uuid.TimeOrderedUUID()), + Comment: fmt.Sprintf("packer_%s", uuid.TimeOrderedUUID()), }) if err != nil { state.Put("error", fmt.Errorf("Error creating temporary keypair: %s", err)) return multistep.ActionHalt } - s.Comm.SSHKeyPairName = kp.Name - s.Comm.SSHTemporaryKeyPairName = kp.Name + s.Comm.SSHKeyPairName = kp.Comment + s.Comm.SSHTemporaryKeyPairName = kp.Comment s.Comm.SSHPrivateKey = kp.PrivateKeyPemBlock s.Comm.SSHPublicKey = kp.PublicKeyAuthorizedKeysLine s.Comm.SSHClearAuthorizedKeys = true diff --git a/helper/ssh/key_pair.go b/helper/ssh/key_pair.go index f6852080d..72dd5b7f6 100644 --- a/helper/ssh/key_pair.go +++ b/helper/ssh/key_pair.go @@ -47,9 +47,10 @@ type KeyPair struct { // as a line in OpenSSH authorized_keys. PublicKeyAuthorizedKeysLine []byte - // Name is the key pair's name. This is used to identify - // the key pair in the SSH server's 'authorized_keys'. - Name string + // Comment is the key pair's comment. This is typically used + // to identify the key pair's owner in the SSH user's + // 'authorized_keys' file. + Comment string } // KeyPairFromPrivateKey returns a KeyPair loaded from an existing private key. @@ -76,7 +77,7 @@ func KeyPairFromPrivateKey(config FromPrivateKeyConfig) (KeyPair, error) { } return KeyPair{ PrivateKeyPemBlock: config.RawPrivateKeyPemBlock, - PublicKeyAuthorizedKeysLine: authorizedKeysLine(publicKey, config.Name), + PublicKeyAuthorizedKeysLine: authorizedKeysLine(publicKey, config.Comment), }, nil case *dsa.PrivateKey: publicKey, err := gossh.NewPublicKey(&pk.PublicKey) @@ -85,7 +86,7 @@ func KeyPairFromPrivateKey(config FromPrivateKeyConfig) (KeyPair, error) { } return KeyPair{ PrivateKeyPemBlock: config.RawPrivateKeyPemBlock, - PublicKeyAuthorizedKeysLine: authorizedKeysLine(publicKey, config.Name), + PublicKeyAuthorizedKeysLine: authorizedKeysLine(publicKey, config.Comment), }, nil } @@ -99,9 +100,10 @@ type FromPrivateKeyConfig struct { // should be loaded from. RawPrivateKeyPemBlock []byte - // Name is the resulting key pair's name. This is used to identify - // the key pair in the SSH server's 'authorized_keys'. - Name string + // Comment is the key pair's comment. This is typically used + // to identify the key pair's owner in the SSH user's + // 'authorized_keys' file. + Comment string } // NewKeyPair generates a new SSH key pair using the specified @@ -169,8 +171,8 @@ func newEcdsaKeyPair(config CreateKeyPairConfig) (KeyPair, error) { return KeyPair{ PrivateKeyPemBlock: privatePem, - PublicKeyAuthorizedKeysLine: authorizedKeysLine(sshPublicKey, config.Name), - Name: config.Name, + PublicKeyAuthorizedKeysLine: authorizedKeysLine(sshPublicKey, config.Comment), + Comment: config.Comment, }, nil } @@ -201,8 +203,8 @@ func newRsaKeyPair(config CreateKeyPairConfig) (KeyPair, error) { return KeyPair{ PrivateKeyPemBlock: privatePemBlock, - PublicKeyAuthorizedKeysLine: authorizedKeysLine(sshPublicKey, config.Name), - Name: config.Name, + PublicKeyAuthorizedKeysLine: authorizedKeysLine(sshPublicKey, config.Comment), + Comment: config.Comment, }, nil } @@ -216,9 +218,10 @@ type CreateKeyPairConfig struct { // 521-bit curve. Bits int - // Name is the resulting key pair's name. This is used to identify - // the key pair in the SSH server's 'authorized_keys'. - Name string + // Comment is the resulting key pair's comment. This is typically + // used to identify the key pair's owner in the SSH user's + // 'authorized_keys' file. + Comment string } // rawPemBlock encodes a pem.Block to a slice of bytes. @@ -235,19 +238,19 @@ func rawPemBlock(block *pem.Block) ([]byte, error) { // authorizedKeysLine serializes key for inclusion in an OpenSSH // authorized_keys file. The return value ends without newline so -// a key name can be appended to the end. -func authorizedKeysLine(key gossh.PublicKey, name string) []byte { +// a comment can be appended to the end. +func authorizedKeysLine(key gossh.PublicKey, comment string) []byte { marshaledPublicKey := gossh.MarshalAuthorizedKey(key) // Remove the mandatory unix new line. Awful, but the go // ssh library automatically appends a unix new line. - // We remove it so a key name can be safely appended to the + // We remove it so a key comment can be safely appended to the // end of the string. marshaledPublicKey = bytes.TrimSpace(marshaledPublicKey) - if len(strings.TrimSpace(name)) > 0 { + if len(strings.TrimSpace(comment)) > 0 { marshaledPublicKey = append(marshaledPublicKey, ' ') - marshaledPublicKey = append(marshaledPublicKey, name...) + marshaledPublicKey = append(marshaledPublicKey, comment...) } return marshaledPublicKey diff --git a/helper/ssh/key_pair_test.go b/helper/ssh/key_pair_test.go index eddb9a3cb..3c98c84e8 100644 --- a/helper/ssh/key_pair_test.go +++ b/helper/ssh/key_pair_test.go @@ -177,9 +177,9 @@ func TestNewKeyPair_ECDSA_Default(t *testing.T) { func TestNewKeyPair_ECDSA_Positive(t *testing.T) { for _, bits := range []int{521, 384, 256} { config := CreateKeyPairConfig{ - Type: Ecdsa, - Bits: bits, - Name: uuid.TimeOrderedUUID(), + Type: Ecdsa, + Bits: bits, + Comment: uuid.TimeOrderedUUID(), } kp, err := NewKeyPair(config) @@ -188,8 +188,8 @@ func TestNewKeyPair_ECDSA_Positive(t *testing.T) { } err = verifyEcdsaKeyPair(kp, expectedData{ - bits: bits, - name: config.Name, + bits: bits, + comment: config.Comment, }) if err != nil { t.Fatal(err.Error()) @@ -212,9 +212,9 @@ func TestNewKeyPair_ECDSA_Negative(t *testing.T) { func TestNewKeyPair_RSA_Positive(t *testing.T) { for _, bits := range []int{4096, 2048} { config := CreateKeyPairConfig{ - Type: Rsa, - Bits: bits, - Name: uuid.TimeOrderedUUID(), + Type: Rsa, + Bits: bits, + Comment: uuid.TimeOrderedUUID(), } kp, err := NewKeyPair(config) @@ -223,8 +223,8 @@ func TestNewKeyPair_RSA_Positive(t *testing.T) { } err = verifyRsaKeyPair(kp, expectedData{ - bits: config.Bits, - name: config.Name, + bits: config.Bits, + comment: config.Comment, }) if err != nil { t.Fatal(err.Error()) @@ -316,8 +316,8 @@ type fromPrivateExpectedData struct { } type expectedData struct { - bits int - name string + bits int + comment string } func verifyEcdsaKeyPair(kp KeyPair, e expectedData) error { @@ -341,9 +341,9 @@ func verifyEcdsaKeyPair(kp KeyPair, e expectedData) error { } expectedBytes := bytes.TrimSuffix(gossh.MarshalAuthorizedKey(publicKey), []byte("\n")) - if len(e.name) > 0 { + if len(e.comment) > 0 { expectedBytes = append(expectedBytes, ' ') - expectedBytes = append(expectedBytes, e.name...) + expectedBytes = append(expectedBytes, e.comment...) } if !bytes.Equal(expectedBytes, kp.PublicKeyAuthorizedKeysLine) { @@ -375,9 +375,9 @@ func verifyRsaKeyPair(kp KeyPair, e expectedData) error { } expectedBytes := bytes.TrimSuffix(gossh.MarshalAuthorizedKey(publicKey), []byte("\n")) - if len(e.name) > 0 { + if len(e.comment) > 0 { expectedBytes = append(expectedBytes, ' ') - expectedBytes = append(expectedBytes, e.name...) + expectedBytes = append(expectedBytes, e.comment...) } if !bytes.Equal(expectedBytes, kp.PublicKeyAuthorizedKeysLine) { @@ -405,9 +405,9 @@ func verifyDsaKeyPair(kp KeyPair, e fromPrivateExpectedData) error { } expectedBytes := bytes.TrimSuffix(gossh.MarshalAuthorizedKey(publicKey), []byte("\n")) - if len(e.d.name) > 0 { + if len(e.d.comment) > 0 { expectedBytes = append(expectedBytes, ' ') - expectedBytes = append(expectedBytes, e.d.name...) + expectedBytes = append(expectedBytes, e.d.comment...) } if !bytes.Equal(expectedBytes, kp.PublicKeyAuthorizedKeysLine) { @@ -435,9 +435,9 @@ func verifyEd25519KeyPair(kp KeyPair, e fromPrivateExpectedData) error { } expectedBytes := bytes.TrimSuffix(gossh.MarshalAuthorizedKey(publicKey), []byte("\n")) - if len(e.d.name) > 0 { + if len(e.d.comment) > 0 { expectedBytes = append(expectedBytes, ' ') - expectedBytes = append(expectedBytes, e.d.name...) + expectedBytes = append(expectedBytes, e.d.comment...) } if !bytes.Equal(expectedBytes, kp.PublicKeyAuthorizedKeysLine) { From d51e300fcd74ad3e7d0ab5414eddc3b6868f2c7d Mon Sep 17 00:00:00 2001 From: Stephen Fox Date: Fri, 1 Mar 2019 12:17:13 -0500 Subject: [PATCH 49/49] Clarified comment for 'defaultRsaBits' constant. That *is* a lot of bits. --- helper/ssh/key_pair.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/helper/ssh/key_pair.go b/helper/ssh/key_pair.go index 72dd5b7f6..fd052dc2e 100644 --- a/helper/ssh/key_pair.go +++ b/helper/ssh/key_pair.go @@ -17,7 +17,8 @@ import ( ) const ( - // That's a lot of bits. + // defaultRsaBits is the default bits of entropy for a new RSA + // key pair. That's a lot of bits. defaultRsaBits = 4096 // Markers for various SSH key pair types.