Skip to content

Commit

Permalink
Merge branch 'trung-f-r-2217'
Browse files Browse the repository at this point in the history
Fixes #2472
  • Loading branch information
jen20 committed Dec 17, 2017
2 parents 6bd9216 + c7838e5 commit 019e6e0
Show file tree
Hide file tree
Showing 4 changed files with 321 additions and 0 deletions.
149 changes: 149 additions & 0 deletions aws/resource_aws_s3_bucket.go
Original file line number Diff line number Diff line change
Expand Up @@ -392,6 +392,43 @@ func resourceAwsS3Bucket() *schema.Resource {
},
},

"server_side_encryption_configuration": {
Type: schema.TypeList,
MaxItems: 1,
Optional: true,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"rule": {
Type: schema.TypeList,
MaxItems: 1,
Required: true,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"apply_server_side_encryption_by_default": {
Type: schema.TypeList,
MaxItems: 1,
Required: true,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"kms_master_key_id": {
Type: schema.TypeString,
Optional: true,
},
"sse_algorithm": {
Type: schema.TypeString,
Required: true,
ValidateFunc: validateS3BucketServerSideEncryptionAlgorithm,
},
},
},
},
},
},
},
},
},
},

"tags": tagsSchema(),
},
}
Expand Down Expand Up @@ -531,6 +568,12 @@ func resourceAwsS3BucketUpdate(d *schema.ResourceData, meta interface{}) error {
}
}

if d.HasChange("server_side_encryption_configuration") {
if err := resourceAwsS3BucketServerSideEncryptionConfigurationUpdate(s3conn, d); err != nil {
return err
}
}

return resourceAwsS3BucketRead(d, meta)
}

Expand Down Expand Up @@ -941,6 +984,31 @@ func resourceAwsS3BucketRead(d *schema.ResourceData, meta interface{}) error {
}
}

// Read the bucket server side encryption configuration

encryptionResponse, err := retryOnAwsCode("NoSuchBucket", func() (interface{}, error) {
return s3conn.GetBucketEncryption(&s3.GetBucketEncryptionInput{
Bucket: aws.String(d.Id()),
})
})
if err != nil {
if isAWSErr(err, "ServerSideEncryptionConfigurationNotFoundError", "encryption configuration was not found") {
log.Printf("[DEBUG] Default encryption is not enabled for %s", d.Id())
d.Set("server_side_encryption_configuration", []map[string]interface{}{})
} else {
return err
}
} else {
encryption := encryptionResponse.(*s3.GetBucketEncryptionOutput)
log.Printf("[DEBUG] S3 Bucket: %s, read encryption configuration: %v", d.Id(), encryption)
if c := encryption.ServerSideEncryptionConfiguration; c != nil {
if err := d.Set("server_side_encryption_configuration", flattenAwsS3ServerSideEncryptionConfiguration(c)); err != nil {
log.Printf("[DEBUG] Error setting server side encryption configuration: %s", err)
return err
}
}
}

// Add the region as an attribute

