Skip to content

Commit

Permalink
New Data Source: aws_kms_secrets
Browse files Browse the repository at this point in the history
Add DeprecationMessage to existing aws_kms_secret data source
  • Loading branch information
bflad committed Jul 14, 2018

Unverified

This user has not yet uploaded their public signing key.
1 parent 3e71c3e commit e006236
Showing 7 changed files with 353 additions and 3 deletions.
3 changes: 2 additions & 1 deletion aws/data_source_aws_kms_secret.go
Original file line number Diff line number Diff line change
@@ -13,7 +13,8 @@ import (

func dataSourceAwsKmsSecret() *schema.Resource {
return &schema.Resource{
Read: dataSourceAwsKmsSecretRead,
DeprecationMessage: "This data source will change or be removed in Terraform AWS provider version 2.0. Please see migration information available in: https://www.terraform.io/docs/providers/aws/d/kms_secrets.html",
Read: dataSourceAwsKmsSecretRead,

Schema: map[string]*schema.Schema{
"secret": {
108 changes: 108 additions & 0 deletions aws/data_source_aws_kms_secrets.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
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 dataSourceAwsKmsSecrets() *schema.Resource {
return &schema.Resource{
Read: dataSourceAwsKmsSecretsRead,

Schema: map[string]*schema.Schema{
"secret": {
Type: schema.TypeSet,
Required: true,
ForceNew: true,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"name": {
Type: schema.TypeString,
Required: true,
},
"payload": {
Type: schema.TypeString,
Required: true,
},
"context": {
Type: schema.TypeMap,
Optional: true,
Elem: &schema.Schema{Type: schema.TypeString},
},
"grant_tokens": {
Type: schema.TypeList,
Optional: true,
Elem: &schema.Schema{Type: schema.TypeString},
},
},
},
},
"plaintext": {
Type: schema.TypeMap,
Computed: true,
Elem: &schema.Schema{
Type: schema.TypeString,
Sensitive: true,
},
},
},
}
}

func dataSourceAwsKmsSecretsRead(d *schema.ResourceData, meta interface{}) error {
conn := meta.(*AWSClient).kmsconn

secrets := d.Get("secret").(*schema.Set)
plaintext := make(map[string]string, len(secrets.List()))

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))
plaintext[secret["name"].(string)] = string(resp.Plaintext)
}

if err := d.Set("plaintext", plaintext); err != nil {
return fmt.Errorf("error setting plaintext: %s", err)
}

d.SetId(time.Now().UTC().String())

return nil
}
105 changes: 105 additions & 0 deletions aws/data_source_aws_kms_secrets_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
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 TestAccAWSKmsSecretsDataSource_basic(t *testing.T) {
var encryptedPayload string
var key kms.KeyMetadata

plaintext := "my-plaintext-string"
resourceName := "aws_kms_key.test"

// Run a resource test to setup our KMS key
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
Steps: []resource.TestStep{
{
Config: testAccCheckAwsKmsSecretsDataSourceKey,
Check: resource.ComposeTestCheckFunc(
testAccCheckAWSKmsKeyExists(resourceName, &key),
testAccDataSourceAwsKmsSecretsEncrypt(&key, plaintext, &encryptedPayload),
// We need to dereference the encryptedPayload in a test Terraform configuration
testAccDataSourceAwsKmsSecretsDecrypt(t, plaintext, &encryptedPayload),
),
},
},
})
}

func testAccDataSourceAwsKmsSecretsEncrypt(key *kms.KeyMetadata, plaintext string, encryptedPayload *string) resource.TestCheckFunc {
return func(s *terraform.State) error {
kmsconn := testAccProvider.Meta().(*AWSClient).kmsconn

input := &kms.EncryptInput{
KeyId: key.Arn,
Plaintext: []byte(plaintext),
EncryptionContext: map[string]*string{
"name": aws.String("value"),
},
}

resp, err := kmsconn.Encrypt(input)
if err != nil {
return fmt.Errorf("failed encrypting string: %s", err)
}

*encryptedPayload = base64.StdEncoding.EncodeToString(resp.CiphertextBlob)

return nil
}
}

