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

Support encryption at rest for DynamoDB tables #3303

Merged
merged 8 commits into from
Mar 6, 2018
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
14 changes: 14 additions & 0 deletions aws/data_source_aws_dynamodb_table.go
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,20 @@ func dataSourceAwsDynamoDbTable() *schema.Resource {
Type: schema.TypeInt,
Computed: true,
},
"server_side_encryption": {
Type: schema.TypeList,
Optional: true,
Computed: true,
MaxItems: 1,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"enabled": {
Type: schema.TypeBool,
Computed: true,
},
},
},
},
},
}
}
Expand Down
11 changes: 6 additions & 5 deletions aws/data_source_aws_dynamodb_table_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ func TestAccDataSourceAwsDynamoDbTable_basic(t *testing.T) {
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"),
),
},
},
Expand All @@ -41,22 +42,22 @@ func testAccDataSourceAwsDynamoDbTableConfigBasic(tableName string) string {
write_capacity = 20
hash_key = "UserId"
range_key = "GameTitle"

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

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

attribute {
name = "TopScore"
type = "N"
}

global_secondary_index {
name = "GameTitleIndex"
hash_key = "GameTitle"
Expand All @@ -66,7 +67,7 @@ func testAccDataSourceAwsDynamoDbTableConfigBasic(tableName string) string {
projection_type = "INCLUDE"
non_key_attributes = ["UserId"]
}

tags {
Name = "dynamodb-table-1"
Environment = "test"
Expand Down
52 changes: 49 additions & 3 deletions aws/resource_aws_dynamodb_table.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (

"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/dynamodb"
"github.com/hashicorp/terraform/helper/customdiff"
"github.com/hashicorp/terraform/helper/hashcode"
"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/helper/schema"
Expand All @@ -30,9 +31,20 @@ func resourceAwsDynamoDbTable() *schema.Resource {
Update: schema.DefaultTimeout(10 * time.Minute),
},

CustomizeDiff: func(diff *schema.ResourceDiff, v interface{}) error {
return validateDynamoDbStreamSpec(diff)
},
CustomizeDiff: customdiff.Sequence(
func(diff *schema.ResourceDiff, v interface{}) error {
return validateDynamoDbStreamSpec(diff)
},
func(diff *schema.ResourceDiff, v interface{}) error {
if diff.Id() != "" && diff.HasChange("server_side_encryption") {
o, n := diff.GetChange("server_side_encryption")
if isDynamoDbTableSSEDisabled(o) && isDynamoDbTableSSEDisabled(n) {
return diff.Clear("server_side_encryption")
}
}
return nil
},
),

SchemaVersion: 1,
MigrateState: resourceAwsDynamoDbTableMigrateState,
Expand Down Expand Up @@ -196,6 +208,21 @@ func resourceAwsDynamoDbTable() *schema.Resource {
Type: schema.TypeString,
Computed: true,
},
"server_side_encryption": {
Type: schema.TypeList,
Optional: true,
Computed: true,
MaxItems: 1,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"enabled": {
Type: schema.TypeBool,
Required: true,
ForceNew: true,
},
},
},
},
"tags": tagsSchema(),
},
}
Expand Down Expand Up @@ -250,6 +277,16 @@ 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)
}

