diff --git a/locals.tf b/locals.tf index 128ba87..a261816 100644 --- a/locals.tf +++ b/locals.tf @@ -9,6 +9,7 @@ locals { cloudfront = { aliases = var.cloudfront.aliases acm_certificate_arn = var.cloudfront.acm_certificate_arn + comment = var.cloudfront.comment assets_paths = coalesce(var.cloudfront.assets_paths, []) custom_headers = coalesce(var.cloudfront.custom_headers, []) geo_restriction = coalesce(try(var.cloudfront.geo_restriction, null), { @@ -28,13 +29,18 @@ locals { override = true preload = true }, var.cloudfront.hsts) - waf_logging_configuration = var.cloudfront.waf_logging_configuration + remove_headers_config = merge({ + items : [] + }, var.cloudfront.remove_headers_config) cache_policy = { - default_ttl = coalesce(try(var.cloudfront.cache_policy.default_ttl, null), 0) - min_ttl = coalesce(try(var.cloudfront.cache_policy.min_ttl, null), 0) - max_ttl = coalesce(try(var.cloudfront.cache_policy.max_ttl, null), 31536000) + default_ttl = coalesce(try(var.cloudfront.cache_policy.default_ttl, null), 0) + min_ttl = coalesce(try(var.cloudfront.cache_policy.min_ttl, null), 0) + max_ttl = coalesce(try(var.cloudfront.cache_policy.max_ttl, null), 31536000) + enable_accept_encoding_brotli = coalesce(try(var.cloudfront.cache_policy.enable_accept_encoding_brotli, null), true) + enable_accept_encoding_gzip = coalesce(try(var.cloudfront.cache_policy.enable_accept_encoding_gzip, null), true) cookies_config = merge({ - cookie_behavior = "all" + cookie_behavior = "all", + items = [] }, try(var.cloudfront.cache_policy.cookies_config, {})) headers_config = merge({ header_behavior = "whitelist", @@ -46,6 +52,9 @@ locals { }, try(var.cloudfront.cache_policy.query_strings_config, {})) } origin_request_policy = try(var.cloudfront.origin_request_policy, null) + + custom_waf = var.cloudfront.custom_waf + waf_logging_configuration = var.cloudfront.waf_logging_configuration } /** diff --git a/main.tf b/main.tf index d31053b..af1fe60 100644 --- a/main.tf +++ b/main.tf @@ -199,6 +199,7 @@ module "cloudfront" { prefix = "${var.prefix}-cloudfront" default_tags = var.default_tags + comment = local.cloudfront.comment logging_bucket_domain_name = module.cloudfront_logs.logs_s3_bucket.bucket_regional_domain_name assets_origin_access_identity = module.assets.cloudfront_origin_access_identity.cloudfront_access_identity_path @@ -208,13 +209,16 @@ module "cloudfront" { image_optimization_function = "${module.image_optimization_function.lambda_function_url.url_id}.lambda-url.${data.aws_region.current.name}.on.aws" } - aliases = local.cloudfront.aliases - acm_certificate_arn = local.cloudfront.acm_certificate_arn - assets_paths = local.cloudfront.assets_paths - custom_headers = local.cloudfront.custom_headers - geo_restriction = local.cloudfront.geo_restriction - cors = local.cloudfront.cors - hsts = local.cloudfront.hsts + aliases = local.cloudfront.aliases + acm_certificate_arn = local.cloudfront.acm_certificate_arn + assets_paths = local.cloudfront.assets_paths + custom_headers = local.cloudfront.custom_headers + geo_restriction = local.cloudfront.geo_restriction + cors = local.cloudfront.cors + hsts = local.cloudfront.hsts + cache_policy = local.cloudfront.cache_policy + remove_headers_config = local.cloudfront.remove_headers_config + + custom_waf = local.cloudfront.custom_waf waf_logging_configuration = local.cloudfront.waf_logging_configuration - cache_policy = local.cloudfront.cache_policy } diff --git a/modules/cloudfront-logs/kms.tf b/modules/cloudfront-logs/kms.tf index d4e1534..0addc89 100644 --- a/modules/cloudfront-logs/kms.tf +++ b/modules/cloudfront-logs/kms.tf @@ -4,6 +4,7 @@ resource "aws_kms_key" "cloudwatch_logs_key" { description = "KMS Key for ${var.log_group_name} log group" deletion_window_in_days = 10 policy = data.aws_iam_policy_document.cloudwatch_logs_key_policy[0].json + enable_key_rotation = true } data "aws_iam_policy_document" "cloudwatch_logs_key_policy" { diff --git a/modules/cloudfront-logs/lambda/yarn.lock b/modules/cloudfront-logs/lambda/yarn.lock index 4f3b215..2f7d53f 100644 --- a/modules/cloudfront-logs/lambda/yarn.lock +++ b/modules/cloudfront-logs/lambda/yarn.lock @@ -2932,9 +2932,9 @@ which@^2.0.1: isexe "^2.0.0" word-wrap@^1.2.3: - version "1.2.3" - resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c" - integrity sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ== + version "1.2.4" + resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.4.tgz#cb4b50ec9aca570abd1f52f33cd45b6c61739a9f" + integrity sha512-2V81OA4ugVo5pRo46hAoD2ivUJx8jXmWXfUkY4KFNw0hEptvN0QfH3K4nHiwzGeKl5rFKedV48QVoqYavy4YpA== wrappy@1: version "1.0.2" diff --git a/modules/opennext-assets/s3.tf b/modules/opennext-assets/s3.tf index e9cf56f..5146639 100644 --- a/modules/opennext-assets/s3.tf +++ b/modules/opennext-assets/s3.tf @@ -165,6 +165,22 @@ data "aws_iam_policy_document" "read_assets_bucket" { identifiers = [var.server_function_role_arn] } } + statement { + effect = "Deny" + actions = ["s3:*"] + resources = [aws_s3_bucket.assets.arn, "${aws_s3_bucket.assets.arn}/*"] + + condition { + test = "Bool" + values = ["false"] + variable = "aws:SecureTransport" + } + + principals { + type = "*" + identifiers = ["*"] + } + } } # Static Assets diff --git a/modules/opennext-cloudfront/cloudfront.tf b/modules/opennext-cloudfront/cloudfront.tf index d04f1ab..e4f755d 100644 --- a/modules/opennext-cloudfront/cloudfront.tf +++ b/modules/opennext-cloudfront/cloudfront.tf @@ -60,8 +60,10 @@ resource "aws_cloudfront_cache_policy" "cache_policy" { min_ttl = var.cache_policy.min_ttl max_ttl = var.cache_policy.max_ttl - parameters_in_cache_key_and_forwarded_to_origin { + enable_accept_encoding_brotli = var.cache_policy.enable_accept_encoding_brotli + enable_accept_encoding_gzip = var.cache_policy.enable_accept_encoding_gzip + cookies_config { cookie_behavior = var.cache_policy.cookies_config.cookie_behavior @@ -144,6 +146,19 @@ resource "aws_cloudfront_response_headers_policy" "response_headers_policy" { } } } + dynamic "remove_headers_config" { + for_each = length(var.remove_headers_config.items) > 0 ? [true] : [] + + content { + dynamic "items" { + for_each = toset(var.remove_headers_config.items) + + content { + header = items.value + } + } + } + } } resource "aws_cloudfront_distribution" "distribution" { @@ -151,9 +166,9 @@ resource "aws_cloudfront_distribution" "distribution" { price_class = "PriceClass_100" enabled = true is_ipv6_enabled = true - comment = "${var.prefix} - CloudFront Distribution for Next.js Application" + comment = coalesce(var.comment, "${var.prefix} - CloudFront Distribution for Next.js Application") aliases = var.aliases - web_acl_id = aws_wafv2_web_acl.cloudfront_waf.arn + web_acl_id = try(var.custom_waf.arn, aws_wafv2_web_acl.cloudfront_waf[0].arn, null) logging_config { include_cookies = false diff --git a/modules/opennext-cloudfront/variables.tf b/modules/opennext-cloudfront/variables.tf index 63e2d58..1ab6fe3 100644 --- a/modules/opennext-cloudfront/variables.tf +++ b/modules/opennext-cloudfront/variables.tf @@ -9,6 +9,10 @@ variable "default_tags" { default = {} } +variable "comment" { + type = string + description = "Comment to add to the CloudFront distribution" +} variable "acm_certificate_arn" { type = string @@ -85,6 +89,13 @@ variable "hsts" { } } +variable "custom_waf" { + description = "ARN value for an externally created AWS WAF" + type = object({ + arn = string + }) +} + variable "waf_logging_configuration" { description = "Logging Configuration for the WAF attached to CloudFront" type = object({ @@ -135,9 +146,11 @@ variable "origin_request_policy" { variable "cache_policy" { type = object({ - default_ttl = number - min_ttl = number - max_ttl = number + default_ttl = number + min_ttl = number + max_ttl = number + enable_accept_encoding_gzip = bool + enable_accept_encoding_brotli = bool cookies_config = object({ cookie_behavior = string items = optional(list(string)) @@ -160,3 +173,11 @@ variable "geo_restriction" { locations = list(string) }) } + +variable "remove_headers_config" { + description = "Response header removal configuration for the CloudFront distribution" + type = object({ + items = list(string) + }) +} + diff --git a/modules/opennext-cloudfront/waf.tf b/modules/opennext-cloudfront/waf.tf index 0df510a..658ac0b 100644 --- a/modules/opennext-cloudfront/waf.tf +++ b/modules/opennext-cloudfront/waf.tf @@ -1,4 +1,6 @@ resource "aws_wafv2_web_acl" "cloudfront_waf" { + count = var.custom_waf == null ? 1 : 0 + provider = aws.global name = "${var.prefix}-waf" scope = "CLOUDFRONT" @@ -120,9 +122,9 @@ resource "aws_wafv2_web_acl" "cloudfront_waf" { } resource "aws_wafv2_web_acl_logging_configuration" "waf_logging" { - count = var.waf_logging_configuration == null ? 0 : 1 + count = var.waf_logging_configuration == null || try(aws_wafv2_web_acl.cloudfront_waf[0], null) == null ? 0 : 1 - resource_arn = aws_wafv2_web_acl.cloudfront_waf.arn + resource_arn = aws_wafv2_web_acl.cloudfront_waf[0].arn log_destination_configs = var.waf_logging_configuration.log_destination_configs dynamic "logging_filter" { diff --git a/modules/opennext-revalidation-queue/kms.tf b/modules/opennext-revalidation-queue/kms.tf index fc566ad..80ae450 100644 --- a/modules/opennext-revalidation-queue/kms.tf +++ b/modules/opennext-revalidation-queue/kms.tf @@ -9,7 +9,8 @@ resource "aws_kms_key" "revalidation_queue_key" { description = "${var.prefix} Revalidation SQS Queue KMS Key" deletion_window_in_days = 10 - policy = data.aws_iam_policy_document.revalidation_queue_key_policy[0].json + policy = data.aws_iam_policy_document.revalidation_queue_key_policy[0].json + enable_key_rotation = true } data "aws_iam_policy_document" "revalidation_queue_key_policy" { diff --git a/variables.tf b/variables.tf index a4e6a12..c11dbac 100644 --- a/variables.tf +++ b/variables.tf @@ -310,6 +310,7 @@ variable "cloudfront" { type = object({ aliases = list(string) acm_certificate_arn = string + comment = optional(string) assets_paths = optional(list(string)) custom_headers = optional(list(object({ header = string @@ -327,42 +328,24 @@ variable "cloudfront" { allow_origins = list(string) origin_override = bool })) + remove_headers_config = optional(object({ + items = list(string) + })) hsts = optional(object({ access_control_max_age_sec = number include_subdomains = bool override = bool preload = bool })) - waf_logging_configuration = optional(object({ - log_destination_configs = list(string) - logging_filter = optional(object({ - default_behavior = string - filter = list(object({ - behavior = string - requirement = string - action_condition = optional(list(object({ - action = string - }))) - label_name_condition = optional(list(object({ - label_name = string - }))) - })) - })) - redacted_fields = optional(list(object({ - method = optional(bool) - query_string = optional(bool) - single_header = optional(object({ - name = string - })) - uri_path = optional(bool) - }))) - })) cache_policy = optional(object({ - default_ttl = optional(number) - min_ttl = optional(number) - max_ttl = optional(number) + default_ttl = optional(number) + min_ttl = optional(number) + max_ttl = optional(number) + enable_accept_encoding_gzip = optional(bool) + enable_accept_encoding_brotli = optional(bool) cookies_config = optional(object({ cookie_behavior = string + items = optional(list(string)) })) headers_config = optional(object({ header_behavior = string @@ -386,5 +369,32 @@ variable "cloudfront" { items = optional(list(string)) }) })) + custom_waf = optional(object({ + arn = string + })) + waf_logging_configuration = optional(object({ + log_destination_configs = list(string) + logging_filter = optional(object({ + default_behavior = string + filter = list(object({ + behavior = string + requirement = string + action_condition = optional(list(object({ + action = string + }))) + label_name_condition = optional(list(object({ + label_name = string + }))) + })) + })) + redacted_fields = optional(list(object({ + method = optional(bool) + query_string = optional(bool) + single_header = optional(object({ + name = string + })) + uri_path = optional(bool) + }))) + })) }) }