_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.
v0.12.1
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.
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>
Terraform creates one CORSRule
with multiple AllowedOrigin
s:
<?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.
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_rule
s.
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"]
}
}
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 singleallowed_origins
, or a singlecors_rule
with multipleallowed_origins
. That should suite most users.