Skip to content

Commit 10bed88

Browse files
authored
feat: S3_hosting create CF but use existing bucket (#47)
* feat: S3_hosting create CF but use existing bucket this is useful for multiple CF pointing to one bucket, user can manually create the s3 bucket policy allowing both CF to view using their assets acess identity
1 parent c26f031 commit 10bed88

4 files changed

Lines changed: 54 additions & 20 deletions

File tree

modules/s3_hosting/README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,16 +28,19 @@ Create an S3 bucket and Cloudfront distribution for holding frontend application
2828
| cf\_lambda\_function\_associations | A config block that triggers a lambda function with specific actions (maximum 4) | <pre>list(object({<br> event_type = string<br> lambda_arn = string<br> include_body = bool<br> }))</pre> | `[]` | no |
2929
| cf\_signed\_downloads | Enable Cloudfront signed URLs | `bool` | `false` | no |
3030
| cf\_trusted\_signers | Only available when cf\_signed\_downloads is enabled, a list of trusted signers(self/account\_id) for Cloudfront, used for signing URLs | `list(string)` | <pre>[<br> "self"<br>]</pre> | no |
31+
| create\_s3\_bucket\_policy | Useful when multiple CF distributions access the same bucket. You would need to create a bucket policy that allows access from multiple distributions | `bool` | `true` | no |
3132
| domain | Domain to host content for. This will be the name of the bucket | `string` | n/a | yes |
3233
| environment | The environment (stage/prod) | `any` | n/a | yes |
3334
| project | The name of the project, mostly for tagging | `any` | n/a | yes |
3435
| route53\_zone\_id | ID of the Route53 zone to create a record in | `string` | n/a | yes |
36+
| use\_existing\_s3\_bucket | Name of existing s3 Bucket to use instead of creating a new one | `string` | `""` | no |
3537

3638
## Outputs
3739

3840
| Name | Description |
3941
|------|-------------|
4042
| bucket\_arn | ARN of the created S3 bucket |
43+
| cf\_origin\_assets\_access\_identity\_arn | Cloudfront origin assets access identity, useful when multiple CF using the same bucket to manage S3 bucket policies |
4144
| cf\_signing\_enabled | Does this require signed URL downloads? |
4245
| cloudfront\_distribution\_id | Identifier of the created cloudfront distribution |
4346

modules/s3_hosting/main.tf

Lines changed: 32 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,38 @@
11
locals {
22
assets_access_identity = "${var.project}-${var.environment}-client-assets-${var.domain}"
33

4-
allDomains = concat([var.domain], var.aliases)
4+
all_domains = concat([var.domain], var.aliases)
55

66
# Find buckets that are the domain apex. These need to have A ALIAS records.
7-
rootDomains = [
8-
for domain in local.allDomains :
7+
root_domains = [
8+
for domain in local.all_domains :
99
domain if length(regexall("\\.", domain)) == 1
1010
]
1111

1212
# Find buckets that are subdomains. These can have CNAME records.
13-
subDomains = [
14-
for domain in local.allDomains :
13+
sub_domains = [
14+
for domain in local.all_domains :
1515
domain if length(regexall("\\.", domain)) > 1
1616
]
1717

1818
# allows frontend application to upload to pre-signed S3 urls
19-
corsRules = length(var.allowed_cors_origins) > 0 ? [{
19+
cors_rules = length(var.allowed_cors_origins) > 0 ? [{
2020
allowed_methods = ["HEAD", "GET", "PUT", "POST"],
2121
allowed_origins = var.allowed_cors_origins,
2222
max_age_seconds = 3000
2323
}] : []
24+
25+
create_s3_bucket = var.use_existing_s3_bucket == "" ? true : false
2426
}
2527

2628
resource "aws_s3_bucket" "client_assets" {
29+
count = local.create_s3_bucket ? 1 : 0
2730
// Our bucket's name is going to be the same as our site's domain name.
2831
bucket = var.domain
2932
acl = "private" // The contents will be available through cloudfront, they should not be accessible publicly
3033

3134
dynamic "cors_rule" {
32-
for_each = local.corsRules
35+
for_each = local.cors_rules
3336
content {
3437
allowed_methods = lookup(cors_rule.value, "allowed_methods", ["GET"])
3538
allowed_origins = lookup(cors_rule.value, "allowed_origins", [""])
@@ -48,25 +51,34 @@ resource "aws_s3_bucket" "client_assets" {
4851
}
4952
}
5053

54+
// Use reference of the s3_bucket so the reference can be the same for both scenario
55+
// whether S3 bucket is existing or created from this module
56+
data "aws_s3_bucket" "client_assets" {
57+
bucket = local.create_s3_bucket ? aws_s3_bucket.client_assets[0].id : var.use_existing_s3_bucket
58+
}
59+
5160
# Deny public access to this bucket
5261
resource "aws_s3_bucket_public_access_block" "client_assets" {
53-
bucket = aws_s3_bucket.client_assets.id
62+
count = local.create_s3_bucket ? 1 : 0
63+
bucket = aws_s3_bucket.client_assets[0].id
5464
block_public_acls = true
5565
block_public_policy = true
5666
ignore_public_acls = true
5767
restrict_public_buckets = true
5868
}
5969

70+
6071
# Access identity for CF access to S3
6172
resource "aws_cloudfront_origin_access_identity" "client_assets" {
6273
comment = local.assets_access_identity
6374
}
6475

6576
# Policy to allow CF access to S3
6677
data "aws_iam_policy_document" "assets_origin" {
78+
count = var.create_s3_bucket_policy ? 1 : 0
6779
statement {
6880
actions = ["s3:GetObject"]
69-
resources = ["arn:aws:s3:::${aws_s3_bucket.client_assets.id}/*"]
81+
resources = ["arn:aws:s3:::${data.aws_s3_bucket.client_assets.id}/*"]
7082

7183
principals {
7284
type = "AWS"
@@ -76,7 +88,7 @@ data "aws_iam_policy_document" "assets_origin" {
7688

7789
statement {
7890
actions = ["s3:ListBucket"]
79-
resources = ["arn:aws:s3:::${aws_s3_bucket.client_assets.id}"]
91+
resources = ["arn:aws:s3:::${data.aws_s3_bucket.client_assets.id}"]
8092

8193
principals {
8294
type = "AWS"
@@ -87,16 +99,17 @@ data "aws_iam_policy_document" "assets_origin" {
8799

88100
# Attach the policy to the bucket
89101
resource "aws_s3_bucket_policy" "client_assets" {
102+
count = var.create_s3_bucket_policy ? 1 : 0
90103
depends_on = [aws_cloudfront_distribution.client_assets_distribution]
91-
bucket = aws_s3_bucket.client_assets.id
92-
policy = data.aws_iam_policy_document.assets_origin.json
104+
bucket = data.aws_s3_bucket.client_assets.id
105+
policy = data.aws_iam_policy_document.assets_origin[0].json
93106
}
94107

95108
# Create the cloudfront distribution
96109
resource "aws_cloudfront_distribution" "client_assets_distribution" {
97110
// origin is where CloudFront gets its content from.
98111
origin {
99-
domain_name = aws_s3_bucket.client_assets.bucket_domain_name
112+
domain_name = data.aws_s3_bucket.client_assets.bucket_domain_name
100113
origin_id = local.assets_access_identity
101114
s3_origin_config {
102115
origin_access_identity = aws_cloudfront_origin_access_identity.client_assets.cloudfront_access_identity_path
@@ -144,7 +157,7 @@ resource "aws_cloudfront_distribution" "client_assets_distribution" {
144157
}
145158
}
146159

147-
aliases = local.allDomains
160+
aliases = local.all_domains
148161

149162
restrictions {
150163
geo_restriction {
@@ -164,10 +177,10 @@ resource "aws_cloudfront_distribution" "client_assets_distribution" {
164177

165178
# Root domains to point at CF
166179
resource "aws_route53_record" "client_assets_root" {
167-
count = length(local.rootDomains)
180+
count = length(local.root_domains)
168181

169182
zone_id = var.route53_zone_id
170-
name = local.rootDomains[count.index]
183+
name = local.root_domains[count.index]
171184
type = "A"
172185

173186
alias {
@@ -177,12 +190,12 @@ resource "aws_route53_record" "client_assets_root" {
177190
}
178191
}
179192

180-
# Subdomains to point at CF
193+
# sub_domains to point at CF
181194
resource "aws_route53_record" "client_assets_subdomain" {
182-
count = length(local.subDomains)
195+
count = length(local.sub_domains)
183196

184197
zone_id = var.route53_zone_id
185-
name = local.subDomains[count.index]
198+
name = local.sub_domains[count.index]
186199
type = "CNAME"
187200
ttl = "120"
188201
records = [aws_cloudfront_distribution.client_assets_distribution.domain_name]

modules/s3_hosting/outputs.tf

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,15 @@ output "cloudfront_distribution_id" {
55

66
output "bucket_arn" {
77
description = "ARN of the created S3 bucket"
8-
value = aws_s3_bucket.client_assets.arn
8+
value = data.aws_s3_bucket.client_assets.arn
99
}
1010

1111
output "cf_signing_enabled" {
1212
description = "Does this require signed URL downloads?"
1313
value = var.cf_signed_downloads
1414
}
15+
16+
output "cf_origin_assets_access_identity_arn" {
17+
description = "Cloudfront origin assets access identity, useful when multiple CF using the same bucket to manage S3 bucket policies"
18+
value = aws_cloudfront_origin_access_identity.client_assets.iam_arn
19+
}

modules/s3_hosting/variables.tf

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,19 @@ variable "domain" {
1111
type = string
1212
}
1313

14+
variable "use_existing_s3_bucket" {
15+
description = "Name of existing s3 Bucket to use instead of creating a new one"
16+
type = string
17+
default = ""
18+
}
19+
20+
variable "create_s3_bucket_policy" {
21+
description = "Useful when multiple CF distributions access the same bucket. You would need to create a bucket policy that allows access from multiple distributions"
22+
type = bool
23+
default = true
24+
}
25+
26+
1427
variable "aliases" {
1528
description = "Additional domains that this cloudfront distribution will serve traffic for"
1629
type = list(string)

0 commit comments

Comments
 (0)