Export public keys from tls_private_key
In most cases private keys are used to produce certs and cert requests, but there are some less-common cases where the PEM-formatted keypair is used alone. The public_key_pem attribute supports such cases. This also includes a public_key_openssh attribute, which allows this resource to be used to generate temporary OpenSSH credentials, so that e.g. a Terraform configuration could generate its own keypair to use with the aws_key_pair resource. This has the same caveats as all cases where we generate private keys in Terraform, but could be useful for temporary/throwaway environments where the state either doesn't live for long or is stored securely. This builds on work started by Simarpreet Singh in #4441 .
This commit is contained in:
parent
e8006f1539
commit
25bd43d6f4
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
},
|
||||
},
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
Loading…
Reference in New Issue