var output *dynamodb.CreateTableOutput
err := resource.Retry(2*time.Minute, func() *resource.RetryError {
var err error
Expand Down Expand Up @@ -670,3 +707,12 @@ func waitForDynamoDbTtlUpdateToBeCompleted(tableName string, toEnable bool, conn
_, err := stateConf.WaitForState()
return err
}

func isDynamoDbTableSSEDisabled(v interface{}) bool {
options := v.([]interface{})
if len(options) == 0 {
return true
}
e := options[0].(map[string]interface{})["enabled"]
return !e.(bool)
}
113 changes: 113 additions & 0 deletions aws/resource_aws_dynamodb_table_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -286,6 +286,31 @@ func TestAccAWSDynamoDbTable_basic(t *testing.T) {

rName := acctest.RandomWithPrefix("TerraformTestTable-")

resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckAWSDynamoDbTableDestroy,
Steps: []resource.TestStep{
{
Config: testAccAWSDynamoDbConfig_basic(rName),
Check: resource.ComposeTestCheckFunc(
testAccCheckInitialAWSDynamoDbTableExists("aws_dynamodb_table.basic-dynamodb-table", &conf),
resource.TestCheckResourceAttr("aws_dynamodb_table.basic-dynamodb-table", "name", rName),
resource.TestCheckResourceAttr("aws_dynamodb_table.basic-dynamodb-table", "read_capacity", "1"),
resource.TestCheckResourceAttr("aws_dynamodb_table.basic-dynamodb-table", "write_capacity", "1"),
resource.TestCheckResourceAttr("aws_dynamodb_table.basic-dynamodb-table", "hash_key", "TestTableHashKey"),
resource.TestCheckResourceAttr("aws_dynamodb_table.basic-dynamodb-table", "attribute.2990477658.name", "TestTableHashKey"),
resource.TestCheckResourceAttr("aws_dynamodb_table.basic-dynamodb-table", "attribute.2990477658.type", "S"),
),
},
},
})
}
func TestAccAWSDynamoDbTable_extended(t *testing.T) {
var conf dynamodb.DescribeTableOutput

rName := acctest.RandomWithPrefix("TerraformTestTable-")

resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
Expand Down Expand Up @@ -625,6 +650,54 @@ func testAccCheckDynamoDbTableTimeToLiveWasUpdated(n string) resource.TestCheckF
}
}

func TestAccAWSDynamoDbTable_encryption(t *testing.T) {
var confEncEnabled, confEncDisabled, confBasic dynamodb.DescribeTableOutput

rName := acctest.RandomWithPrefix("TerraformTestTable-")

resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckAWSDynamoDbTableDestroy,
Steps: []resource.TestStep{
{
Config: testAccAWSDynamoDbConfigInitialStateWithEncryption(rName, true),
Check: resource.ComposeTestCheckFunc(
testAccCheckInitialAWSDynamoDbTableExists("aws_dynamodb_table.basic-dynamodb-table", &confEncEnabled),
resource.TestCheckResourceAttr("aws_dynamodb_table.basic-dynamodb-table", "server_side_encryption.#", "1"),
resource.TestCheckResourceAttr("aws_dynamodb_table.basic-dynamodb-table", "server_side_encryption.0.enabled", "true"),
),
},
{
Config: testAccAWSDynamoDbConfigInitialStateWithEncryption(rName, false),
Check: resource.ComposeTestCheckFunc(
testAccCheckInitialAWSDynamoDbTableExists("aws_dynamodb_table.basic-dynamodb-table", &confEncDisabled),
resource.TestCheckResourceAttr("aws_dynamodb_table.basic-dynamodb-table", "server_side_encryption.#", "0"),
func(s *terraform.State) error {
if confEncDisabled.Table.CreationDateTime.Equal(*confEncEnabled.Table.CreationDateTime) {
return fmt.Errorf("[ERROR] DynamoDB table not recreated when changing SSE")
}
return nil
},
),
},
{
Config: testAccAWSDynamoDbConfig_basic(rName),
Check: resource.ComposeTestCheckFunc(
testAccCheckInitialAWSDynamoDbTableExists("aws_dynamodb_table.basic-dynamodb-table", &confBasic),
resource.TestCheckResourceAttr("aws_dynamodb_table.basic-dynamodb-table", "server_side_encryption.#", "0"),
func(s *terraform.State) error {
if !confBasic.Table.CreationDateTime.Equal(*confEncDisabled.Table.CreationDateTime) {
return fmt.Errorf("[ERROR] DynamoDB table was recreated unexpectedly")
}
return nil
},
),
},
},
})
}

