From 6855c480ad1c26c49884d75f79dcb4b159bd7095 Mon Sep 17 00:00:00 2001 From: Rickard von Essen Date: Sun, 31 Jan 2016 20:03:59 +0100 Subject: [PATCH] OpenStack: convert SSH keys from BER to DER with OpenSSL, work around for GH-2526. Try to parse the temporary keypair received from OpenStack, if it fails try to use OpenSSL to convert it from BER encoding to DER. --- builder/openstack/step_key_pair.go | 48 +++++++++ builder/openstack/step_key_pair_test.go | 101 ++++++++++++++++++ .../docs/builders/openstack.html.markdown | 7 ++ 3 files changed, 156 insertions(+) create mode 100644 builder/openstack/step_key_pair_test.go diff --git a/builder/openstack/step_key_pair.go b/builder/openstack/step_key_pair.go index 32d7430ef..2fcb8bc51 100644 --- a/builder/openstack/step_key_pair.go +++ b/builder/openstack/step_key_pair.go @@ -3,13 +3,16 @@ package openstack import ( "fmt" "io/ioutil" + "log" "os" + "os/exec" "runtime" "github.com/mitchellh/multistep" "github.com/mitchellh/packer/common/uuid" "github.com/mitchellh/packer/packer" "github.com/rackspace/gophercloud/openstack/compute/v2/extensions/keypairs" + "golang.org/x/crypto/ssh" ) type StepKeyPair struct { @@ -64,6 +67,8 @@ func (s *StepKeyPair) Run(state multistep.StateBag) multistep.StepAction { ui.Say(fmt.Sprintf("Created temporary keypair: %s", keyName)) + keypair.PrivateKey = berToDer(keypair.PrivateKey, ui) + // If we're in debug mode, output the private key to the working // directory. if s.Debug { @@ -100,6 +105,49 @@ func (s *StepKeyPair) Run(state multistep.StateBag) multistep.StepAction { return multistep.ActionContinue } +// Work around for https://github.com/mitchellh/packer/issues/2526 +func berToDer(ber string, ui packer.Ui) string { + // Check if x/crypto/ssh can parse the key + _, err := ssh.ParsePrivateKey([]byte(ber)) + if err == nil { + return ber + } + // Can't parse the key, maybe it's BER encoded. Try to convert it with OpenSSL. + log.Println("Couldn't parse SSH key, trying work around for [GH-2526].") + + openSslPath, err := exec.LookPath("openssl") + if err != nil { + log.Println("Couldn't find OpenSSL, aborting work around.") + return ber + } + + berKey, err := ioutil.TempFile("", "packer-ber-privatekey-") + defer os.Remove(berKey.Name()) + if err != nil { + return ber + } + ioutil.WriteFile(berKey.Name(), []byte(ber), os.ModeAppend) + derKey, err := ioutil.TempFile("", "packer-der-privatekey-") + defer os.Remove(derKey.Name()) + if err != nil { + return ber + } + + args := []string{"rsa", "-in", berKey.Name(), "-out", derKey.Name()} + log.Printf("Executing: %s %v", openSslPath, args) + if err := exec.Command(openSslPath, args...).Run(); err != nil { + log.Printf("OpenSSL failed with error: %s", err) + return ber + } + + der, err := ioutil.ReadFile(derKey.Name()) + if err != nil { + return ber + } + ui.Say("Successfully converted BER encoded SSH key to DER encoding.") + return string(der) +} + func (s *StepKeyPair) Cleanup(state multistep.StateBag) { // If we used an SSH private key file, do not go about deleting // keypairs diff --git a/builder/openstack/step_key_pair_test.go b/builder/openstack/step_key_pair_test.go new file mode 100644 index 000000000..67b6ed558 --- /dev/null +++ b/builder/openstack/step_key_pair_test.go @@ -0,0 +1,101 @@ +package openstack + +import ( + "bytes" + "github.com/mitchellh/packer/packer" + "golang.org/x/crypto/ssh" + "os/exec" + "testing" +) + +var ber_encoded_key = ` +-----BEGIN RSA PRIVATE KEY----- +MIIEqQIBAAKCAQEAo1C/iQtu7iSUJROyQgKm5VtJbaPg5GEP7IC+7TTZ2kVnWa9V +MU37sXKWk0J7oRHcSZ/dhRyNDcsvscFAU6V3FRpZxiTEvCqIBgLyoV3g+wkan+gD +AqNmqm77ldUIwMXT7n3AeWipw0uOBzZNjANhAf8qHIT2PXoT6LfaCof4PlPucCTx +eC+oT5zA/MbQoGneNkHnR26ijMac2vRC90+1WpZ5KwCVE6qd2kERlb9gbAGsNLE/ +WrqR7bx4d8esLtE3l5zwkBTB63KdojMCrX/ZBiHT15TBQVsFPQqhdBT3BXfssnut +MkCf6+X0PIQkQcW6RqKR5nOHMFdB1kEChaKYMwIDAQABAoIBAQCRBPv/opJvjzWp +stLAgQBYe/Y5EKN7yKDOPwjLM/obMzPx1JqOvJO6X2lL/GYxgd2d1wJq2A586CdC +7brETBLxP0PmifHUsOO2itmO5wEHiW8F/Yzmw9g/kWuAAfrSyxhFF49Zf9H3ZFkL +GHJF2R5EGqP3TS4nKwcQyGkqntCV7p+QmPvlB05jT3vsc2jLn2yXXVEbu1X5Zj82 +cdFwH4ZSc2BDv8ixBHy+zOGLx0TMF0hxEHdNIeAjGTyYfiyDr5mgMP4w6igGvP8q +pB5fE60ZKCEPLGyxMw69nDbXJK6YAKyNCVAD79FEl1yLYJlWfYAtOI6UeE0RUPeD +i42vINyBAoIAgQC9zZzl4kJUTdzYLYrxQ7f51B+eOF621kleTYLprr8I4VZUExht +KtsHdHWzMkxVtd7Q7UcG9L+Pqa3DVnD3S2C1IuUkO3eCpa93jhiwCIRYQfR7EzuR +ntVrQHfYaCgr8ahmWdoeZUr8lSDDp0/h+MamEksNEjZA+XwFWg6ycLgGwQKCAIEA +3EYz5iHqaOdekBYXcFpkq8uk+Sn27Cmnd7pLfK/YWEAb3Pie9X0pftA1O8z0tW9p +vEBS+pA27S55a38avmZSkZAiXMJOZ3HV99VZi5WU0Pg9A9oIiQvW6td9b1uuVl5q +o9fCK/r17E7NCFimtqSNtrNR/X08g+l4Dp1Ourgm7/MCggCAIGMohbWhGd+bcqv6 +zIaAqzm+F3KI/uv74wKY9yUhZfOFlp0XivFIJLKDrwtDKVD6b249s3sqAOq0QuPK +LPiIzP/iV9dp4jpBgcYWgltBsgm3HRVAEe4nfsCmcp/7UtxOnwBwDsW8EPOlfp1b +LTUVOJtggR99cILh3cvrPBmt3UECggCBALsRH7g8a1+lxmglashu6/n+G1/DZMER +avjCDKOajuf7oe4acpzXK6tX1S2xFM0VDj3iftXuLcdl5ZYGPsceDNc0CgqutXki +cu1jkgV6BgUmHGMuAnuow19znEI7ISaWTohQjsVc/wctsPB6oTKRMwzK40Gc3wzD +9MKsk5T9GYxDAoIAgAFW9agGS4th1RrVwngjKHCPenQR1QSvHDHv3E3EQta1d0K3 +Cx7RCdBpDNeNIpHVq8uTaWjSZ+Em6YDja7CqIfPj0HcykizxS4L7Dh0gt7I5+kuy +yBgJhEgIw3mK7U47nm9MmR/67RI4IS978ekVXP2oGdYqVUhHvMuOix8a72xk +-----END RSA PRIVATE KEY----- +` + +var der_encoded_key = ` +-----BEGIN RSA PRIVATE KEY----- +MIIEpAIBAAKCAQEAmfhARpOHdm5i551GEtOO2NEH36Dm2NjJp6eHQpnrwDW0bqd6 +qHJSmhocSb/3r2WwPOsfBKa135aCEOqXKcrc2gY+IsgGMF8bFG//UVcHA0KGG9A8 +mWwYBIU4lpb7DY54IllDDO11BERrxOEsPqmNgGzE0yOYApC2t8OcXsUE15kznhLn +d4V53kN//jRGR44KrGMcL8nrKDIBudz90ae7tRGBHYiMWPB89yYdUt06dXPLqqbf +JIUhuOBwle3IOuwJPmPEwsXFXkxKCYzKDAyi+weCDKtqeUl3HNbCB0f7g/uxUDhQ +wRzZ1v4g35LHh5zQR26dizl4Uj2dNLL6K5xkPwIDAQABAoIBABdpTPSuSAG1BSrs +mhQQwP6swgK553//bqIkcgepedRPFjFhG+BzCaZO5BA+tT2hO6v3oE7Hvo3Rx9Mk +qHl9VBl+q4IEYhSG0YpJAUxv7CwNuHCQODan3fsJ+rHDIUdNa2zln7FehdVxReW4 +y05334EwiLkGB34UXQQSJTuvv228rF5F7wCs0luZWJ9IpGNWpwI2K6pg3ME/bIb6 +xFMf6jKWUUZht1m+40UG2bhsMPPknde2zAq4DjJTl7bJuuhceqp3ylm5h+oUDCl2 +twyGhn8gpHrMXVCcEvxwno8aJfNJ18iZu+lIQTT1DVuRrFprGDc9dFW6zVFauv6W +ZG5AUXkCgYEAyoBUPVFaf3E6nDqq26J5x6+DBe6afS7tqNn5E/z2cI54KLiUDwjs +P9axjdVMqqUiNw0jJRi8nUe1psDLguSZo4tFSgsWbsdnZQutQz2fy87jIgZOzZpj +fqud64O/fMm8fpGBz5LQjo27FLRfQcr23KVjWeQgBSMfMk2oe8Mw8+sCgYEAwqWc +abQBpG5TqtgvR3M9W0tAK51SSZF5okKif8KMoDJ67pXX2iTgh4bnUugaYObcG3qV +5VH9xlOuN4td4RfBybzRcKJDN/oinwZhMjQKKjyhpjLInDqnF4ogLPevT6cCq8I+ +rHfK6DhtqLUG4BYDD7QWOBZpCTwd3n+7Xk39v/0CgYEAoaM9mpRNgFyJRBswNpDC +VDosg5epiTLkUVtsDiBlNgMCtr5esIGW0n40y9nukGevn/HEk9/i7khHHwvVZm3C +lWCdtjSTe2l/hpCDhKCz5KMHeik+za7mrD2gmFVZi+obo4vR6jZuctt+8U/omUPB +OO5rF12YkYEvbZ+/VMrBUHECgYEArWGpvvpJ0Dc6Ld9d1e5PxCd2pKMBLmj4CNIE +P3uDmhr9J9KvsC/TFMXU/iOjg5eAjrWWGev7+pKFiBKLcDqiMtoPUZ4n9A/KkQ60 +u2xhdZgGga2QxqD0P+KYoJWMQo5IschX3XbjdhD1lSaTVj4lQfKvLAzCSSiUjqIG +u40LL90CgYB9t3CISlVJ+l1zy7QPIvAul5VuhwjxBHaotdLuXzvXFJXZH2dQ9j/a +/p2GMUq+VZbKq45x4I5Z3ax462qMRDmpx9yjtrwNavDV47uf34pQrNpuWO7kQfsM +mKMH6Gf6COfSIbLuejdzSOUAmjkFpm+nwBkka1eHdAy4ALn9wNQz3w== +-----END RSA PRIVATE KEY----- +` + +func TestBerToDer(t *testing.T) { + _, err := exec.LookPath("openssl") + if err != nil { + t.Skipf("OpenSSL not availible skippint test.") + } + + msg := new(bytes.Buffer) + ui := &packer.BasicUi{ + Reader: new(bytes.Buffer), + Writer: msg, + } + + // Test - a DER encoded key commes back unchanged. + newKey := berToDer(der_encoded_key, ui) + if newKey != der_encoded_key { + t.Errorf("Trying to convert a DER encoded key should return the same key.") + } + if string(msg.Bytes()) != "" { + t.Errorf("Doing nothing with a DER encoded key result in no messages to the UI .") + } + + // Test - a BER encoded key should be converted to DER. + newKey = berToDer(ber_encoded_key, ui) + _, err = ssh.ParsePrivateKey([]byte(newKey)) + if err != nil { + t.Errorf("Trying to convert a BER encoded key should return a DER encoded key parsable by Go.") + } + if string(msg.Bytes()) != "Successfully converted BER encoded SSH key to DER encoding.\n" { + t.Errorf("Trying to convert a BER encoded key should tell the UI about the success.") + } +} diff --git a/website/source/docs/builders/openstack.html.markdown b/website/source/docs/builders/openstack.html.markdown index e8f14d506..cc240e3f5 100644 --- a/website/source/docs/builders/openstack.html.markdown +++ b/website/source/docs/builders/openstack.html.markdown @@ -26,6 +26,13 @@ created. This simplifies configuration quite a bit. The builder does *not* manage images. Once it creates an image, it is up to you to use it or delete it. +\~> **OpenStack Liberty or later requires OpenSSL!** To use the OpenStack +builder with OpenStack Liberty (Oct 2015) or later you need to have OpenSSL +installed _if you are using temporary key pairs_, i.g. don't use +[`ssh_keypair_name`](openstack.html#ssh_keypair_name) nor +[`ssh_password`](/docs/templates/communicator.html#ssh_password). All major +OS'es have OpenSSL installed by default except Windows. + ## Configuration Reference There are many configuration options available for the builder. They are