Terraform-provider-aws: aws_s3_bucket cors_rule should create mulltiple CORSRule per allowed_origins

Created on 13 Jul 2019  路  7Comments  路  Source: hashicorp/terraform-provider-aws

_This issue was originally opened by @tomfotherby as hashicorp/terraform#22054. It was migrated here as a result of the provider split. The original body of the issue is below._


When using multiple allowed_origins in the cors_rule of a aws_s3_bucket resource, the CORS configuration on the S3 bucket that terraform produces, doesn't work consistently in some Browsers.

Terraform Version

v0.12.1

Terraform Configuration Files

resource "aws_s3_bucket" "example" {
  bucket = "example"
  acl    = "private"

  cors_rule {
    allowed_headers = ["*"]
    allowed_methods = ["GET", "PUT", "POST", "DELETE", "HEAD"]
    allowed_origins = ["https://example.io", "example.io", "*.example.io"]
    max_age_seconds = 3000
    expose_headers  = ["ETag"]
  }

The important bit is that allowed_origins has multiple values.

Expected Behavior

According to S3 CORS configuration Amazon documentation each CORS rule need to consist only one AllowedOrigin , for example:

<?xml version="1.0" encoding="UTF-8"?>
<CORSConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
<CORSRule>
    <AllowedOrigin>https://example.io</AllowedOrigin>
    <AllowedMethod>GET</AllowedMethod>
    <AllowedMethod>PUT</AllowedMethod>
    <AllowedMethod>POST</AllowedMethod>
    <AllowedMethod>DELETE</AllowedMethod>
    <AllowedMethod>HEAD</AllowedMethod>
    <MaxAgeSeconds>3000</MaxAgeSeconds>
    <AllowedHeader>*</AllowedHeader>
</CORSRule>
<CORSRule>
    <AllowedOrigin>example.io</AllowedOrigin>
    <AllowedMethod>GET</AllowedMethod>
    <AllowedMethod>PUT</AllowedMethod>
    <AllowedMethod>POST</AllowedMethod>
    <AllowedMethod>DELETE</AllowedMethod>
    <AllowedMethod>HEAD</AllowedMethod>
    <MaxAgeSeconds>3000</MaxAgeSeconds>
    <AllowedHeader>*</AllowedHeader>
</CORSRule>
<CORSRule>
    <AllowedOrigin>*.example.io</AllowedOrigin>
    <AllowedMethod>GET</AllowedMethod>
    <AllowedMethod>PUT</AllowedMethod>
    <AllowedMethod>POST</AllowedMethod>
    <AllowedMethod>DELETE</AllowedMethod>
    <AllowedMethod>HEAD</AllowedMethod>
    <MaxAgeSeconds>3000</MaxAgeSeconds>
    <AllowedHeader>*</AllowedHeader>
</CORSRule>
</CORSConfiguration>

Actual Behavior

Terraform creates one CORSRule with multiple AllowedOrigins:

<?xml version="1.0" encoding="UTF-8"?>
<CORSConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
<CORSRule>
    <AllowedOrigin>https://example.io</AllowedOrigin>
    <AllowedOrigin>example.io</AllowedOrigin>
    <AllowedOrigin>*.example.io</AllowedOrigin>
    <AllowedMethod>GET</AllowedMethod>
    <AllowedMethod>PUT</AllowedMethod>
    <AllowedMethod>POST</AllowedMethod>
    <AllowedMethod>DELETE</AllowedMethod>
    <AllowedMethod>HEAD</AllowedMethod>
    <MaxAgeSeconds>3000</MaxAgeSeconds>
    <AllowedHeader>*</AllowedHeader>
</CORSRule>
</CORSConfiguration>

This looks better but in our testing, it doesn't work consistently. For example, Google Chrome v75 is not loading our Fonts hosted in S3 even though the Origin is correct. I think AWS is only using the first AllowedOrigin and ignoring the duplicates. Whether or not this is a AWS bug, manually changing the CORS configuration of the S3 bucket to put each AllowedOrigin in it's own CORSRule fixed our issue.

References

needs-triage servics3

Most helpful comment

Thanks @jwieringa - I tried multiple cors_rule statements like your example - it worked a treat.

I think we can close this issue. Terraform provides the flexiblity to specify multiple cors_rule directives each with a single allowed_origins, or a single cors_rule with multiple allowed_origins. That should suite most users.

All 7 comments

Is there a solution to this issue?

clearly a bug in the provider, I'm surprised this has never been picked up/fixed

any update?

I propose that Terraform currently exposes the best interface on cors_rule even though it is effortless to produce poor results. I agree with @tomfotherby's summary that a good implementation of CORS is multiple rules per allowed origin. The reason I also agree with the Terraform behavior is because the AWS SDK AllowedOrigins on CORSRules is a list of strings. I reason that is better to mirror the SDK behavior than to provide behavioral changes on top of it.

To create multiple CORS rules, I modified the configuration to create multiple cors_rules.

resource "aws_s3_bucket" "example" {
  bucket = "example"
  acl    = "private"

  cors_rule {
    allowed_headers = ["*"]
    allowed_methods = ["GET", "PUT", "POST", "DELETE", "HEAD"]
    allowed_origins = ["*.example.io"]
    max_age_seconds = 3000
    expose_headers  = ["ETag"]
  }

  cors_rule {
    allowed_headers = ["*"]
    allowed_methods = ["GET", "PUT", "POST", "DELETE", "HEAD"]
    allowed_origins = ["https://example.io"]
    max_age_seconds = 3000
    expose_headers  = ["ETag"]
  }
}

Thanks @jwieringa - I tried multiple cors_rule statements like your example - it worked a treat.

I think we can close this issue. Terraform provides the flexiblity to specify multiple cors_rule directives each with a single allowed_origins, or a single cors_rule with multiple allowed_origins. That should suite most users.

Can you create multiple cors_rule dynamically though? In my case, I have different configurations (one per environment) and each have a different amount of cors_rule (and various domains). Since the cors_rule has to be embedded in the aws_s3_bucket, I can't really use count (that would be ideal and easy).
I think the latest 0.12.x has something like a foreach, or something like that, right?
https://www.hashicorp.com/blog/hashicorp-terraform-0-12-preview-for-and-for-each/

Especially interesting is the Dynamic Nested Blocks for this case. That might do the trick?

for_each in 0.13:

resource "aws_s3_bucket" "example" {
  bucket = "example"
  acl    = "private"

  dynamic "cors_rule" {
    for_each = [for s in ["*.example.io", "https://example.io"]: {
      allowed_origin = s
    }]
    allowed_headers = ["*"]
    allowed_methods = ["GET", "PUT", "POST", "DELETE", "HEAD"]
    allowed_origins = [cors_rule.value.allowed_origin]
    max_age_seconds = 3000
    expose_headers  = ["ETag"]
  }
}
Was this page helpful?
0 / 5 - 0 ratings