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

EBS default encryption #8771

Merged
merged 8 commits into from
Jun 20, 2019
Merged
Show file tree
Hide file tree
Changes from 3 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
2 changes: 2 additions & 0 deletions aws/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -426,6 +426,8 @@ func Provider() terraform.ResourceProvider {
"aws_dynamodb_table": resourceAwsDynamoDbTable(),
"aws_dynamodb_table_item": resourceAwsDynamoDbTableItem(),
"aws_dynamodb_global_table": resourceAwsDynamoDbGlobalTable(),
"aws_ebs_default_kms_key": resourceAwsEbsDefaultKmsKey(),
"aws_ebs_encryption_by_default": resourceAwsEbsEncryptionByDefault(),
"aws_ebs_snapshot": resourceAwsEbsSnapshot(),
"aws_ebs_snapshot_copy": resourceAwsEbsSnapshotCopy(),
"aws_ebs_volume": resourceAwsEbsVolume(),
Expand Down
80 changes: 80 additions & 0 deletions aws/resource_aws_ebs_default_kms_key.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
package aws

import (
"fmt"

"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/ec2"

"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/helper/schema"
)

func resourceAwsEbsDefaultKmsKey() *schema.Resource {
return &schema.Resource{
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should add import support from the get-go, e.g. via the key ARN as the ID 🚀

Importer: &schema.ResourceImporter{
	State: schema.ImportStatePassthrough,
},

In the testing:

{
	ResourceName:      resourceName,
	ImportState:       true,
	ImportStateVerify: true,
},

And the documentation:

## Import

EBS Default KMS Key can be imported with the KMS Key ARN, e.g.

```console
$ terraform import aws_ebs_default_kms_key.example arn:aws:kms:us-east-1:123456789012:key/abcd-1234
```

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since resourceAwsEbsDefaultKmsKeyRead() doesn't actually use the resource's ID for the AWS API call (there's a single default EBS CMK per region per account) do we want to check that the KMS key ARN passed to terraform import is really the current default EBS CMK?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the Terraform code corresponding to the imported resource has the same key_arn value as the ARN passed to terraform import then I think that having ForceNew on the key_arn attribute will cause the resource to be recreated.

Create: resourceAwsEbsDefaultKmsKeyCreate,
Read: resourceAwsEbsDefaultKmsKeyRead,
Update: resourceAwsEbsDefaultKmsKeyUpdate,
Delete: resourceAwsEbsDefaultKmsKeyDelete,

Schema: map[string]*schema.Schema{
"key_id": {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since the API requires the ARN instead of just the ID, it seems like we should prefer to naming this more clearly (even though its slightly off from the API itself 👍 )

Suggested change
"key_id": {
"key_arn": {

Type: schema.TypeString,
Required: true,
ValidateFunc: validateArn,
},
},
}
}

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

_, err := conn.ModifyEbsDefaultKmsKeyId(&ec2.ModifyEbsDefaultKmsKeyIdInput{
KmsKeyId: aws.String(d.Get("key_id").(string)),
})
if err != nil {
return fmt.Errorf("error creating EBS default KMS key: %s", err)
}

d.SetId(resource.UniqueId())
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we set this to the key ARN/ID instead of a random identifier so its usable in downstream resources? 😄

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Currently we are allowing the ARN to change and doing a resource update - If we go with using the ARN as the ID then we need to add ForceNew: true to the key_arn attribute and remove resourceAwsEbsDefaultKmsKeyUpdate(). IMHO this is actually a better way of modeling the resource.


return resourceAwsEbsDefaultKmsKeyRead(d, meta)
}

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

resp, err := conn.GetEbsDefaultKmsKeyId(&ec2.GetEbsDefaultKmsKeyIdInput{})
if err != nil {
return fmt.Errorf("error reading EBS default KMS key: %s", err)
}

d.Set("key_id", aws.StringValue(resp.KmsKeyId))

return nil
}

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

_, err := conn.ModifyEbsDefaultKmsKeyId(&ec2.ModifyEbsDefaultKmsKeyIdInput{
KmsKeyId: aws.String(d.Get("key_id").(string)),
})
if err != nil {
return fmt.Errorf("error updating EBS default KMS key: %s", err)
}

return resourceAwsEbsDefaultKmsKeyRead(d, meta)
}

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

_, err := conn.ResetEbsDefaultKmsKeyId(&ec2.ResetEbsDefaultKmsKeyIdInput{})
if err != nil {
return fmt.Errorf("error deleting EBS default KMS key: %s", err)
}

return nil
}
103 changes: 103 additions & 0 deletions aws/resource_aws_ebs_default_kms_key_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
package aws

import (
"fmt"
"testing"

"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/arn"
"github.com/aws/aws-sdk-go/service/ec2"
"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/terraform"
)

