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

Add support for DynamoDB customer managed CMKs for server-side encryption #11081

Merged
merged 1 commit into from
Jan 29, 2020
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
4 changes: 4 additions & 0 deletions aws/data_source_aws_dynamodb_table.go
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,10 @@ func dataSourceAwsDynamoDbTable() *schema.Resource {
Type: schema.TypeBool,
Computed: true,
},
"kms_key_arn": {
Type: schema.TypeString,
Computed: true,
},
},
},
},
Expand Down
37 changes: 19 additions & 18 deletions aws/data_source_aws_dynamodb_table_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
)

func TestAccDataSourceAwsDynamoDbTable_basic(t *testing.T) {
datasourceName := "data.aws_dynamodb_table.test"
tableName := fmt.Sprintf("testaccawsdynamodbtable-basic-%s", acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum))

resource.ParallelTest(t, resource.TestCase{
Expand All @@ -18,21 +19,21 @@ func TestAccDataSourceAwsDynamoDbTable_basic(t *testing.T) {
{
Config: testAccDataSourceAwsDynamoDbTableConfigBasic(tableName),
Check: resource.ComposeAggregateTestCheckFunc(
resource.TestCheckResourceAttr("data.aws_dynamodb_table.dynamodb_table_test", "name", tableName),
resource.TestCheckResourceAttr("data.aws_dynamodb_table.dynamodb_table_test", "read_capacity", "20"),
resource.TestCheckResourceAttr("data.aws_dynamodb_table.dynamodb_table_test", "write_capacity", "20"),
resource.TestCheckResourceAttr("data.aws_dynamodb_table.dynamodb_table_test", "hash_key", "UserId"),
resource.TestCheckResourceAttr("data.aws_dynamodb_table.dynamodb_table_test", "range_key", "GameTitle"),
resource.TestCheckResourceAttr("data.aws_dynamodb_table.dynamodb_table_test", "attribute.#", "3"),
resource.TestCheckResourceAttr("data.aws_dynamodb_table.dynamodb_table_test", "global_secondary_index.#", "1"),
resource.TestCheckResourceAttr("data.aws_dynamodb_table.dynamodb_table_test", "ttl.#", "1"),
resource.TestCheckResourceAttr("data.aws_dynamodb_table.dynamodb_table_test", "tags.%", "2"),
resource.TestCheckResourceAttr("data.aws_dynamodb_table.dynamodb_table_test", "tags.Name", "dynamodb-table-1"),
resource.TestCheckResourceAttr("data.aws_dynamodb_table.dynamodb_table_test", "tags.Environment", "test"),
resource.TestCheckResourceAttr("data.aws_dynamodb_table.dynamodb_table_test", "server_side_encryption.#", "0"),
resource.TestCheckResourceAttr("data.aws_dynamodb_table.dynamodb_table_test", "billing_mode", "PROVISIONED"),
resource.TestCheckResourceAttr("data.aws_dynamodb_table.dynamodb_table_test", "point_in_time_recovery.#", "1"),
resource.TestCheckResourceAttr("data.aws_dynamodb_table.dynamodb_table_test", "point_in_time_recovery.0.enabled", "false"),
resource.TestCheckResourceAttr(datasourceName, "name", tableName),
resource.TestCheckResourceAttr(datasourceName, "read_capacity", "20"),
resource.TestCheckResourceAttr(datasourceName, "write_capacity", "20"),
resource.TestCheckResourceAttr(datasourceName, "hash_key", "UserId"),
resource.TestCheckResourceAttr(datasourceName, "range_key", "GameTitle"),
resource.TestCheckResourceAttr(datasourceName, "attribute.#", "3"),
resource.TestCheckResourceAttr(datasourceName, "global_secondary_index.#", "1"),
resource.TestCheckResourceAttr(datasourceName, "ttl.#", "1"),
resource.TestCheckResourceAttr(datasourceName, "tags.%", "2"),
resource.TestCheckResourceAttr(datasourceName, "tags.Name", "dynamodb-table-1"),
resource.TestCheckResourceAttr(datasourceName, "tags.Environment", "test"),
resource.TestCheckResourceAttr(datasourceName, "server_side_encryption.#", "0"),
resource.TestCheckResourceAttr(datasourceName, "billing_mode", "PROVISIONED"),
resource.TestCheckResourceAttr(datasourceName, "point_in_time_recovery.#", "1"),
resource.TestCheckResourceAttr(datasourceName, "point_in_time_recovery.0.enabled", "false"),
),
},
},
Expand All @@ -41,7 +42,7 @@ func TestAccDataSourceAwsDynamoDbTable_basic(t *testing.T) {

func testAccDataSourceAwsDynamoDbTableConfigBasic(tableName string) string {
return fmt.Sprintf(`
resource "aws_dynamodb_table" "dynamodb_table_test" {
resource "aws_dynamodb_table" "test" {
name = "%s"
read_capacity = 20
write_capacity = 20
Expand Down Expand Up @@ -79,8 +80,8 @@ resource "aws_dynamodb_table" "dynamodb_table_test" {
}
}

data "aws_dynamodb_table" "dynamodb_table_test" {
name = "${aws_dynamodb_table.dynamodb_table_test.name}"
data "aws_dynamodb_table" "test" {
name = "${aws_dynamodb_table.test.name}"
}
`, tableName)
}
62 changes: 54 additions & 8 deletions aws/resource_aws_dynamodb_table.go
Original file line number Diff line number Diff line change
Expand Up @@ -254,7 +254,12 @@ func resourceAwsDynamoDbTable() *schema.Resource {
"enabled": {
Type: schema.TypeBool,
Required: true,
ForceNew: true,
},
"kms_key_arn": {
Type: schema.TypeString,
Optional: true,
Computed: true,
ValidateFunc: validateArn,
},
},
},
Expand Down Expand Up @@ -345,13 +350,7 @@ func resourceAwsDynamoDbTableCreate(d *schema.ResourceData, meta interface{}) er
}

if v, ok := d.GetOk("server_side_encryption"); ok {
options := v.([]interface{})
if options[0] == nil {
return fmt.Errorf("At least one field is expected inside server_side_encryption")
}

s := options[0].(map[string]interface{})
req.SSESpecification = expandDynamoDbEncryptAtRestOptions(s)
req.SSESpecification = expandDynamoDbEncryptAtRestOptions(v.([]interface{}))
}

var output *dynamodb.CreateTableOutput
Expand Down Expand Up @@ -562,6 +561,21 @@ func resourceAwsDynamoDbTableUpdate(d *schema.ResourceData, meta interface{}) er
}
}

