diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 71da442..02bf72e 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,6 @@ repos: - repo: https://github.com/antonbabenko/pre-commit-terraform - rev: v1.103.0 + rev: v1.104.0 hooks: - id: terraform_fmt - id: terraform_wrapper_module_for_each diff --git a/README.md b/README.md index b644ea3..25acc85 100644 --- a/README.md +++ b/README.md @@ -107,13 +107,13 @@ ordered_cache_behavior = [{ | Name | Version | |------|---------| | [terraform](#requirement\_terraform) | >= 1.5.7 | -| [aws](#requirement\_aws) | >= 5.83 | +| [aws](#requirement\_aws) | >= 5.100 | ## Providers | Name | Version | |------|---------| -| [aws](#provider\_aws) | >= 5.83 | +| [aws](#provider\_aws) | >= 5.100 | ## Modules @@ -127,6 +127,7 @@ No modules. | [aws_cloudfront_monitoring_subscription.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/cloudfront_monitoring_subscription) | resource | | [aws_cloudfront_origin_access_control.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/cloudfront_origin_access_control) | resource | | [aws_cloudfront_origin_access_identity.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/cloudfront_origin_access_identity) | resource | +| [aws_cloudfront_response_headers_policy.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/cloudfront_response_headers_policy) | resource | | [aws_cloudfront_vpc_origin.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/cloudfront_vpc_origin) | resource | | [aws_cloudfront_cache_policy.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/cloudfront_cache_policy) | data source | | [aws_cloudfront_origin_request_policy.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/cloudfront_origin_request_policy) | data source | @@ -143,6 +144,7 @@ No modules. | [create\_monitoring\_subscription](#input\_create\_monitoring\_subscription) | If enabled, the resource for monitoring subscription will created. | `bool` | `false` | no | | [create\_origin\_access\_control](#input\_create\_origin\_access\_control) | Controls if CloudFront origin access control should be created | `bool` | `false` | no | | [create\_origin\_access\_identity](#input\_create\_origin\_access\_identity) | Controls if CloudFront origin access identity should be created | `bool` | `false` | no | +| [create\_response\_headers\_policy](#input\_create\_response\_headers\_policy) | Controls if CloudFront response headers policies should be created | `bool` | `false` | no | | [create\_vpc\_origin](#input\_create\_vpc\_origin) | If enabled, the resource for VPC origin will be created. | `bool` | `false` | no | | [custom\_error\_response](#input\_custom\_error\_response) | One or more custom error response elements | `any` | `{}` | no | | [default\_cache\_behavior](#input\_default\_cache\_behavior) | The default cache behavior for this distribution | `any` | `null` | no | @@ -159,6 +161,7 @@ No modules. | [origin\_group](#input\_origin\_group) | One or more origin\_group for this distribution (multiples allowed). | `any` | `{}` | no | | [price\_class](#input\_price\_class) | The price class for this distribution. One of PriceClass\_All, PriceClass\_200, PriceClass\_100 | `string` | `null` | no | | [realtime\_metrics\_subscription\_status](#input\_realtime\_metrics\_subscription\_status) | A flag that indicates whether additional CloudWatch metrics are enabled for a given CloudFront distribution. Valid values are `Enabled` and `Disabled`. | `string` | `"Enabled"` | no | +| [response\_headers\_policies](#input\_response\_headers\_policies) | Map of CloudFront response headers policies with their configurations |
map(object({
name = optional(string)
comment = optional(string)
cors_config = optional(object({
access_control_allow_credentials = bool
origin_override = bool
access_control_allow_headers = object({
items = list(string)
})
access_control_allow_methods = object({
items = list(string)
})
access_control_allow_origins = object({
items = list(string)
})
access_control_expose_headers = optional(object({
items = list(string)
}))
access_control_max_age_sec = optional(number)
}))
custom_headers_config = optional(object({
items = list(object({
header = string
override = bool
value = string
}))
}))
remove_headers_config = optional(object({
items = list(object({
header = string
}))
}))
security_headers_config = optional(object({
content_security_policy = optional(object({
content_security_policy = string
override = bool
}))
content_type_options = optional(object({
override = bool
}))
frame_options = optional(object({
frame_option = string
override = bool
}))
referrer_policy = optional(object({
referrer_policy = string
override = bool
}))
strict_transport_security = optional(object({
access_control_max_age_sec = number
override = bool
include_subdomains = optional(bool)
preload = optional(bool)
}))
xss_protection = optional(object({
mode_block = bool
override = bool
protection = bool
report_uri = optional(string)
}))
}))
server_timing_headers_config = optional(object({
enabled = bool
sampling_rate = number
}))
}))
| `null` | no | | [retain\_on\_delete](#input\_retain\_on\_delete) | Disables the distribution instead of deleting it when destroying the resource through Terraform. If this is set, the distribution needs to be deleted manually afterwards. | `bool` | `false` | no | | [staging](#input\_staging) | Whether the distribution is a staging distribution. | `bool` | `false` | no | | [tags](#input\_tags) | A map of tags to assign to the resource. | `map(string)` | `null` | no | @@ -189,6 +192,7 @@ No modules. | [cloudfront\_origin\_access\_identities](#output\_cloudfront\_origin\_access\_identities) | The origin access identities created | | [cloudfront\_origin\_access\_identity\_iam\_arns](#output\_cloudfront\_origin\_access\_identity\_iam\_arns) | The IAM arns of the origin access identities created | | [cloudfront\_origin\_access\_identity\_ids](#output\_cloudfront\_origin\_access\_identity\_ids) | The IDS of the origin access identities created | +| [cloudfront\_response\_headers\_policies](#output\_cloudfront\_response\_headers\_policies) | The response headers policies created | | [cloudfront\_vpc\_origin\_ids](#output\_cloudfront\_vpc\_origin\_ids) | The IDS of the VPC origin created | diff --git a/examples/complete/README.md b/examples/complete/README.md index d2b2039..020da2c 100644 --- a/examples/complete/README.md +++ b/examples/complete/README.md @@ -28,7 +28,7 @@ Note that this example may create resources which cost money. Run `terraform des | Name | Version | |------|---------| | [terraform](#requirement\_terraform) | >= 1.5.7 | -| [aws](#requirement\_aws) | >= 5.83 | +| [aws](#requirement\_aws) | >= 5.100 | | [null](#requirement\_null) | >= 2.0 | | [random](#requirement\_random) | >= 2.0 | @@ -36,7 +36,7 @@ Note that this example may create resources which cost money. Run `terraform des | Name | Version | |------|---------| -| [aws](#provider\_aws) | >= 5.83 | +| [aws](#provider\_aws) | >= 5.100 | | [null](#provider\_null) | >= 2.0 | | [random](#provider\_random) | >= 2.0 | diff --git a/examples/complete/main.tf b/examples/complete/main.tf index 76bc588..7b368a5 100644 --- a/examples/complete/main.tf +++ b/examples/complete/main.tf @@ -176,6 +176,8 @@ module "cloudfront" { cache_policy_name = "Managed-CachingOptimized" origin_request_policy_name = "Managed-UserAgentRefererHeaders" response_headers_policy_name = "Managed-SimpleCORS" + # using a response header policy you're dynamically creating below + # response_header_policy: "cors_policy" function_association = { # Valid keys: viewer-request, viewer-response @@ -231,6 +233,79 @@ module "cloudfront" { locations = ["NO", "UA", "US", "GB"] } + create_response_headers_policy = true + response_headers_policies = { + cors_policy = { + name = "CORSPolicy" + comment = "CORS configuration for API" + + cors_config = { + access_control_allow_credentials = true + origin_override = true + + access_control_allow_headers = { + items = ["*"] + } + + access_control_allow_methods = { + items = ["GET", "POST", "PUT", "DELETE", "OPTIONS"] + } + + access_control_allow_origins = { + items = ["https://example.com", "https://app.example.com"] + } + + access_control_expose_headers = { + items = ["X-Custom-Header", "X-Request-Id"] + } + + access_control_max_age_sec = 3600 + } + } + custom_headers = { + name = "CustomHeadersPolicy" + comment = "Add custom response headers" + + custom_headers_config = { + items = [ + { + header = "X-Powered-By" + override = true + value = "MyApp/1.0" + }, + { + header = "X-API-Version" + override = false + value = "v2" + }, + { + header = "Cache-Control" + override = true + value = "public, max-age=3600" + } + ] + } + } + remove_headers = { + name = "RemoveHeadersPolicy" + comment = "Remove unwanted headers from origin" + + remove_headers_config = { + items = [ + { + header = "x-robots-tag" + }, + { + header = "server" + }, + { + header = "x-powered-by" + } + ] + } + } + } + } ###### diff --git a/examples/complete/versions.tf b/examples/complete/versions.tf index e69fcd0..d00553b 100644 --- a/examples/complete/versions.tf +++ b/examples/complete/versions.tf @@ -4,7 +4,7 @@ terraform { required_providers { aws = { source = "hashicorp/aws" - version = ">= 5.83" + version = ">= 5.100" } random = { source = "hashicorp/random" diff --git a/main.tf b/main.tf index f773d7e..78e7d63 100644 --- a/main.tf +++ b/main.tf @@ -4,6 +4,145 @@ locals { create_vpc_origin = var.create_vpc_origin && length(keys(var.vpc_origin)) > 0 } +resource "aws_cloudfront_response_headers_policy" "this" { + for_each = var.create_response_headers_policy && var.response_headers_policies != null ? var.response_headers_policies : {} + + name = try(coalesce(each.value.name, each.key)) + comment = each.value.comment + + dynamic "cors_config" { + for_each = each.value.cors_config != null ? [each.value.cors_config] : [] + + content { + access_control_allow_credentials = cors_config.value.access_control_allow_credentials + origin_override = cors_config.value.origin_override + access_control_max_age_sec = cors_config.value.access_control_max_age_sec + + access_control_allow_headers { + items = cors_config.value.access_control_allow_headers.items + } + + access_control_allow_methods { + items = cors_config.value.access_control_allow_methods.items + } + + access_control_allow_origins { + items = cors_config.value.access_control_allow_origins.items + } + + dynamic "access_control_expose_headers" { + for_each = cors_config.value.access_control_expose_headers != null ? [cors_config.value.access_control_expose_headers] : [] + + content { + items = access_control_expose_headers.value.items + } + } + } + } + + dynamic "custom_headers_config" { + for_each = each.value.custom_headers_config != null ? [each.value.custom_headers_config] : [] + + content { + dynamic "items" { + for_each = custom_headers_config.value.items + + content { + header = items.value.header + override = items.value.override + value = items.value.value + } + } + } + } + + dynamic "remove_headers_config" { + for_each = each.value.remove_headers_config != null ? [each.value.remove_headers_config] : [] + + content { + dynamic "items" { + for_each = remove_headers_config.value.items + + content { + header = items.value.header + } + } + } + } + + dynamic "security_headers_config" { + for_each = each.value.security_headers_config != null ? [each.value.security_headers_config] : [] + + content { + dynamic "content_security_policy" { + for_each = security_headers_config.value.content_security_policy != null ? [security_headers_config.value.content_security_policy] : [] + + content { + content_security_policy = content_security_policy.value.content_security_policy + override = content_security_policy.value.override + } + } + + dynamic "content_type_options" { + for_each = security_headers_config.value.content_type_options != null ? [security_headers_config.value.content_type_options] : [] + + content { + override = content_type_options.value.override + } + } + + dynamic "frame_options" { + for_each = security_headers_config.value.frame_options != null ? [security_headers_config.value.frame_options] : [] + + content { + frame_option = frame_options.value.frame_option + override = frame_options.value.override + } + } + + dynamic "referrer_policy" { + for_each = security_headers_config.value.referrer_policy != null ? [security_headers_config.value.referrer_policy] : [] + + content { + referrer_policy = referrer_policy.value.referrer_policy + override = referrer_policy.value.override + } + } + + dynamic "strict_transport_security" { + for_each = security_headers_config.value.strict_transport_security != null ? [security_headers_config.value.strict_transport_security] : [] + + content { + access_control_max_age_sec = strict_transport_security.value.access_control_max_age_sec + override = strict_transport_security.value.override + include_subdomains = strict_transport_security.value.include_subdomains + preload = strict_transport_security.value.preload + } + } + + dynamic "xss_protection" { + for_each = security_headers_config.value.xss_protection != null ? [security_headers_config.value.xss_protection] : [] + + content { + mode_block = xss_protection.value.mode_block + override = xss_protection.value.override + protection = xss_protection.value.protection + report_uri = xss_protection.value.report_uri + } + } + } + } + + dynamic "server_timing_headers_config" { + for_each = each.value.server_timing_headers_config != null ? [each.value.server_timing_headers_config] : [] + + content { + enabled = server_timing_headers_config.value.enabled + sampling_rate = server_timing_headers_config.value.sampling_rate + } + } +} + resource "aws_cloudfront_origin_access_identity" "this" { for_each = local.create_origin_access_identity ? var.origin_access_identities : {} diff --git a/outputs.tf b/outputs.tf index 29e7642..ddd6246 100644 --- a/outputs.tf +++ b/outputs.tf @@ -87,3 +87,8 @@ output "cloudfront_vpc_origin_ids" { description = "The IDS of the VPC origin created" value = local.create_vpc_origin ? [for v in aws_cloudfront_vpc_origin.this : v.id] : [] } + +output "cloudfront_response_headers_policies" { + description = "The response headers policies created" + value = aws_cloudfront_response_headers_policy.this +} diff --git a/variables.tf b/variables.tf index afeec33..a75b4a8 100644 --- a/variables.tf +++ b/variables.tf @@ -210,3 +210,80 @@ variable "vpc_origin_timeouts" { type = map(string) default = {} } + +variable "create_response_headers_policy" { + description = "Controls if CloudFront response headers policies should be created" + type = bool + default = false +} + +variable "response_headers_policies" { + description = "Map of CloudFront response headers policies with their configurations" + type = map(object({ + name = optional(string) + comment = optional(string) + cors_config = optional(object({ + access_control_allow_credentials = bool + origin_override = bool + access_control_allow_headers = object({ + items = list(string) + }) + access_control_allow_methods = object({ + items = list(string) + }) + access_control_allow_origins = object({ + items = list(string) + }) + access_control_expose_headers = optional(object({ + items = list(string) + })) + access_control_max_age_sec = optional(number) + })) + custom_headers_config = optional(object({ + items = list(object({ + header = string + override = bool + value = string + })) + })) + remove_headers_config = optional(object({ + items = list(object({ + header = string + })) + })) + security_headers_config = optional(object({ + content_security_policy = optional(object({ + content_security_policy = string + override = bool + })) + content_type_options = optional(object({ + override = bool + })) + frame_options = optional(object({ + frame_option = string + override = bool + })) + referrer_policy = optional(object({ + referrer_policy = string + override = bool + })) + strict_transport_security = optional(object({ + access_control_max_age_sec = number + override = bool + include_subdomains = optional(bool) + preload = optional(bool) + })) + xss_protection = optional(object({ + mode_block = bool + override = bool + protection = bool + report_uri = optional(string) + })) + })) + server_timing_headers_config = optional(object({ + enabled = bool + sampling_rate = number + })) + })) + default = null +} diff --git a/versions.tf b/versions.tf index 5ce9aba..879fe5a 100644 --- a/versions.tf +++ b/versions.tf @@ -4,7 +4,7 @@ terraform { required_providers { aws = { source = "hashicorp/aws" - version = ">= 5.83" + version = ">= 5.100" } } } diff --git a/wrappers/main.tf b/wrappers/main.tf index 750d27e..289cae1 100644 --- a/wrappers/main.tf +++ b/wrappers/main.tf @@ -10,6 +10,7 @@ module "wrapper" { create_monitoring_subscription = try(each.value.create_monitoring_subscription, var.defaults.create_monitoring_subscription, false) create_origin_access_control = try(each.value.create_origin_access_control, var.defaults.create_origin_access_control, false) create_origin_access_identity = try(each.value.create_origin_access_identity, var.defaults.create_origin_access_identity, false) + create_response_headers_policy = try(each.value.create_response_headers_policy, var.defaults.create_response_headers_policy, false) create_vpc_origin = try(each.value.create_vpc_origin, var.defaults.create_vpc_origin, false) custom_error_response = try(each.value.custom_error_response, var.defaults.custom_error_response, {}) default_cache_behavior = try(each.value.default_cache_behavior, var.defaults.default_cache_behavior, null) @@ -33,6 +34,7 @@ module "wrapper" { origin_group = try(each.value.origin_group, var.defaults.origin_group, {}) price_class = try(each.value.price_class, var.defaults.price_class, null) realtime_metrics_subscription_status = try(each.value.realtime_metrics_subscription_status, var.defaults.realtime_metrics_subscription_status, "Enabled") + response_headers_policies = try(each.value.response_headers_policies, var.defaults.response_headers_policies, null) retain_on_delete = try(each.value.retain_on_delete, var.defaults.retain_on_delete, false) staging = try(each.value.staging, var.defaults.staging, false) tags = try(each.value.tags, var.defaults.tags, null) diff --git a/wrappers/versions.tf b/wrappers/versions.tf index 5ce9aba..879fe5a 100644 --- a/wrappers/versions.tf +++ b/wrappers/versions.tf @@ -4,7 +4,7 @@ terraform { required_providers { aws = { source = "hashicorp/aws" - version = ">= 5.83" + version = ">= 5.100" } } }