From 507164a1ea64fec425de784cd39b8a393be18320 Mon Sep 17 00:00:00 2001 From: Andy Singleton Date: Wed, 19 Jun 2024 11:03:10 +0100 Subject: [PATCH 1/7] Parameterize some values to allow more tailored deployments - TLS protocol version - Custom error responses --- aws_cloudfront_distribution.tf | 8 ++++---- variables.tf | 30 +++++++++++++++++++++++++++--- 2 files changed, 31 insertions(+), 7 deletions(-) diff --git a/aws_cloudfront_distribution.tf b/aws_cloudfront_distribution.tf index 0eb519a..6d9fdbf 100644 --- a/aws_cloudfront_distribution.tf +++ b/aws_cloudfront_distribution.tf @@ -18,13 +18,13 @@ resource "aws_cloudfront_distribution" "s3_distribution" { cloudfront_default_certificate = var.use_cloudfront_default_certificate acm_certificate_arn = aws_acm_certificate.certificate.arn ssl_support_method = "sni-only" - minimum_protocol_version = "TLSv1.2_2021" + minimum_protocol_version = var.minimum_protocol_version } custom_error_response { - error_caching_min_ttl = 300 - error_code = 404 - response_code = 200 + error_caching_min_ttl = var.custom_error_response_min_ttl + error_code = var.custom_error_response_error_code + response_code = var.custom_error_response_code response_page_path = "/index.html" } diff --git a/variables.tf b/variables.tf index dbf0514..620685e 100644 --- a/variables.tf +++ b/variables.tf @@ -22,13 +22,31 @@ variable "cloudfront_cache_compress_content" { default = false } +variable "custom_error_response_error_code" { + description = "Custom error code for error response" + type = number + default = 404 +} + +variable "custom_error_response_min_ttl" { + description = "Minimum time-to-live for error caching" + type = number + default = 300 +} + +variable "custom_error_response_code" { + description = "Custom error code for error response" + type = number + default = 200 +} + variable "distribution_fqdn" { type = string description = "Fully qualified domain bound to Cloudfront." } variable "distribution_name" { - type = string + type = string description = "A unique name give to the distribution." } @@ -37,10 +55,16 @@ variable "hosted_zone_name" { description = "The route53 zone." } +variable "minimum_protocol_version" { + description = "Minimum protocol version for the viewer certificate" + type = string + default = "TLSv1.2_2021" +} + variable "price_class" { - type = string + type = string description = "The price class for this distribution." - default = "PriceClass_100" + default = "PriceClass_100" } variable "s3_source_bukcet_name" { From 57d5410e0d0f9cc536736de9a2d85d0d2eff4b31 Mon Sep 17 00:00:00 2001 From: Andy Singleton Date: Wed, 19 Jun 2024 11:06:31 +0100 Subject: [PATCH 2/7] Overhaul bucket management to public module --- aws_cloudfront_distribution.tf | 2 +- data_aws_s3_cloudfront_origin_bucket.tf | 2 +- example/module_cloudfront_example.tf | 2 +- module_s3_bucket_cloudfront_logging.tf | 37 +++++++++++++++---------- outputs.tf | 2 +- variables.tf | 2 +- 6 files changed, 28 insertions(+), 19 deletions(-) diff --git a/aws_cloudfront_distribution.tf b/aws_cloudfront_distribution.tf index 6d9fdbf..6184e0c 100644 --- a/aws_cloudfront_distribution.tf +++ b/aws_cloudfront_distribution.tf @@ -33,7 +33,7 @@ resource "aws_cloudfront_distribution" "s3_distribution" { ] logging_config { - bucket = module.bucket_cloudwatch_logs_backup.bucket_domain_name + bucket = module.bucket_cloudwatch_logs_backup.s3_bucket_bucket_domain_name include_cookies = false prefix = "cloudfront/" } diff --git a/data_aws_s3_cloudfront_origin_bucket.tf b/data_aws_s3_cloudfront_origin_bucket.tf index f4e4fe3..675951b 100644 --- a/data_aws_s3_cloudfront_origin_bucket.tf +++ b/data_aws_s3_cloudfront_origin_bucket.tf @@ -1,3 +1,3 @@ data "aws_s3_bucket" "origin_bucket" { - bucket = var.s3_source_bukcet_name + bucket = var.s3_source_bucket_name } \ No newline at end of file diff --git a/example/module_cloudfront_example.tf b/example/module_cloudfront_example.tf index d4a400e..2419348 100644 --- a/example/module_cloudfront_example.tf +++ b/example/module_cloudfront_example.tf @@ -1,6 +1,6 @@ module "cloudfront_example" { source = "git::ssh://git@github.com/osodevops/aws-terraform-module-cloudfront-s3.git" - s3_source_bukcet_name = local.example_bucket_name + s3_source_bucket_name = local.example_bucket_name distribution_fqdn = "example.domain-name.com" distribution_name = "example" hosted_zone_name = "domain-name.com" diff --git a/module_s3_bucket_cloudfront_logging.tf b/module_s3_bucket_cloudfront_logging.tf index b152838..b92e3bf 100644 --- a/module_s3_bucket_cloudfront_logging.tf +++ b/module_s3_bucket_cloudfront_logging.tf @@ -1,9 +1,17 @@ module "bucket_cloudwatch_logs_backup" { - source = "git::ssh://git@github.com/osodevops/aws-terraform-module-s3.git" - s3_bucket_name = local.logging_bucket_name - s3_bucket_force_destroy = false - s3_bucket_policy = "" - common_tags = var.common_tags + source = "terraform-aws-modules/s3-bucket/aws" + version = "~>3.0" + + bucket = local.logging_bucket_name + force_destroy = false + tags = var.common_tags + grant = [ + { + type = "CanonicalUser" + permission = "FULL_CONTROL" + id = data.aws_canonical_user_id.current.id + } + ] # Bucket public access restrict_public_buckets = true @@ -12,16 +20,17 @@ module "bucket_cloudwatch_logs_backup" { ignore_public_acls = true versioning = { - status = "Enabled" + status = "Suspended" mfa_delete = "Disabled" } - cors_rule = { - allowed_headers = ["Authorization"] - allowed_methods = ["GET"] - allowed_origins = ["*"] - expose_headers = [] - max_age_seconds = 3000 - } + server_side_encryption_configuration = { + rule = { + bucket_key_enabled = false -} \ No newline at end of file + apply_server_side_encryption_by_default = { + sse_algorithm = "AES256" + } + } + } +} diff --git a/outputs.tf b/outputs.tf index 694c8fa..f135447 100644 --- a/outputs.tf +++ b/outputs.tf @@ -1,5 +1,5 @@ output "logging_bucket" { - value = module.bucket_cloudwatch_logs_backup.s3_id + value = module.bucket_cloudwatch_logs_backup.s3_bucket_id } output "distribution" { diff --git a/variables.tf b/variables.tf index 620685e..86444eb 100644 --- a/variables.tf +++ b/variables.tf @@ -67,7 +67,7 @@ variable "price_class" { default = "PriceClass_100" } -variable "s3_source_bukcet_name" { +variable "s3_source_bucket_name" { type = string } From 1a6123ee9db487af935c9352859b8a1c27707b85 Mon Sep 17 00:00:00 2001 From: Andy Singleton Date: Wed, 19 Jun 2024 15:30:13 +0100 Subject: [PATCH 3/7] Correct the function_association dynamic block --- aws_cloudfront_distribution.tf | 21 ++++++++++----------- example/module_cloudfront_example.tf | 2 +- variables.tf | 8 +++----- 3 files changed, 14 insertions(+), 17 deletions(-) diff --git a/aws_cloudfront_distribution.tf b/aws_cloudfront_distribution.tf index 6184e0c..6bbfaa4 100644 --- a/aws_cloudfront_distribution.tf +++ b/aws_cloudfront_distribution.tf @@ -8,7 +8,7 @@ resource "aws_cloudfront_distribution" "s3_distribution" { origin_access_identity = aws_cloudfront_origin_access_identity.current.cloudfront_access_identity_path } } - comment = "${var.distribution_name} distribution" + comment = "${var.distribution_name} distribution" enabled = true is_ipv6_enabled = true @@ -40,7 +40,7 @@ resource "aws_cloudfront_distribution" "s3_distribution" { #caching default_cache_behavior { - response_headers_policy_id = aws_cloudfront_response_headers_policy.security_headers_policy.id + response_headers_policy_id = var.response_header_policy_enable ? one(aws_cloudfront_response_headers_policy.security_headers_policy).id : "" min_ttl = var.cloudfront_cache_min_ttl default_ttl = var.cloudfront_cache_default_ttl @@ -68,12 +68,11 @@ resource "aws_cloudfront_distribution" "s3_distribution" { target_origin_id = "${data.aws_s3_bucket.origin_bucket.id}-origin" viewer_protocol_policy = "redirect-to-https" - dynamic "lambda_function_association" { - for_each = var.lambda_function_association + dynamic "function_association" { + for_each = var.function_associations content { - event_type = lambda_function_association.value.event_type - include_body = lookup(lambda_function_association.value, "include_body", null) - lambda_arn = lambda_function_association.value.lambda_arn + event_type = function_association.value.event_type + function_arn = function_association.value.function_arn } } } @@ -106,7 +105,7 @@ resource "aws_cloudfront_origin_access_identity" "current" {} # https://infosec.mozilla.org/guidelines/web_security#x-frame-options frame_options { frame_option = "DENY" - override = true + override = true } # https://infosec.mozilla.org/guidelines/web_security#referrer-policy # referrer_policy { @@ -122,9 +121,9 @@ resource "aws_cloudfront_origin_access_identity" "current" {} # https://infosec.mozilla.org/guidelines/web_security#http-strict-transport-security strict_transport_security { access_control_max_age_sec = "63072000" - include_subdomains = true - preload = true - override = true + include_subdomains = true + preload = true + override = true } # https://infosec.mozilla.org/guidelines/web_security#content-security-policy # content_security_policy { diff --git a/example/module_cloudfront_example.tf b/example/module_cloudfront_example.tf index 2419348..fd4c93d 100644 --- a/example/module_cloudfront_example.tf +++ b/example/module_cloudfront_example.tf @@ -6,4 +6,4 @@ module "cloudfront_example" { hosted_zone_name = "domain-name.com" common_tags = var.common_tags cloudfront_cache_compress_content = var.cloudfront_cache_compress_content -} \ No newline at end of file +} diff --git a/variables.tf b/variables.tf index 86444eb..1e7a256 100644 --- a/variables.tf +++ b/variables.tf @@ -76,14 +76,12 @@ variable "ttl" { default = "300" } -variable "lambda_function_association" { +variable "function_associations" { + description = "A config block that triggers a function with specific actions" type = list(object({ event_type = string - include_body = bool - lambda_arn = string + function_arn = string })) - - description = "A config block that triggers a lambda function with specific actions" default = [] } From 17b7b14307db71bf0bfc412398c30b99987a88bc Mon Sep 17 00:00:00 2001 From: Andy Singleton Date: Wed, 19 Jun 2024 15:31:28 +0100 Subject: [PATCH 4/7] Parameterize the following features - response_headers are now optional - Versioning of the logging S3 bucket - Cors rules --- aws_cloudfront_distribution.tf | 5 +++-- aws_route53_a_record.tf | 4 +++- data_aws_caller_identity.tf | 2 ++ module_s3_bucket_cloudfront_logging.tf | 5 ++++- variables.tf | 24 ++++++++++++++++++++++++ 5 files changed, 36 insertions(+), 4 deletions(-) diff --git a/aws_cloudfront_distribution.tf b/aws_cloudfront_distribution.tf index 6bbfaa4..b6f20f4 100644 --- a/aws_cloudfront_distribution.tf +++ b/aws_cloudfront_distribution.tf @@ -95,8 +95,9 @@ resource "aws_cloudfront_distribution" "s3_distribution" { resource "aws_cloudfront_origin_access_identity" "current" {} - resource "aws_cloudfront_response_headers_policy" "security_headers_policy" { - name = "${var.distribution_name}-cloudfront-security-headers-policy" +resource "aws_cloudfront_response_headers_policy" "security_headers_policy" { + name = "${var.distribution_name}-cloudfront-security-headers-policy" + count = var.response_header_policy_enable ? 1 : 0 security_headers_config { # https://infosec.mozilla.org/guidelines/web_security#x-content-type-options # content_type_options { diff --git a/aws_route53_a_record.tf b/aws_route53_a_record.tf index dd6e866..58ca9e9 100644 --- a/aws_route53_a_record.tf +++ b/aws_route53_a_record.tf @@ -1,7 +1,9 @@ resource "aws_route53_record" "fqdn_cloudfront_dist" { zone_id = data.aws_route53_zone.current.zone_id name = var.distribution_fqdn - type = "A" + + allow_overwrite = false + type = "A" alias { evaluate_target_health = false name = aws_cloudfront_distribution.s3_distribution.domain_name diff --git a/data_aws_caller_identity.tf b/data_aws_caller_identity.tf index 5ea1bc1..3b55507 100644 --- a/data_aws_caller_identity.tf +++ b/data_aws_caller_identity.tf @@ -4,4 +4,6 @@ data "aws_availability_zones" "this" {} data "aws_caller_identity" "current" {} +data "aws_canonical_user_id" "current" {} + data "aws_iam_account_alias" "current" {} \ No newline at end of file diff --git a/module_s3_bucket_cloudfront_logging.tf b/module_s3_bucket_cloudfront_logging.tf index b92e3bf..29151ce 100644 --- a/module_s3_bucket_cloudfront_logging.tf +++ b/module_s3_bucket_cloudfront_logging.tf @@ -20,7 +20,7 @@ module "bucket_cloudwatch_logs_backup" { ignore_public_acls = true versioning = { - status = "Suspended" + status = var.s3_logging_versioning mfa_delete = "Disabled" } @@ -33,4 +33,7 @@ module "bucket_cloudwatch_logs_backup" { } } } + + cors_rule = var.cors_rules + } diff --git a/variables.tf b/variables.tf index 1e7a256..e23588d 100644 --- a/variables.tf +++ b/variables.tf @@ -22,6 +22,18 @@ variable "cloudfront_cache_compress_content" { default = false } +variable "cors_rules" { + description = "List of maps of cors rules to ap[ply to the logging bucket" + type = list(object({ + allowed_headers = list(string) + allowed_methods = list(string) + allowed_origins = list(string) + expose_headers = list(string) + max_age_seconds = number + })) + default = [] +} + variable "custom_error_response_error_code" { description = "Custom error code for error response" type = number @@ -55,6 +67,12 @@ variable "hosted_zone_name" { description = "The route53 zone." } +variable "s3_logging_versioning" { + description = "Whether to version the contents of the logging bucket" + type = string + default = "Suspended" +} + variable "minimum_protocol_version" { description = "Minimum protocol version for the viewer certificate" type = string @@ -85,6 +103,12 @@ variable "function_associations" { default = [] } +variable "response_header_policy_enable" { + description = "Feature-flag for including response header policy" + type = bool + default = true +} + variable "use_cloudfront_default_certificate" { type = bool description = "Default SSL certificate." From 8c060747445e210fbe2c10b18556993fd497a72d Mon Sep 17 00:00:00 2001 From: Andy Singleton Date: Wed, 19 Jun 2024 15:31:43 +0100 Subject: [PATCH 5/7] The private acl is new preferred --- module_s3_bucket_cloudfront_logging.tf | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/module_s3_bucket_cloudfront_logging.tf b/module_s3_bucket_cloudfront_logging.tf index 29151ce..245670d 100644 --- a/module_s3_bucket_cloudfront_logging.tf +++ b/module_s3_bucket_cloudfront_logging.tf @@ -5,13 +5,7 @@ module "bucket_cloudwatch_logs_backup" { bucket = local.logging_bucket_name force_destroy = false tags = var.common_tags - grant = [ - { - type = "CanonicalUser" - permission = "FULL_CONTROL" - id = data.aws_canonical_user_id.current.id - } - ] + acl = "private" # Bucket public access restrict_public_buckets = true From 50ee82138b742eca487d67015284807c9b6214a9 Mon Sep 17 00:00:00 2001 From: Andy Singleton Date: Wed, 19 Jun 2024 15:42:33 +0100 Subject: [PATCH 6/7] Update aws provider requirement --- terraform.tf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/terraform.tf b/terraform.tf index f563c66..8e338d5 100644 --- a/terraform.tf +++ b/terraform.tf @@ -2,7 +2,7 @@ terraform { required_providers { aws = { source = "hashicorp/aws" - version = "4.67.0" + version = ">=4.67.0" } } } \ No newline at end of file From 5a24f13bdd549ea6c8242f6b29f61598bb7e7f5d Mon Sep 17 00:00:00 2001 From: Andy Singleton Date: Mon, 24 Jun 2024 08:54:40 +0100 Subject: [PATCH 7/7] Allow whitelabelled cloudfront distributions This covers distributions where the primary domain for the distribution is owned externally --- aws_acm_certificates.tf | 8 ++++---- aws_route53_a_record.tf | 3 ++- data.aws_route53_zone.tf | 1 + module_s3_bucket_cloudfront_logging.tf | 13 ++++++++----- variables.tf | 18 ++++++++++++------ 5 files changed, 27 insertions(+), 16 deletions(-) diff --git a/aws_acm_certificates.tf b/aws_acm_certificates.tf index 234bd01..1d578df 100644 --- a/aws_acm_certificates.tf +++ b/aws_acm_certificates.tf @@ -13,19 +13,19 @@ resource "aws_acm_certificate_validation" "cert" { provider = aws.cloudfront certificate_arn = aws_acm_certificate.certificate.arn validation_record_fqdns = [for record in aws_route53_record.certificate_validation : record.fqdn] - depends_on = [aws_route53_record.certificate_validation] + depends_on = [aws_route53_record.certificate_validation] } resource "aws_route53_record" "certificate_validation" { - for_each = { + for_each = var.whitelabel_domain == false ? { for dvo in aws_acm_certificate.certificate.domain_validation_options : dvo.domain_name => { name = dvo.resource_record_name record = dvo.resource_record_value type = dvo.resource_record_type } - } + } : {} - zone_id = data.aws_route53_zone.current.zone_id + zone_id = one(data.aws_route53_zone.current).zone_id allow_overwrite = true name = each.value.name records = [each.value.record] diff --git a/aws_route53_a_record.tf b/aws_route53_a_record.tf index 58ca9e9..ea7337f 100644 --- a/aws_route53_a_record.tf +++ b/aws_route53_a_record.tf @@ -1,5 +1,6 @@ resource "aws_route53_record" "fqdn_cloudfront_dist" { - zone_id = data.aws_route53_zone.current.zone_id + count = var.whitelabel_domain ? 0 : 1 + zone_id = one(data.aws_route53_zone.current).zone_id name = var.distribution_fqdn allow_overwrite = false diff --git a/data.aws_route53_zone.tf b/data.aws_route53_zone.tf index cfc5328..2a75147 100644 --- a/data.aws_route53_zone.tf +++ b/data.aws_route53_zone.tf @@ -1,4 +1,5 @@ data "aws_route53_zone" "current" { + count = var.whitelabel_domain ? 0 : 1 name = var.hosted_zone_name private_zone = false } diff --git a/module_s3_bucket_cloudfront_logging.tf b/module_s3_bucket_cloudfront_logging.tf index 245670d..7ede0f6 100644 --- a/module_s3_bucket_cloudfront_logging.tf +++ b/module_s3_bucket_cloudfront_logging.tf @@ -1,11 +1,14 @@ module "bucket_cloudwatch_logs_backup" { source = "terraform-aws-modules/s3-bucket/aws" - version = "~>3.0" + version = "~>4.0" - bucket = local.logging_bucket_name - force_destroy = false - tags = var.common_tags - acl = "private" + bucket = local.logging_bucket_name + force_destroy = false + tags = var.common_tags + acl = var.whitelabel_domain ? null : "private" + object_ownership = "ObjectWriter" + control_object_ownership = var.whitelabel_domain ? true : false + attach_access_log_delivery_policy = var.whitelabel_domain ? true : false # Bucket public access restrict_public_buckets = true diff --git a/variables.tf b/variables.tf index e23588d..9dc84e5 100644 --- a/variables.tf +++ b/variables.tf @@ -24,7 +24,7 @@ variable "cloudfront_cache_compress_content" { variable "cors_rules" { description = "List of maps of cors rules to ap[ply to the logging bucket" - type = list(object({ + type = list(object({ allowed_headers = list(string) allowed_methods = list(string) allowed_origins = list(string) @@ -69,8 +69,8 @@ variable "hosted_zone_name" { variable "s3_logging_versioning" { description = "Whether to version the contents of the logging bucket" - type = string - default = "Suspended" + type = string + default = "Suspended" } variable "minimum_protocol_version" { @@ -96,11 +96,11 @@ variable "ttl" { variable "function_associations" { description = "A config block that triggers a function with specific actions" - type = list(object({ + type = list(object({ event_type = string - function_arn = string + function_arn = string })) - default = [] + default = [] } variable "response_header_policy_enable" { @@ -121,6 +121,12 @@ variable "web_acl_id" { default = "" } +variable "whitelabel_domain" { + description = "Flag to toggle whitelabeling the domain" + type = bool + default = false +} + variable "common_tags" { type = map(string) description = "Implements the common tags."