-
Notifications
You must be signed in to change notification settings - Fork 9.6k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
provider/aws: Add aws_s3_bucket_policy resource
This commit adds a new "attachment" style resource for setting the policy of an AWS S3 bucket. This is desirable such that the ARN of the bucket can be referenced in an IAM Policy Document. In addition, we now suppress diffs on the (now-computed) policy in the S3 bucket for structurally equivalent policies, which prevents flapping because of whitespace and map ordering changes made by the S3 endpoint.
- Loading branch information
Showing
5 changed files
with
314 additions
and
6 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
package aws | ||
|
||
import ( | ||
"github.com/hashicorp/terraform/helper/schema" | ||
"github.com/jen20/awspolicyequivalence" | ||
) | ||
|
||
func suppressEquivalentAwsPolicyDiffs(k, old, new string, d *schema.ResourceData) bool { | ||
equivalent, err := awspolicy.PoliciesAreEquivalent(old, new) | ||
if err != nil { | ||
return false | ||
} | ||
|
||
return equivalent | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,112 @@ | ||
package aws | ||
|
||
import ( | ||
//"encoding/json" | ||
"fmt" | ||
"log" | ||
"time" | ||
|
||
"github.com/aws/aws-sdk-go/aws" | ||
"github.com/aws/aws-sdk-go/aws/awserr" | ||
"github.com/aws/aws-sdk-go/service/s3" | ||
"github.com/hashicorp/terraform/helper/resource" | ||
"github.com/hashicorp/terraform/helper/schema" | ||
) | ||
|
||
func resourceAwsS3BucketPolicy() *schema.Resource { | ||
return &schema.Resource{ | ||
Create: resourceAwsS3BucketPolicyPut, | ||
Read: resourceAwsS3BucketPolicyRead, | ||
Update: resourceAwsS3BucketPolicyPut, | ||
Delete: resourceAwsS3BucketPolicyDelete, | ||
|
||
Schema: map[string]*schema.Schema{ | ||
"bucket": { | ||
Type: schema.TypeString, | ||
Required: true, | ||
ForceNew: true, | ||
}, | ||
|
||
"policy": { | ||
Type: schema.TypeString, | ||
Required: true, | ||
DiffSuppressFunc: suppressEquivalentAwsPolicyDiffs, | ||
}, | ||
}, | ||
} | ||
} | ||
|
||
func resourceAwsS3BucketPolicyPut(d *schema.ResourceData, meta interface{}) error { | ||
s3conn := meta.(*AWSClient).s3conn | ||
|
||
bucket := d.Get("bucket").(string) | ||
policy := d.Get("policy").(string) | ||
|
||
d.SetId(bucket) | ||
|
||
log.Printf("[DEBUG] S3 bucket: %s, put policy: %s", bucket, policy) | ||
|
||
params := &s3.PutBucketPolicyInput{ | ||
Bucket: aws.String(bucket), | ||
Policy: aws.String(policy), | ||
} | ||
|
||
err := resource.Retry(1*time.Minute, func() *resource.RetryError { | ||
if _, err := s3conn.PutBucketPolicy(params); err != nil { | ||
if awserr, ok := err.(awserr.Error); ok { | ||
if awserr.Code() == "MalformedPolicy" { | ||
return resource.RetryableError(awserr) | ||
} | ||
} | ||
return resource.NonRetryableError(err) | ||
} | ||
return nil | ||
}) | ||
|
||
if err != nil { | ||
return fmt.Errorf("Error putting S3 policy: %s", err) | ||
} | ||
|
||
return nil | ||
} | ||
|
||
func resourceAwsS3BucketPolicyRead(d *schema.ResourceData, meta interface{}) error { | ||
s3conn := meta.(*AWSClient).s3conn | ||
|
||
pol, err := s3conn.GetBucketPolicy(&s3.GetBucketPolicyInput{ | ||
Bucket: aws.String(d.Id()), | ||
}) | ||
log.Printf("[DEBUG] S3 bucket: %s, read policy", d.Id()) | ||
if err != nil { | ||
if err := d.Set("policy", ""); err != nil { | ||
return err | ||
} | ||
} else { | ||
if v := pol.Policy; v == nil { | ||
if err := d.Set("policy", ""); err != nil { | ||
return err | ||
} | ||
} else if err := d.Set("policy", v); err != nil { | ||
return err | ||
} | ||
} | ||
|
||
return nil | ||
} | ||
|
||
func resourceAwsS3BucketPolicyDelete(d *schema.ResourceData, meta interface{}) error { | ||
s3conn := meta.(*AWSClient).s3conn | ||
|
||
bucket := d.Get("bucket").(string) | ||
|
||
log.Printf("[DEBUG] S3 bucket: %s, delete policy", bucket) | ||
_, err := s3conn.DeleteBucketPolicy(&s3.DeleteBucketPolicyInput{ | ||
Bucket: aws.String(bucket), | ||
}) | ||
|
||
if err != nil { | ||
return fmt.Errorf("Error deleting S3 policy: %s", err) | ||
} | ||
|
||
return nil | ||
} |
180 changes: 180 additions & 0 deletions
180
builtin/providers/aws/resource_aws_s3_bucket_policy_test.go
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,180 @@ | ||
package aws | ||
|
||
import ( | ||
"fmt" | ||
"testing" | ||
|
||
"github.com/aws/aws-sdk-go/aws" | ||
"github.com/aws/aws-sdk-go/service/s3" | ||
"github.com/hashicorp/terraform/helper/acctest" | ||
"github.com/hashicorp/terraform/helper/resource" | ||
"github.com/hashicorp/terraform/terraform" | ||
"github.com/jen20/awspolicyequivalence" | ||
) | ||
|
||
func TestAccAWSS3BucketPolicy_basic(t *testing.T) { | ||
name := fmt.Sprintf("tf-test-bucket-%d", acctest.RandInt()) | ||
|
||
expectedPolicyText := fmt.Sprintf( | ||
`{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Principal":{"AWS":"*"},"Action":"s3:*","Resource":["arn:aws:s3:::%s","arn:aws:s3:::%s/*"]}]}`, | ||
name, name) | ||
|
||
resource.Test(t, resource.TestCase{ | ||
PreCheck: func() { testAccPreCheck(t) }, | ||
Providers: testAccProviders, | ||
CheckDestroy: testAccCheckAWSS3BucketDestroy, | ||
Steps: []resource.TestStep{ | ||
{ | ||
Config: testAccAWSS3BucketPolicyConfig(name), | ||
Check: resource.ComposeTestCheckFunc( | ||
testAccCheckAWSS3BucketExists("aws_s3_bucket.bucket"), | ||
testAccCheckAWSS3BucketHasPolicy("aws_s3_bucket.bucket", expectedPolicyText), | ||
), | ||
}, | ||
}, | ||
}) | ||
} | ||
|
||
func TestAccAWSS3BucketPolicy_policyUpdate(t *testing.T) { | ||
name := fmt.Sprintf("tf-test-bucket-%d", acctest.RandInt()) | ||
|
||
expectedPolicyText1 := fmt.Sprintf( | ||
`{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Principal":{"AWS":"*"},"Action":"s3:*","Resource":["arn:aws:s3:::%s","arn:aws:s3:::%s/*"]}]}`, | ||
name, name) | ||
|
||
expectedPolicyText2 := fmt.Sprintf( | ||
`{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Principal":{"AWS":"*"},"Action":["s3:DeleteBucket", "s3:ListBucket", "s3:ListBucketVersions"], "Resource":["arn:aws:s3:::%s","arn:aws:s3:::%s/*"]}]}`, | ||
name, name) | ||
|
||
resource.Test(t, resource.TestCase{ | ||
PreCheck: func() { testAccPreCheck(t) }, | ||
Providers: testAccProviders, | ||
CheckDestroy: testAccCheckAWSS3BucketDestroy, | ||
Steps: []resource.TestStep{ | ||
{ | ||
Config: testAccAWSS3BucketPolicyConfig(name), | ||
Check: resource.ComposeTestCheckFunc( | ||
testAccCheckAWSS3BucketExists("aws_s3_bucket.bucket"), | ||
testAccCheckAWSS3BucketHasPolicy("aws_s3_bucket.bucket", expectedPolicyText1), | ||
), | ||
}, | ||
|
||
{ | ||
Config: testAccAWSS3BucketPolicyConfig_updated(name), | ||
Check: resource.ComposeTestCheckFunc( | ||
testAccCheckAWSS3BucketExists("aws_s3_bucket.bucket"), | ||
testAccCheckAWSS3BucketHasPolicy("aws_s3_bucket.bucket", expectedPolicyText2), | ||
), | ||
}, | ||
}, | ||
}) | ||
} | ||
|
||
func testAccCheckAWSS3BucketHasPolicy(n string, expectedPolicyText string) resource.TestCheckFunc { | ||
return func(s *terraform.State) error { | ||
rs, ok := s.RootModule().Resources[n] | ||
if !ok { | ||
return fmt.Errorf("Not found: %s", n) | ||
} | ||
|
||
if rs.Primary.ID == "" { | ||
return fmt.Errorf("No S3 Bucket ID is set") | ||
} | ||
|
||
conn := testAccProvider.Meta().(*AWSClient).s3conn | ||
|
||
policy, err := conn.GetBucketPolicy(&s3.GetBucketPolicyInput{ | ||
Bucket: aws.String(rs.Primary.ID), | ||
}) | ||
if err != nil { | ||
return fmt.Errorf("GetBucketPolicy error: %v", err) | ||
} | ||
|
||
actualPolicyText := *policy.Policy | ||
|
||
equivalent, err := awspolicy.PoliciesAreEquivalent(actualPolicyText, expectedPolicyText) | ||
if err != nil { | ||
return fmt.Errorf("Error testing policy equivalence: %s", err) | ||
} | ||
if !equivalent { | ||
return fmt.Errorf("Non-equivalent policy error:\n\nexpected: %s\n\n got: %s\n", | ||
expectedPolicyText, actualPolicyText) | ||
} | ||
|
||
return nil | ||
} | ||
} | ||
|
||
func testAccAWSS3BucketPolicyConfig(bucketName string) string { | ||
return fmt.Sprintf(` | ||
resource "aws_s3_bucket" "bucket" { | ||
bucket = "%s" | ||
tags { | ||
TestName = "TestAccAWSS3BucketPolicy_basic" | ||
} | ||
} | ||
resource "aws_s3_bucket_policy" "bucket" { | ||
bucket = "${aws_s3_bucket.bucket.bucket}" | ||
policy = "${data.aws_iam_policy_document.policy.json}" | ||
} | ||
data "aws_iam_policy_document" "policy" { | ||
statement { | ||
effect = "Allow" | ||
actions = [ | ||
"s3:*", | ||
] | ||
resources = [ | ||
"${aws_s3_bucket.bucket.arn}", | ||
"${aws_s3_bucket.bucket.arn}/*", | ||
] | ||
principals { | ||
type = "AWS" | ||
identifiers = ["*"] | ||
} | ||
} | ||
} | ||
`, bucketName) | ||
} | ||
|
||
func testAccAWSS3BucketPolicyConfig_updated(bucketName string) string { | ||
return fmt.Sprintf(` | ||
resource "aws_s3_bucket" "bucket" { | ||
bucket = "%s" | ||
tags { | ||
TestName = "TestAccAWSS3BucketPolicy_basic" | ||
} | ||
} | ||
resource "aws_s3_bucket_policy" "bucket" { | ||
bucket = "${aws_s3_bucket.bucket.bucket}" | ||
policy = "${data.aws_iam_policy_document.policy.json}" | ||
} | ||
data "aws_iam_policy_document" "policy" { | ||
statement { | ||
effect = "Allow" | ||
actions = [ | ||
"s3:DeleteBucket", | ||
"s3:ListBucket", | ||
"s3:ListBucketVersions" | ||
] | ||
resources = [ | ||
"${aws_s3_bucket.bucket.arn}", | ||
"${aws_s3_bucket.bucket.arn}/*", | ||
] | ||
principals { | ||
type = "AWS" | ||
identifiers = ["*"] | ||
} | ||
} | ||
} | ||
`, bucketName) | ||
} |