locationResponse, err := retryOnAwsCode("NoSuchBucket", func() (interface{}, error) {
Expand Down Expand Up @@ -1493,6 +1561,68 @@ func resourceAwsS3BucketRequestPayerUpdate(s3conn *s3.S3, d *schema.ResourceData
return nil
}

func resourceAwsS3BucketServerSideEncryptionConfigurationUpdate(s3conn *s3.S3, d *schema.ResourceData) error {
bucket := d.Get("bucket").(string)
serverSideEncryptionConfiguration := d.Get("server_side_encryption_configuration").([]interface{})
if len(serverSideEncryptionConfiguration) == 0 {
log.Printf("[DEBUG] Delete server side encryption configuration: %#v", serverSideEncryptionConfiguration)
i := &s3.DeleteBucketEncryptionInput{
Bucket: aws.String(bucket),
}

err := resource.Retry(1*time.Minute, func() *resource.RetryError {
if _, err := s3conn.DeleteBucketEncryption(i); err != nil {
return resource.NonRetryableError(err)
}
return nil
})
if err != nil {
return fmt.Errorf("error removing S3 bucket server side encryption: %s", err)
}
return nil
}

c := serverSideEncryptionConfiguration[0].(map[string]interface{})

rc := &s3.ServerSideEncryptionConfiguration{}

rcRules := c["rule"].([]interface{})
var rules []*s3.ServerSideEncryptionRule
for _, v := range rcRules {
rr := v.(map[string]interface{})
rrDefault := rr["apply_server_side_encryption_by_default"].([]interface{})
sseAlgorithm := rrDefault[0].(map[string]interface{})["sse_algorithm"].(string)
kmsMasterKeyId := rrDefault[0].(map[string]interface{})["kms_master_key_id"].(string)
rcDefaultRule := &s3.ServerSideEncryptionByDefault{
SSEAlgorithm: aws.String(sseAlgorithm),
}
if kmsMasterKeyId != "" {
rcDefaultRule.KMSMasterKeyID = aws.String(kmsMasterKeyId)
}
rcRule := &s3.ServerSideEncryptionRule{
ApplyServerSideEncryptionByDefault: rcDefaultRule,
}

rules = append(rules, rcRule)
}

rc.Rules = rules
i := &s3.PutBucketEncryptionInput{
Bucket: aws.String(bucket),
ServerSideEncryptionConfiguration: rc,
}
log.Printf("[DEBUG] S3 put bucket replication configuration: %#v", i)

_, err := retryOnAwsCode("NoSuchBucket", func() (interface{}, error) {
return s3conn.PutBucketEncryption(i)
})
if err != nil {
return fmt.Errorf("error putting S3 server side encryption configuration: %s", err)
}

return nil
}

func resourceAwsS3BucketReplicationConfigurationUpdate(s3conn *s3.S3, d *schema.ResourceData) error {
bucket := d.Get("bucket").(string)
replicationConfiguration := d.Get("replication_configuration").([]interface{})
Expand Down Expand Up @@ -1739,6 +1869,25 @@ func resourceAwsS3BucketLifecycleUpdate(s3conn *s3.S3, d *schema.ResourceData) e
return nil
}

func flattenAwsS3ServerSideEncryptionConfiguration(c *s3.ServerSideEncryptionConfiguration) []map[string]interface{} {
var encryptionConfiguration []map[string]interface{}
rules := make([]interface{}, 0, len(c.Rules))
for _, v := range c.Rules {
if v.ApplyServerSideEncryptionByDefault != nil {
r := make(map[string]interface{})
d := make(map[string]interface{})
d["kms_master_key_id"] = aws.StringValue(v.ApplyServerSideEncryptionByDefault.KMSMasterKeyID)
d["sse_algorithm"] = aws.StringValue(v.ApplyServerSideEncryptionByDefault.SSEAlgorithm)
r["apply_server_side_encryption_by_default"] = []map[string]interface{}{d}
rules = append(rules, r)
}
}
encryptionConfiguration = append(encryptionConfiguration, map[string]interface{}{
"rule": rules,
})
return encryptionConfiguration
}

func flattenAwsS3BucketReplicationConfiguration(r *s3.ReplicationConfiguration) []map[string]interface{} {
replication_configuration := make([]map[string]interface{}, 0, 1)
m := make(map[string]interface{})
Expand Down
127 changes: 127 additions & 0 deletions aws/resource_aws_s3_bucket_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -412,6 +412,74 @@ func TestAccAWSS3Bucket_WebsiteRoutingRules(t *testing.T) {
})
}

func TestAccAWSS3Bucket_enableDefaultEncryption_whenTypical(t *testing.T) {
rInt := acctest.RandInt()
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckAWSS3BucketDestroy,
Steps: []resource.TestStep{
{
Config: testAccAWSS3BucketEnableDefaultEncryption(rInt),
Check: resource.ComposeTestCheckFunc(
testAccCheckAWSS3BucketExists("aws_s3_bucket.arbitrary"),
resource.TestCheckResourceAttr("aws_s3_bucket.arbitrary", "server_side_encryption_configuration.#", "1"),
resource.TestCheckResourceAttr("aws_s3_bucket.arbitrary", "server_side_encryption_configuration.0.rule.#", "1"),
resource.TestCheckResourceAttr("aws_s3_bucket.arbitrary", "server_side_encryption_configuration.0.rule.0.apply_server_side_encryption_by_default.#", "1"),
resource.TestCheckResourceAttr("aws_s3_bucket.arbitrary", "server_side_encryption_configuration.0.rule.0.apply_server_side_encryption_by_default.0.sse_algorithm", "aws:kms"),
resource.TestMatchResourceAttr("aws_s3_bucket.arbitrary", "server_side_encryption_configuration.0.rule.0.apply_server_side_encryption_by_default.0.kms_master_key_id", regexp.MustCompile("^arn")),
),
},
},
})
}

