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.
This commit is contained in:
parent
1f04942292
commit
93f31fce17
|
@ -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
|
||||
}
|
|
@ -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(),
|
||||
|
|
|
@ -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 {
|
||||
|
@ -49,7 +48,8 @@ func resourceAwsS3Bucket() *schema.Resource {
|
|||
"policy": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
StateFunc: normalizeJson,
|
||||
Computed: true,
|
||||
DiffSuppressFunc: suppressEquivalentAwsPolicyDiffs,
|
||||
},
|
||||
|
||||
"cors_rule": &schema.Schema{
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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.
|
|
@ -788,6 +788,9 @@
|
|||
<a href="/docs/providers/aws/r/s3_bucket_object.html">aws_s3_bucket_object</a>
|
||||
</li>
|
||||
|
||||
<li<%= sidebar_current("docs-aws-resource-s3-bucket-policy") %>>
|
||||
<a href="/docs/providers/aws/r/s3_bucket_policy.html">aws_s3_bucket_policy</a>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
|
||||
|
|
Loading…
Reference in New Issue