func TestAccAWSEBSDefaultKmsKey_basic(t *testing.T) {
resourceName := "aws_ebs_default_kms_key.test"
resourceNameKey1 := "aws_kms_key.test1"
resourceNameKey2 := "aws_kms_key.test2"

resource.ParallelTest(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckAwsEbsDefaultKmsKeyDestroy,
Steps: []resource.TestStep{
{
Config: testAccAwsEbsDefaultKmsKeyConfig_basic,
Check: resource.ComposeTestCheckFunc(
testAccCheckEbsDefaultKmsKey(resourceName),
resource.TestCheckResourceAttrPair(resourceName, "key_id", resourceNameKey1, "arn"),
),
},
{
Config: testAccAwsEbsDefaultKmsKeyConfig_updated,
Check: resource.ComposeTestCheckFunc(
testAccCheckEbsDefaultKmsKey(resourceName),
resource.TestCheckResourceAttrPair(resourceName, "key_id", resourceNameKey2, "arn"),
),
},
},
})
}

func testAccCheckAwsEbsDefaultKmsKeyDestroy(s *terraform.State) error {
ec2conn := testAccProvider.Meta().(*AWSClient).ec2conn
kmsconn := testAccProvider.Meta().(*AWSClient).kmsconn

alias, err := findKmsAliasByName(kmsconn, "alias/aws/ebs", nil)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice!

if err != nil {
return err
}

aliasARN, err := arn.Parse(aws.StringValue(alias.AliasArn))
if err != nil {
return err
}

arn := arn.ARN{
Partition: aliasARN.Partition,
Service: aliasARN.Service,
Region: aliasARN.Region,
AccountID: aliasARN.AccountID,
Resource: fmt.Sprintf("key/%s", aws.StringValue(alias.TargetKeyId)),
}

resp, err := ec2conn.GetEbsDefaultKmsKeyId(&ec2.GetEbsDefaultKmsKeyIdInput{})
if err != nil {
return err
}

if aws.StringValue(resp.KmsKeyId) != arn.String() {
return fmt.Errorf("Default CMK (%s) is not the account's AWS-managed default CMK (%s)", aws.StringValue(resp.KmsKeyId), arn.String())
}

return nil
}

func testAccCheckEbsDefaultKmsKey(name string) resource.TestCheckFunc {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it worth having this function do the inverse of the destroy check?

return func(s *terraform.State) error {
_, ok := s.RootModule().Resources[name]
if !ok {
return fmt.Errorf("Not found: %s", name)
}

return nil
}
}

const testAccAwsEbsDefaultKmsKeyConfigBase = `
resource "aws_kms_key" "test1" {}

resource "aws_kms_key" "test2" {}
`

const testAccAwsEbsDefaultKmsKeyConfig_basic = testAccAwsEbsDefaultKmsKeyConfigBase + `
resource "aws_ebs_default_kms_key" "test" {
key_id = "${aws_kms_key.test1.arn}"
}
`

const testAccAwsEbsDefaultKmsKeyConfig_updated = testAccAwsEbsDefaultKmsKeyConfigBase + `
resource "aws_ebs_default_kms_key" "test" {
key_id = "${aws_kms_key.test2.arn}"
}
`
80 changes: 80 additions & 0 deletions aws/resource_aws_ebs_encryption_by_default.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
package aws

import (
"fmt"

"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/ec2"

"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/helper/schema"
)

func resourceAwsEbsEncryptionByDefault() *schema.Resource {
return &schema.Resource{
Create: resourceAwsEbsEncryptionByDefaultCreate,
Read: resourceAwsEbsEncryptionByDefaultRead,
Update: resourceAwsEbsEncryptionByDefaultUpdate,
Delete: resourceAwsEbsEncryptionByDefaultDelete,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we are going to do nothing in the Delete function, this can use schema.Noop instead of a custom function.

Suggested change
Delete: resourceAwsEbsEncryptionByDefaultDelete,
Delete: schema.Noop,

The documentation in that case should also explicitly state that it is only removing Terraform's management of the setting.

Instead of an empty Delete function though, I think a better user experience would be do disable encryption when this resource is removed. 👍


Schema: map[string]*schema.Schema{
"enabled": {
Type: schema.TypeBool,
Required: true,
ewbankkit marked this conversation as resolved.
Show resolved Hide resolved
},
},
}
}

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

enabled := d.Get("enabled").(bool)
if err := setEbsEncryptionByDefault(conn, enabled); err != nil {
return fmt.Errorf("error creating EBS encryption by default (%t): %s", enabled, err)
}

d.SetId(resource.UniqueId())

return resourceAwsEbsEncryptionByDefaultRead(d, meta)
}

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

resp, err := conn.GetEbsEncryptionByDefault(&ec2.GetEbsEncryptionByDefaultInput{})
if err != nil {
return fmt.Errorf("error reading EBS encryption by default: %s", err)
}

d.Set("enabled", aws.BoolValue(resp.EbsEncryptionByDefault))

return nil
}

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

enabled := d.Get("enabled").(bool)
if err := setEbsEncryptionByDefault(conn, enabled); err != nil {
return fmt.Errorf("error updating EBS encryption by default (%t): %s", enabled, err)
}

