Skip to content

Commit

Permalink
Merge pull request #4606 from hashicorp/tls-public-key
Browse files Browse the repository at this point in the history
Export public keys from tls_private_key
  • Loading branch information
apparentlymart committed Jan 22, 2016
2 parents 1b39086 + 25bd43d commit 7450abe
Show file tree
Hide file tree
Showing 3 changed files with 123 additions and 14 deletions.
42 changes: 38 additions & 4 deletions builtin/providers/tls/resource_private_key.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import (
"encoding/pem"
"fmt"

"golang.org/x/crypto/ssh"

"github.com/hashicorp/terraform/helper/schema"
)

Expand Down Expand Up @@ -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,
},
},
}
}
Expand All @@ -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
}
Expand Down
89 changes: 79 additions & 10 deletions builtin/providers/tls/resource_private_key_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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(gotPrivate) > 1700 {
return fmt.Errorf("private key PEM looks too long for a 2048-bit key (got %v characters)", len(gotPrivate))
}
if len(got) > 1700 {
return fmt.Errorf("key PEM looks too long for a 2048-bit key (got %v characters)", len(got))

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
},
},
Expand Down Expand Up @@ -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
},
},
Expand Down
6 changes: 6 additions & 0 deletions website/source/docs/providers/tls/r/private_key.html.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down

0 comments on commit 7450abe

Please sign in to comment.