func TestResourceAWSDynamoDbTableStreamViewType_validation(t *testing.T) {
cases := []struct {
Value string
Expand Down Expand Up @@ -761,6 +834,10 @@ func testAccCheckInitialAWSDynamoDbTableConf(n string) resource.TestCheckFunc {
return fmt.Errorf("Provisioned read capacity was %d, not 10!", table.ProvisionedThroughput.ReadCapacityUnits)
}

if table.SSEDescription != nil && *table.SSEDescription.Status != dynamodb.SSEStatusDisabled {
return fmt.Errorf("SSE status was %s, not %s", *table.SSEDescription.Status, dynamodb.SSEStatusDisabled)
}

attrCount := len(table.AttributeDefinitions)
gsiCount := len(table.GlobalSecondaryIndexes)
lsiCount := len(table.LocalSecondaryIndexes)
Expand Down Expand Up @@ -880,6 +957,22 @@ func dynamoDbAttributesToMap(attributes *[]*dynamodb.AttributeDefinition) map[st
return attrmap
}

func testAccAWSDynamoDbConfig_basic(rName string) string {
return fmt.Sprintf(`
resource "aws_dynamodb_table" "basic-dynamodb-table" {
name = "%s"
read_capacity = 1
write_capacity = 1
hash_key = "TestTableHashKey"

attribute {
name = "TestTableHashKey"
type = "S"
}
}
`, rName)
}

func testAccAWSDynamoDbConfigInitialState(rName string) string {
return fmt.Sprintf(`
resource "aws_dynamodb_table" "basic-dynamodb-table" {
Expand Down Expand Up @@ -927,6 +1020,26 @@ resource "aws_dynamodb_table" "basic-dynamodb-table" {
`, rName)
}

func testAccAWSDynamoDbConfigInitialStateWithEncryption(rName string, enabled bool) string {
return fmt.Sprintf(`
resource "aws_dynamodb_table" "basic-dynamodb-table" {
name = "%s"
read_capacity = 1
write_capacity = 1
hash_key = "TestTableHashKey"

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

server_side_encryption {
enabled = %t
}
}
`, rName, enabled)
}

func testAccAWSDynamoDbConfigAddSecondaryGSI(rName string) string {
return fmt.Sprintf(`
resource "aws_dynamodb_table" "basic-dynamodb-table" {
Expand Down
21 changes: 21 additions & 0 deletions aws/structure.go
Original file line number Diff line number Diff line change
Expand Up @@ -3522,6 +3522,17 @@ func flattenAwsDynamoDbTableResource(d *schema.ResourceData, table *dynamodb.Tab
return err
}

sseOptions := []map[string]interface{}{}
if table.SSEDescription != nil {
m := map[string]interface{}{}
m["enabled"] = aws.StringValue(table.SSEDescription.Status) == dynamodb.SSEStatusEnabled
sseOptions = []map[string]interface{}{m}
}
err = d.Set("server_side_encryption", sseOptions)
if err != nil {
return err
}

d.Set("arn", table.TableArn)

return nil
Expand Down Expand Up @@ -3610,6 +3621,16 @@ func expandDynamoDbKeySchema(data map[string]interface{}) []*dynamodb.KeySchemaE
return keySchema
}

func expandDynamoDbEncryptAtRestOptions(m map[string]interface{}) *dynamodb.SSESpecification {
options := dynamodb.SSESpecification{}

if v, ok := m["enabled"]; ok {
options.Enabled = aws.Bool(v.(bool))
}

return &options
}

func flattenVpcEndpointServiceAllowedPrincipals(allowedPrincipals []*ec2.AllowedPrincipal) []string {
result := make([]string, 0, len(allowedPrincipals))
for _, allowedPrincipal := range allowedPrincipals {
Expand Down
5 changes: 5 additions & 0 deletions website/docs/r/dynamodb_table.html.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ definition after you have created the resource.
attributes, etc.
* `stream_enabled` - (Optional) Indicates whether Streams are to be enabled (true) or disabled (false).
* `stream_view_type` - (Optional) When an item in the table is modified, StreamViewType determines what information is written to the table's stream. Valid values are `KEYS_ONLY`, `NEW_IMAGE`, `OLD_IMAGE`, `NEW_AND_OLD_IMAGES`.
* `server_side_encryption` - (Optional) Encrypt at rest options.
* `tags` - (Optional) A map of tags to populate on the created table.

### Timeouts
Expand Down Expand Up @@ -130,6 +131,10 @@ The `timeouts` block allows you to specify [timeouts](https://www.terraform.io/d
projection type; a list of attributes to project into the index. These
do not need to be defined as attributes on the table.

#### `server_side_encryption`

* `enabled` - (Required) Whether to enable encryption at rest. If the `server_side_encryption` block is not provided then this defaults to `false`.

### A note about attributes

Only define attributes on the table object that are going to be used as:
Expand Down