From b9085fa3755fefcf52e1275ecf7441ad6811325d Mon Sep 17 00:00:00 2001 From: franklinpashok Date: Sun, 15 Jan 2023 23:34:15 +0800 Subject: [PATCH 1/6] initial commit for static site tf module --- README.md | 67 +++++++++++++ cloudfront.tf | 68 +++++++++++++ data.tf | 21 ++++ output.tf | 39 ++++++++ s3.tf | 35 +++++++ templates/viewer-request-default.js | 15 +++ variables.tf | 143 ++++++++++++++++++++++++++++ versions.tf | 0 8 files changed, 388 insertions(+) create mode 100644 cloudfront.tf create mode 100644 data.tf create mode 100644 output.tf create mode 100644 s3.tf create mode 100644 templates/viewer-request-default.js create mode 100644 variables.tf create mode 100644 versions.tf diff --git a/README.md b/README.md index 2854f8d..397233f 100644 --- a/README.md +++ b/README.md @@ -1 +1,68 @@ # Terraform Modules Template + + +## Requirements + +No requirements. + +## Providers + +| Name | Version | +|------|---------| +| [aws](#provider\_aws) | n/a | + +## Modules + +| Name | Source | Version | +|------|--------|---------| +| [cdn](#module\_cdn) | terraform-aws-modules/cloudfront/aws | n/a | +| [s3](#module\_s3) | terraform-aws-modules/s3-bucket/aws | 3.5.0 | + +## Resources + +| Name | Type | +|------|------| +| [aws_cloudfront_function.viewer_request](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/cloudfront_function) | resource | +| [aws_s3_bucket_policy.docs](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_policy) | resource | +| [aws_iam_policy_document.s3_policy](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | +| [aws_iam_policy_document.s3_policy_merge](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [acl](#input\_acl) | Private or Public ACL | `string` | `"private"` | no | +| [attach\_policy](#input\_attach\_policy) | Controls if S3 bucket should have bucket policy attached (set to `true` to use value of `policy` as bucket policy) | `bool` | `true` | no | +| [block\_public\_acls](#input\_block\_public\_acls) | Whether Amazon S3 should block public ACLs for this bucket. | `bool` | `true` | no | +| [block\_public\_policy](#input\_block\_public\_policy) | Whether Amazon S3 should block public bucket policies for this bucket. | `bool` | `true` | no | +| [bucket\_name](#input\_bucket\_name) | bucket name | `string` | `""` | no | +| [cors\_rule](#input\_cors\_rule) | List of maps containing rules for Cross-Origin Resource Sharing. | `any` |
{
"cors_rule": {
"allowed_headers": [
"*"
],
"allowed_methods": [
"PUT",
"POST",
"GET",
"DELETE"
],
"allowed_origins": [
"*"
],
"expose_headers": [
"ETag"
],
"max_age_seconds": 3000
}
}
| no | +| [create\_origin\_access\_identity](#input\_create\_origin\_access\_identity) | Whether Amazon S3 should restrict public bucket policies for this bucket. | `bool` | `true` | no | +| [default\_cache\_behavior](#input\_default\_cache\_behavior) | The default cache behavior for this distribution | `any` | `{}` | no | +| [ignore\_public\_acls](#input\_ignore\_public\_acls) | Whether Amazon S3 should ignore public ACLs for this bucket. | `bool` | `true` | no | +| [lifecycle\_rule](#input\_lifecycle\_rule) | List of maps containing configuration of object lifecycle management. | `any` | `[]` | no | +| [logging](#input\_logging) | Map containing access bucket logging configuration. | `map(string)` | `{}` | no | +| [ordered\_cache\_behavior](#input\_ordered\_cache\_behavior) | An ordered list of cache behaviors resource for this distribution. List from top to bottom in order of precedence. The topmost cache behavior will have precedence 0. | `any` | `[]` | no | +| [origin](#input\_origin) | One or more origins for this distribution (multiples allowed). | `any` | `{}` | no | +| [origin\_access\_identities](#input\_origin\_access\_identities) | Map of CloudFront origin access identities (value as a comment) | `map(string)` | `{}` | no | +| [policy](#input\_policy) | (Optional) A valid bucket policy JSON document. Note that if the policy document is not specific enough (but still valid), Terraform may view the policy as constantly changing in a terraform plan. In this case, please make sure you use the verbose/specific version of the policy. For more information about building AWS IAM policy documents with Terraform, see the AWS IAM Policy Document Guide. | `string` | `""` | no | +| [price\_class](#input\_price\_class) | The price class for this distribution. One of PriceClass\_All, PriceClass\_200, PriceClass\_100 | `string` | `"PriceClass_All"` | no | +| [restrict\_public\_buckets](#input\_restrict\_public\_buckets) | Whether Amazon S3 should restrict public bucket policies for this bucket. | `bool` | `true` | no | +| [server\_side\_encryption\_configuration](#input\_server\_side\_encryption\_configuration) | Map containing server-side encryption configuration. | `any` | `{}` | no | +| [versioning](#input\_versioning) | Map containing versioning configuration. | `map(string)` |
{
"enabled": true
}
| no | +| [wait\_for\_deployment](#input\_wait\_for\_deployment) | Whether Amazon S3 should restrict public bucket policies for this bucket. | `bool` | `false` | no | +| [website](#input\_website) | Map containing static web-site hosting or redirect configuration. | `any` |
{
"error_document": "error.html",
"index_document": "index.html"
}
| no | + +## Outputs + +| Name | Description | +|------|-------------| +| [cloudfront\_distribution\_arn](#output\_cloudfront\_distribution\_arn) | The ARN (Amazon Resource Name) for the distribution. | +| [cloudfront\_distribution\_domain\_name](#output\_cloudfront\_distribution\_domain\_name) | The domain name corresponding to the distribution. | +| [cloudfront\_distribution\_id](#output\_cloudfront\_distribution\_id) | The Arn of the cloudfront distribution | +| [cloudfront\_origin\_access\_identity\_iam\_arns](#output\_cloudfront\_origin\_access\_identity\_iam\_arns) | The IAM arns of the origin access identities created | +| [s3\_bucket\_arn](#output\_s3\_bucket\_arn) | The ARN of the bucket. Will be of format arn:aws:s3:::bucketname. | +| [s3\_bucket\_bucket\_domain\_name](#output\_s3\_bucket\_bucket\_domain\_name) | The bucket domain name. Will be of format bucketname.s3.amazonaws.com. | +| [s3\_bucket\_bucket\_regional\_domain\_name](#output\_s3\_bucket\_bucket\_regional\_domain\_name) | The bucket region-specific domain name. The bucket domain name including the region name, please refer here for format. Note: The AWS CloudFront allows specifying S3 region-specific endpoint when creating S3 origin, it will prevent redirect issues from CloudFront to S3 Origin URL. | +| [s3\_bucket\_id](#output\_s3\_bucket\_id) | The name of the bucket. | + \ No newline at end of file diff --git a/cloudfront.tf b/cloudfront.tf new file mode 100644 index 0000000..8740197 --- /dev/null +++ b/cloudfront.tf @@ -0,0 +1,68 @@ +#################################### +##### Cloud Front Distribution ##### +#################################### +module "cdn" { + source = "terraform-aws-modules/cloudfront/aws" + comment = "Distribution for static website" + is_ipv6_enabled = true + price_class = var.price_class + wait_for_deployment = var.wait_for_deployment + create_origin_access_identity = var.create_origin_access_identity + + origin_access_identities = merge({ + origin_access_identity = module.s3.s3_bucket_id + }, var.origin_access_identities) + + origin = merge({ + origin_access_identity = { + domain_name = module.s3.s3_bucket_bucket_regional_domain_name + s3_origin_config = { + origin_access_identity = "origin_access_identity" + # key in `origin_access_identities` + } } + }, var.origin) + + default_cache_behavior = merge({ + target_origin_id = "origin_access_identity" # key in `origin` above + viewer_protocol_policy = "redirect-to-https" + + default_ttl = 360 + min_ttl = 300 + max_ttl = 600 + + allowed_methods = ["GET", "HEAD"] + cached_methods = ["GET", "HEAD"] + compress = true + query_string = false + + function_association = { + viewer-request = { + function_arn = aws_cloudfront_function.viewer_request.arn + } + } + }, var.default_cache_behavior) + + ordered_cache_behavior = var.ordered_cache_behavior + + default_root_object = "index.html" + + custom_error_response = [ + { + error_code = 404 + response_code = 404 + response_page_path = "/errors/404.html" + }, + { + error_code = 403 + response_code = 403 + response_page_path = "/errors/403.html" + } + ] +} + +resource "aws_cloudfront_function" "viewer_request" { + name = "default_viewer_request" + runtime = "cloudfront-js-1.0" + publish = true + code = file("${path.module}/templates/viewer-request-default.js") +} diff --git a/data.tf b/data.tf new file mode 100644 index 0000000..2109428 --- /dev/null +++ b/data.tf @@ -0,0 +1,21 @@ +data "aws_iam_policy_document" "s3_policy" { + statement { + actions = ["s3:GetObject"] + resources = ["${module.s3.s3_bucket_arn}/*"] + principals { + type = "AWS" + identifiers = module.cdn.cloudfront_origin_access_identity_iam_arns + } + } + depends_on = [ + module.cdn.cloudfront_distribution_id + ] +} + +data "aws_iam_policy_document" "s3_policy_merge" { + + source_policy_documents = [ + data.aws_iam_policy_document.s3_policy.json, + var.policy + ] +} diff --git a/output.tf b/output.tf new file mode 100644 index 0000000..d831e8d --- /dev/null +++ b/output.tf @@ -0,0 +1,39 @@ +output "s3_bucket_id" { + description = "The name of the bucket." + value = module.s3.s3_bucket_id +} + +output "s3_bucket_arn" { + description = "The ARN of the bucket. Will be of format arn:aws:s3:::bucketname." + value = module.s3.s3_bucket_arn +} + +output "s3_bucket_bucket_domain_name" { + description = "The bucket domain name. Will be of format bucketname.s3.amazonaws.com." + value = module.s3.s3_bucket_bucket_domain_name +} + +output "s3_bucket_bucket_regional_domain_name" { + description = "The bucket region-specific domain name. The bucket domain name including the region name, please refer here for format. Note: The AWS CloudFront allows specifying S3 region-specific endpoint when creating S3 origin, it will prevent redirect issues from CloudFront to S3 Origin URL." + value = module.s3.s3_bucket_bucket_regional_domain_name +} + +output "cloudfront_distribution_id" { + description = "The Arn of the cloudfront distribution" + value = module.cdn.cloudfront_distribution_id +} + +output "cloudfront_distribution_arn" { + description = "The ARN (Amazon Resource Name) for the distribution." + value = module.cdn.cloudfront_distribution_arn +} + +output "cloudfront_distribution_domain_name" { + description = "The domain name corresponding to the distribution." + value = module.cdn.cloudfront_distribution_domain_name +} + +output "cloudfront_origin_access_identity_iam_arns" { + description = "The IAM arns of the origin access identities created" + value = module.cdn.cloudfront_origin_access_identity_iam_arns +} diff --git a/s3.tf b/s3.tf new file mode 100644 index 0000000..a5b42d8 --- /dev/null +++ b/s3.tf @@ -0,0 +1,35 @@ +######################################################## +##### S3 bucket policy for frontend static website ##### +######################################################## + +module "s3" { + source = "terraform-aws-modules/s3-bucket/aws" + version = "3.5.0" + + bucket = var.bucket_name + acl = var.acl + block_public_acls = var.block_public_acls + block_public_policy = var.block_public_policy + ignore_public_acls = var.ignore_public_acls + restrict_public_buckets = var.restrict_public_buckets + + server_side_encryption_configuration = var.server_side_encryption_configuration + cors_rule = var.cors_rule + lifecycle_rule = var.lifecycle_rule + + versioning = var.versioning + logging = var.logging + + website = var.website + +} + + +resource "aws_s3_bucket_policy" "docs" { + bucket = module.s3.s3_bucket_id + policy = data.aws_iam_policy_document.s3_policy_merge.json + + depends_on = [ + module.cdn.cloudfront_distribution_id + ] +} diff --git a/templates/viewer-request-default.js b/templates/viewer-request-default.js new file mode 100644 index 0000000..49c03a0 --- /dev/null +++ b/templates/viewer-request-default.js @@ -0,0 +1,15 @@ +function handler(event) { + var request = event.request; + var uri = request.uri; + + // Check whether the URI is missing a file name. + if (uri.endsWith('/')) { + request.uri += 'index.html'; + } + // Check whether the URI is missing a file extension. + else if (!uri.includes('.')) { + request.uri += '/index.html'; + } + + return request; +} diff --git a/variables.tf b/variables.tf new file mode 100644 index 0000000..5754074 --- /dev/null +++ b/variables.tf @@ -0,0 +1,143 @@ +################## +### Static S3 +################# + +variable "bucket_name" { + description = "bucket name" + type = string + default = "" +} + +variable "acl" { + description = "Private or Public ACL" + type = string + default = "private" +} + +variable "attach_policy" { + description = "Controls if S3 bucket should have bucket policy attached (set to `true` to use value of `policy` as bucket policy)" + type = bool + default = true +} + +variable "policy" { + description = "(Optional) A valid bucket policy JSON document. Note that if the policy document is not specific enough (but still valid), Terraform may view the policy as constantly changing in a terraform plan. In this case, please make sure you use the verbose/specific version of the policy. For more information about building AWS IAM policy documents with Terraform, see the AWS IAM Policy Document Guide." + type = string + default = "" +} + +variable "block_public_acls" { + description = "Whether Amazon S3 should block public ACLs for this bucket." + type = bool + default = true +} + +variable "block_public_policy" { + description = "Whether Amazon S3 should block public bucket policies for this bucket." + type = bool + default = true +} + +variable "ignore_public_acls" { + description = "Whether Amazon S3 should ignore public ACLs for this bucket." + type = bool + default = true +} + +variable "restrict_public_buckets" { + description = "Whether Amazon S3 should restrict public bucket policies for this bucket." + type = bool + default = true +} + +variable "server_side_encryption_configuration" { + description = "Map containing server-side encryption configuration." + type = any + default = {} +} + +variable "lifecycle_rule" { + description = "List of maps containing configuration of object lifecycle management." + type = any + default = [] +} + +variable "cors_rule" { + description = "List of maps containing rules for Cross-Origin Resource Sharing." + type = any + default = { + cors_rule = { + allowed_headers = ["*"] + allowed_methods = ["PUT", "POST", "GET", "DELETE"] + allowed_origins = ["*"] + expose_headers = ["ETag"] + max_age_seconds = 3000 + } + } +} + +variable "versioning" { + description = "Map containing versioning configuration." + type = map(string) + default = { + enabled = true + } +} + +variable "logging" { + description = "Map containing access bucket logging configuration." + type = map(string) + default = {} +} + +variable "website" { + description = "Map containing static web-site hosting or redirect configuration." + type = any # map(string) + default = { + index_document = "index.html" + error_document = "error.html" + } +} + +variable "wait_for_deployment" { + description = "Whether Amazon S3 should restrict public bucket policies for this bucket." + type = bool + default = false +} + +variable "create_origin_access_identity" { + description = "Whether Amazon S3 should restrict public bucket policies for this bucket." + type = bool + default = true +} + +variable "ordered_cache_behavior" { + description = "An ordered list of cache behaviors resource for this distribution. List from top to bottom in order of precedence. The topmost cache behavior will have precedence 0." + type = any + default = [] +} + +variable "price_class" { + description = "The price class for this distribution. One of PriceClass_All, PriceClass_200, PriceClass_100" + type = string + default = "PriceClass_All" +} + +variable "origin_access_identities" { + description = "Map of CloudFront origin access identities (value as a comment)" + type = map(string) + default = {} +} + +variable "origin" { + description = "One or more origins for this distribution (multiples allowed)." + type = any + default = {} +} + +variable "default_cache_behavior" { + description = "The default cache behavior for this distribution" + type = any + default = {} +} + diff --git a/versions.tf b/versions.tf new file mode 100644 index 0000000..e69de29 From ba37727959cdcb6ed7bbb63f643cc4313fdf8653 Mon Sep 17 00:00:00 2001 From: franklinpashok Date: Sun, 15 Jan 2023 23:36:29 +0800 Subject: [PATCH 2/6] initial commit for static site tf module --- versions.tf | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/versions.tf b/versions.tf index e69de29..5ec8d0f 100644 --- a/versions.tf +++ b/versions.tf @@ -0,0 +1,9 @@ +terraform { + required_version = ">= 1.0" + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 4.18" + } + } +} From 3a1e3cbd9039a623d426a9da178e012ac9530989 Mon Sep 17 00:00:00 2001 From: franklinpashok Date: Mon, 16 Jan 2023 08:52:23 +0800 Subject: [PATCH 3/6] fix fmt error and update variable description --- variables.tf | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/variables.tf b/variables.tf index 5754074..0fbfd97 100644 --- a/variables.tf +++ b/variables.tf @@ -1,7 +1,3 @@ -################## -### Static S3 -################# - variable "bucket_name" { description = "bucket name" type = string @@ -21,7 +17,7 @@ variable "attach_policy" { } variable "policy" { - description = "(Optional) A valid bucket policy JSON document. Note that if the policy document is not specific enough (but still valid), Terraform may view the policy as constantly changing in a terraform plan. In this case, please make sure you use the verbose/specific version of the policy. For more information about building AWS IAM policy documents with Terraform, see the AWS IAM Policy Document Guide." + description = "A valid bucket policy JSON document (Optional)" type = string default = "" } @@ -140,4 +136,3 @@ variable "default_cache_behavior" { type = any default = {} } - From 856c59aaaa1254110b9ec24b72abea30403517ce Mon Sep 17 00:00:00 2001 From: franklinpashok Date: Mon, 16 Jan 2023 08:56:15 +0800 Subject: [PATCH 4/6] Add permission for github workflow --- .github/workflows/ci.yml | 6 ++++++ README.md | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e34bbd4..ea7cc6c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -4,6 +4,12 @@ on: push: branches: - main +permissions: + actions: read + checks: read + contents: read + pull-requests: write + security-events: write jobs: ci: uses: SPHTech-Platform/reusable-workflows/.github/workflows/terraform.yaml@main diff --git a/README.md b/README.md index 397233f..262238b 100644 --- a/README.md +++ b/README.md @@ -65,4 +65,4 @@ No requirements. | [s3\_bucket\_bucket\_domain\_name](#output\_s3\_bucket\_bucket\_domain\_name) | The bucket domain name. Will be of format bucketname.s3.amazonaws.com. | | [s3\_bucket\_bucket\_regional\_domain\_name](#output\_s3\_bucket\_bucket\_regional\_domain\_name) | The bucket region-specific domain name. The bucket domain name including the region name, please refer here for format. Note: The AWS CloudFront allows specifying S3 region-specific endpoint when creating S3 origin, it will prevent redirect issues from CloudFront to S3 Origin URL. | | [s3\_bucket\_id](#output\_s3\_bucket\_id) | The name of the bucket. | - \ No newline at end of file + From 7dce21fb8e230f36cb7b4b8211220b2bd016dc5f Mon Sep 17 00:00:00 2001 From: franklinpashok Date: Mon, 16 Jan 2023 08:59:40 +0800 Subject: [PATCH 5/6] Remove comments from cloudfront and s3 tf files --- cloudfront.tf | 3 --- s3.tf | 4 ---- 2 files changed, 7 deletions(-) diff --git a/cloudfront.tf b/cloudfront.tf index 8740197..8ade99c 100644 --- a/cloudfront.tf +++ b/cloudfront.tf @@ -1,6 +1,3 @@ -#################################### -##### Cloud Front Distribution ##### -#################################### module "cdn" { source = "terraform-aws-modules/cloudfront/aws" comment = "Distribution for static website" diff --git a/s3.tf b/s3.tf index a5b42d8..0a479ea 100644 --- a/s3.tf +++ b/s3.tf @@ -1,7 +1,3 @@ -######################################################## -##### S3 bucket policy for frontend static website ##### -######################################################## - module "s3" { source = "terraform-aws-modules/s3-bucket/aws" version = "3.5.0" From 12796687777b50622525b03b6c96d5108fcc5764 Mon Sep 17 00:00:00 2001 From: franklinpashok Date: Mon, 16 Jan 2023 11:35:04 +0800 Subject: [PATCH 6/6] Update the cloudfront module version and tf provider version --- cloudfront.tf | 4 +++- s3.tf | 2 +- versions.tf | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/cloudfront.tf b/cloudfront.tf index 8ade99c..61b87fa 100644 --- a/cloudfront.tf +++ b/cloudfront.tf @@ -1,5 +1,7 @@ module "cdn" { - source = "terraform-aws-modules/cloudfront/aws" + source = "terraform-aws-modules/cloudfront/aws" + version = "~> 3.1.0" + comment = "Distribution for static website" is_ipv6_enabled = true price_class = var.price_class diff --git a/s3.tf b/s3.tf index 0a479ea..8fe30a7 100644 --- a/s3.tf +++ b/s3.tf @@ -1,6 +1,6 @@ module "s3" { source = "terraform-aws-modules/s3-bucket/aws" - version = "3.5.0" + version = "~> 3.5.0" bucket = var.bucket_name acl = var.acl diff --git a/versions.tf b/versions.tf index 5ec8d0f..b3f404e 100644 --- a/versions.tf +++ b/versions.tf @@ -3,7 +3,7 @@ terraform { required_providers { aws = { source = "hashicorp/aws" - version = ">= 4.18" + version = ">= 4.50" } } }