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_service": dataSourceAwsVpcEndpointService(),
|
||||
"aws_vpc_peering_connection": dataSourceAwsVpcPeeringConnection(),
|
||||
"aws_kms_secret": dataSourceAwsKmsSecret(),
|
||||
},
|
||||
|
||||
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") %>>
|
||||
<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>
|
||||
|
|
Loading…
Reference in New Issue