func TestAccAWSS3Bucket_enableDefaultEncryption_whenAES256IsUsed(t *testing.T) {
rInt := acctest.RandInt()
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckAWSS3BucketDestroy,
Steps: []resource.TestStep{
{
Config: testAccAWSS3BucketEnableDefaultEncryptionWithAES256(rInt),
Check: resource.ComposeTestCheckFunc(
testAccCheckAWSS3BucketExists("aws_s3_bucket.arbitrary"),
resource.TestCheckResourceAttr("aws_s3_bucket.arbitrary", "server_side_encryption_configuration.#", "1"),
resource.TestCheckResourceAttr("aws_s3_bucket.arbitrary", "server_side_encryption_configuration.0.rule.#", "1"),
resource.TestCheckResourceAttr("aws_s3_bucket.arbitrary", "server_side_encryption_configuration.0.rule.0.apply_server_side_encryption_by_default.#", "1"),
resource.TestCheckResourceAttr("aws_s3_bucket.arbitrary", "server_side_encryption_configuration.0.rule.0.apply_server_side_encryption_by_default.0.sse_algorithm", "AES256"),
resource.TestCheckResourceAttr("aws_s3_bucket.arbitrary", "server_side_encryption_configuration.0.rule.0.apply_server_side_encryption_by_default.0.kms_master_key_id", ""),
),
},
},
})
}

func TestAccAWSS3Bucket_disableDefaultEncryption_whenDefaultEncryptionIsEnabled(t *testing.T) {
rInt := acctest.RandInt()
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckAWSS3BucketDestroy,
Steps: []resource.TestStep{
{
Config: testAccAWSS3BucketEnableDefaultEncryptionWithDefaultKey(rInt),
Check: resource.ComposeTestCheckFunc(
testAccCheckAWSS3BucketExists("aws_s3_bucket.arbitrary"),
),
},
{
Config: testAccAWSS3BucketDisableDefaultEncryption(rInt),
Check: resource.ComposeTestCheckFunc(
testAccCheckAWSS3BucketExists("aws_s3_bucket.arbitrary"),
resource.TestCheckResourceAttr("aws_s3_bucket.arbitrary", "server_side_encryption_configuration.#", "0"),
),
},
},
})
}

