From 01a6bd7592c55af2d610725a04d396c06953682b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Borgstrom=20=E2=99=95?= Date: Sun, 29 Jan 2017 13:01:38 -0800 Subject: [PATCH] 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 --- .../aws/data_source_aws_kms_secret.go | 99 +++++++++++++++++++ .../aws/data_source_aws_kms_secret_test.go | 96 ++++++++++++++++++ builtin/providers/aws/provider.go | 1 + .../providers/aws/d/kms_secret.html.markdown | 86 ++++++++++++++++ website/source/layouts/aws.erb | 3 + 5 files changed, 285 insertions(+) create mode 100644 builtin/providers/aws/data_source_aws_kms_secret.go create mode 100644 builtin/providers/aws/data_source_aws_kms_secret_test.go create mode 100644 website/source/docs/providers/aws/d/kms_secret.html.markdown diff --git a/builtin/providers/aws/data_source_aws_kms_secret.go b/builtin/providers/aws/data_source_aws_kms_secret.go new file mode 100644 index 000000000..92d5134fd --- /dev/null +++ b/builtin/providers/aws/data_source_aws_kms_secret.go @@ -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 +} diff --git a/builtin/providers/aws/data_source_aws_kms_secret_test.go b/builtin/providers/aws/data_source_aws_kms_secret_test.go new file mode 100644 index 000000000..4d0bf139e --- /dev/null +++ b/builtin/providers/aws/data_source_aws_kms_secret_test.go @@ -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" + } + } +} +` diff --git a/builtin/providers/aws/provider.go b/builtin/providers/aws/provider.go index 15552ab11..49acd9d36 100644 --- a/builtin/providers/aws/provider.go +++ b/builtin/providers/aws/provider.go @@ -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{ diff --git a/website/source/docs/providers/aws/d/kms_secret.html.markdown b/website/source/docs/providers/aws/d/kms_secret.html.markdown new file mode 100644 index 000000000..0ec791f57 --- /dev/null +++ b/website/source/docs/providers/aws/d/kms_secret.html.markdown @@ -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. diff --git a/website/source/layouts/aws.erb b/website/source/layouts/aws.erb index 6679e96ad..915590cd4 100644 --- a/website/source/layouts/aws.erb +++ b/website/source/layouts/aws.erb @@ -71,6 +71,9 @@ > aws_ip_ranges + > + aws_kms_secret + > aws_prefix_list