func testAccDataSourceAwsKmsSecretsDecrypt(t *testing.T, plaintext string, encryptedPayload *string) resource.TestCheckFunc {
return func(s *terraform.State) error {
dataSourceName := "data.aws_kms_secrets.test"

resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
Steps: []resource.TestStep{
{
Config: testAccCheckAwsKmsSecretsDataSourceSecret(*encryptedPayload),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr(dataSourceName, "plaintext.%", "1"),
resource.TestCheckResourceAttr(dataSourceName, "plaintext.secret1", plaintext),
),
},
},
})

return nil
}
}

const testAccCheckAwsKmsSecretsDataSourceKey = `
resource "aws_kms_key" "test" {
deletion_window_in_days = 7
description = "Testing the Terraform AWS KMS Secrets data_source"
}
`

func testAccCheckAwsKmsSecretsDataSourceSecret(payload string) string {
return testAccCheckAwsKmsSecretsDataSourceKey + fmt.Sprintf(`
data "aws_kms_secrets" "test" {
secret {
name = "secret1"
payload = %q
context {
name = "value"
}
}
}
`, payload)
}
1 change: 1 addition & 0 deletions aws/provider.go
Original file line number Diff line number Diff line change
@@ -223,6 +223,7 @@ func Provider() terraform.ResourceProvider {
"aws_kms_ciphertext": dataSourceAwsKmsCiphertext(),
"aws_kms_key": dataSourceAwsKmsKey(),
"aws_kms_secret": dataSourceAwsKmsSecret(),
"aws_kms_secrets": dataSourceAwsKmsSecrets(),
"aws_lambda_function": dataSourceAwsLambdaFunction(),
"aws_lambda_invocation": dataSourceAwsLambdaInvocation(),
"aws_launch_configuration": dataSourceAwsLaunchConfiguration(),
5 changes: 4 additions & 1 deletion website/aws.erb
Original file line number Diff line number Diff line change
@@ -235,9 +235,12 @@
<li<%= sidebar_current("docs-aws-datasource-kms-ciphertext") %>>
<a href="/docs/providers/aws/d/kms_ciphertext.html">aws_kms_ciphertext</a>
</li>
<li<%= sidebar_current("docs-aws-datasource-kms-secret") %>>
<li<%= sidebar_current("docs-aws-datasource-kms-secret-x") %>>
<a href="/docs/providers/aws/d/kms_secret.html">aws_kms_secret</a>
</li>
<li<%= sidebar_current("docs-aws-datasource-kms-secrets") %>>
<a href="/docs/providers/aws/d/kms_secrets.html">aws_kms_secrets</a>
</li>
<li<%= sidebar_current("docs-aws-datasource-lambda-invocation") %>>
<a href="/docs/providers/aws/d/lambda_invocation.html">aws_lambda_invocation</a>
</li>
4 changes: 3 additions & 1 deletion website/docs/d/kms_secret.html.markdown
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
---
layout: "aws"
page_title: "AWS: aws_kms_secret"
sidebar_current: "docs-aws-datasource-kms-secret"
sidebar_current: "docs-aws-datasource-kms-secret-x"
description: |-
Provides secret data encrypted with the KMS service
---

# Data Source: aws_kms_secret

!> **WARNING:** The `aws_kms_secret` data source contains behavior that cannot be supported on Terraform 0.12+ and it will either introduce breaking changes or be removed completely in the next major version (2.0.0) of the AWS provider. You can migrate existing Terraform configurations to the `aws_kms_secrets` data source, which closely resembles this data source, following instructions available in the [`aws_kms_secrets` data source documentation](/docs/providers/aws/d/kms_secrets.html).

The KMS secret data source allows you to use data encrypted with the AWS KMS
service within your resource definitions.

130 changes: 130 additions & 0 deletions website/docs/d/kms_secrets.html.markdown
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
---
layout: "aws"
page_title: "AWS: aws_kms_secrets"
sidebar_current: "docs-aws-datasource-kms-secrets"
description: |-
Decrypt multiple secrets from data encrypted with the AWS KMS service
---

# Data Source: aws_kms_secrets

Decrypt multiple secrets from data encrypted with the AWS KMS service.

~> **NOTE**: 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 all Terraform logging and state output. Please take care to secure your secret data beyond just the Terraform configuration.

## Example Usage

If you do not already have a `CiphertextBlob` from encrypting a KMS secret, you can use the below commands to obtain one using the [AWS CLI kms encrypt](https://docs.aws.amazon.com/cli/latest/reference/kms/encrypt.html) command. This requires you to have your AWS CLI setup correctly and replace the `--key-id` with your own. Alternatively you can use `--plaintext 'password'` instead of reading from a file.

-> If you have a newline character at the end of your file, it will be decrypted with this newline character intact. For most use cases this is undesirable and leads to incorrect passwords or invalid values, as well as possible changes in the plan. Be sure to use `echo -n` if necessary.

```sh
$ echo -n 'master-password' > plaintext-password
$ aws kms encrypt --key-id ab123456-c012-4567-890a-deadbeef123 --plaintext fileb://plaintext-password --encryption-context foo=bar --output text --query CiphertextBlob
AQECAHgaPa0J8WadplGCqqVAr4HNvDaFSQ+NaiwIBhmm6qDSFwAAAGIwYAYJKoZIhvcNAQcGoFMwUQIBADBMBgkqhkiG9w0BBwEwHgYJYIZIAWUDBAEuMBEEDI+LoLdvYv8l41OhAAIBEIAfx49FFJCLeYrkfMfAw6XlnxP23MmDBdqP8dPp28OoAQ==
```

That encrypted output can now be inserted into Terraform configurations without exposing the plaintext secret directly.

```hcl
data "aws_kms_secrets" "example" {
secret {
name = "master_password"
payload = "AQECAHgaPa0J8WadplGCqqVAr4HNvDaFSQ+NaiwIBhmm6qDSFwAAAGIwYAYJKoZIhvcNAQcGoFMwUQIBADBMBgkqhkiG9w0BBwEwHgYJYIZIAWUDBAEuMBEEDI+LoLdvYv8l41OhAAIBEIAfx49FFJCLeYrkfMfAw6XlnxP23MmDBdqP8dPp28OoAQ=="
context {
foo = "bar"
}
}
}
resource "aws_rds_cluster" "example" {
# ... other configuration ...
master_password = "${data.aws_kms_secrets.example.plaintext["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` 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 operation.
* `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](https://docs.aws.amazon.com/kms/latest/developerguide/concepts.html)

## Attributes Reference

In addition to all arguments above, the following attributes are exported:

* `plaintext` - Map containing each `secret` `name` as the key with its decrypted plaintext value

## Migrating From aws_kms_secret Data Source Prior to Terraform AWS Provider Version 2.0

The implementation of the `aws_kms_secret` data source, prior to Terraform AWS provider version 2.0, used dynamic attribute behavior which is not supported with Terraform 0.12 and beyond (full details available in [this GitHub issue](https://github.com/terraform-providers/terraform-provider-aws/issues/5144)).

Terraform configuration migration steps:

* Change the data source type from `aws_kms_secret` to `aws_kms_secrets`
* Change any attribute reference (e.g. `"${data.aws_kms_secret.example.ATTRIBUTE}"`) from `.ATTRIBUTE` to `.plaintext["ATTRIBUTE"]`

As an example, lets take the below sample configuration and migrate it.

```hcl
# Below example configuration will not be supported in Terraform AWS provider version 2.0
data "aws_kms_secret" "example" {
secret {
# ... potentially other configration ...
name = "master_password"
payload = "AQEC..."
}
secret {
# ... potentially other configration ...
name = "master_username"
payload = "AQEC..."
}
}
resource "aws_rds_cluster" "example" {
# ... other configuration ...
master_password = "${data.aws_kms_secret.example.master_password}"
master_username = "${data.aws_kms_secret.example.master_username}"
}
```

Notice that the `aws_kms_secret` data source previously was taking the two `secret` configuration block `name` arguments and generating those as attribute names (`master_password` and `master_username` in this case). To remove the incompatible behavior, this updated version of the data source provides the decrypted value of each of those `secret` configuration block `name` arguments within a map attribute named `plaintext`.

Updating the sample configuration from above:

```hcl
data "aws_kms_secrets" "example" {
secret {
# ... potentially other configration ...
name = "master_password"
payload = "AQEC..."
}
secret {
# ... potentially other configration ...
name = "master_username"
payload = "AQEC..."
}
}
resource "aws_rds_cluster" "example" {
# ... other configuration ...
master_password = "${data.aws_kms_secrets.example.plaintext["master_password"]}"
master_username = "${data.aws_kms_secrets.example.plaintext["master_username"]}"
}
```

0 comments on commit e006236

Please sign in to comment.