provider/aws: New data provider to decrypt KMS secrets (#11460)
* Add a new data provider to decrypt AWS KMS secrets * Address feedback * Rename aws_kms_secrets to aws_kms_secret * Add more examples to the documentation
This commit is contained in:
parent
3913f06d46
commit
01a6bd7592
|
@ -0,0 +1,99 @@
|
||||||
|
package aws
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/base64"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/aws/aws-sdk-go/aws"
|
||||||
|
"github.com/aws/aws-sdk-go/service/kms"
|
||||||
|
"github.com/hashicorp/terraform/helper/schema"
|
||||||
|
)
|
||||||
|
|
||||||
|
func dataSourceAwsKmsSecret() *schema.Resource {
|
||||||
|
return &schema.Resource{
|
||||||
|
Read: dataSourceAwsKmsSecretRead,
|
||||||
|
|
||||||
|
Schema: map[string]*schema.Schema{
|
||||||
|
"secret": &schema.Schema{
|
||||||
|
Type: schema.TypeSet,
|
||||||
|
Required: true,
|
||||||
|
ForceNew: true,
|
||||||
|
Elem: &schema.Resource{
|
||||||
|
Schema: map[string]*schema.Schema{
|
||||||
|
"name": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Required: true,
|
||||||
|
},
|
||||||
|
"payload": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Required: true,
|
||||||
|
},
|
||||||
|
"context": &schema.Schema{
|
||||||
|
Type: schema.TypeMap,
|
||||||
|
Optional: true,
|
||||||
|
Elem: &schema.Schema{Type: schema.TypeString},
|
||||||
|
},
|
||||||
|
"grant_tokens": &schema.Schema{
|
||||||
|
Type: schema.TypeList,
|
||||||
|
Optional: true,
|
||||||
|
Elem: &schema.Schema{Type: schema.TypeString},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"__has_dynamic_attributes": {
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Optional: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// dataSourceAwsKmsSecretRead decrypts the specified secrets
|
||||||
|
func dataSourceAwsKmsSecretRead(d *schema.ResourceData, meta interface{}) error {
|
||||||
|
conn := meta.(*AWSClient).kmsconn
|
||||||
|
secrets := d.Get("secret").(*schema.Set)
|
||||||
|
|
||||||
|
d.SetId(time.Now().UTC().String())
|
||||||
|
|
||||||
|
for _, v := range secrets.List() {
|
||||||
|
secret := v.(map[string]interface{})
|
||||||
|
|
||||||
|
// base64 decode the payload
|
||||||
|
payload, err := base64.StdEncoding.DecodeString(secret["payload"].(string))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Invalid base64 value for secret '%s': %v", secret["name"].(string), err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// build the kms decrypt params
|
||||||
|
params := &kms.DecryptInput{
|
||||||
|
CiphertextBlob: []byte(payload),
|
||||||
|
}
|
||||||
|
if context, exists := secret["context"]; exists {
|
||||||
|
params.EncryptionContext = make(map[string]*string)
|
||||||
|
for k, v := range context.(map[string]interface{}) {
|
||||||
|
params.EncryptionContext[k] = aws.String(v.(string))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if grant_tokens, exists := secret["grant_tokens"]; exists {
|
||||||
|
params.GrantTokens = make([]*string, 0)
|
||||||
|
for _, v := range grant_tokens.([]interface{}) {
|
||||||
|
params.GrantTokens = append(params.GrantTokens, aws.String(v.(string)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// decrypt
|
||||||
|
resp, err := conn.Decrypt(params)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Failed to decrypt '%s': %s", secret["name"].(string), err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the secret via the name
|
||||||
|
log.Printf("[DEBUG] aws_kms_secret - successfully decrypted secret: %s", secret["name"].(string))
|
||||||
|
d.UnsafeSetFieldRaw(secret["name"].(string), string(resp.Plaintext))
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,96 @@
|
||||||
|
package aws
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/base64"
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/aws/aws-sdk-go/aws"
|
||||||
|
"github.com/aws/aws-sdk-go/service/kms"
|
||||||
|
|
||||||
|
"github.com/hashicorp/terraform/helper/resource"
|
||||||
|
"github.com/hashicorp/terraform/terraform"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestAccAWSKmsSecretDataSource_basic(t *testing.T) {
|
||||||
|
// Run a resource test to setup our KMS key
|
||||||
|
resource.Test(t, resource.TestCase{
|
||||||
|
PreCheck: func() { testAccPreCheck(t) },
|
||||||
|
Providers: testAccProviders,
|
||||||
|
Steps: []resource.TestStep{
|
||||||
|
{
|
||||||
|
Config: testAccCheckAwsKmsSecretDataSourceKey,
|
||||||
|
Check: func(s *terraform.State) error {
|
||||||
|
encryptedPayload, err := testAccCheckAwsKmsSecretDataSourceCheckKeySetup(s)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// We run the actual test on our data source nested in the
|
||||||
|
// Check function of the KMS key so we can access the
|
||||||
|
// encrypted output, above, and so that the key will be
|
||||||
|
// deleted at the end of the test
|
||||||
|
resource.Test(t, resource.TestCase{
|
||||||
|
PreCheck: func() { testAccPreCheck(t) },
|
||||||
|
Providers: testAccProviders,
|
||||||
|
Steps: []resource.TestStep{
|
||||||
|
{
|
||||||
|
Config: fmt.Sprintf(testAccCheckAwsKmsSecretDataSourceSecret, encryptedPayload),
|
||||||
|
Check: resource.ComposeTestCheckFunc(
|
||||||
|
resource.TestCheckResourceAttr("data.aws_kms_secret.testing", "secret_name", "PAYLOAD"),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func testAccCheckAwsKmsSecretDataSourceCheckKeySetup(s *terraform.State) (string, error) {
|
||||||
|
rs, ok := s.RootModule().Resources["aws_kms_key.terraform_data_source_testing"]
|
||||||
|
if !ok {
|
||||||
|
return "", fmt.Errorf("Failed to setup a KMS key for data source testing!")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now that the key is setup encrypt a string using it
|
||||||
|
// XXX TODO: Set up and test with grants
|
||||||
|
params := &kms.EncryptInput{
|
||||||
|
KeyId: aws.String(rs.Primary.Attributes["arn"]),
|
||||||
|
Plaintext: []byte("PAYLOAD"),
|
||||||
|
EncryptionContext: map[string]*string{
|
||||||
|
"name": aws.String("value"),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
kmsconn := testAccProvider.Meta().(*AWSClient).kmsconn
|
||||||
|
resp, err := kmsconn.Encrypt(params)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("Failed encrypting string with KMS for data source testing: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return base64.StdEncoding.EncodeToString(resp.CiphertextBlob), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
const testAccCheckAwsKmsSecretDataSourceKey = `
|
||||||
|
resource "aws_kms_key" "terraform_data_source_testing" {
|
||||||
|
description = "Testing the Terraform AWS KMS Secret data_source"
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
const testAccCheckAwsKmsSecretDataSourceSecret = `
|
||||||
|
data "aws_kms_secret" "testing" {
|
||||||
|
secret {
|
||||||
|
name = "secret_name"
|
||||||
|
payload = "%s"
|
||||||
|
|
||||||
|
context {
|
||||||
|
name = "value"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`
|
|
@ -184,6 +184,7 @@ func Provider() terraform.ResourceProvider {
|
||||||
"aws_vpc_endpoint": dataSourceAwsVpcEndpoint(),
|
"aws_vpc_endpoint": dataSourceAwsVpcEndpoint(),
|
||||||
"aws_vpc_endpoint_service": dataSourceAwsVpcEndpointService(),
|
"aws_vpc_endpoint_service": dataSourceAwsVpcEndpointService(),
|
||||||
"aws_vpc_peering_connection": dataSourceAwsVpcPeeringConnection(),
|
"aws_vpc_peering_connection": dataSourceAwsVpcPeeringConnection(),
|
||||||
|
"aws_kms_secret": dataSourceAwsKmsSecret(),
|
||||||
},
|
},
|
||||||
|
|
||||||
ResourcesMap: map[string]*schema.Resource{
|
ResourcesMap: map[string]*schema.Resource{
|
||||||
|
|
|
@ -0,0 +1,86 @@
|
||||||
|
---
|
||||||
|
layout: "aws"
|
||||||
|
page_title: "AWS: aws_kms_secret"
|
||||||
|
sidebar_current: "docs-aws-datasource-kms-secret"
|
||||||
|
description: |-
|
||||||
|
Provides secret data encrypted with the KMS service
|
||||||
|
---
|
||||||
|
|
||||||
|
# aws\_kms\_secret
|
||||||
|
|
||||||
|
The KMS secret data source allows you to use data encrypted with the AWS KMS
|
||||||
|
service within your resource definitions.
|
||||||
|
|
||||||
|
## Note about encrypted data
|
||||||
|
|
||||||
|
Using this data provider will allow you to conceal secret data within your
|
||||||
|
resource definitions but does not take care of protecting that data in the
|
||||||
|
logging output, plan output or state output.
|
||||||
|
|
||||||
|
Please take care to secure your secret data outside of resource definitions.
|
||||||
|
|
||||||
|
## Example Usage
|
||||||
|
|
||||||
|
First, let's encrypt a password with KMS using the [AWS CLI
|
||||||
|
tools](http://docs.aws.amazon.com/cli/latest/reference/kms/encrypt.html). This
|
||||||
|
requires you to have your AWS CLI setup correctly, and you would replace the
|
||||||
|
key-id with your own.
|
||||||
|
|
||||||
|
```
|
||||||
|
$ echo 'master-password' > plaintext-password
|
||||||
|
$ aws kms encrypt \
|
||||||
|
> --key-id ab123456-c012-4567-890a-deadbeef123 \
|
||||||
|
> --plaintext fileb://plaintext-example \
|
||||||
|
> --encryption-context foo=bar \
|
||||||
|
> --output text --query CiphertextBlob
|
||||||
|
AQECAHgaPa0J8WadplGCqqVAr4HNvDaFSQ+NaiwIBhmm6qDSFwAAAGIwYAYJKoZIhvcNAQcGoFMwUQIBADBMBgkqhkiG9w0BBwEwHgYJYIZIAWUDBAEuMBEEDI+LoLdvYv8l41OhAAIBEIAfx49FFJCLeYrkfMfAw6XlnxP23MmDBdqP8dPp28OoAQ==
|
||||||
|
```
|
||||||
|
|
||||||
|
Now, take that output and add it to your resource definitions.
|
||||||
|
|
||||||
|
```
|
||||||
|
data "aws_kms_secret" "db" {
|
||||||
|
secret {
|
||||||
|
name = "master_password"
|
||||||
|
payload = "AQECAHgaPa0J8WadplGCqqVAr4HNvDaFSQ+NaiwIBhmm6qDSFwAAAGIwYAYJKoZIhvcNAQcGoFMwUQIBADBMBgkqhkiG9w0BBwEwHgYJYIZIAWUDBAEuMBEEDI+LoLdvYv8l41OhAAIBEIAfx49FFJCLeYrkfMfAw6XlnxP23MmDBdqP8dPp28OoAQ=="
|
||||||
|
|
||||||
|
context {
|
||||||
|
foo = "bar"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "aws_rds_cluster" "rds" {
|
||||||
|
master_username = "root"
|
||||||
|
master_password = "${data.aws_kms_secret.db.master_password}"
|
||||||
|
...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
And your RDS cluster would have the root password set to "master-password"
|
||||||
|
|
||||||
|
## Argument Reference
|
||||||
|
|
||||||
|
The following arguments are supported:
|
||||||
|
|
||||||
|
* `secret` - (Required) One or more encrypted payload definitions from the KMS
|
||||||
|
service. See the Secret Definitions below.
|
||||||
|
|
||||||
|
|
||||||
|
### Secret Definitions
|
||||||
|
|
||||||
|
Each secret definition supports the following arguments:
|
||||||
|
|
||||||
|
* `name` - (Required) The name to export this secret under in the attributes.
|
||||||
|
* `payload` - (Required) Base64 encoded payload, as returned from a KMS encrypt
|
||||||
|
opertation.
|
||||||
|
* `context` - (Optional) An optional mapping that makes up the Encryption
|
||||||
|
Context for the secret.
|
||||||
|
* `grant_tokens` (Optional) An optional list of Grant Tokens for the secret.
|
||||||
|
|
||||||
|
For more information on `context` and `grant_tokens` see the [KMS
|
||||||
|
Concepts](http://docs.aws.amazon.com/kms/latest/developerguide/concepts.html)
|
||||||
|
|
||||||
|
## Attributes Reference
|
||||||
|
|
||||||
|
Each `secret` defined is exported under its `name` as a top-level attribute.
|
|
@ -71,6 +71,9 @@
|
||||||
<li<%= sidebar_current("docs-aws-datasource-ip_ranges") %>>
|
<li<%= sidebar_current("docs-aws-datasource-ip_ranges") %>>
|
||||||
<a href="/docs/providers/aws/d/ip_ranges.html">aws_ip_ranges</a>
|
<a href="/docs/providers/aws/d/ip_ranges.html">aws_ip_ranges</a>
|
||||||
</li>
|
</li>
|
||||||
|
<li<%= sidebar_current("docs-aws-datasource-kms-secret") %>>
|
||||||
|
<a href="/docs/providers/aws/d/kms_secret.html">aws_kms_secret</a>
|
||||||
|
</li>
|
||||||
<li<%= sidebar_current("docs-aws-datasource-prefix-list") %>>
|
<li<%= sidebar_current("docs-aws-datasource-prefix-list") %>>
|
||||||
<a href="/docs/providers/aws/d/prefix_list.html">aws_prefix_list</a>
|
<a href="/docs/providers/aws/d/prefix_list.html">aws_prefix_list</a>
|
||||||
</li>
|
</li>
|
||||||
|
|
Loading…
Reference in New Issue