// Test TestAccAWSS3Bucket_shouldFailNotFound is designed to fail with a "plan
// not empty" error in Terraform, to check against regresssions.
// See https://github.com/hashicorp/terraform/pull/2925
Expand Down Expand Up @@ -1423,6 +1491,65 @@ resource "aws_s3_bucket" "bucket" {
`, randInt)
}

func testAccAWSS3BucketEnableDefaultEncryption(randInt int) string {
return fmt.Sprintf(`
resource "aws_kms_key" "arbitrary" {
description = "KMS Key for Bucket Testing %d"
deletion_window_in_days = 10
}
resource "aws_s3_bucket" "arbitrary" {
bucket = "tf-test-bucket-%d"
server_side_encryption_configuration {
rule {
apply_server_side_encryption_by_default {
kms_master_key_id = "${aws_kms_key.arbitrary.arn}"
sse_algorithm = "aws:kms"
}
}
}
}
`, randInt, randInt)
}

func testAccAWSS3BucketEnableDefaultEncryptionWithAES256(randInt int) string {
return fmt.Sprintf(`
resource "aws_s3_bucket" "arbitrary" {
bucket = "tf-test-bucket-%d"
server_side_encryption_configuration {
rule {
apply_server_side_encryption_by_default {
sse_algorithm = "AES256"
}
}
}
}
`, randInt)
}

func testAccAWSS3BucketEnableDefaultEncryptionWithDefaultKey(randInt int) string {
return fmt.Sprintf(`
resource "aws_s3_bucket" "arbitrary" {
bucket = "tf-test-bucket-%d"
server_side_encryption_configuration {
rule {
apply_server_side_encryption_by_default {
sse_algorithm = "aws:kms"
}
}
}
}
`, randInt)
}

func testAccAWSS3BucketDisableDefaultEncryption(randInt int) string {
return fmt.Sprintf(`
resource "aws_s3_bucket" "arbitrary" {
bucket = "tf-test-bucket-%d"
}
`, randInt)
}

func testAccAWSS3BucketConfigWithEmptyPolicy(randInt int) string {
return fmt.Sprintf(`
resource "aws_s3_bucket" "bucket" {
Expand Down
10 changes: 10 additions & 0 deletions aws/validators.go
Original file line number Diff line number Diff line change
Expand Up @@ -616,6 +616,16 @@ func validateS3BucketReplicationRulePrefix(v interface{}, k string) (ws []string
return
}

func validateS3BucketServerSideEncryptionAlgorithm(v interface{}, k string) (ws []string, errors []error) {
value := v.(string)
if value != s3.ServerSideEncryptionAes256 && value != s3.ServerSideEncryptionAwsKms {
errors = append(errors, fmt.Errorf(
"%q must be one of %q or %q", k, s3.ServerSideEncryptionAwsKms, s3.ServerSideEncryptionAes256))
}

return
}

func validateS3BucketReplicationDestinationStorageClass(v interface{}, k string) (ws []string, errors []error) {
value := v.(string)
if value != s3.StorageClassStandard && value != s3.StorageClassStandardIa && value != s3.StorageClassReducedRedundancy {
Expand Down
35 changes: 35 additions & 0 deletions website/docs/r/s3_bucket.html.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -287,6 +287,27 @@ resource "aws_s3_bucket" "bucket" {
}
```

### Enable Default Server Side Encryption

```hcl
resource "aws_kms_key" "mykey" {
description = "This key is used to encrypt bucket objects"
deletion_window_in_days = 10
}
resource "aws_s3_bucket" "mybucket" {
bucket = "mybucket"
server_side_encryption_configuration {
rule {
apply_server_side_encryption_by_default {
kms_master_key_id = "${aws_kms_key.mykey.arn}"
sse_algorithm = "aws:kms"
}
}
}
}
```

## Argument Reference

The following arguments are supported:
Expand All @@ -310,6 +331,7 @@ Can be either `BucketOwner` or `Requester`. By default, the owner of the S3 buck
the costs of any data transfer. See [Requester Pays Buckets](http://docs.aws.amazon.com/AmazonS3/latest/dev/RequesterPaysBuckets.html)
developer guide for more information.
* `replication_configuration` - (Optional) A configuration of [replication configuration](http://docs.aws.amazon.com/AmazonS3/latest/dev/crr.html) (documented below).
* `server_side_encryption_configuration` - (Optional) A confguration of [server-side encryption configuration](http://docs.aws.amazon.com/AmazonS3/latest/dev/bucket-encryption.html) (documented blow)

~> **NOTE:** You cannot use `acceleration_status` in `cn-north-1` or `us-gov-west-1`

Expand Down Expand Up @@ -391,6 +413,19 @@ The `destination` object supports the following:
* `bucket` - (Required) The ARN of the S3 bucket where you want Amazon S3 to store replicas of the object identified by the rule.
* `storage_class` - (Optional) The class of storage used to store the object.

The `server_side_encryption_configuration` object supports the following:

* `rule` - (required) A single object for server-side encryption by default configuration. (documented below)

The 'rule' object supports the following:

* `apply_server_side_encryption_by_default` - (required) A single object for setting server-side encryption by default. (documented below)

The `apply_server_side_encryption_by_default` object supports the following:

* `sse_algorithm` - (required) The server-side encryption algorithm to use. Valid values are `AES256` and `aws:kms`
* `kms_master_key_id` - (optional) The AWS KMS master key ID used for the SSE-KMS encryption. This can only be used when you set the value of `sse_algorithm` as `aws:kms`. The default `aws/s3` AWS KMS master key is used if this element is absent while the `sse_algorithm` is `aws:kms`.

## Attributes Reference

The following attributes are exported:
Expand Down

0 comments on commit 019e6e0

Please sign in to comment.