Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

provider/aws: New data provider to decrypt KMS secrets #11460

Merged
merged 2 commits into from
Jan 29, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -172,6 +172,7 @@ func Provider() terraform.ResourceProvider {
"aws_vpc": dataSourceAwsVpc(),
"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 @@ -65,6 +65,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