if d.HasChange("server_side_encryption") {
// "ValidationException: One or more parameter values were invalid: Server-Side Encryption modification must be the only operation in the request".
_, err := conn.UpdateTable(&dynamodb.UpdateTableInput{
TableName: aws.String(d.Id()),
SSESpecification: expandDynamoDbEncryptAtRestOptions(d.Get("server_side_encryption").([]interface{})),
})
if err != nil {
return fmt.Errorf("error updating DynamoDB Table (%s) SSE: %s", d.Id(), err)
}

if err := waitForDynamoDbSSEUpdateToBeCompleted(d.Id(), d.Timeout(schema.TimeoutUpdate), conn); err != nil {
return fmt.Errorf("error waiting for DynamoDB Table (%s) SSE update: %s", d.Id(), err)
}
}

if d.HasChange("ttl") {
if err := updateDynamoDbTimeToLive(d.Id(), d.Get("ttl").([]interface{}), conn); err != nil {
return fmt.Errorf("error updating DynamoDB Table (%s) time to live: %s", d.Id(), err)
Expand Down Expand Up @@ -972,6 +986,38 @@ func waitForDynamoDbTtlUpdateToBeCompleted(tableName string, toEnable bool, conn
return err
}

func waitForDynamoDbSSEUpdateToBeCompleted(tableName string, timeout time.Duration, conn *dynamodb.DynamoDB) error {
stateConf := &resource.StateChangeConf{
Pending: []string{
dynamodb.SSEStatusDisabling,
dynamodb.SSEStatusEnabling,
dynamodb.SSEStatusUpdating,
},
Target: []string{
dynamodb.SSEStatusDisabled,
dynamodb.SSEStatusEnabled,
},
Timeout: timeout,
Refresh: func() (interface{}, string, error) {
result, err := conn.DescribeTable(&dynamodb.DescribeTableInput{
TableName: aws.String(tableName),
})
if err != nil {
return 42, "", err
}

// Disabling SSE returns null SSEDescription.
if result.Table.SSEDescription == nil {
return result, dynamodb.SSEStatusDisabled, nil
}
return result, aws.StringValue(result.Table.SSEDescription.Status), nil
},
}
_, err := stateConf.WaitForState()

return err
}

func isDynamoDbTableOptionDisabled(v interface{}) bool {
options := v.([]interface{})
if len(options) == 0 {
Expand Down
80 changes: 59 additions & 21 deletions aws/resource_aws_dynamodb_table_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -473,8 +473,8 @@ func TestAccAWSDynamoDbTable_enablePitr(t *testing.T) {
}

func TestAccAWSDynamoDbTable_BillingMode_PayPerRequestToProvisioned(t *testing.T) {
rName := acctest.RandomWithPrefix("TerraformTestTable-")
resourceName := "aws_dynamodb_table.test"
rName := acctest.RandomWithPrefix("TerraformTestTable-")

resource.ParallelTest(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Expand Down Expand Up @@ -503,8 +503,8 @@ func TestAccAWSDynamoDbTable_BillingMode_PayPerRequestToProvisioned(t *testing.T
}

func TestAccAWSDynamoDbTable_BillingMode_ProvisionedToPayPerRequest(t *testing.T) {
rName := acctest.RandomWithPrefix("TerraformTestTable-")
resourceName := "aws_dynamodb_table.test"
rName := acctest.RandomWithPrefix("TerraformTestTable-")

resource.ParallelTest(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Expand Down Expand Up @@ -533,8 +533,8 @@ func TestAccAWSDynamoDbTable_BillingMode_ProvisionedToPayPerRequest(t *testing.T
}

func TestAccAWSDynamoDbTable_BillingMode_GSI_PayPerRequestToProvisioned(t *testing.T) {
rName := acctest.RandomWithPrefix("TerraformTestTable-")
resourceName := "aws_dynamodb_table.test"
rName := acctest.RandomWithPrefix("TerraformTestTable-")

resource.ParallelTest(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Expand Down Expand Up @@ -563,8 +563,8 @@ func TestAccAWSDynamoDbTable_BillingMode_GSI_PayPerRequestToProvisioned(t *testi
}

func TestAccAWSDynamoDbTable_BillingMode_GSI_ProvisionedToPayPerRequest(t *testing.T) {
rName := acctest.RandomWithPrefix("TerraformTestTable-")
resourceName := "aws_dynamodb_table.test"
rName := acctest.RandomWithPrefix("TerraformTestTable-")

resource.ParallelTest(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Expand Down Expand Up @@ -675,8 +675,8 @@ func TestAccAWSDynamoDbTable_tags(t *testing.T) {
// https://github.com/hashicorp/terraform/issues/13243
func TestAccAWSDynamoDbTable_gsiUpdateCapacity(t *testing.T) {
var conf dynamodb.DescribeTableOutput
name := acctest.RandString(10)
resourceName := "aws_dynamodb_table.test"
name := acctest.RandString(10)

resource.ParallelTest(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Expand Down Expand Up @@ -720,8 +720,8 @@ func TestAccAWSDynamoDbTable_gsiUpdateCapacity(t *testing.T) {

func TestAccAWSDynamoDbTable_gsiUpdateOtherAttributes(t *testing.T) {
var conf dynamodb.DescribeTableOutput
name := acctest.RandString(10)
resourceName := "aws_dynamodb_table.test"
name := acctest.RandString(10)

resource.ParallelTest(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Expand Down Expand Up @@ -797,8 +797,8 @@ func TestAccAWSDynamoDbTable_gsiUpdateOtherAttributes(t *testing.T) {
// https://github.com/terraform-providers/terraform-provider-aws/issues/566
func TestAccAWSDynamoDbTable_gsiUpdateNonKeyAttributes(t *testing.T) {
var conf dynamodb.DescribeTableOutput
name := acctest.RandString(10)
resourceName := "aws_dynamodb_table.test"
name := acctest.RandString(10)

resource.ParallelTest(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Expand Down Expand Up @@ -877,8 +877,8 @@ func TestAccAWSDynamoDbTable_gsiUpdateNonKeyAttributes(t *testing.T) {
// ValidationException: Time to live has been modified multiple times within a fixed interval
func TestAccAWSDynamoDbTable_Ttl_Enabled(t *testing.T) {
var table dynamodb.DescribeTableOutput
rName := acctest.RandomWithPrefix("TerraformTestTable-")
resourceName := "aws_dynamodb_table.test"
rName := acctest.RandomWithPrefix("TerraformTestTable-")

resource.ParallelTest(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Expand Down Expand Up @@ -906,8 +906,8 @@ func TestAccAWSDynamoDbTable_Ttl_Enabled(t *testing.T) {
// ValidationException: Time to live has been modified multiple times within a fixed interval
func TestAccAWSDynamoDbTable_Ttl_Disabled(t *testing.T) {
var table dynamodb.DescribeTableOutput
rName := acctest.RandomWithPrefix("TerraformTestTable-")
resourceName := "aws_dynamodb_table.test"
rName := acctest.RandomWithPrefix("TerraformTestTable-")

resource.ParallelTest(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Expand Down Expand Up @@ -1003,21 +1003,24 @@ func TestAccAWSDynamoDbTable_attributeUpdateValidation(t *testing.T) {
}

func TestAccAWSDynamoDbTable_encryption(t *testing.T) {
var confEncEnabled, confEncDisabled, confBasic dynamodb.DescribeTableOutput
var confBYOK, confEncEnabled, confEncDisabled dynamodb.DescribeTableOutput
resourceName := "aws_dynamodb_table.test"
rName := acctest.RandomWithPrefix("TerraformTestTable-")
kmsKeyResourceName := "aws_kms_key.test"
kmsAliasDatasourceName := "data.aws_kms_alias.dynamodb"

resource.ParallelTest(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckAWSDynamoDbTableDestroy,
Steps: []resource.TestStep{
{
Config: testAccAWSDynamoDbConfigInitialStateWithEncryption(rName, true),
Config: testAccAWSDynamoDbConfigInitialStateWithEncryptionBYOK(rName),
Check: resource.ComposeTestCheckFunc(
testAccCheckInitialAWSDynamoDbTableExists(resourceName, &confEncEnabled),
testAccCheckInitialAWSDynamoDbTableExists(resourceName, &confBYOK),
resource.TestCheckResourceAttr(resourceName, "server_side_encryption.#", "1"),
resource.TestCheckResourceAttr(resourceName, "server_side_encryption.0.enabled", "true"),
resource.TestCheckResourceAttrPair(resourceName, "server_side_encryption.0.kms_key_arn", kmsKeyResourceName, "arn"),
),
},
{
Expand All @@ -1026,26 +1029,28 @@ func TestAccAWSDynamoDbTable_encryption(t *testing.T) {
ImportStateVerify: true,
},
{
Config: testAccAWSDynamoDbConfigInitialStateWithEncryption(rName, false),
Config: testAccAWSDynamoDbConfigInitialStateWithEncryptionAmazonCMK(rName, false),
Check: resource.ComposeTestCheckFunc(
testAccCheckInitialAWSDynamoDbTableExists(resourceName, &confEncDisabled),
resource.TestCheckResourceAttr(resourceName, "server_side_encryption.#", "0"),
func(s *terraform.State) error {
if confEncDisabled.Table.CreationDateTime.Equal(*confEncEnabled.Table.CreationDateTime) {
return fmt.Errorf("DynamoDB table not recreated when changing SSE")
if !confEncDisabled.Table.CreationDateTime.Equal(*confBYOK.Table.CreationDateTime) {
return fmt.Errorf("DynamoDB table recreated when changing SSE")
}
return nil
},
),
},
{
Config: testAccAWSDynamoDbConfig_basic(rName),
Config: testAccAWSDynamoDbConfigInitialStateWithEncryptionAmazonCMK(rName, true),
Check: resource.ComposeTestCheckFunc(
testAccCheckInitialAWSDynamoDbTableExists(resourceName, &confBasic),
resource.TestCheckResourceAttr(resourceName, "server_side_encryption.#", "0"),
testAccCheckInitialAWSDynamoDbTableExists(resourceName, &confEncEnabled),
resource.TestCheckResourceAttr(resourceName, "server_side_encryption.#", "1"),
resource.TestCheckResourceAttr(resourceName, "server_side_encryption.0.enabled", "true"),
resource.TestCheckResourceAttrPair(resourceName, "server_side_encryption.0.kms_key_arn", kmsAliasDatasourceName, "target_key_arn"),
func(s *terraform.State) error {
if !confBasic.Table.CreationDateTime.Equal(*confEncDisabled.Table.CreationDateTime) {
return fmt.Errorf("DynamoDB table was recreated unexpectedly")
if !confEncEnabled.Table.CreationDateTime.Equal(*confEncDisabled.Table.CreationDateTime) {
return fmt.Errorf("DynamoDB table recreated when changing SSE")
}
return nil
},
Expand Down Expand Up @@ -1569,8 +1574,16 @@ resource "aws_dynamodb_table" "test" {
`, rName)
}

func testAccAWSDynamoDbConfigInitialStateWithEncryption(rName string, enabled bool) string {
func testAccAWSDynamoDbConfigInitialStateWithEncryptionAmazonCMK(rName string, enabled bool) string {
return fmt.Sprintf(`
data "aws_kms_alias" "dynamodb" {
name = "alias/aws/dynamodb"
}

resource "aws_kms_key" "test" {
description = "DynamoDbTest"
}

resource "aws_dynamodb_table" "test" {
name = "%s"
read_capacity = 1
Expand All @@ -1589,6 +1602,31 @@ resource "aws_dynamodb_table" "test" {
`, rName, enabled)
}

func testAccAWSDynamoDbConfigInitialStateWithEncryptionBYOK(rName string) string {
return fmt.Sprintf(`
resource "aws_kms_key" "test" {
description = "DynamoDbTest"
}

resource "aws_dynamodb_table" "test" {
name = "%s"
read_capacity = 2
write_capacity = 2
hash_key = "TestTableHashKey"

attribute {
name = "TestTableHashKey"
type = "S"
}

server_side_encryption {
enabled = true
kms_key_arn = "${aws_kms_key.test.arn}"
}
}
`, rName)
}

func testAccAWSDynamoDbConfigAddSecondaryGSI(rName string) string {
return fmt.Sprintf(`
resource "aws_dynamodb_table" "test" {
Expand Down
Loading