Skip to content

Commit

Permalink
provider/aws: New data provider to decrypt KMS secrets (#11460)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
borgstrom authored and stack72 committed Jan 29, 2017
1 parent 3913f06 commit 01a6bd7
Show file tree
Hide file tree
Showing 5 changed files with 285 additions and 0 deletions.
99 changes: 99 additions & 0 deletions builtin/providers/aws/data_source_aws_kms_secret.go
Original file line number Diff line number Diff line change
@@ -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
}
96 changes: 96 additions & 0 deletions builtin/providers/aws/data_source_aws_kms_secret_test.go
Original file line number Diff line number Diff line change
@@ -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"
}
}
}
`
1 change: 1 addition & 0 deletions builtin/providers/aws/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,7 @@ func Provider() terraform.ResourceProvider {
"aws_vpc_endpoint": dataSourceAwsVpcEndpoint(),
"aws_vpc_endpoint_service": dataSourceAwsVpcEndpointService(),
"aws_vpc_peering_connection": dataSourceAwsVpcPeeringConnection(),
"aws_kms_secret": dataSourceAwsKmsSecret(),
},

ResourcesMap: map[string]*schema.Resource{
Expand Down
86 changes: 86 additions & 0 deletions website/source/docs/providers/aws/d/kms_secret.html.markdown
Original file line number Diff line number Diff line change
@@ -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.
3 changes: 3 additions & 0 deletions website/source/layouts/aws.erb
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,9 @@
<li<%= sidebar_current("docs-aws-datasource-ip_ranges") %>>
<a href="/docs/providers/aws/d/ip_ranges.html">aws_ip_ranges</a>
</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") %>>
<a href="/docs/providers/aws/d/prefix_list.html">aws_prefix_list</a>
</li>
Expand Down

0 comments on commit 01a6bd7

Please sign in to comment.