From 1f049422920cae7b232daf86d86542d0e9d2a915 Mon Sep 17 00:00:00 2001 From: James Nugent Date: Thu, 1 Sep 2016 17:31:01 -0500 Subject: [PATCH 1/2] deps: Vendor github.com/jen20/awspolicyequivalence --- .../jen20/awspolicyequivalence/README.md | 7 + .../aws_policy_equivalence.go | 334 ++++++++++++++++++ vendor/vendor.json | 6 + 3 files changed, 347 insertions(+) create mode 100644 vendor/github.com/jen20/awspolicyequivalence/README.md create mode 100644 vendor/github.com/jen20/awspolicyequivalence/aws_policy_equivalence.go diff --git a/vendor/github.com/jen20/awspolicyequivalence/README.md b/vendor/github.com/jen20/awspolicyequivalence/README.md new file mode 100644 index 000000000000..3883fafaaad5 --- /dev/null +++ b/vendor/github.com/jen20/awspolicyequivalence/README.md @@ -0,0 +1,7 @@ +## AWS Policy Equivalence Library + +This library checks for structural equivalence of two AWS policy documents. See Godoc for more information on usage. + +### CI + +Travis CI Build Status diff --git a/vendor/github.com/jen20/awspolicyequivalence/aws_policy_equivalence.go b/vendor/github.com/jen20/awspolicyequivalence/aws_policy_equivalence.go new file mode 100644 index 000000000000..8c2a66c48628 --- /dev/null +++ b/vendor/github.com/jen20/awspolicyequivalence/aws_policy_equivalence.go @@ -0,0 +1,334 @@ +package awspolicy + +import ( + "reflect" + "encoding/json" + + "github.com/hashicorp/errwrap" +) + +// PoliciesAreEquivalent tests for the structural equivalence of two +// AWS policies. It does not read into the semantics, other than treating +// single element string arrays as equivalent to a string without an +// array, as the AWS endpoints do. +// +// It will, however, detect reordering and ignore whitespace. +// +// Returns true if the policies are structurally equivalent, false +// otherwise. If either of the input strings are not valid JSON, +// false is returned along with an error. +func PoliciesAreEquivalent(policy1, policy2 string) (bool, error) { + policy1doc := &awsPolicyDocument{} + if err := json.Unmarshal([]byte(policy1), policy1doc); err != nil { + return false, errwrap.Wrapf("Error unmarshaling policy: {{err}}", err) + } + + policy2doc := &awsPolicyDocument{} + if err := json.Unmarshal([]byte(policy2), policy2doc); err != nil { + return false, errwrap.Wrapf("Error unmarshaling policy: {{err}}", err) + } + + return policy1doc.equals(policy2doc), nil +} + +type awsPolicyDocument struct { + Version string `json:",omitempty"` + Id string `json:",omitempty"` + Statements []*awsPolicyStatement `json:"Statement"` +} + +func (doc *awsPolicyDocument) equals(other *awsPolicyDocument) bool { + // Check the basic fields of the document + if doc.Version != other.Version { + return false + } + if doc.Id != other.Id { + return false + } + + // If we have different number of statements we are very unlikely + // to have them be equivalent. + if len(doc.Statements) != len(other.Statements) { + return false + } + + // If we have the same number of statements in the policy, does + // each statement in the doc have a corresponding statement in + // other which is equal? If no, policies are not equal, if yes, + // then they may be. + for _, ours := range doc.Statements { + found := false + for _, theirs := range other.Statements { + if ours.equals(theirs) { + found = true + } + } + + if !found { + return false + } + } + + // Now we need to repeat this process the other way around to + // ensure we don't have any matching errors. + for _, theirs := range other.Statements { + found := false + for _, ours := range doc.Statements { + if theirs.equals(ours) { + found = true + } + } + + if !found { + return false + } + } + + return true +} + +type awsPolicyStatement struct { + Sid string `json:",omitempty"` + Effect string `json:",omitempty"` + Actions interface{} `json:"Action,omitempty"` + NotActions interface{} `json:"NotAction,omitempty"` + Resources interface{} `json:"Resource,omitempty"` + NotResources interface{} `json:"NotResource,omitempty"` + Principals interface{} `json:"Principal,omitempty"` + NotPrincipals interface{} `json:"NotPrincipal,omitempty"` + Conditions map[string]map[string]interface{} `json:"Condition,omitempty"` +} + +func (statement *awsPolicyStatement) equals(other *awsPolicyStatement) bool { + if statement.Sid != other.Sid { + return false + } + + if statement.Effect != other.Effect { + return false + } + + ourActions := newAWSStringSet(statement.Actions) + theirActions := newAWSStringSet(other.Actions) + if !ourActions.equals(theirActions) { + return false + } + + ourNotActions := newAWSStringSet(statement.NotActions) + theirNotActions := newAWSStringSet(other.NotActions) + if !ourNotActions.equals(theirNotActions) { + return false + } + + ourResources := newAWSStringSet(statement.Resources) + theirResources := newAWSStringSet(other.Resources) + if !ourResources.equals(theirResources) { + return false + } + + ourNotResources := newAWSStringSet(statement.NotResources) + theirNotResources := newAWSStringSet(other.NotResources) + if !ourNotResources.equals(theirNotResources) { + return false + } + + ourConditionsBlock := awsConditionsBlock(statement.Conditions) + theirConditionsBlock := awsConditionsBlock(other.Conditions) + if !ourConditionsBlock.Equals(theirConditionsBlock) { + return false + } + + if statement.Principals != nil || other.Principals != nil { + stringPrincipalsEqual := stringPrincipalsEqual(statement.Principals, other.Principals) + mapPrincipalsEqual := mapPrincipalsEqual(statement.Principals, other.Principals) + if !(stringPrincipalsEqual || mapPrincipalsEqual) { + return false + } + } + + if statement.NotPrincipals != nil || other.NotPrincipals != nil { + stringNotPrincipalsEqual := stringPrincipalsEqual(statement.NotPrincipals, other.NotPrincipals) + mapNotPrincipalsEqual := mapPrincipalsEqual(statement.NotPrincipals, other.NotPrincipals) + if !(stringNotPrincipalsEqual || mapNotPrincipalsEqual) { + return false + } + } + + return true +} + +func mapPrincipalsEqual(ours, theirs interface{}) bool { + ourPrincipalMap, ok := ours.(map[string]interface{}) + if !ok { + return false + } + + theirPrincipalMap, ok := theirs.(map[string]interface{}) + if ! ok { + return false + } + + oursNormalized := make(map[string]awsStringSet) + for key, val := range ourPrincipalMap { + oursNormalized[key] = newAWSStringSet(val) + } + + theirsNormalized := make(map[string]awsStringSet) + for key, val := range theirPrincipalMap { + theirsNormalized[key] = newAWSStringSet(val) + } + + for key, ours := range oursNormalized { + theirs, ok := theirsNormalized[key] + if !ok { + return false + } + + if !ours.equals(theirs) { + return false + } + } + + for key, theirs := range theirsNormalized { + ours, ok := oursNormalized[key] + if !ok { + return false + } + + if !theirs.equals(ours) { + return false + } + } + + return true +} + +func stringPrincipalsEqual(ours, theirs interface{}) bool { + ourPrincipal, oursIsString := ours.(string) + theirPrincipal, theirsIsString := theirs.(string) + + if !(oursIsString && theirsIsString) { + return false + } + + if ourPrincipal == theirPrincipal { + return true + } + + return false +} + + +type awsConditionsBlock map[string]map[string]interface{} + +func (conditions awsConditionsBlock) Equals(other awsConditionsBlock) bool { + if conditions == nil && other != nil || other == nil && conditions != nil { + return false + } + + if len(conditions) != len(other) { + return false + } + + oursNormalized := make(map[string]map[string]awsStringSet) + for key, condition := range conditions { + normalizedCondition := make(map[string]awsStringSet) + for innerKey, val := range condition { + normalizedCondition[innerKey] = newAWSStringSet(val) + } + oursNormalized[key] = normalizedCondition + } + + theirsNormalized := make(map[string]map[string]awsStringSet) + for key, condition := range other { + normalizedCondition := make(map[string]awsStringSet) + for innerKey, val := range condition { + normalizedCondition[innerKey] = newAWSStringSet(val) + } + theirsNormalized[key] = normalizedCondition + } + + for key, ours := range oursNormalized { + theirs, ok := theirsNormalized[key] + if !ok { + return false + } + + for innerKey, oursInner := range ours { + theirsInner, ok := theirs[innerKey] + if ! ok { + return false + } + + if !oursInner.equals(theirsInner) { + return false + } + } + } + + for key, theirs := range theirsNormalized { + ours, ok := oursNormalized[key] + if !ok { + return false + } + + for innerKey, theirsInner := range theirs { + oursInner, ok := ours[innerKey] + if ! ok { + return false + } + + if !theirsInner.equals(oursInner) { + return false + } + } + } + + return true +} + + +type awsStringSet []string + +// newAWSStringSet constructs an awsStringSet from an interface{} - which +// may be nil, a single string, or []interface{} (each of which is a string). +// This corresponds with how structures come off the JSON unmarshaler +// without any custom encoding rules. +func newAWSStringSet(members interface{}) awsStringSet { + if members == nil { + return awsStringSet{} + } + + if single, ok := members.(string); ok { + return awsStringSet{single} + } + + if multiple, ok := members.([]interface{}); ok { + actions := make([]string, len(multiple)) + for i, action := range multiple { + actions[i] = action.(string) + } + return awsStringSet(actions) + } + + return nil +} + +func (actions awsStringSet) equals(other awsStringSet) bool { + if len(actions) != len(other) { + return false + } + + ourMap := map[string]struct{}{} + theirMap := map[string]struct{}{} + + for _, action := range actions { + ourMap[action] = struct{}{} + } + + for _, action := range other { + theirMap[action] = struct{}{} + } + + return reflect.DeepEqual(ourMap, theirMap) +} diff --git a/vendor/vendor.json b/vendor/vendor.json index e4ea8389781f..524759386e6e 100644 --- a/vendor/vendor.json +++ b/vendor/vendor.json @@ -1295,6 +1295,12 @@ "path": "github.com/influxdata/influxdb/pkg/escape", "revision": "f233a8bac88d1f2dc282a98186f5a3363b806181" }, + { + "checksumSHA1": "cCSJGF1h+suYcgMq7wEm1carknw=", + "path": "github.com/jen20/awspolicyequivalence", + "revision": "6b9230008577fc3dcd10c104ce8fb16ed679bf66", + "revisionTime": "2016-09-01T18:24:20Z" + }, { "checksumSHA1": "oPpOfZn11Ef6DWOoETxSW9Venzs=", "path": "github.com/jen20/riviera/azure", From 93f31fce17f15e518da2ffb1093680fd9b3e379d Mon Sep 17 00:00:00 2001 From: James Nugent Date: Thu, 1 Sep 2016 17:34:45 -0500 Subject: [PATCH 2/2] 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. --- builtin/providers/aws/diff_suppress_funcs.go | 15 ++ builtin/providers/aws/provider.go | 1 + .../providers/aws/resource_aws_s3_bucket.go | 12 +- .../aws/resource_aws_s3_bucket_policy.go | 106 +++++++++++ .../aws/resource_aws_s3_bucket_policy_test.go | 180 ++++++++++++++++++ .../aws/r/s3_bucket_policy.html.markdown | 37 ++++ website/source/layouts/aws.erb | 3 + 7 files changed, 348 insertions(+), 6 deletions(-) create mode 100644 builtin/providers/aws/diff_suppress_funcs.go create mode 100644 builtin/providers/aws/resource_aws_s3_bucket_policy.go create mode 100644 builtin/providers/aws/resource_aws_s3_bucket_policy_test.go create mode 100644 website/source/docs/providers/aws/r/s3_bucket_policy.html.markdown diff --git a/builtin/providers/aws/diff_suppress_funcs.go b/builtin/providers/aws/diff_suppress_funcs.go new file mode 100644 index 000000000000..408063e2668c --- /dev/null +++ b/builtin/providers/aws/diff_suppress_funcs.go @@ -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 +} diff --git a/builtin/providers/aws/provider.go b/builtin/providers/aws/provider.go index e95ad615b51a..9d1580d2b6ef 100644 --- a/builtin/providers/aws/provider.go +++ b/builtin/providers/aws/provider.go @@ -308,6 +308,7 @@ func Provider() terraform.ResourceProvider { "aws_ses_receipt_rule": resourceAwsSesReceiptRule(), "aws_ses_receipt_rule_set": resourceAwsSesReceiptRuleSet(), "aws_s3_bucket": resourceAwsS3Bucket(), + "aws_s3_bucket_policy": resourceAwsS3BucketPolicy(), "aws_s3_bucket_object": resourceAwsS3BucketObject(), "aws_s3_bucket_notification": resourceAwsS3BucketNotification(), "aws_security_group": resourceAwsSecurityGroup(), diff --git a/builtin/providers/aws/resource_aws_s3_bucket.go b/builtin/providers/aws/resource_aws_s3_bucket.go index f4637d93ece2..28ae61e34395 100644 --- a/builtin/providers/aws/resource_aws_s3_bucket.go +++ b/builtin/providers/aws/resource_aws_s3_bucket.go @@ -8,13 +8,12 @@ import ( "net/url" "time" - "github.com/hashicorp/terraform/helper/resource" - "github.com/hashicorp/terraform/helper/schema" - "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/hashcode" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/helper/schema" ) func resourceAwsS3Bucket() *schema.Resource { @@ -47,9 +46,10 @@ func resourceAwsS3Bucket() *schema.Resource { }, "policy": &schema.Schema{ - Type: schema.TypeString, - Optional: true, - StateFunc: normalizeJson, + Type: schema.TypeString, + Optional: true, + Computed: true, + DiffSuppressFunc: suppressEquivalentAwsPolicyDiffs, }, "cors_rule": &schema.Schema{ diff --git a/builtin/providers/aws/resource_aws_s3_bucket_policy.go b/builtin/providers/aws/resource_aws_s3_bucket_policy.go new file mode 100644 index 000000000000..4485f11a77be --- /dev/null +++ b/builtin/providers/aws/resource_aws_s3_bucket_policy.go @@ -0,0 +1,106 @@ +package aws + +import ( + "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 + + log.Printf("[DEBUG] S3 bucket policy, read for bucket: %s", d.Id()) + pol, err := s3conn.GetBucketPolicy(&s3.GetBucketPolicyInput{ + Bucket: aws.String(d.Id()), + }) + + v := "" + if err == nil && pol.Policy != nil { + v = *pol.Policy + } + 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 +} diff --git a/builtin/providers/aws/resource_aws_s3_bucket_policy_test.go b/builtin/providers/aws/resource_aws_s3_bucket_policy_test.go new file mode 100644 index 000000000000..8dedae1a09e7 --- /dev/null +++ b/builtin/providers/aws/resource_aws_s3_bucket_policy_test.go @@ -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) +} diff --git a/website/source/docs/providers/aws/r/s3_bucket_policy.html.markdown b/website/source/docs/providers/aws/r/s3_bucket_policy.html.markdown new file mode 100644 index 000000000000..b9789e2a6fa6 --- /dev/null +++ b/website/source/docs/providers/aws/r/s3_bucket_policy.html.markdown @@ -0,0 +1,37 @@ +--- +layout: "aws" +page_title: "AWS: aws_s3_bucket_policy" +sidebar_current: "docs-aws-resource-s3-bucket-policy" +description: |- + Attaches a policy to an S3 bucket resource. +--- + +# aws\_s3\_bucket\_policy + +Attaches a policy to an S3 bucket resource. + +## Example Usage + +### Using versioning + +``` +resource "aws_s3_bucket" "b" { + # Arguments +} + +data "aws_iam_policy_document" "b" { + # Policy statements +} + +resource "aws_s3_bucket_policy" "b" { + bucket = "${aws_s3_bucket.b.bucket}" + policy = "${data.aws_iam_policy_document.b.json}" +} +``` + +## Argument Reference + +The following arguments are supported: + +* `bucket` - (Required) The name of the bucket to which to apply the policy. +* `policy` - (Required) The text of the policy. diff --git a/website/source/layouts/aws.erb b/website/source/layouts/aws.erb index 04dbccfbd733..6a2ae1614bee 100644 --- a/website/source/layouts/aws.erb +++ b/website/source/layouts/aws.erb @@ -788,6 +788,9 @@ aws_s3_bucket_object + > + aws_s3_bucket_policy +