return resourceAwsEbsEncryptionByDefaultRead(d, meta)
}

func resourceAwsEbsEncryptionByDefaultDelete(d *schema.ResourceData, meta interface{}) error {
return nil
}

func setEbsEncryptionByDefault(conn *ec2.EC2, enabled bool) error {
var err error

if enabled {
_, err = conn.EnableEbsEncryptionByDefault(&ec2.EnableEbsEncryptionByDefaultInput{})
} else {
_, err = conn.DisableEbsEncryptionByDefault(&ec2.DisableEbsEncryptionByDefaultInput{})
}

return err
}
70 changes: 70 additions & 0 deletions aws/resource_aws_ebs_encryption_by_default_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package aws

import (
"fmt"
"testing"

"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/ec2"
"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/terraform"
)

func TestAccAWSEBSEncryptionByDefault_basic(t *testing.T) {
resourceName := "aws_ebs_encryption_by_default.test"

resource.ParallelTest(t, resource.TestCase{
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the Delete function is to remain empty, can you please add an explicit CheckDestroy: nil, to this TestCase?

Ideally though, this resource seems like it should enable encryption by default when its added and disable encryption when its removed. The CheckDestroy in that case would verify that encryption is disabled. 😄

PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
Steps: []resource.TestStep{
{
Config: testAccAwsEbsEncryptionByDefaultConfig(false),
Check: resource.ComposeTestCheckFunc(
testAccCheckEbsEncryptionByDefault(resourceName, false),
resource.TestCheckResourceAttr(resourceName, "enabled", "false"),
),
},
{
Config: testAccAwsEbsEncryptionByDefaultConfig(true),
Check: resource.ComposeTestCheckFunc(
testAccCheckEbsEncryptionByDefault(resourceName, true),
resource.TestCheckResourceAttr(resourceName, "enabled", "true"),
),
},
},
})
}

func testAccCheckEbsEncryptionByDefault(n string, enabled bool) resource.TestCheckFunc {
return func(s *terraform.State) error {
rs, ok := s.RootModule().Resources[n]
if !ok {
return fmt.Errorf("Not found: %s", n)
}

if rs.Primary.ID == "" {
return fmt.Errorf("No ID is set")
}

conn := testAccProvider.Meta().(*AWSClient).ec2conn

response, err := conn.GetEbsEncryptionByDefault(&ec2.GetEbsEncryptionByDefaultInput{})
if err != nil {
return err
}

if aws.BoolValue(response.EbsEncryptionByDefault) != enabled {
return fmt.Errorf("EBS encryption by default is not in expected state (%t)", enabled)
}

return nil
}
}

func testAccAwsEbsEncryptionByDefaultConfig(enabled bool) string {
return fmt.Sprintf(`
resource "aws_ebs_encryption_by_default" "test" {
enabled = %[1]t
}
`, enabled)
}
8 changes: 8 additions & 0 deletions website/aws.erb
Original file line number Diff line number Diff line change
Expand Up @@ -1135,6 +1135,14 @@
<a href="/docs/providers/aws/r/ami_launch_permission.html">aws_ami_launch_permission</a>
</li>

<li>
<a href="/docs/providers/aws/r/ebs_default_kms_key.html">aws_ebs_default_kms_key</a>
</li>

<li>
<a href="/docs/providers/aws/r/ebs_encryption_by_default.html">aws_ebs_encryption_by_default</a>
</li>

<li>
<a href="/docs/providers/aws/r/ebs_snapshot.html">aws_ebs_snapshot</a>
</li>
Expand Down
32 changes: 32 additions & 0 deletions website/docs/r/ebs_default_kms_key.html.markdown
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
---
layout: "aws"
page_title: "AWS: aws_ebs_default_kms_key"
sidebar_current: "docs-aws-ebs-default-kms-key"
description: |-
Manages the default customer master key (CMK) that your AWS account uses to encrypt EBS volumes.
---

# Resource: aws_ebs_default_kms_key

Provides a resource to manage the default customer master key (CMK) that your AWS account uses to encrypt EBS volumes.

Your AWS account has an AWS-managed default CMK that is used for encrypting an EBS volume when no CMK is specified in the API call that creates the volume.
By using the `aws_ebs_default_kms_key` resource, you can specify a customer-managed CMK to use in place of the AWS-managed default CMK.

~> **NOTE:** Creating an `aws_ebs_default_kms_key` resource does not enable default EBS encryption. Use the [`aws_ebs_encryption_by_default`](ebs_encryption_by_default.html) to enable default EBS encryption.

~> **NOTE:** Destroying this resource will reset the default CMK to the account's AWS-managed default CMK for EBS.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💯


## Example Usage

```hcl
resource "aws_ebs_default_kms_key" "example" {
key_id = "${aws_kms_key.example.arn}"
}
```

## Argument Reference

The following arguments are supported:

* `key_id` - (Required) The ARN of the AWS Key Management Service (AWS KMS) customer master key (CMK) to use to encrypt the EBS volume.
Loading