Skip to content

Commit

Permalink
Merge pull request #10388 from terraform-providers/b-aws_s3_bucket-fo…
Browse files Browse the repository at this point in the history
…rce_destroy-infinite

resource/aws_s3_bucket: Prevent infinite deletion recursion with force_destroy argument and object keys with empty "directory" prefixes
  • Loading branch information
bflad authored Oct 4, 2019
2 parents 666a205 + 6803a54 commit a00139b
Show file tree
Hide file tree
Showing 4 changed files with 45 additions and 3 deletions.
13 changes: 12 additions & 1 deletion aws/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -280,6 +280,7 @@ type AWSClient struct {
resourcegroupsconn *resourcegroups.ResourceGroups
route53resolverconn *route53resolver.Route53Resolver
s3conn *s3.S3
s3connUriCleaningDisabled *s3.S3
s3controlconn *s3control.S3Control
sagemakerconn *sagemaker.SageMaker
scconn *servicecatalog.ServiceCatalog
Expand Down Expand Up @@ -470,7 +471,6 @@ func (c *Config) Client() (interface{}, error) {
region: c.Region,
resourcegroupsconn: resourcegroups.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints["resourcegroups"])})),
route53resolverconn: route53resolver.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints["route53resolver"])})),
s3conn: s3.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints["s3"]), S3ForcePathStyle: aws.Bool(c.S3ForcePathStyle)})),
s3controlconn: s3control.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints["s3control"])})),
sagemakerconn: sagemaker.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints["sagemaker"])})),
scconn: servicecatalog.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints["servicecatalog"])})),
Expand Down Expand Up @@ -508,6 +508,17 @@ func (c *Config) Client() (interface{}, error) {
Endpoint: aws.String(c.Endpoints["shield"]),
}

// Services that require multiple client configurations
s3Config := &aws.Config{
Endpoint: aws.String(c.Endpoints["s3"]),
S3ForcePathStyle: aws.Bool(c.S3ForcePathStyle),
}

client.s3conn = s3.New(sess.Copy(s3Config))

s3Config.DisableRestProtocolURICleaning = aws.Bool(true)
client.s3connUriCleaningDisabled = s3.New(sess.Copy(s3Config))

// Handle deprecated endpoint configurations
if c.Endpoints["kinesis_analytics"] != "" {
client.kinesisanalyticsconn = kinesisanalytics.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints["kinesis_analytics"])}))
Expand Down
5 changes: 5 additions & 0 deletions aws/resource_aws_s3_bucket.go
Original file line number Diff line number Diff line change
Expand Up @@ -1256,6 +1256,11 @@ func resourceAwsS3BucketDelete(d *schema.ResourceData, meta interface{}) error {

if isAWSErr(err, "BucketNotEmpty", "") {
if d.Get("force_destroy").(bool) {
// Use a S3 service client that can handle multiple slashes in URIs.
// While aws_s3_bucket_object resources cannot create these object
// keys, other AWS services and applications using the S3 Bucket can.
s3conn = meta.(*AWSClient).s3connUriCleaningDisabled

// bucket may have things delete them
log.Printf("[DEBUG] S3 Bucket attempting to forceDestroy %+v", err)

Expand Down
2 changes: 1 addition & 1 deletion aws/resource_aws_s3_bucket_object_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ func testSweepS3BucketObjects(region string) error {
return fmt.Errorf("error getting client: %s", err)
}

conn := client.(*AWSClient).s3conn
conn := client.(*AWSClient).s3connUriCleaningDisabled
input := &s3.ListBucketsInput{}

output, err := conn.ListBuckets(input)
Expand Down
28 changes: 27 additions & 1 deletion aws/resource_aws_s3_bucket_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1923,6 +1923,32 @@ func TestAccAWSS3Bucket_forceDestroy(t *testing.T) {
})
}

// By default, the AWS Go SDK cleans up URIs by removing extra slashes
// when the service API requests use the URI as part of making a request.
// While the aws_s3_bucket_object resource automatically cleans the key
// to not contain these extra slashes, out-of-band handling and other AWS
// services may create keys with extra slashes (empty "directory" prefixes).
func TestAccAWSS3Bucket_forceDestroyWithEmptyPrefixes(t *testing.T) {
resourceName := "aws_s3_bucket.bucket"
rInt := acctest.RandInt()
bucketName := fmt.Sprintf("tf-test-bucket-%d", rInt)

resource.ParallelTest(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckAWSS3BucketDestroy,
Steps: []resource.TestStep{
{
Config: testAccAWSS3BucketConfig_forceDestroy(bucketName),
Check: resource.ComposeTestCheckFunc(
testAccCheckAWSS3BucketExists(resourceName),
testAccCheckAWSS3BucketAddObjects(resourceName, "data.txt", "/extraleadingslash.txt"),
),
},
},
})
}

func TestAccAWSS3Bucket_forceDestroyWithObjectLockEnabled(t *testing.T) {
resourceName := "aws_s3_bucket.bucket"
rInt := acctest.RandInt()
Expand Down Expand Up @@ -2161,7 +2187,7 @@ func testAccCheckAWSS3DestroyBucket(n string) resource.TestCheckFunc {
func testAccCheckAWSS3BucketAddObjects(n string, keys ...string) resource.TestCheckFunc {
return func(s *terraform.State) error {
rs := s.RootModule().Resources[n]
conn := testAccProvider.Meta().(*AWSClient).s3conn
conn := testAccProvider.Meta().(*AWSClient).s3connUriCleaningDisabled

for _, key := range keys {
_, err := conn.PutObject(&s3.PutObjectInput{
Expand Down

0 comments on commit a00139b

Please sign in to comment.