Merge pull request #4606 from hashicorp/tls-public-key

Export public keys from tls_private_key
This commit is contained in:
Martin Atkins 2016-01-21 17:01:18 -08:00
commit 7450abe408
3 changed files with 123 additions and 14 deletions

View File

@ -9,6 +9,8 @@ import (
"encoding/pem"
"fmt"
"golang.org/x/crypto/ssh"
"github.com/hashicorp/terraform/helper/schema"
)
@ -80,6 +82,16 @@ func resourcePrivateKey() *schema.Resource {
Type: schema.TypeString,
Computed: true,
},
"public_key_pem": &schema.Schema{
Type: schema.TypeString,
Computed: true,
},
"public_key_openssh": &schema.Schema{
Type: schema.TypeString,
Computed: true,
},
},
}
}
@ -100,25 +112,47 @@ func CreatePrivateKey(d *schema.ResourceData, meta interface{}) error {
var keyPemBlock *pem.Block
switch k := key.(type) {
case *rsa.PrivateKey:
keyPemBlock = &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(k)}
keyPemBlock = &pem.Block{
Type: "RSA PRIVATE KEY",
Bytes: x509.MarshalPKCS1PrivateKey(k),
}
case *ecdsa.PrivateKey:
b, err := x509.MarshalECPrivateKey(k)
keyBytes, err := x509.MarshalECPrivateKey(k)
if err != nil {
return fmt.Errorf("error encoding key to PEM: %s", err)
}
keyPemBlock = &pem.Block{Type: "EC PRIVATE KEY", Bytes: b}
keyPemBlock = &pem.Block{
Type: "EC PRIVATE KEY",
Bytes: keyBytes,
}
default:
return fmt.Errorf("unsupported private key type")
}
keyPem := string(pem.EncodeToMemory(keyPemBlock))
pubKeyBytes, err := x509.MarshalPKIXPublicKey(publicKey(key))
pubKey := publicKey(key)
pubKeyBytes, err := x509.MarshalPKIXPublicKey(pubKey)
if err != nil {
return fmt.Errorf("failed to marshal public key: %s", err)
}
pubKeyPemBlock := &pem.Block{
Type: "PUBLIC KEY",
Bytes: pubKeyBytes,
}
d.SetId(hashForState(string((pubKeyBytes))))
d.Set("private_key_pem", keyPem)
d.Set("public_key_pem", string(pem.EncodeToMemory(pubKeyPemBlock)))
sshPubKey, err := ssh.NewPublicKey(pubKey)
if err == nil {
// Not all EC types can be SSH keys, so we'll produce this only
// if an appropriate type was selected.
sshPubKeyBytes := ssh.MarshalAuthorizedKey(sshPubKey)
d.Set("public_key_openssh", string(sshPubKeyBytes))
} else {
d.Set("public_key_openssh", "")
}
return nil
}

View File

@ -18,18 +18,35 @@ func TestPrivateKeyRSA(t *testing.T) {
resource "tls_private_key" "test" {
algorithm = "RSA"
}
output "key_pem" {
output "private_key_pem" {
value = "${tls_private_key.test.private_key_pem}"
}
output "public_key_pem" {
value = "${tls_private_key.test.public_key_pem}"
}
output "public_key_openssh" {
value = "${tls_private_key.test.public_key_openssh}"
}
`,
Check: func(s *terraform.State) error {
got := s.RootModule().Outputs["key_pem"]
if !strings.HasPrefix(got, "-----BEGIN RSA PRIVATE KEY----") {
return fmt.Errorf("key is missing RSA key PEM preamble")
gotPrivate := s.RootModule().Outputs["private_key_pem"]
if !strings.HasPrefix(gotPrivate, "-----BEGIN RSA PRIVATE KEY----") {
return fmt.Errorf("private key is missing RSA key PEM preamble")
}
if len(got) > 1700 {
return fmt.Errorf("key PEM looks too long for a 2048-bit key (got %v characters)", len(got))
if len(gotPrivate) > 1700 {
return fmt.Errorf("private key PEM looks too long for a 2048-bit key (got %v characters)", len(gotPrivate))
}
gotPublic := s.RootModule().Outputs["public_key_pem"]
if !strings.HasPrefix(gotPublic, "-----BEGIN PUBLIC KEY----") {
return fmt.Errorf("public key is missing public key PEM preamble")
}
gotPublicSSH := s.RootModule().Outputs["public_key_openssh"]
if !strings.HasPrefix(gotPublicSSH, "ssh-rsa ") {
return fmt.Errorf("SSH public key is missing ssh-rsa prefix")
}
return nil
},
},
@ -67,15 +84,67 @@ func TestPrivateKeyECDSA(t *testing.T) {
resource "tls_private_key" "test" {
algorithm = "ECDSA"
}
output "key_pem" {
output "private_key_pem" {
value = "${tls_private_key.test.private_key_pem}"
}
output "public_key_pem" {
value = "${tls_private_key.test.public_key_pem}"
}
output "public_key_openssh" {
value = "${tls_private_key.test.public_key_openssh}"
}
`,
Check: func(s *terraform.State) error {
got := s.RootModule().Outputs["key_pem"]
if !strings.HasPrefix(got, "-----BEGIN EC PRIVATE KEY----") {
return fmt.Errorf("Key is missing EC key PEM preamble")
gotPrivate := s.RootModule().Outputs["private_key_pem"]
if !strings.HasPrefix(gotPrivate, "-----BEGIN EC PRIVATE KEY----") {
return fmt.Errorf("Private key is missing EC key PEM preamble")
}
gotPublic := s.RootModule().Outputs["public_key_pem"]
if !strings.HasPrefix(gotPublic, "-----BEGIN PUBLIC KEY----") {
return fmt.Errorf("public key is missing public key PEM preamble")
}
gotPublicSSH := s.RootModule().Outputs["public_key_openssh"]
if gotPublicSSH != "" {
return fmt.Errorf("P224 EC key should not generate OpenSSH public key")
}
return nil
},
},
r.TestStep{
Config: `
resource "tls_private_key" "test" {
algorithm = "ECDSA"
ecdsa_curve = "P256"
}
output "private_key_pem" {
value = "${tls_private_key.test.private_key_pem}"
}
output "public_key_pem" {
value = "${tls_private_key.test.public_key_pem}"
}
output "public_key_openssh" {
value = "${tls_private_key.test.public_key_openssh}"
}
`,
Check: func(s *terraform.State) error {
gotPrivate := s.RootModule().Outputs["private_key_pem"]
if !strings.HasPrefix(gotPrivate, "-----BEGIN EC PRIVATE KEY----") {
return fmt.Errorf("Private key is missing EC key PEM preamble")
}
gotPublic := s.RootModule().Outputs["public_key_pem"]
if !strings.HasPrefix(gotPublic, "-----BEGIN PUBLIC KEY----") {
return fmt.Errorf("public key is missing public key PEM preamble")
}
gotPublicSSH := s.RootModule().Outputs["public_key_openssh"]
if !strings.HasPrefix(gotPublicSSH, "ecdsa-sha2-nistp256 ") {
return fmt.Errorf("P256 SSH public key is missing ecdsa prefix")
}
return nil
},
},

View File

@ -50,6 +50,12 @@ The following attributes are exported:
* `algorithm` - The algorithm that was selected for the key.
* `private_key_pem` - The private key data in PEM format.
* `public_key_pem` - The public key data in PEM format.
* `public_key_openssh` - The public key data in OpenSSH `authorized_keys`
format, if the selected private key format is compatible. All RSA keys
are supported, and ECDSA keys with curves "P256", "P384" and "P251"
are supported. This attribute is empty if an incompatible ECDSA curve
is selected.
## Generating a New Key