diff --git a/aws/resource_aws_s3_bucket.go b/aws/resource_aws_s3_bucket.go index d42025eddcf..3c36d8fed67 100644 --- a/aws/resource_aws_s3_bucket.go +++ b/aws/resource_aws_s3_bucket.go @@ -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(), }, } @@ -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) } @@ -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) { @@ -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{}) @@ -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{}) diff --git a/aws/resource_aws_s3_bucket_test.go b/aws/resource_aws_s3_bucket_test.go index fb581a318ef..a31be8114e8 100644 --- a/aws/resource_aws_s3_bucket_test.go +++ b/aws/resource_aws_s3_bucket_test.go @@ -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 @@ -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" { diff --git a/aws/validators.go b/aws/validators.go index 14b1036ae3d..9c5ecb4da85 100644 --- a/aws/validators.go +++ b/aws/validators.go @@ -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 { diff --git a/website/docs/r/s3_bucket.html.markdown b/website/docs/r/s3_bucket.html.markdown index 8a9d745914b..cf7d514c26e 100644 --- a/website/docs/r/s3_bucket.html.markdown +++ b/website/docs/r/s3_bucket.html.markdown @@ -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: @@ -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` @@ -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: