Skip to content

Commit c9d91b5

Browse files
committed
CloudFront frontend
* Creates a CloudFront endpoint to serve the Publii S3 bucket * Creates logging buckets for CloudFront * Adds a default WAF resource for CloudFront * Adds read permission to the frontend S3 bucket for CloudFront
1 parent dcba680 commit c9d91b5

File tree

10 files changed

+304
-7
lines changed

10 files changed

+304
-7
lines changed

README.md

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,26 +23,49 @@ Terraform module to host a static site generated by Publii
2323

2424
| Name | Type |
2525
|------|------|
26+
| [aws_cloudfront_distribution.frontend](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/cloudfront_distribution) | resource |
27+
| [aws_cloudfront_origin_access_identity.frontend](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/cloudfront_origin_access_identity) | resource |
2628
| [aws_iam_policy.publii_s3_frontend](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_policy) | resource |
2729
| [aws_iam_user.publii_s3_frontend](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_user) | resource |
2830
| [aws_iam_user_policy_attachment.publii_s3_frontend](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_user_policy_attachment) | resource |
31+
| [aws_kms_key.s3_bucket_cloudfront_frontend_logging](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/kms_key) | resource |
32+
| [aws_kms_key.s3_bucket_cloudfront_frontend_logging_logging](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/kms_key) | resource |
2933
| [aws_kms_key.s3_bucket_frontend](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/kms_key) | resource |
3034
| [aws_kms_key.s3_bucket_frontend_logging](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/kms_key) | resource |
35+
| [aws_s3_bucket.cloudfront_frontend_logging](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket) | resource |
36+
| [aws_s3_bucket.cloudfront_frontend_logging_logging](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket) | resource |
3137
| [aws_s3_bucket.frontend](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket) | resource |
3238
| [aws_s3_bucket.frontend_logging](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket) | resource |
39+
| [aws_s3_bucket_acl.cloudfront_frontend_logging](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_acl) | resource |
40+
| [aws_s3_bucket_acl.cloudfront_frontend_logging_logging](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_acl) | resource |
3341
| [aws_s3_bucket_acl.frontend](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_acl) | resource |
3442
| [aws_s3_bucket_acl.frontend_logging](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_acl) | resource |
35-
| [aws_s3_bucket_logging.example](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_logging) | resource |
43+
| [aws_s3_bucket_logging.cloudfront_frontend_logging](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_logging) | resource |
44+
| [aws_s3_bucket_logging.frontend](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_logging) | resource |
45+
| [aws_s3_bucket_policy.cloudfront_frontend_logging](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_policy) | resource |
46+
| [aws_s3_bucket_policy.cloudfront_frontend_logging_logging](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_policy) | resource |
3647
| [aws_s3_bucket_policy.frontend](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_policy) | resource |
3748
| [aws_s3_bucket_policy.frontend_logging](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_policy) | resource |
49+
| [aws_s3_bucket_public_access_block.cloudfront_frontend_logging](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_public_access_block) | resource |
50+
| [aws_s3_bucket_public_access_block.cloudfront_frontend_logging_logging](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_public_access_block) | resource |
3851
| [aws_s3_bucket_public_access_block.frontend](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_public_access_block) | resource |
3952
| [aws_s3_bucket_public_access_block.frontend_logging](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_public_access_block) | resource |
53+
| [aws_s3_bucket_server_side_encryption_configuration.cloudfront_frontend_logging](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_server_side_encryption_configuration) | resource |
54+
| [aws_s3_bucket_server_side_encryption_configuration.cloudfront_frontend_logging_logging](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_server_side_encryption_configuration) | resource |
4055
| [aws_s3_bucket_server_side_encryption_configuration.frontend](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_server_side_encryption_configuration) | resource |
4156
| [aws_s3_bucket_server_side_encryption_configuration.frontend_logging](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_server_side_encryption_configuration) | resource |
57+
| [aws_s3_bucket_versioning.cloudfront_frontend_logging](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_versioning) | resource |
58+
| [aws_s3_bucket_versioning.cloudfront_frontend_logging_logging](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_versioning) | resource |
4259
| [aws_s3_bucket_versioning.frontend](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_versioning) | resource |
4360
| [aws_s3_bucket_versioning.frontend_logging](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_versioning) | resource |
4461
| [aws_s3_bucket_website_configuration.frontend](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_website_configuration) | resource |
62+
| [aws_wafv2_web_acl.cloudfront_waf](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/wafv2_web_acl) | resource |
4563
| [random_id.project](https://registry.terraform.io/providers/hashicorp/random/latest/docs/resources/id) | resource |
64+
| [template_file.cloudfront_frontend_logging_bucket_enforce_tls_statement](https://registry.terraform.io/providers/hashicorp/template/latest/docs/data-sources/file) | data source |
65+
| [template_file.cloudfront_frontend_logging_bucket_policy](https://registry.terraform.io/providers/hashicorp/template/latest/docs/data-sources/file) | data source |
66+
| [template_file.cloudfront_frontend_logging_logging_bucket_enforce_tls_statement](https://registry.terraform.io/providers/hashicorp/template/latest/docs/data-sources/file) | data source |
67+
| [template_file.cloudfront_frontend_logging_logging_bucket_policy](https://registry.terraform.io/providers/hashicorp/template/latest/docs/data-sources/file) | data source |
68+
| [template_file.frontend_bucket_cloudfront_read](https://registry.terraform.io/providers/hashicorp/template/latest/docs/data-sources/file) | data source |
4669
| [template_file.frontend_bucket_enforce_tls_statement](https://registry.terraform.io/providers/hashicorp/template/latest/docs/data-sources/file) | data source |
4770
| [template_file.frontend_bucket_policy](https://registry.terraform.io/providers/hashicorp/template/latest/docs/data-sources/file) | data source |
4871
| [template_file.frontend_logging_bucket_enforce_tls_statement](https://registry.terraform.io/providers/hashicorp/template/latest/docs/data-sources/file) | data source |
@@ -53,6 +76,9 @@ Terraform module to host a static site generated by Publii
5376

5477
| Name | Description | Type | Default | Required |
5578
|------|-------------|------|---------|:--------:|
79+
| <a name="input_cloudfront_enable_ipv6"></a> [cloudfront\_enable\_ipv6](#input\_cloudfront\_enable\_ipv6) | Enable IPv6 on CloudFront | `bool` | `true` | no |
80+
| <a name="input_cloudfront_tls_certificate_arn"></a> [cloudfront\_tls\_certificate\_arn](#input\_cloudfront\_tls\_certificate\_arn) | CloudFront TLS certificate ARN (must be created in us-east-1 region) | `string` | n/a | yes |
81+
| <a name="input_s3_bucket_acl"></a> [s3\_bucket\_acl](#input\_s3\_bucket\_acl) | S3 bucket ACL | `string` | `"private"` | no |
5682
| <a name="input_site_url"></a> [site\_url](#input\_site\_url) | The desired site URL | `string` | n/a | yes |
5783

5884
## Outputs
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
# tfsec requires CloudFront S3 buckets to also have logging buckets
2+
resource "aws_s3_bucket" "cloudfront_frontend_logging_logging" {
3+
bucket = "cloudfront-frontend-${local.project_name}-logs-logs"
4+
force_destroy = false
5+
}
6+
7+
resource "aws_s3_bucket_versioning" "cloudfront_frontend_logging_logging" {
8+
bucket = aws_s3_bucket.cloudfront_frontend_logging_logging.id
9+
versioning_configuration {
10+
status = "Enabled"
11+
}
12+
}
13+
14+
resource "aws_s3_bucket_acl" "cloudfront_frontend_logging_logging" {
15+
bucket = aws_s3_bucket.cloudfront_frontend_logging_logging.id
16+
acl = "private"
17+
}
18+
19+
resource "aws_s3_bucket_public_access_block" "cloudfront_frontend_logging_logging" {
20+
bucket = aws_s3_bucket.cloudfront_frontend_logging_logging.id
21+
block_public_acls = true
22+
block_public_policy = true
23+
ignore_public_acls = true
24+
restrict_public_buckets = true
25+
}
26+
27+
resource "aws_s3_bucket_server_side_encryption_configuration" "cloudfront_frontend_logging_logging" {
28+
bucket = aws_s3_bucket.cloudfront_frontend_logging_logging.bucket
29+
30+
rule {
31+
apply_server_side_encryption_by_default {
32+
kms_master_key_id = aws_kms_key.s3_bucket_cloudfront_frontend_logging_logging.arn
33+
sse_algorithm = "aws:kms"
34+
}
35+
}
36+
}
37+
38+
data "template_file" "cloudfront_frontend_logging_logging_bucket_enforce_tls_statement" {
39+
template = file("${path.module}/policies/s3-bucket-policy-statements/enforce-tls.json.tpl")
40+
41+
vars = {
42+
bucket_arn = aws_s3_bucket.cloudfront_frontend_logging_logging.arn
43+
}
44+
}
45+
46+
data "template_file" "cloudfront_frontend_logging_logging_bucket_policy" {
47+
template = file("${path.module}/policies/s3-bucket-policy.json.tpl")
48+
49+
vars = {
50+
statement = <<EOT
51+
[
52+
${data.template_file.cloudfront_frontend_logging_logging_bucket_enforce_tls_statement.rendered}
53+
]
54+
EOT
55+
}
56+
}
57+
58+
resource "aws_s3_bucket_policy" "cloudfront_frontend_logging_logging" {
59+
bucket = aws_s3_bucket.cloudfront_frontend_logging_logging.id
60+
policy = data.template_file.cloudfront_frontend_logging_logging_bucket_policy.rendered
61+
}

cloudfront-frontend-logging.tf

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
resource "aws_s3_bucket" "cloudfront_frontend_logging" {
2+
bucket = "cloudfront-frontend-${local.project_name}-logs"
3+
force_destroy = false
4+
}
5+
6+
resource "aws_s3_bucket_logging" "cloudfront_frontend_logging" {
7+
bucket = aws_s3_bucket.cloudfront_frontend_logging.id
8+
target_bucket = aws_s3_bucket.cloudfront_frontend_logging_logging.id
9+
target_prefix = "log/"
10+
}
11+
12+
resource "aws_s3_bucket_versioning" "cloudfront_frontend_logging" {
13+
bucket = aws_s3_bucket.cloudfront_frontend_logging.id
14+
versioning_configuration {
15+
status = "Enabled"
16+
}
17+
}
18+
19+
resource "aws_s3_bucket_acl" "cloudfront_frontend_logging" {
20+
bucket = aws_s3_bucket.cloudfront_frontend_logging.id
21+
acl = "private"
22+
}
23+
24+
resource "aws_s3_bucket_public_access_block" "cloudfront_frontend_logging" {
25+
bucket = aws_s3_bucket.cloudfront_frontend_logging.id
26+
block_public_acls = true
27+
block_public_policy = true
28+
ignore_public_acls = true
29+
restrict_public_buckets = true
30+
}
31+
32+
resource "aws_s3_bucket_server_side_encryption_configuration" "cloudfront_frontend_logging" {
33+
bucket = aws_s3_bucket.cloudfront_frontend_logging.bucket
34+
35+
rule {
36+
apply_server_side_encryption_by_default {
37+
kms_master_key_id = aws_kms_key.s3_bucket_cloudfront_frontend_logging.arn
38+
sse_algorithm = "aws:kms"
39+
}
40+
}
41+
}
42+
43+
data "template_file" "cloudfront_frontend_logging_bucket_enforce_tls_statement" {
44+
template = file("${path.module}/policies/s3-bucket-policy-statements/enforce-tls.json.tpl")
45+
46+
vars = {
47+
bucket_arn = aws_s3_bucket.cloudfront_frontend_logging.arn
48+
}
49+
}
50+
51+
data "template_file" "cloudfront_frontend_logging_bucket_policy" {
52+
template = file("${path.module}/policies/s3-bucket-policy.json.tpl")
53+
54+
vars = {
55+
statement = <<EOT
56+
[
57+
${data.template_file.cloudfront_frontend_logging_bucket_enforce_tls_statement.rendered}
58+
]
59+
EOT
60+
}
61+
}
62+
63+
resource "aws_s3_bucket_policy" "cloudfront_frontend_logging" {
64+
bucket = aws_s3_bucket.cloudfront_frontend_logging.id
65+
policy = data.template_file.cloudfront_frontend_logging_bucket_policy.rendered
66+
}

cloudfront-frontend.tf

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
resource "aws_cloudfront_origin_access_identity" "frontend" {
2+
comment = "${local.project_name} frontend"
3+
}
4+
5+
resource "aws_cloudfront_distribution" "frontend" {
6+
origin {
7+
domain_name = aws_s3_bucket.frontend.bucket_regional_domain_name
8+
origin_id = local.project_name
9+
10+
s3_origin_config {
11+
origin_access_identity = aws_cloudfront_origin_access_identity.frontend.cloudfront_access_identity_path
12+
}
13+
}
14+
15+
enabled = true
16+
aliases = [
17+
local.site_url
18+
]
19+
20+
default_cache_behavior {
21+
allowed_methods = ["GET", "HEAD"]
22+
cached_methods = ["GET", "HEAD"]
23+
target_origin_id = local.project_name
24+
25+
forwarded_values {
26+
query_string = false
27+
28+
cookies {
29+
forward = "none"
30+
}
31+
}
32+
33+
min_ttl = 0
34+
default_ttl = 86400
35+
max_ttl = 31536000
36+
compress = true
37+
38+
viewer_protocol_policy = "redirect-to-https"
39+
}
40+
41+
default_root_object = "index.html"
42+
43+
custom_error_response {
44+
error_code = "404"
45+
response_code = "404"
46+
response_page_path = "/404.html"
47+
}
48+
49+
custom_error_response {
50+
error_code = "403"
51+
response_code = "404"
52+
response_page_path = "/404.html"
53+
}
54+
55+
price_class = "PriceClass_100"
56+
57+
restrictions {
58+
geo_restriction {
59+
restriction_type = "none"
60+
}
61+
}
62+
63+
viewer_certificate {
64+
acm_certificate_arn = local.cloudfront_tls_certificate_arn
65+
minimum_protocol_version = "TLSv1.2_2021"
66+
ssl_support_method = "sni-only"
67+
}
68+
69+
is_ipv6_enabled = local.cloudfront_enable_ipv6
70+
71+
web_acl_id = aws_wafv2_web_acl.cloudfront_waf.id
72+
73+
logging_config {
74+
include_cookies = false
75+
bucket = aws_s3_bucket.cloudfront_frontend_logging.bucket_domain_name
76+
prefix = "log/"
77+
}
78+
}

cloudfront-waf.tf

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
resource "aws_wafv2_web_acl" "cloudfront_waf" {
2+
name = "${local.project_name}-acl"
3+
description = "${local.project_name} ACL"
4+
scope = "CLOUDFRONT"
5+
6+
visibility_config {
7+
cloudwatch_metrics_enabled = false
8+
metric_name = local.project_name
9+
sampled_requests_enabled = true
10+
}
11+
12+
default_action {
13+
allow {}
14+
}
15+
}

kms.tf

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,15 @@ resource "aws_kms_key" "s3_bucket_frontend_logging" {
99
deletion_window_in_days = 10
1010
enable_key_rotation = true
1111
}
12+
13+
resource "aws_kms_key" "s3_bucket_cloudfront_frontend_logging" {
14+
description = "This key is used to encrypt bucket objects within ${aws_s3_bucket.cloudfront_frontend_logging.id}"
15+
deletion_window_in_days = 10
16+
enable_key_rotation = true
17+
}
18+
19+
resource "aws_kms_key" "s3_bucket_cloudfront_frontend_logging_logging" {
20+
description = "This key is used to encrypt bucket objects within ${aws_s3_bucket.cloudfront_frontend_logging_logging.id}"
21+
deletion_window_in_days = 10
22+
enable_key_rotation = true
23+
}

locals.tf

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
locals {
2-
site_url = var.site_url
3-
project_random_id = random_id.project.dec
4-
project_name = "${replace(local.site_url, ".", "-")}-${local.project_random_id}"
2+
site_url = var.site_url
3+
project_random_id = random_id.project.dec
4+
project_name = "${replace(local.site_url, ".", "-")}-${local.project_random_id}"
5+
cloudfront_tls_certificate_arn = var.cloudfront_tls_certificate_arn
6+
cloudfront_enable_ipv6 = var.cloudfront_enable_ipv6
57
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{
2+
"Principal": {
3+
"AWS": "${origin_access_identity}"
4+
},
5+
"Action": "s3:GetObject",
6+
"Effect": "Allow",
7+
"Resource": [
8+
"${bucket_arn}",
9+
"${bucket_arn}/*"
10+
]
11+
}

s3-frontend.tf

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,7 @@ resource "aws_s3_bucket_versioning" "frontend" {
3333
}
3434
}
3535

36-
37-
resource "aws_s3_bucket_logging" "example" {
36+
resource "aws_s3_bucket_logging" "frontend" {
3837
bucket = aws_s3_bucket.frontend.id
3938
target_bucket = aws_s3_bucket.frontend_logging.id
4039
target_prefix = "log/"
@@ -80,13 +79,23 @@ data "template_file" "frontend_bucket_enforce_tls_statement" {
8079
}
8180
}
8281

82+
data "template_file" "frontend_bucket_cloudfront_read" {
83+
template = file("./policies/s3-bucket-policy-statements/cloudfront-read.json.tpl")
84+
85+
vars = {
86+
bucket_arn = aws_s3_bucket.frontend.arn
87+
origin_access_identity = aws_cloudfront_origin_access_identity.frontend.iam_arn
88+
}
89+
}
90+
8391
data "template_file" "frontend_bucket_policy" {
8492
template = file("${path.module}/policies/s3-bucket-policy.json.tpl")
8593

8694
vars = {
8795
statement = <<EOT
8896
[
89-
${data.template_file.frontend_bucket_enforce_tls_statement.rendered}
97+
${data.template_file.frontend_bucket_enforce_tls_statement.rendered},
98+
${data.template_file.frontend_bucket_cloudfront_read.rendered}
9099
]
91100
EOT
92101
}

variables.tf

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,20 @@ variable "site_url" {
22
description = "The desired site URL"
33
type = string
44
}
5+
6+
variable "s3_bucket_acl" {
7+
description = "S3 bucket ACL"
8+
default = "private"
9+
type = string
10+
}
11+
12+
variable "cloudfront_tls_certificate_arn" {
13+
description = "CloudFront TLS certificate ARN (must be created in us-east-1 region)"
14+
type = string
15+
}
16+
17+
variable "cloudfront_enable_ipv6" {
18+
description = "Enable IPv6 on CloudFront"
19+
type = bool
20+
default = true
21+
}

0 commit comments

Comments
 (0)