From 69a56327fc3155d10e608b7726633ffb40f9ee36 Mon Sep 17 00:00:00 2001 From: Bryant Biggs Date: Thu, 27 Nov 2025 09:12:54 -0600 Subject: [PATCH 1/4] chore: Add initial upgrade guide --- docs/UPGRADE-6.0.md | 74 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 74 insertions(+) create mode 100644 docs/UPGRADE-6.0.md diff --git a/docs/UPGRADE-6.0.md b/docs/UPGRADE-6.0.md new file mode 100644 index 0000000..bec312f --- /dev/null +++ b/docs/UPGRADE-6.0.md @@ -0,0 +1,74 @@ +# Upgrade from v5.x to v6.x + +If you have any questions regarding this upgrade process, please consult the [`examples`](https://github.com/terraform-aws-modules/terraform-aws-cloudfront/tree/master/examples) directory: +If you find a bug, please open an issue with supporting configuration to reproduce. + +## List of backwards incompatible changes + +- AWS provider `v6.0` is now minimum supported version + +## Additional changes + +### Added + +- Support for `region` parameter to specify the AWS region for the resources created if different from the provider region. + +### Modified + +- Variable definitions now contain detailed `object` types in place of the previously used any type. + +### Variable and output changes + +1. Removed variables: + + - + +2. Renamed variables: + + - + +3. Added variables: + + - + +4. Removed outputs: + + - + +5. Renamed outputs: + + - + +6. Added outputs: + + - + +## Upgrade Migrations + +### Before 5.x Example + +```hcl +module "cloudfront" { + source = "terraform-aws-modules/cloudfront/aws/" + version = "~> 5.0" + + # Truncated for brevity ... + +} +``` + +### After 6.x Example + +```hcl +module "cloudfront" { + source = "terraform-aws-modules/cloudfront/aws/" + version = "~> 6.0" + + # Truncated for brevity ... + +} +``` + +### State Changes + +TBD From 109240caaea4d50a3829e2cda51f6c47dca777fc Mon Sep 17 00:00:00 2001 From: Bryant Biggs Date: Thu, 27 Nov 2025 09:45:44 -0600 Subject: [PATCH 2/4] fix: Remove origin access identity in favor of origin access control --- README.md | 81 ++++--- docs/UPGRADE-6.0.md | 13 +- examples/complete/README.md | 9 +- examples/complete/main.tf | 7 +- examples/complete/outputs.tf | 53 ++++- main.tf | 418 +++++++++++++++++------------------ outputs.tf | 81 ++++--- variables.tf | 136 ++++++------ wrappers/main.tf | 2 - 9 files changed, 419 insertions(+), 381 deletions(-) diff --git a/README.md b/README.md index ee8286b..a8bbda0 100644 --- a/README.md +++ b/README.md @@ -19,9 +19,14 @@ module "cdn" { retain_on_delete = false wait_for_deployment = false - create_origin_access_identity = true - origin_access_identities = { - s3_bucket_one = "My awesome CloudFront can access" + create_origin_access_control = true + origin_access_control = { + s3_oac = { + description = "CloudFront access to S3" + origin_type = "s3" + signing_behavior = "always" + signing_protocol = "sigv4" + } } logging_config = { @@ -38,13 +43,6 @@ module "cdn" { origin_ssl_protocols = ["TLSv1", "TLSv1.1", "TLSv1.2"] } } - - s3_one = { - domain_name = "my-s3-bycket.s3.amazonaws.com" - s3_origin_config = { - origin_access_identity = "s3_bucket_one" - } - } } default_cache_behavior = { @@ -92,6 +90,16 @@ module "cdn" { retain_on_delete = false wait_for_deployment = false + create_origin_access_control = true + origin_access_control = { + s3_oac = { + description = "CloudFront access to S3" + origin_type = "s3" + signing_behavior = "always" + signing_protocol = "sigv4" + } + } + # Enable CloudFront Functions create_cloudfront_function = true @@ -116,9 +124,6 @@ module "cdn" { origin = { s3_bucket = { domain_name = "my-bucket.s3.amazonaws.com" - s3_origin_config = { - origin_access_identity = "s3_bucket" - } } } @@ -207,7 +212,6 @@ No modules. | [aws_cloudfront_function.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/cloudfront_function) | resource | | [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 | @@ -218,41 +222,39 @@ No modules. | Name | Description | Type | Default | Required | |------|-------------|------|---------|:--------:| -| [aliases](#input\_aliases) | Extra CNAMEs (alternate domain names), if any, for this distribution. | `list(string)` | `null` | no | -| [cloudfront\_functions](#input\_cloudfront\_functions) | Map of CloudFront Function configurations. Key is used as default function name if 'name' not specified. |
map(object({
name = optional(string)
runtime = optional(string, "cloudfront-js-2.0")
comment = optional(string)
publish = optional(bool)
code = string
key_value_store_associations = optional(list(string))
}))
| `null` | no | -| [comment](#input\_comment) | Any comments you want to include about the distribution. | `string` | `null` | no | -| [continuous\_deployment\_policy\_id](#input\_continuous\_deployment\_policy\_id) | Identifier of a continuous deployment policy. This argument should only be set on a production distribution. | `string` | `null` | no | +| [aliases](#input\_aliases) | Extra CNAMEs (alternate domain names), if any, for this distribution | `list(string)` | `null` | no | +| [cloudfront\_functions](#input\_cloudfront\_functions) | Map of CloudFront Function configurations. Key is used as default function name if 'name' not specified |
map(object({
name = optional(string)
runtime = optional(string, "cloudfront-js-2.0")
comment = optional(string)
publish = optional(bool)
code = string
key_value_store_associations = optional(list(string))
}))
| `null` | no | +| [comment](#input\_comment) | Any comments you want to include about the distribution | `string` | `null` | no | +| [continuous\_deployment\_policy\_id](#input\_continuous\_deployment\_policy\_id) | Identifier of a continuous deployment policy. This argument should only be set on a production distribution | `string` | `null` | no | | [create\_cloudfront\_function](#input\_create\_cloudfront\_function) | Controls if CloudFront Functions should be created | `bool` | `false` | no | | [create\_distribution](#input\_create\_distribution) | Controls if CloudFront distribution should be created | `bool` | `true` | no | -| [create\_monitoring\_subscription](#input\_create\_monitoring\_subscription) | If enabled, the resource for monitoring subscription will created. | `bool` | `false` | no | +| [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 | +| [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 | -| [default\_root\_object](#input\_default\_root\_object) | The object that you want CloudFront to return (for example, index.html) when an end user requests the root URL. | `string` | `null` | no | -| [enabled](#input\_enabled) | Whether the distribution is enabled to accept end user requests for content. | `bool` | `true` | no | +| [default\_root\_object](#input\_default\_root\_object) | The object that you want CloudFront to return (for example, index.html) when an end user requests the root URL | `string` | `null` | no | +| [enabled](#input\_enabled) | Whether the distribution is enabled to accept end user requests for content | `bool` | `true` | no | | [geo\_restriction](#input\_geo\_restriction) | The restriction configuration for this distribution (geo\_restrictions) | `any` | `{}` | no | -| [http\_version](#input\_http\_version) | The maximum HTTP version to support on the distribution. Allowed values are http1.1, http2, http2and3, and http3. The default is http2. | `string` | `"http2"` | no | -| [is\_ipv6\_enabled](#input\_is\_ipv6\_enabled) | Whether the IPv6 is enabled for the distribution. | `bool` | `null` | no | -| [logging\_config](#input\_logging\_config) | The logging configuration that controls how logs are written to your distribution (maximum one). | `any` | `{}` | 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` | `null` | no | +| [http\_version](#input\_http\_version) | The maximum HTTP version to support on the distribution. Allowed values are http1.1, http2, http2and3, and http3. The default is http2 | `string` | `"http2"` | no | +| [is\_ipv6\_enabled](#input\_is\_ipv6\_enabled) | Whether the IPv6 is enabled for the distribution | `bool` | `null` | no | +| [logging\_config](#input\_logging\_config) | The logging configuration that controls how logs are written to your distribution (maximum one) | `any` | `{}` | 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` | `null` | no | | [origin\_access\_control](#input\_origin\_access\_control) | Map of CloudFront origin access control |
map(object({
name = optional(string)
description = string
origin_type = string
signing_behavior = string
signing_protocol = string
}))
|
{
"s3": {
"description": "",
"origin_type": "s3",
"signing_behavior": "always",
"signing_protocol": "sigv4"
}
}
| no | -| [origin\_access\_identities](#input\_origin\_access\_identities) | Map of CloudFront origin access identities (value as a comment) | `map(string)` | `{}` | no | -| [origin\_group](#input\_origin\_group) | One or more origin\_group for this distribution (multiples allowed). | `any` | `{}` | no | +| [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 | +| [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 | +| [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 | | [viewer\_certificate](#input\_viewer\_certificate) | The SSL configuration for this distribution | `any` |
{
"cloudfront_default_certificate": true,
"minimum_protocol_version": "TLSv1"
}
| no | | [vpc\_origin](#input\_vpc\_origin) | Map of CloudFront VPC origin |
map(object({
name = string
arn = string
http_port = number
https_port = number
origin_protocol_policy = string
origin_ssl_protocols = object({
items = list(string)
quantity = number
})
}))
| `{}` | no | | [vpc\_origin\_timeouts](#input\_vpc\_origin\_timeouts) | Create, update, and delete timeout configurations for vpc origin | `map(string)` | `{}` | no | -| [wait\_for\_deployment](#input\_wait\_for\_deployment) | If enabled, the resource will wait for the distribution status to change from InProgress to Deployed. Setting this to false will skip the process. | `bool` | `true` | no | -| [web\_acl\_id](#input\_web\_acl\_id) | If you're using AWS WAF to filter CloudFront requests, the Id of the AWS WAF web ACL that is associated with the distribution. The WAF Web ACL must exist in the WAF Global (CloudFront) region and the credentials configuring this argument must have waf:GetWebACL permissions assigned. If using WAFv2, provide the ARN of the web ACL. | `string` | `null` | no | +| [wait\_for\_deployment](#input\_wait\_for\_deployment) | If enabled, the resource will wait for the distribution status to change from InProgress to Deployed. Setting this to false will skip the process | `bool` | `true` | no | +| [web\_acl\_id](#input\_web\_acl\_id) | If you're using AWS WAF to filter CloudFront requests, the Id of the AWS WAF web ACL that is associated with the distribution. The WAF Web ACL must exist in the WAF Global (CloudFront) region and the credentials configuring this argument must have waf:GetWebACL permissions assigned. If using WAFv2, provide the ARN of the web ACL | `string` | `null` | no | ## Outputs @@ -267,17 +269,12 @@ No modules. | [cloudfront\_distribution\_in\_progress\_validation\_batches](#output\_cloudfront\_distribution\_in\_progress\_validation\_batches) | The number of invalidation batches currently in progress. | | [cloudfront\_distribution\_last\_modified\_time](#output\_cloudfront\_distribution\_last\_modified\_time) | The date and time the distribution was last modified. | | [cloudfront\_distribution\_status](#output\_cloudfront\_distribution\_status) | The current status of the distribution. Deployed if the distribution's information is fully propagated throughout the Amazon CloudFront system. | -| [cloudfront\_distribution\_tags](#output\_cloudfront\_distribution\_tags) | Tags of the distribution's | | [cloudfront\_distribution\_trusted\_signers](#output\_cloudfront\_distribution\_trusted\_signers) | List of nested attributes for active trusted signers, if the distribution is set up to serve private content with signed URLs | | [cloudfront\_functions](#output\_cloudfront\_functions) | The CloudFront Functions created | | [cloudfront\_monitoring\_subscription\_id](#output\_cloudfront\_monitoring\_subscription\_id) | The ID of the CloudFront monitoring subscription, which corresponds to the `distribution_id`. | | [cloudfront\_origin\_access\_controls](#output\_cloudfront\_origin\_access\_controls) | The origin access controls created | -| [cloudfront\_origin\_access\_controls\_ids](#output\_cloudfront\_origin\_access\_controls\_ids) | The IDS of the origin access identities created | -| [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 | +| [cloudfront\_vpc\_origins](#output\_cloudfront\_vpc\_origins) | The IDS of the VPC origin created | ## Authors diff --git a/docs/UPGRADE-6.0.md b/docs/UPGRADE-6.0.md index bec312f..fda8d91 100644 --- a/docs/UPGRADE-6.0.md +++ b/docs/UPGRADE-6.0.md @@ -6,6 +6,7 @@ If you find a bug, please open an issue with supporting configuration to reprodu ## List of backwards incompatible changes - AWS provider `v6.0` is now minimum supported version +- Support for `aws_cloudfront_origin_access_identity` has been removed in favor of `aws_cloudfront_origin_access_control` ## Additional changes @@ -21,7 +22,8 @@ If you find a bug, please open an issue with supporting configuration to reprodu 1. Removed variables: - - + - `create_origin_access_identity` + - `origin_access_identities` 2. Renamed variables: @@ -33,7 +35,12 @@ If you find a bug, please open an issue with supporting configuration to reprodu 4. Removed outputs: - - + - `cloudfront_vpc_origin_ids` + - `cloudfront_origin_access_controls_ids` + - `cloudfront_origin_access_identities` + - `cloudfront_origin_access_identity_ids` + - `cloudfront_origin_access_identity_iam_arns` + - `cloudfront_distribution_tags` 5. Renamed outputs: @@ -41,7 +48,7 @@ If you find a bug, please open an issue with supporting configuration to reprodu 6. Added outputs: - - + - `cloudfront_vpc_origins` ## Upgrade Migrations diff --git a/examples/complete/README.md b/examples/complete/README.md index 66323eb..49158bf 100644 --- a/examples/complete/README.md +++ b/examples/complete/README.md @@ -87,8 +87,9 @@ No inputs. | [cloudfront\_distribution\_last\_modified\_time](#output\_cloudfront\_distribution\_last\_modified\_time) | The date and time the distribution was last modified. | | [cloudfront\_distribution\_status](#output\_cloudfront\_distribution\_status) | The current status of the distribution. Deployed if the distribution's information is fully propagated throughout the Amazon CloudFront system. | | [cloudfront\_distribution\_trusted\_signers](#output\_cloudfront\_distribution\_trusted\_signers) | List of nested attributes for active trusted signers, if the distribution is set up to serve private content with signed URLs | -| [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\_vpc\_origin\_ids](#output\_cloudfront\_vpc\_origin\_ids) | The IDS of the VPC origin created | +| [cloudfront\_functions](#output\_cloudfront\_functions) | The CloudFront Functions created | +| [cloudfront\_monitoring\_subscription\_id](#output\_cloudfront\_monitoring\_subscription\_id) | The ID of the CloudFront monitoring subscription, which corresponds to the `distribution_id`. | +| [cloudfront\_origin\_access\_controls](#output\_cloudfront\_origin\_access\_controls) | The origin access controls created | +| [cloudfront\_response\_headers\_policies](#output\_cloudfront\_response\_headers\_policies) | The response headers policies created | +| [cloudfront\_vpc\_origins](#output\_cloudfront\_vpc\_origins) | The IDS of the VPC origin created | diff --git a/examples/complete/main.tf b/examples/complete/main.tf index 63ebb2f..2f1b6c8 100644 --- a/examples/complete/main.tf +++ b/examples/complete/main.tf @@ -38,11 +38,6 @@ module "cloudfront" { # This rate is charged only once per month, per metric (up to 8 metrics per distribution). create_monitoring_subscription = true - create_origin_access_identity = true - origin_access_identities = { - s3_bucket_one = "My awesome CloudFront can access" - } - create_origin_access_control = true origin_access_control = { s3_oac = { @@ -486,7 +481,7 @@ data "aws_iam_policy_document" "s3_policy" { principals { type = "AWS" - identifiers = module.cloudfront.cloudfront_origin_access_identity_iam_arns + identifiers = ["TODO XXX"] } } diff --git a/examples/complete/outputs.tf b/examples/complete/outputs.tf index fa4f64b..998a3bb 100644 --- a/examples/complete/outputs.tf +++ b/examples/complete/outputs.tf @@ -1,3 +1,7 @@ +################################################################################ +# Distribution +################################################################################ + output "cloudfront_distribution_id" { description = "The identifier for the distribution." value = module.cloudfront.cloudfront_distribution_id @@ -48,22 +52,47 @@ output "cloudfront_distribution_hosted_zone_id" { value = module.cloudfront.cloudfront_distribution_hosted_zone_id } -output "cloudfront_origin_access_identities" { - description = "The origin access identities created" - value = module.cloudfront.cloudfront_origin_access_identities +################################################################################ +# Origin Access Control +################################################################################ + +output "cloudfront_origin_access_controls" { + description = "The origin access controls created" + value = module.cloudfront.cloudfront_origin_access_controls +} + +################################################################################ +# VPC Origin +################################################################################ + +output "cloudfront_vpc_origins" { + description = "The IDS of the VPC origin created" + value = module.cloudfront.cloudfront_vpc_origins } -output "cloudfront_origin_access_identity_ids" { - description = "The IDS of the origin access identities created" - value = module.cloudfront.cloudfront_origin_access_identity_ids +################################################################################ +# Response Headers Policy +################################################################################ + +output "cloudfront_response_headers_policies" { + description = "The response headers policies created" + value = module.cloudfront.cloudfront_response_headers_policies } -output "cloudfront_origin_access_identity_iam_arns" { - description = "The IAM arns of the origin access identities created" - value = module.cloudfront.cloudfront_origin_access_identity_iam_arns +################################################################################ +# Function(s) +################################################################################ + +output "cloudfront_functions" { + description = "The CloudFront Functions created" + value = module.cloudfront.cloudfront_functions } -output "cloudfront_vpc_origin_ids" { - description = "The IDS of the VPC origin created" - value = module.cloudfront.cloudfront_vpc_origin_ids +################################################################################ +# Monitoring Subscription +################################################################################ + +output "cloudfront_monitoring_subscription_id" { + description = " The ID of the CloudFront monitoring subscription, which corresponds to the `distribution_id`." + value = module.cloudfront.cloudfront_monitoring_subscription_id } diff --git a/main.tf b/main.tf index bbdb15d..464dbb7 100644 --- a/main.tf +++ b/main.tf @@ -1,204 +1,6 @@ -locals { - create_origin_access_identity = var.create_origin_access_identity && length(keys(var.origin_access_identities)) > 0 - create_origin_access_control = var.create_origin_access_control && length(keys(var.origin_access_control)) > 0 - 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_function" "this" { - for_each = var.create_cloudfront_function && var.cloudfront_functions != null ? var.cloudfront_functions : {} - - code = each.value.code - comment = each.value.comment - key_value_store_associations = each.value.key_value_store_associations - name = try(coalesce(each.value.name, each.key)) - publish = each.value.publish - runtime = each.value.runtime -} - -resource "aws_cloudfront_origin_access_identity" "this" { - for_each = local.create_origin_access_identity ? var.origin_access_identities : {} - - comment = each.value - - lifecycle { - create_before_destroy = true - } -} - -resource "aws_cloudfront_origin_access_control" "this" { - for_each = local.create_origin_access_control ? var.origin_access_control : {} - - name = try(each.value.name, null) != null ? each.value.name : each.key - - description = each.value["description"] - origin_access_control_origin_type = each.value["origin_type"] - signing_behavior = each.value["signing_behavior"] - signing_protocol = each.value["signing_protocol"] -} - -resource "aws_cloudfront_vpc_origin" "this" { - for_each = local.create_vpc_origin ? var.vpc_origin : {} - - vpc_origin_endpoint_config { - name = each.value["name"] - arn = each.value["arn"] - http_port = each.value["http_port"] - https_port = each.value["https_port"] - origin_protocol_policy = each.value["origin_protocol_policy"] - - origin_ssl_protocols { - items = each.value.origin_ssl_protocols.items - quantity = each.value.origin_ssl_protocols.quantity - } - } - - timeouts { - create = try(var.vpc_origin_timeouts.create, null) - update = try(var.vpc_origin_timeouts.update, null) - delete = try(var.vpc_origin_timeouts.delete, null) - } - - tags = var.tags -} +################################################################################ +# Distribution +################################################################################ resource "aws_cloudfront_distribution" "this" { count = var.create_distribution ? 1 : 0 @@ -238,14 +40,6 @@ resource "aws_cloudfront_distribution" "this" { connection_timeout = lookup(origin.value, "connection_timeout", null) origin_access_control_id = lookup(origin.value, "origin_access_control_id", lookup(lookup(aws_cloudfront_origin_access_control.this, lookup(origin.value, "origin_access_control", ""), {}), "id", null)) - dynamic "s3_origin_config" { - for_each = length(keys(lookup(origin.value, "s3_origin_config", {}))) == 0 ? [] : [lookup(origin.value, "s3_origin_config", {})] - - content { - origin_access_identity = lookup(s3_origin_config.value, "cloudfront_access_identity_path", lookup(lookup(aws_cloudfront_origin_access_identity.this, lookup(s3_origin_config.value, "origin_access_identity", ""), {}), "cloudfront_access_identity_path", null)) - } - } - dynamic "custom_origin_config" { for_each = length(lookup(origin.value, "custom_origin_config", "")) == 0 ? [] : [lookup(origin.value, "custom_origin_config", "")] @@ -489,6 +283,212 @@ resource "aws_cloudfront_distribution" "this" { ] } +################################################################################ +# Origin Access Control +################################################################################ + +resource "aws_cloudfront_origin_access_control" "this" { + for_each = var.create_origin_access_control && length(keys(var.origin_access_control)) > 0 ? var.origin_access_control : {} + + name = try(each.value.name, null) != null ? each.value.name : each.key + + description = each.value["description"] + origin_access_control_origin_type = each.value["origin_type"] + signing_behavior = each.value["signing_behavior"] + signing_protocol = each.value["signing_protocol"] +} + +################################################################################ +# VPC Origin +################################################################################ + +resource "aws_cloudfront_vpc_origin" "this" { + for_each = var.create_vpc_origin && length(keys(var.vpc_origin)) > 0 ? var.vpc_origin : {} + + vpc_origin_endpoint_config { + name = each.value["name"] + arn = each.value["arn"] + http_port = each.value["http_port"] + https_port = each.value["https_port"] + origin_protocol_policy = each.value["origin_protocol_policy"] + + origin_ssl_protocols { + items = each.value.origin_ssl_protocols.items + quantity = each.value.origin_ssl_protocols.quantity + } + } + + timeouts { + create = try(var.vpc_origin_timeouts.create, null) + update = try(var.vpc_origin_timeouts.update, null) + delete = try(var.vpc_origin_timeouts.delete, null) + } + + tags = var.tags +} + +################################################################################ +# Response Headers Policy +################################################################################ + +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 + } + } +} + +################################################################################ +# Function(s) +################################################################################ + +resource "aws_cloudfront_function" "this" { + for_each = var.create_cloudfront_function && var.cloudfront_functions != null ? var.cloudfront_functions : {} + + code = each.value.code + comment = each.value.comment + key_value_store_associations = each.value.key_value_store_associations + name = try(coalesce(each.value.name, each.key)) + publish = each.value.publish + runtime = each.value.runtime +} + +################################################################################ +# Monitoring Subscription +################################################################################ + resource "aws_cloudfront_monitoring_subscription" "this" { count = var.create_distribution && var.create_monitoring_subscription ? 1 : 0 diff --git a/outputs.tf b/outputs.tf index 8d89d5b..0fedfd6 100644 --- a/outputs.tf +++ b/outputs.tf @@ -1,99 +1,98 @@ +################################################################################ +# Distribution +################################################################################ + output "cloudfront_distribution_id" { description = "The identifier for the distribution." - value = try(aws_cloudfront_distribution.this[0].id, "") + value = try(aws_cloudfront_distribution.this[0].id, null) } output "cloudfront_distribution_arn" { description = "The ARN (Amazon Resource Name) for the distribution." - value = try(aws_cloudfront_distribution.this[0].arn, "") + value = try(aws_cloudfront_distribution.this[0].arn, null) } output "cloudfront_distribution_caller_reference" { description = "Internal value used by CloudFront to allow future updates to the distribution configuration." - value = try(aws_cloudfront_distribution.this[0].caller_reference, "") + value = try(aws_cloudfront_distribution.this[0].caller_reference, null) } output "cloudfront_distribution_status" { description = "The current status of the distribution. Deployed if the distribution's information is fully propagated throughout the Amazon CloudFront system." - value = try(aws_cloudfront_distribution.this[0].status, "") + value = try(aws_cloudfront_distribution.this[0].status, null) } output "cloudfront_distribution_trusted_signers" { description = "List of nested attributes for active trusted signers, if the distribution is set up to serve private content with signed URLs" - value = try(aws_cloudfront_distribution.this[0].trusted_signers, "") + value = try(aws_cloudfront_distribution.this[0].trusted_signers, null) } output "cloudfront_distribution_domain_name" { description = "The domain name corresponding to the distribution." - value = try(aws_cloudfront_distribution.this[0].domain_name, "") + value = try(aws_cloudfront_distribution.this[0].domain_name, null) } output "cloudfront_distribution_last_modified_time" { description = "The date and time the distribution was last modified." - value = try(aws_cloudfront_distribution.this[0].last_modified_time, "") + value = try(aws_cloudfront_distribution.this[0].last_modified_time, null) } output "cloudfront_distribution_in_progress_validation_batches" { description = "The number of invalidation batches currently in progress." - value = try(aws_cloudfront_distribution.this[0].in_progress_validation_batches, "") + value = try(aws_cloudfront_distribution.this[0].in_progress_validation_batches, null) } output "cloudfront_distribution_etag" { description = "The current version of the distribution's information." - value = try(aws_cloudfront_distribution.this[0].etag, "") + value = try(aws_cloudfront_distribution.this[0].etag, null) } output "cloudfront_distribution_hosted_zone_id" { description = "The CloudFront Route 53 zone ID that can be used to route an Alias Resource Record Set to." - value = try(aws_cloudfront_distribution.this[0].hosted_zone_id, "") -} - -output "cloudfront_origin_access_identities" { - description = "The origin access identities created" - value = { for k, v in aws_cloudfront_origin_access_identity.this : k => v if local.create_origin_access_identity } -} - -output "cloudfront_origin_access_identity_ids" { - description = "The IDS of the origin access identities created" - value = [for v in aws_cloudfront_origin_access_identity.this : v.id if local.create_origin_access_identity] -} - -output "cloudfront_origin_access_identity_iam_arns" { - description = "The IAM arns of the origin access identities created" - value = [for v in aws_cloudfront_origin_access_identity.this : v.iam_arn if local.create_origin_access_identity] -} - -output "cloudfront_monitoring_subscription_id" { - description = " The ID of the CloudFront monitoring subscription, which corresponds to the `distribution_id`." - value = try(aws_cloudfront_monitoring_subscription.this[0].id, "") + value = try(aws_cloudfront_distribution.this[0].hosted_zone_id, null) } -output "cloudfront_distribution_tags" { - description = "Tags of the distribution's" - value = try(aws_cloudfront_distribution.this[0].tags_all, "") -} +################################################################################ +# Origin Access Control +################################################################################ output "cloudfront_origin_access_controls" { description = "The origin access controls created" - value = local.create_origin_access_control ? { for k, v in aws_cloudfront_origin_access_control.this : k => v } : {} + value = aws_cloudfront_origin_access_control.this } -output "cloudfront_origin_access_controls_ids" { - description = "The IDS of the origin access identities created" - value = local.create_origin_access_control ? [for v in aws_cloudfront_origin_access_control.this : v.id] : [] -} +################################################################################ +# VPC Origin +################################################################################ -output "cloudfront_vpc_origin_ids" { +output "cloudfront_vpc_origins" { description = "The IDS of the VPC origin created" - value = local.create_vpc_origin ? [for v in aws_cloudfront_vpc_origin.this : v.id] : [] + value = aws_cloudfront_vpc_origin.this } +################################################################################ +# Response Headers Policy +################################################################################ + output "cloudfront_response_headers_policies" { description = "The response headers policies created" value = aws_cloudfront_response_headers_policy.this } +################################################################################ +# Function(s) +################################################################################ + output "cloudfront_functions" { description = "The CloudFront Functions created" value = aws_cloudfront_function.this } + +################################################################################ +# Monitoring Subscription +################################################################################ + +output "cloudfront_monitoring_subscription_id" { + description = " The ID of the CloudFront monitoring subscription, which corresponds to the `distribution_id`." + value = try(aws_cloudfront_monitoring_subscription.this[0].id, null) +} diff --git a/variables.tf b/variables.tf index 4de8a49..ce5220b 100644 --- a/variables.tf +++ b/variables.tf @@ -1,85 +1,51 @@ +################################################################################ +# Distribution +################################################################################ + variable "create_distribution" { description = "Controls if CloudFront distribution should be created" type = bool default = true } -variable "create_origin_access_identity" { - description = "Controls if CloudFront origin access identity should be created" - type = bool - default = false -} - -variable "origin_access_identities" { - description = "Map of CloudFront origin access identities (value as a comment)" - type = map(string) - default = {} -} - -variable "create_origin_access_control" { - description = "Controls if CloudFront origin access control should be created" - type = bool - default = false -} - -variable "origin_access_control" { - description = "Map of CloudFront origin access control" - type = map(object({ - name = optional(string) - description = string - origin_type = string - signing_behavior = string - signing_protocol = string - })) - - default = { - s3 = { - description = "", - origin_type = "s3", - signing_behavior = "always", - signing_protocol = "sigv4" - } - } -} - variable "aliases" { - description = "Extra CNAMEs (alternate domain names), if any, for this distribution." + description = "Extra CNAMEs (alternate domain names), if any, for this distribution" type = list(string) default = null } variable "comment" { - description = "Any comments you want to include about the distribution." + description = "Any comments you want to include about the distribution" type = string default = null } variable "continuous_deployment_policy_id" { - description = "Identifier of a continuous deployment policy. This argument should only be set on a production distribution." + description = "Identifier of a continuous deployment policy. This argument should only be set on a production distribution" type = string default = null } variable "default_root_object" { - description = "The object that you want CloudFront to return (for example, index.html) when an end user requests the root URL." + description = "The object that you want CloudFront to return (for example, index.html) when an end user requests the root URL" type = string default = null } variable "enabled" { - description = "Whether the distribution is enabled to accept end user requests for content." + description = "Whether the distribution is enabled to accept end user requests for content" type = bool default = true } variable "http_version" { - description = "The maximum HTTP version to support on the distribution. Allowed values are http1.1, http2, http2and3, and http3. The default is http2." + description = "The maximum HTTP version to support on the distribution. Allowed values are http1.1, http2, http2and3, and http3. The default is http2" type = string default = "http2" } variable "is_ipv6_enabled" { - description = "Whether the IPv6 is enabled for the distribution." + description = "Whether the IPv6 is enabled for the distribution" type = bool default = null } @@ -91,43 +57,43 @@ variable "price_class" { } variable "retain_on_delete" { - description = "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." + description = "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" type = bool default = false } variable "wait_for_deployment" { - description = "If enabled, the resource will wait for the distribution status to change from InProgress to Deployed. Setting this to false will skip the process." + description = "If enabled, the resource will wait for the distribution status to change from InProgress to Deployed. Setting this to false will skip the process" type = bool default = true } variable "web_acl_id" { - description = "If you're using AWS WAF to filter CloudFront requests, the Id of the AWS WAF web ACL that is associated with the distribution. The WAF Web ACL must exist in the WAF Global (CloudFront) region and the credentials configuring this argument must have waf:GetWebACL permissions assigned. If using WAFv2, provide the ARN of the web ACL." + description = "If you're using AWS WAF to filter CloudFront requests, the Id of the AWS WAF web ACL that is associated with the distribution. The WAF Web ACL must exist in the WAF Global (CloudFront) region and the credentials configuring this argument must have waf:GetWebACL permissions assigned. If using WAFv2, provide the ARN of the web ACL" type = string default = null } variable "staging" { - description = "Whether the distribution is a staging distribution." + description = "Whether the distribution is a staging distribution" type = bool default = false } variable "tags" { - description = "A map of tags to assign to the resource." + description = "A map of tags to assign to the resource" type = map(string) default = null } variable "origin" { - description = "One or more origins for this distribution (multiples allowed)." + description = "One or more origins for this distribution (multiples allowed)" type = any default = null } variable "origin_group" { - description = "One or more origin_group for this distribution (multiples allowed)." + description = "One or more origin_group for this distribution (multiples allowed)" type = any default = {} } @@ -148,7 +114,7 @@ variable "geo_restriction" { } variable "logging_config" { - description = "The logging configuration that controls how logs are written to your distribution (maximum one)." + description = "The logging configuration that controls how logs are written to your distribution (maximum one)" type = any default = {} } @@ -166,25 +132,47 @@ variable "default_cache_behavior" { } 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." + 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 "create_monitoring_subscription" { - description = "If enabled, the resource for monitoring subscription will created." +################################################################################ +# Origin Access Control +################################################################################ + +variable "create_origin_access_control" { + description = "Controls if CloudFront origin access control should be created" type = bool default = false } -variable "realtime_metrics_subscription_status" { - description = "A flag that indicates whether additional CloudWatch metrics are enabled for a given CloudFront distribution. Valid values are `Enabled` and `Disabled`." - type = string - default = "Enabled" +variable "origin_access_control" { + description = "Map of CloudFront origin access control" + type = map(object({ + name = optional(string) + description = string + origin_type = string + signing_behavior = string + signing_protocol = string + })) + + default = { + s3 = { + description = "", + origin_type = "s3", + signing_behavior = "always", + signing_protocol = "sigv4" + } + } } +################################################################################ +# VPC Origin +################################################################################ + variable "create_vpc_origin" { - description = "If enabled, the resource for VPC origin will be created." + description = "If enabled, the resource for VPC origin will be created" type = bool default = false } @@ -211,6 +199,10 @@ variable "vpc_origin_timeouts" { default = {} } +################################################################################ +# Response Headers Policy +################################################################################ + variable "create_response_headers_policy" { description = "Controls if CloudFront response headers policies should be created" type = bool @@ -288,6 +280,10 @@ variable "response_headers_policies" { default = null } +################################################################################ +# Function(s) +################################################################################ + variable "create_cloudfront_function" { description = "Controls if CloudFront Functions should be created" type = bool @@ -295,7 +291,7 @@ variable "create_cloudfront_function" { } variable "cloudfront_functions" { - description = "Map of CloudFront Function configurations. Key is used as default function name if 'name' not specified." + description = "Map of CloudFront Function configurations. Key is used as default function name if 'name' not specified" type = map(object({ name = optional(string) runtime = optional(string, "cloudfront-js-2.0") @@ -306,3 +302,19 @@ variable "cloudfront_functions" { })) default = null } + +################################################################################ +# Monitoring Subscription +################################################################################ + +variable "create_monitoring_subscription" { + description = "If enabled, the resource for monitoring subscription will created" + type = bool + default = false +} + +variable "realtime_metrics_subscription_status" { + description = "A flag that indicates whether additional CloudWatch metrics are enabled for a given CloudFront distribution. Valid values are `Enabled` and `Disabled`" + type = string + default = "Enabled" +} diff --git a/wrappers/main.tf b/wrappers/main.tf index 6f3449f..01c2bf1 100644 --- a/wrappers/main.tf +++ b/wrappers/main.tf @@ -11,7 +11,6 @@ module "wrapper" { create_distribution = try(each.value.create_distribution, var.defaults.create_distribution, true) 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, {}) @@ -32,7 +31,6 @@ module "wrapper" { signing_protocol = "sigv4" } }) - origin_access_identities = try(each.value.origin_access_identities, var.defaults.origin_access_identities, {}) 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") From aaaa014dac527e70683fcc3953ce3446e176b6a9 Mon Sep 17 00:00:00 2001 From: Bryant Biggs Date: Thu, 27 Nov 2025 13:18:43 -0600 Subject: [PATCH 3/4] feat: Add variable optional attribute type definitions --- README.md | 42 ++-- docs/UPGRADE-6.0.md | 10 +- examples/complete/README.md | 4 +- examples/complete/main.tf | 56 ++--- examples/complete/versions.tf | 2 +- main.tf | 428 ++++++++++++++++++---------------- variables.tf | 330 ++++++++++++++++++-------- versions.tf | 2 +- wrappers/main.tf | 42 ++-- wrappers/versions.tf | 2 +- 10 files changed, 533 insertions(+), 385 deletions(-) diff --git a/README.md b/README.md index a8bbda0..a43bc76 100644 --- a/README.md +++ b/README.md @@ -192,13 +192,13 @@ ordered_cache_behavior = [{ | Name | Version | |------|---------| | [terraform](#requirement\_terraform) | >= 1.5.7 | -| [aws](#requirement\_aws) | >= 5.100 | +| [aws](#requirement\_aws) | >= 6.20 | ## Providers | Name | Version | |------|---------| -| [aws](#provider\_aws) | >= 5.100 | +| [aws](#provider\_aws) | >= 6.20 | ## Modules @@ -223,37 +223,33 @@ No modules. | Name | Description | Type | Default | Required | |------|-------------|------|---------|:--------:| | [aliases](#input\_aliases) | Extra CNAMEs (alternate domain names), if any, for this distribution | `list(string)` | `null` | no | +| [anycast\_ip\_list\_id](#input\_anycast\_ip\_list\_id) | ID of the Anycast static IP list that is associated with the distribution | `string` | `null` | no | | [cloudfront\_functions](#input\_cloudfront\_functions) | Map of CloudFront Function configurations. Key is used as default function name if 'name' not specified |
map(object({
name = optional(string)
runtime = optional(string, "cloudfront-js-2.0")
comment = optional(string)
publish = optional(bool)
code = string
key_value_store_associations = optional(list(string))
}))
| `null` | no | | [comment](#input\_comment) | Any comments you want to include about the distribution | `string` | `null` | no | | [continuous\_deployment\_policy\_id](#input\_continuous\_deployment\_policy\_id) | Identifier of a continuous deployment policy. This argument should only be set on a production distribution | `string` | `null` | no | -| [create\_cloudfront\_function](#input\_create\_cloudfront\_function) | Controls if CloudFront Functions should be created | `bool` | `false` | no | -| [create\_distribution](#input\_create\_distribution) | Controls if CloudFront distribution should be created | `bool` | `true` | no | +| [create](#input\_create) | Controls if resources should be created (affects nearly all resources) | `bool` | `true` | no | | [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\_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 | +| [custom\_error\_response](#input\_custom\_error\_response) | One or more custom error response elements |
list(object({
error_caching_min_ttl = optional(number)
error_code = number
response_code = optional(number)
response_page_path = optional(string)
}))
| `null` | no | +| [default\_cache\_behavior](#input\_default\_cache\_behavior) | The default cache behavior for this distribution |
object({
allowed_methods = optional(list(string), ["GET", "HEAD", "OPTIONS"])
cache_policy_id = optional(string)
catch_policy_name = optional(string)
cached_methods = optional(list(string), ["GET", "HEAD"])
compress = optional(bool, true)
default_ttl = optional(number)
field_level_encryption_id = optional(string)
forwarded_values = optional(object({
cookies = object({
forward = optional(string, "none")
whitelisted_names = optional(list(string))
})
headers = optional(list(string))
query_string = optional(bool, false)
query_string_cache_keys = optional(list(string))
}))
function_association = optional(map(object({
event_type = optional(string)
function_arn = optional(string)
function_key = optional(string)
})))
grpc_config = optional(object({
enabled = optional(bool)
}))
lambda_function_association = optional(map(object({
event_type = optional(string)
include_body = optional(bool)
lambda_arn = string
})))
max_ttl = optional(number)
min_ttl = optional(number)
origin_request_policy_id = optional(string)
origin_request_policy_name = optional(string)
realtime_log_config_arn = optional(string)
response_headers_policy_id = optional(string)
response_headers_policy_name = optional(string)
smooth_streaming = optional(bool)
target_origin_id = string
trusted_key_groups = optional(list(string))
trusted_signers = optional(list(string))
viewer_protocol_policy = optional(string, "https-only")
})
| n/a | yes | | [default\_root\_object](#input\_default\_root\_object) | The object that you want CloudFront to return (for example, index.html) when an end user requests the root URL | `string` | `null` | no | | [enabled](#input\_enabled) | Whether the distribution is enabled to accept end user requests for content | `bool` | `true` | no | -| [geo\_restriction](#input\_geo\_restriction) | The restriction configuration for this distribution (geo\_restrictions) | `any` | `{}` | no | | [http\_version](#input\_http\_version) | The maximum HTTP version to support on the distribution. Allowed values are http1.1, http2, http2and3, and http3. The default is http2 | `string` | `"http2"` | no | -| [is\_ipv6\_enabled](#input\_is\_ipv6\_enabled) | Whether the IPv6 is enabled for the distribution | `bool` | `null` | no | -| [logging\_config](#input\_logging\_config) | The logging configuration that controls how logs are written to your distribution (maximum one) | `any` | `{}` | 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` | `null` | no | -| [origin\_access\_control](#input\_origin\_access\_control) | Map of CloudFront origin access control |
map(object({
name = optional(string)
description = string
origin_type = string
signing_behavior = string
signing_protocol = string
}))
|
{
"s3": {
"description": "",
"origin_type": "s3",
"signing_behavior": "always",
"signing_protocol": "sigv4"
}
}
| no | -| [origin\_group](#input\_origin\_group) | One or more origin\_group for this distribution (multiples allowed) | `any` | `{}` | no | +| [is\_ipv6\_enabled](#input\_is\_ipv6\_enabled) | Whether the IPv6 is enabled for the distribution | `bool` | `true` | no | +| [logging\_config](#input\_logging\_config) | The logging configuration that controls how logs are written to your distribution (maximum one) |
object({
bucket = optional(string)
include_cookies = optional(bool)
prefix = optional(string)
})
| `null` | 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 |
list(object({
allowed_methods = optional(list(string), ["GET", "HEAD", "OPTIONS"])
cached_methods = optional(list(string), ["GET", "HEAD"])
cache_policy_id = optional(string)
catch_policy_name = optional(string)
compress = optional(bool, true)
default_ttl = optional(number)
field_level_encryption_id = optional(string)
forwarded_values = optional(object({
cookies = object({
forward = optional(string, "none")
whitelisted_names = optional(list(string))
})
headers = optional(list(string))
query_string = optional(bool, false)
query_string_cache_keys = optional(list(string))
}))
function_association = optional(map(object({
event_type = optional(string)
function_arn = optional(string)
function_key = optional(string)
})))
grpc_config = optional(object({
enabled = optional(bool)
}))
lambda_function_association = optional(map(object({
event_type = optional(string)
include_body = optional(bool)
lambda_arn = string
})))
max_ttl = optional(number)
min_ttl = optional(number)
origin_request_policy_id = optional(string)
origin_request_policy_name = optional(string)
path_pattern = string
realtime_log_config_arn = optional(string)
response_headers_policy_id = optional(string)
response_headers_policy_name = optional(string)
smooth_streaming = optional(bool)
target_origin_id = string
trusted_key_groups = optional(list(string))
trusted_signers = optional(list(string))
viewer_protocol_policy = string
}))
| `null` | no | +| [origin](#input\_origin) | One or more origins for this distribution (multiples allowed) |
map(object({
connection_attempts = optional(number)
connection_timeout = optional(number)
custom_header = optional(map(string))
custom_origin_config = optional(object({
http_port = number
https_port = number
ip_address_type = optional(string)
origin_keepalive_timeout = optional(number)
origin_read_timeout = optional(number)
origin_protocol_policy = string
origin_ssl_protocols = list(string)
}))
domain_name = string
origin_access_control_key = optional(string)
origin_access_control_id = optional(string)
origin_id = optional(string)
origin_path = optional(string)
origin_shield = optional(object({
enabled = bool
origin_shield_region = optional(string)
}))
response_completion_timeout = optional(number)
vpc_origin_config = optional(object({
origin_keepalive_timeout = optional(number)
origin_read_timeout = optional(number)
vpc_origin_id = optional(string)
vpc_origin_key = optional(string)
}))
}))
| `{}` | no | +| [origin\_access\_control](#input\_origin\_access\_control) | Map of CloudFront origin access control |
map(object({
description = optional(string)
name = optional(string)
origin_type = string
signing_behavior = string
signing_protocol = string
}))
|
{
"s3": {
"origin_type": "s3",
"signing_behavior": "always",
"signing_protocol": "sigv4"
}
}
| no | +| [origin\_group](#input\_origin\_group) | One or more origin\_group for this distribution (multiples allowed) |
map(object({
failover_criteria = object({
status_codes = list(number)
})
member = list(object({
origin_id = string
}))
origin_id = optional(string)
}))
| `null` | 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 | -| [viewer\_certificate](#input\_viewer\_certificate) | The SSL configuration for this distribution | `any` |
{
"cloudfront_default_certificate": true,
"minimum_protocol_version": "TLSv1"
}
| no | -| [vpc\_origin](#input\_vpc\_origin) | Map of CloudFront VPC origin |
map(object({
name = string
arn = string
http_port = number
https_port = number
origin_protocol_policy = string
origin_ssl_protocols = object({
items = list(string)
quantity = number
})
}))
| `{}` | no | -| [vpc\_origin\_timeouts](#input\_vpc\_origin\_timeouts) | Create, update, and delete timeout configurations for vpc origin | `map(string)` | `{}` | no | -| [wait\_for\_deployment](#input\_wait\_for\_deployment) | If enabled, the resource will wait for the distribution status to change from InProgress to Deployed. Setting this to false will skip the process | `bool` | `true` | no | +| [restrictions](#input\_restrictions) | The restrictions configuration for this distribution |
object({
geo_restriction = object({
locations = optional(list(string))
restriction_type = optional(string, "none")
})
})
|
{
"geo_restriction": {
"restriction_type": "none"
}
}
| 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` | `null` | no | +| [staging](#input\_staging) | Whether the distribution is a staging distribution | `bool` | `null` | no | +| [tags](#input\_tags) | A map of tags to add to all resources | `map(string)` | `{}` | no | +| [viewer\_certificate](#input\_viewer\_certificate) | The SSL configuration for this distribution |
object({
acm_certificate_arn = optional(string)
cloudfront_default_certificate = optional(bool, true)
iam_certificate_id = optional(string)
minimum_protocol_version = optional(string, "TLSv1.2_2025")
ssl_support_method = optional(string)
})
| `{}` | no | +| [vpc\_origin](#input\_vpc\_origin) | Map of CloudFront VPC origins |
map(object({
arn = string
http_port = number
https_port = number
name = optional(string)
origin_protocol_policy = string
origin_ssl_protocols = object({
items = list(string)
quantity = number
})
timeouts = optional(object({
create = optional(string)
update = optional(string)
delete = optional(string)
}))
tags = optional(map(string), {})
}))
| `null` | no | +| [wait\_for\_deployment](#input\_wait\_for\_deployment) | If enabled, the resource will wait for the distribution status to change from InProgress to Deployed. Setting this to false will skip the process | `bool` | `null` | no | | [web\_acl\_id](#input\_web\_acl\_id) | If you're using AWS WAF to filter CloudFront requests, the Id of the AWS WAF web ACL that is associated with the distribution. The WAF Web ACL must exist in the WAF Global (CloudFront) region and the credentials configuring this argument must have waf:GetWebACL permissions assigned. If using WAFv2, provide the ARN of the web ACL | `string` | `null` | no | ## Outputs diff --git a/docs/UPGRADE-6.0.md b/docs/UPGRADE-6.0.md index fda8d91..97e23da 100644 --- a/docs/UPGRADE-6.0.md +++ b/docs/UPGRADE-6.0.md @@ -17,6 +17,7 @@ If you find a bug, please open an issue with supporting configuration to reprodu ### Modified - Variable definitions now contain detailed `object` types in place of the previously used any type. +- `is_ipv6_enabled` now defaults to `true` if not specified. ### Variable and output changes @@ -24,14 +25,19 @@ If you find a bug, please open an issue with supporting configuration to reprodu - `create_origin_access_identity` - `origin_access_identities` + - `create_origin_access_control` + - `create_vpc_origin` + - `vpc_origin_timeouts` - use `timeouts` block within `vpc_origin` variable instead + - `create_response_headers_policy` + - `create_cloudfront_function` 2. Renamed variables: - - + - `create_distribution` -> `create` 3. Added variables: - - + - `anycast_ip_list_id` 4. Removed outputs: diff --git a/examples/complete/README.md b/examples/complete/README.md index 49158bf..b120458 100644 --- a/examples/complete/README.md +++ b/examples/complete/README.md @@ -32,7 +32,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.100 | +| [aws](#requirement\_aws) | >= 6.20 | | [null](#requirement\_null) | >= 2.0 | | [random](#requirement\_random) | >= 2.0 | @@ -40,7 +40,7 @@ Note that this example may create resources which cost money. Run `terraform des | Name | Version | |------|---------| -| [aws](#provider\_aws) | >= 5.100 | +| [aws](#provider\_aws) | >= 6.20 | | [null](#provider\_null) | >= 2.0 | | [random](#provider\_random) | >= 2.0 | diff --git a/examples/complete/main.tf b/examples/complete/main.tf index 2f1b6c8..4a4ad0c 100644 --- a/examples/complete/main.tf +++ b/examples/complete/main.tf @@ -38,7 +38,6 @@ module "cloudfront" { # This rate is charged only once per month, per metric (up to 8 metrics per distribution). create_monitoring_subscription = true - create_origin_access_control = true origin_access_control = { s3_oac = { description = "CloudFront access to S3" @@ -48,7 +47,6 @@ module "cloudfront" { } } - create_vpc_origin = true vpc_origin = { ec2_vpc_origin = { name = random_pet.this.id @@ -60,18 +58,13 @@ module "cloudfront" { items = ["TLSv1.2"] quantity = 1 } - } - } - - vpc_origin_timeouts = { - create = "20m" - update = "20m" - delete = "20m" - } - logging_config = { - bucket = module.log_bucket.s3_bucket_bucket_domain_name - prefix = "cloudfront" + timeouts = { + create = "20m" + update = "20m" + delete = "20m" + } + } } origin = { @@ -84,16 +77,10 @@ module "cloudfront" { origin_ssl_protocols = ["TLSv1", "TLSv1.1", "TLSv1.2"] } - custom_header = [ - { - name = "X-Forwarded-Scheme" - value = "https" - }, - { - name = "X-Frame-Options" - value = "SAMEORIGIN" - } - ] + custom_header = { + "X-Forwarded-Scheme" = "https" + "X-Frame-Options" = "SAMEORIGIN" + } origin_shield = { enabled = true @@ -125,10 +112,14 @@ module "cloudfront" { } origin_group = { - group_one = { - failover_status_codes = [403, 404, 500, 502] - primary_member_origin_id = "appsync" - secondary_member_origin_id = "s3_one" + group-one = { + failover_criteria = { + status_codes = [403, 404, 500, 502] + } + member = [ + { origin_id = "appsync" }, + { origin_id = "s3_one" } + ] } } @@ -218,7 +209,6 @@ module "cloudfront" { allowed_methods = ["GET", "HEAD", "OPTIONS"] cached_methods = ["GET", "HEAD"] } - ] viewer_certificate = { @@ -236,13 +226,14 @@ module "cloudfront" { response_page_path = "/errors/403.html" }] - geo_restriction = { - restriction_type = "whitelist" - locations = ["NO", "UA", "US", "GB"] + restrictions = { + geo_restriction = { + restriction_type = "whitelist" + locations = ["NO", "UA", "US", "GB"] + } } # CloudFront Functions - module managed - create_cloudfront_function = true cloudfront_functions = { viewer-request-security = { runtime = "cloudfront-js-2.0" @@ -274,7 +265,6 @@ module "cloudfront" { # } } - create_response_headers_policy = true response_headers_policies = { cors_policy = { name = "CORSPolicy" diff --git a/examples/complete/versions.tf b/examples/complete/versions.tf index d00553b..e8acfa2 100644 --- a/examples/complete/versions.tf +++ b/examples/complete/versions.tf @@ -4,7 +4,7 @@ terraform { required_providers { aws = { source = "hashicorp/aws" - version = ">= 5.100" + version = ">= 6.20" } random = { source = "hashicorp/random" diff --git a/main.tf b/main.tf index 464dbb7..553da58 100644 --- a/main.tf +++ b/main.tf @@ -3,281 +3,297 @@ ################################################################################ resource "aws_cloudfront_distribution" "this" { - count = var.create_distribution ? 1 : 0 + count = var.create ? 1 : 0 aliases = var.aliases + anycast_ip_list_id = var.anycast_ip_list_id comment = var.comment continuous_deployment_policy_id = var.continuous_deployment_policy_id - default_root_object = var.default_root_object - enabled = var.enabled - http_version = var.http_version - is_ipv6_enabled = var.is_ipv6_enabled - price_class = var.price_class - retain_on_delete = var.retain_on_delete - staging = var.staging - wait_for_deployment = var.wait_for_deployment - web_acl_id = var.web_acl_id - tags = var.tags - dynamic "logging_config" { - for_each = length(keys(var.logging_config)) == 0 ? [] : [var.logging_config] + dynamic "custom_error_response" { + for_each = var.custom_error_response != null ? var.custom_error_response : [] content { - bucket = logging_config.value["bucket"] - prefix = lookup(logging_config.value, "prefix", null) - include_cookies = lookup(logging_config.value, "include_cookies", null) + error_caching_min_ttl = custom_error_response.value.error_caching_min_ttl + error_code = custom_error_response.value.error_code + response_code = custom_error_response.value.response_code + response_page_path = custom_error_response.value.response_page_path } } - dynamic "origin" { - for_each = var.origin + dynamic "default_cache_behavior" { + for_each = [var.default_cache_behavior] content { - domain_name = origin.value.domain_name - origin_id = lookup(origin.value, "origin_id", origin.key) - origin_path = lookup(origin.value, "origin_path", "") - connection_attempts = lookup(origin.value, "connection_attempts", null) - connection_timeout = lookup(origin.value, "connection_timeout", null) - origin_access_control_id = lookup(origin.value, "origin_access_control_id", lookup(lookup(aws_cloudfront_origin_access_control.this, lookup(origin.value, "origin_access_control", ""), {}), "id", null)) + allowed_methods = default_cache_behavior.value.allowed_methods + cache_policy_id = try(coalesce(default_cache_behavior.value.cache_policy_id, try(data.aws_cloudfront_cache_policy.this[default_cache_behavior.value.cache_policy_name].id, null))) + cached_methods = default_cache_behavior.value.cached_methods + compress = default_cache_behavior.value.compress + default_ttl = default_cache_behavior.value.default_ttl + field_level_encryption_id = default_cache_behavior.value.field_level_encryption_id - dynamic "custom_origin_config" { - for_each = length(lookup(origin.value, "custom_origin_config", "")) == 0 ? [] : [lookup(origin.value, "custom_origin_config", "")] + dynamic "forwarded_values" { + for_each = default_cache_behavior.value.forwarded_values != null ? [default_cache_behavior.value.forwarded_values] : [] content { - http_port = custom_origin_config.value.http_port - https_port = custom_origin_config.value.https_port - origin_protocol_policy = custom_origin_config.value.origin_protocol_policy - origin_ssl_protocols = custom_origin_config.value.origin_ssl_protocols - origin_keepalive_timeout = lookup(custom_origin_config.value, "origin_keepalive_timeout", null) - origin_read_timeout = lookup(custom_origin_config.value, "origin_read_timeout", null) + dynamic "cookies" { + for_each = [forwarded_values.value.cookies] + + content { + forward = cookies.value.cookies_forward + whitelisted_names = cookies.value.cookies_whitelisted_names + } + } + + headers = forwarded_values.value.headers + query_string = forwarded_values.value.query_string + query_string_cache_keys = forwarded_values.value.query_string_cache_keys } } - dynamic "custom_header" { - for_each = lookup(origin.value, "custom_header", []) + dynamic "function_association" { + for_each = default_cache_behavior.value.function_association != null ? default_cache_behavior.value.function_association : {} content { - name = custom_header.value.name - value = custom_header.value.value + event_type = try(coalesce(function_association.value.event_type, function_association.key)) + function_arn = try(coalesce(function_association.value.function_arn, try(aws_cloudfront_function.this[function_association.value.function_key].arn, null)), null) } } - dynamic "origin_shield" { - for_each = length(keys(lookup(origin.value, "origin_shield", {}))) == 0 ? [] : [lookup(origin.value, "origin_shield", {})] + dynamic "grpc_config" { + for_each = default_cache_behavior.value.grpc_config != null ? [default_cache_behavior.value.grpc_config] : [] content { - enabled = origin_shield.value.enabled - origin_shield_region = origin_shield.value.origin_shield_region + enabled = grpc_config.value.enabled } } - dynamic "vpc_origin_config" { - for_each = length(keys(lookup(origin.value, "vpc_origin_config", {}))) == 0 ? [] : [lookup(origin.value, "vpc_origin_config", {})] + dynamic "lambda_function_association" { + for_each = default_cache_behavior.value.lambda_function_association != null ? default_cache_behavior.value.lambda_function_association : {} content { - vpc_origin_id = lookup(vpc_origin_config.value, "vpc_origin_id", lookup(lookup(aws_cloudfront_vpc_origin.this, lookup(vpc_origin_config.value, "vpc_origin", ""), {}), "id", null)) - origin_keepalive_timeout = lookup(vpc_origin_config.value, "origin_keepalive_timeout", null) - origin_read_timeout = lookup(vpc_origin_config.value, "origin_read_timeout", null) + event_type = try(coalesce(lambda_function_association.value.event_type, lambda_function_association.key)) + include_body = lambda_function_association.value.include_body + lambda_arn = lambda_function_association.value.lambda_arn } } + + max_ttl = default_cache_behavior.value.max_ttl + min_ttl = default_cache_behavior.value.min_ttl + origin_request_policy_id = try(coalesce(default_cache_behavior.value.origin_request_policy_id, try(data.aws_cloudfront_origin_request_policy.this[default_cache_behavior.value.origin_request_policy_name].id, null))) + realtime_log_config_arn = default_cache_behavior.value.realtime_log_config_arn + response_headers_policy_id = try(coalesce(default_cache_behavior.value.response_headers_policy_id, try(data.aws_cloudfront_response_headers_policy.this[default_cache_behavior.value.response_headers_policy_name].id, null))) + smooth_streaming = default_cache_behavior.value.smooth_streaming + target_origin_id = default_cache_behavior.value.target_origin_id + trusted_key_groups = default_cache_behavior.value.trusted_key_groups + trusted_signers = default_cache_behavior.value.trusted_signers + viewer_protocol_policy = default_cache_behavior.value.viewer_protocol_policy } } - dynamic "origin_group" { - for_each = var.origin_group - - content { - origin_id = lookup(origin_group.value, "origin_id", origin_group.key) - - failover_criteria { - status_codes = origin_group.value["failover_status_codes"] - } + default_root_object = var.default_root_object + enabled = var.enabled + http_version = var.http_version + is_ipv6_enabled = var.is_ipv6_enabled - member { - origin_id = origin_group.value["primary_member_origin_id"] - } + dynamic "logging_config" { + for_each = var.logging_config != null ? [var.logging_config] : [] - member { - origin_id = origin_group.value["secondary_member_origin_id"] - } + content { + bucket = logging_config.value.bucket + include_cookies = logging_config.value.include_cookies + prefix = logging_config.value.prefix } } - dynamic "default_cache_behavior" { - for_each = [var.default_cache_behavior] - iterator = i + dynamic "ordered_cache_behavior" { + for_each = var.ordered_cache_behavior != null ? var.ordered_cache_behavior : [] content { - target_origin_id = i.value["target_origin_id"] - viewer_protocol_policy = i.value["viewer_protocol_policy"] - - allowed_methods = lookup(i.value, "allowed_methods", ["GET", "HEAD", "OPTIONS"]) - cached_methods = lookup(i.value, "cached_methods", ["GET", "HEAD"]) - compress = lookup(i.value, "compress", null) - field_level_encryption_id = lookup(i.value, "field_level_encryption_id", null) - smooth_streaming = lookup(i.value, "smooth_streaming", null) - trusted_signers = lookup(i.value, "trusted_signers", null) - trusted_key_groups = lookup(i.value, "trusted_key_groups", null) - - cache_policy_id = try(i.value.cache_policy_id, data.aws_cloudfront_cache_policy.this[i.value.cache_policy_name].id, null) - origin_request_policy_id = try(i.value.origin_request_policy_id, data.aws_cloudfront_origin_request_policy.this[i.value.origin_request_policy_name].id, null) - response_headers_policy_id = try(i.value.response_headers_policy_id, data.aws_cloudfront_response_headers_policy.this[i.value.response_headers_policy_name].id, null) - - realtime_log_config_arn = lookup(i.value, "realtime_log_config_arn", null) - - min_ttl = lookup(i.value, "min_ttl", null) - default_ttl = lookup(i.value, "default_ttl", null) - max_ttl = lookup(i.value, "max_ttl", null) + allowed_methods = ordered_cache_behavior.value.allowed_methods + cached_methods = ordered_cache_behavior.value.cached_methods + cache_policy_id = try(coalesce(ordered_cache_behavior.value.cache_policy_id, try(data.aws_cloudfront_cache_policy.this[ordered_cache_behavior.value.cache_policy_name].id, null))) + compress = ordered_cache_behavior.value.compress + default_ttl = ordered_cache_behavior.value.default_ttl + field_level_encryption_id = ordered_cache_behavior.value.field_level_encryption_id dynamic "forwarded_values" { - for_each = lookup(i.value, "use_forwarded_values", true) ? [true] : [] + for_each = ordered_cache_behavior.value.forwarded_values != null ? [ordered_cache_behavior.value.forwarded_values] : [] content { - query_string = lookup(i.value, "query_string", false) - query_string_cache_keys = lookup(i.value, "query_string_cache_keys", []) - headers = lookup(i.value, "headers", []) + dynamic "cookies" { + for_each = [forwarded_values.value.cookies] - cookies { - forward = lookup(i.value, "cookies_forward", "none") - whitelisted_names = lookup(i.value, "cookies_whitelisted_names", null) + content { + forward = cookies.value.cookies_forward + whitelisted_names = cookies.value.cookies_whitelisted_names + } } + + headers = forwarded_values.value.headers + query_string = forwarded_values.value.query_string + query_string_cache_keys = forwarded_values.value.query_string_cache_keys } } - dynamic "lambda_function_association" { - for_each = lookup(i.value, "lambda_function_association", []) - iterator = l + dynamic "function_association" { + for_each = ordered_cache_behavior.value.function_association != null ? ordered_cache_behavior.value.function_association : {} content { - event_type = l.key - lambda_arn = l.value.lambda_arn - include_body = lookup(l.value, "include_body", null) + event_type = try(coalesce(function_association.value.event_type, function_association.key)) + function_arn = try(coalesce(function_association.value.function_arn, try(aws_cloudfront_function.this[function_association.value.function_key].arn, null)), null) } } - dynamic "function_association" { - for_each = lookup(i.value, "function_association", []) - iterator = f + dynamic "grpc_config" { + for_each = ordered_cache_behavior.value.grpc_config != null ? [ordered_cache_behavior.value.grpc_config] : [] content { - event_type = f.key - function_arn = lookup(f.value, "function_arn", try(aws_cloudfront_function.this[f.value.function_key].arn, null)) + enabled = grpc_config.value.enabled } } - dynamic "grpc_config" { - for_each = try([i.value.grpc_config], []) + dynamic "lambda_function_association" { + for_each = ordered_cache_behavior.value.lambda_function_association != null ? ordered_cache_behavior.value.lambda_function_association : [] + content { - enabled = grpc_config.value.enabled + event_type = try(coalesce(lambda_function_association.value.event_type, lambda_function_association.key)) + include_body = lambda_function_association.value.include_body + lambda_arn = lambda_function_association.value.lambda_arn } } + + max_ttl = ordered_cache_behavior.value.max_ttl + min_ttl = ordered_cache_behavior.value.min_ttl + origin_request_policy_id = try(coalesce(ordered_cache_behavior.value.origin_request_policy_id, try(data.aws_cloudfront_origin_request_policy.this[ordered_cache_behavior.value.origin_request_policy_name].id, null))) + path_pattern = ordered_cache_behavior.value.path_pattern + realtime_log_config_arn = ordered_cache_behavior.value.realtime_log_config_arn + response_headers_policy_id = try(coalesce(ordered_cache_behavior.value.response_headers_policy_id, try(data.aws_cloudfront_response_headers_policy.this[ordered_cache_behavior.value.response_headers_policy_name].id, null))) + smooth_streaming = ordered_cache_behavior.value.smooth_streaming + target_origin_id = ordered_cache_behavior.value.target_origin_id + trusted_key_groups = ordered_cache_behavior.value.trusted_key_groups + trusted_signers = ordered_cache_behavior.value.trusted_signers + viewer_protocol_policy = ordered_cache_behavior.value.viewer_protocol_policy } } - dynamic "ordered_cache_behavior" { - for_each = var.ordered_cache_behavior - iterator = i + dynamic "origin_group" { + for_each = var.origin_group != null ? var.origin_group : {} content { - path_pattern = i.value["path_pattern"] - target_origin_id = i.value["target_origin_id"] - viewer_protocol_policy = i.value["viewer_protocol_policy"] + dynamic "failover_criteria" { + for_each = [origin_group.value.failover_criteria] + content { + status_codes = origin_group.value.failover_status_codes + } + } - allowed_methods = lookup(i.value, "allowed_methods", ["GET", "HEAD", "OPTIONS"]) - cached_methods = lookup(i.value, "cached_methods", ["GET", "HEAD"]) - compress = lookup(i.value, "compress", null) - field_level_encryption_id = lookup(i.value, "field_level_encryption_id", null) - smooth_streaming = lookup(i.value, "smooth_streaming", null) - trusted_signers = lookup(i.value, "trusted_signers", null) - trusted_key_groups = lookup(i.value, "trusted_key_groups", null) + dynamic "member" { + for_each = origin_group.value.member - cache_policy_id = try(i.value.cache_policy_id, data.aws_cloudfront_cache_policy.this[i.value.cache_policy_name].id, null) - origin_request_policy_id = try(i.value.origin_request_policy_id, data.aws_cloudfront_origin_request_policy.this[i.value.origin_request_policy_name].id, null) - response_headers_policy_id = try(i.value.response_headers_policy_id, data.aws_cloudfront_response_headers_policy.this[i.value.response_headers_policy_name].id, null) + content { + origin_id = member.value.origin_id + } + } - realtime_log_config_arn = lookup(i.value, "realtime_log_config_arn", null) + origin_id = try(coalesce(origin_group.value.origin_id, origin_group.key)) + } + } - min_ttl = lookup(i.value, "min_ttl", null) - default_ttl = lookup(i.value, "default_ttl", null) - max_ttl = lookup(i.value, "max_ttl", null) + dynamic "origin" { + for_each = var.origin - dynamic "forwarded_values" { - for_each = lookup(i.value, "use_forwarded_values", true) ? [true] : [] + content { + connection_attempts = origin.value.connection_attempts + connection_timeout = origin.value.connection_timeout - content { - query_string = lookup(i.value, "query_string", false) - query_string_cache_keys = lookup(i.value, "query_string_cache_keys", []) - headers = lookup(i.value, "headers", []) + dynamic "custom_header" { + for_each = origin.value.custom_header != null ? origin.value.custom_header : {} - cookies { - forward = lookup(i.value, "cookies_forward", "none") - whitelisted_names = lookup(i.value, "cookies_whitelisted_names", null) - } + content { + name = custom_header.key + value = custom_header.value } } - dynamic "lambda_function_association" { - for_each = lookup(i.value, "lambda_function_association", []) - iterator = l + dynamic "custom_origin_config" { + for_each = origin.value.custom_origin_config != null ? [origin.value.custom_origin_config] : [] content { - event_type = l.key - lambda_arn = l.value.lambda_arn - include_body = lookup(l.value, "include_body", null) + http_port = custom_origin_config.value.http_port + https_port = custom_origin_config.value.https_port + ip_address_type = custom_origin_config.value.ip_address_type + origin_keepalive_timeout = custom_origin_config.value.origin_keepalive_timeout + origin_read_timeout = custom_origin_config.value.origin_read_timeout + origin_protocol_policy = custom_origin_config.value.origin_protocol_policy + origin_ssl_protocols = custom_origin_config.value.origin_ssl_protocols } } - dynamic "function_association" { - for_each = lookup(i.value, "function_association", []) - iterator = f + domain_name = origin.value.domain_name + origin_access_control_id = try(coalesce(origin.value.origin_access_control_id, try(aws_cloudfront_origin_access_control.this[origin_access_control_key].id, null), null)) + origin_id = try(coalesce(origin.value.origin_id, origin.key)) + origin_path = origin.value.origin_path + + dynamic "origin_shield" { + for_each = origin.value.origin_shield != null ? [origin.value.origin_shield] : [] content { - event_type = f.key - function_arn = lookup(f.value, "function_arn", try(aws_cloudfront_function.this[f.value.function_key].arn, null)) + enabled = origin_shield.value.enabled + origin_shield_region = origin_shield.value.origin_shield_region } } - dynamic "grpc_config" { - for_each = try([i.value.grpc_config], []) + response_completion_timeout = origin.value.response_completion_timeout + + dynamic "vpc_origin_config" { + for_each = origin.value.vpc_origin_config != null ? [origin.value.vpc_origin_config] : [] + content { - enabled = grpc_config.value.enabled + origin_keepalive_timeout = vpc_origin_config.value.origin_keepalive_timeout + origin_read_timeout = vpc_origin_config.value.origin_read_timeout + vpc_origin_id = try(coalesce(vpc_origin_config.value.vpc_origin_id, try(aws_cloudfront_vpc_origin.this[vpc_origin_key], null), null)) } } } } - viewer_certificate { - acm_certificate_arn = lookup(var.viewer_certificate, "acm_certificate_arn", null) - cloudfront_default_certificate = lookup(var.viewer_certificate, "cloudfront_default_certificate", null) - iam_certificate_id = lookup(var.viewer_certificate, "iam_certificate_id", null) + price_class = var.price_class - minimum_protocol_version = lookup(var.viewer_certificate, "minimum_protocol_version", "TLSv1") - ssl_support_method = lookup(var.viewer_certificate, "ssl_support_method", null) - } - - dynamic "custom_error_response" { - for_each = length(flatten([var.custom_error_response])[0]) > 0 ? flatten([var.custom_error_response]) : [] + dynamic "restrictions" { + for_each = [var.restrictions] content { - error_code = custom_error_response.value["error_code"] + dynamic "geo_restriction" { + for_each = [restrictions.value.geo_restriction] - response_code = lookup(custom_error_response.value, "response_code", null) - response_page_path = lookup(custom_error_response.value, "response_page_path", null) - error_caching_min_ttl = lookup(custom_error_response.value, "error_caching_min_ttl", null) + content { + restriction_type = geo_restriction.value.restriction_type + locations = geo_restriction.value.locations + } + } } } - restrictions { - dynamic "geo_restriction" { - for_each = [var.geo_restriction] + retain_on_delete = var.retain_on_delete + staging = var.staging - content { - restriction_type = lookup(geo_restriction.value, "restriction_type", "none") - locations = lookup(geo_restriction.value, "locations", []) - } + dynamic "viewer_certificate" { + for_each = [var.viewer_certificate] + + content { + acm_certificate_arn = viewer_certificate.value.acm_certificate_arn + cloudfront_default_certificate = viewer_certificate.value.cloudfront_default_certificate + iam_certificate_id = viewer_certificate.value.iam_certificate_id + minimum_protocol_version = viewer_certificate.value.minimum_protocol_version + ssl_support_method = viewer_certificate.value.ssl_support_method } } + wait_for_deployment = var.wait_for_deployment + web_acl_id = var.web_acl_id + tags = var.tags + depends_on = [ aws_cloudfront_function.this ] @@ -288,14 +304,13 @@ resource "aws_cloudfront_distribution" "this" { ################################################################################ resource "aws_cloudfront_origin_access_control" "this" { - for_each = var.create_origin_access_control && length(keys(var.origin_access_control)) > 0 ? var.origin_access_control : {} - - name = try(each.value.name, null) != null ? each.value.name : each.key + for_each = var.origin_access_control != null ? var.origin_access_control : {} - description = each.value["description"] - origin_access_control_origin_type = each.value["origin_type"] - signing_behavior = each.value["signing_behavior"] - signing_protocol = each.value["signing_protocol"] + description = try(coalesce(each.value.description, "Origin Access Control for ${try(coalesce(each.value.name, each.key))}")) + name = try(coalesce(each.value.name, each.key)) + origin_access_control_origin_type = each.value.origin_type + signing_behavior = each.value.signing_behavior + signing_protocol = each.value.signing_protocol } ################################################################################ @@ -303,28 +318,38 @@ resource "aws_cloudfront_origin_access_control" "this" { ################################################################################ resource "aws_cloudfront_vpc_origin" "this" { - for_each = var.create_vpc_origin && length(keys(var.vpc_origin)) > 0 ? var.vpc_origin : {} + for_each = var.vpc_origin != null ? var.vpc_origin : {} vpc_origin_endpoint_config { - name = each.value["name"] - arn = each.value["arn"] - http_port = each.value["http_port"] - https_port = each.value["https_port"] - origin_protocol_policy = each.value["origin_protocol_policy"] - - origin_ssl_protocols { - items = each.value.origin_ssl_protocols.items - quantity = each.value.origin_ssl_protocols.quantity + arn = each.value.arn + http_port = each.value.http_port + https_port = each.value.https_port + name = try(coalesce(each.value.name, each.key)) + origin_protocol_policy = each.value.origin_protocol_policy + dynamic "origin_ssl_protocols" { + for_each = each.value.origin_ssl_protocols != null ? [each.value.origin_ssl_protocols] : [] + + content { + items = origin_ssl_protocols.value.items + quantity = origin_ssl_protocols.value.quantity + } } } - timeouts { - create = try(var.vpc_origin_timeouts.create, null) - update = try(var.vpc_origin_timeouts.update, null) - delete = try(var.vpc_origin_timeouts.delete, null) + dynamic "timeouts" { + for_each = each.value.timeouts != null ? [each.value.timeouts] : [] + + content { + create = timeouts.value.create + update = timeouts.value.update + delete = timeouts.value.delete + } } - tags = var.tags + tags = merge( + var.tags, + each.value.tags, + ) } ################################################################################ @@ -332,7 +357,7 @@ resource "aws_cloudfront_vpc_origin" "this" { ################################################################################ resource "aws_cloudfront_response_headers_policy" "this" { - for_each = var.create_response_headers_policy && var.response_headers_policies != null ? var.response_headers_policies : {} + for_each = var.response_headers_policies != null ? var.response_headers_policies : {} name = try(coalesce(each.value.name, each.key)) comment = each.value.comment @@ -475,7 +500,7 @@ resource "aws_cloudfront_response_headers_policy" "this" { ################################################################################ resource "aws_cloudfront_function" "this" { - for_each = var.create_cloudfront_function && var.cloudfront_functions != null ? var.cloudfront_functions : {} + for_each = var.cloudfront_functions != null ? var.cloudfront_functions : {} code = each.value.code comment = each.value.comment @@ -490,7 +515,7 @@ resource "aws_cloudfront_function" "this" { ################################################################################ resource "aws_cloudfront_monitoring_subscription" "this" { - count = var.create_distribution && var.create_monitoring_subscription ? 1 : 0 + count = var.create && var.create_monitoring_subscription ? 1 : 0 distribution_id = aws_cloudfront_distribution.this[0].id @@ -501,20 +526,29 @@ resource "aws_cloudfront_monitoring_subscription" "this" { } } +################################################################################ +# Data source reverse lookup by name +# These are used to refer to resources by name instead of ID +################################################################################ + +locals { + cache_behaviors = concat([var.default_cache_behavior], var.ordered_cache_behavior) +} + data "aws_cloudfront_cache_policy" "this" { - for_each = toset([for v in concat([var.default_cache_behavior], var.ordered_cache_behavior) : v.cache_policy_name if can(v.cache_policy_name)]) + for_each = toset([for v in local.cache_behaviors : v.cache_policy_name if v.cache_policy_name != null]) name = each.key } data "aws_cloudfront_origin_request_policy" "this" { - for_each = toset([for v in concat([var.default_cache_behavior], var.ordered_cache_behavior) : v.origin_request_policy_name if can(v.origin_request_policy_name)]) + for_each = toset([for v in local.cache_behaviors : v.origin_request_policy_name if v.origin_request_policy_name != null]) name = each.key } data "aws_cloudfront_response_headers_policy" "this" { - for_each = toset([for v in concat([var.default_cache_behavior], var.ordered_cache_behavior) : v.response_headers_policy_name if can(v.response_headers_policy_name)]) + for_each = toset([for v in local.cache_behaviors : v.response_headers_policy_name if v.response_headers_policy_name != null]) name = each.key } diff --git a/variables.tf b/variables.tf index ce5220b..3e34c58 100644 --- a/variables.tf +++ b/variables.tf @@ -1,19 +1,31 @@ -################################################################################ -# Distribution -################################################################################ - -variable "create_distribution" { - description = "Controls if CloudFront distribution should be created" +variable "create" { + description = "Controls if resources should be created (affects nearly all resources)" type = bool default = true } +variable "tags" { + description = "A map of tags to add to all resources" + type = map(string) + default = {} +} + +################################################################################ +# Distribution +################################################################################ + variable "aliases" { description = "Extra CNAMEs (alternate domain names), if any, for this distribution" type = list(string) default = null } +variable "anycast_ip_list_id" { + description = "ID of the Anycast static IP list that is associated with the distribution" + type = string + default = null +} + variable "comment" { description = "Any comments you want to include about the distribution" type = string @@ -26,6 +38,65 @@ variable "continuous_deployment_policy_id" { default = null } +variable "custom_error_response" { + description = "One or more custom error response elements" + type = list(object({ + error_caching_min_ttl = optional(number) + error_code = number + response_code = optional(number) + response_page_path = optional(string) + })) + default = null +} + +variable "default_cache_behavior" { + description = "The default cache behavior for this distribution" + type = object({ + allowed_methods = optional(list(string), ["GET", "HEAD", "OPTIONS"]) + cache_policy_id = optional(string) + catch_policy_name = optional(string) + cached_methods = optional(list(string), ["GET", "HEAD"]) + compress = optional(bool, true) + default_ttl = optional(number) + field_level_encryption_id = optional(string) + forwarded_values = optional(object({ + cookies = object({ + forward = optional(string, "none") + whitelisted_names = optional(list(string)) + }) + headers = optional(list(string)) + query_string = optional(bool, false) + query_string_cache_keys = optional(list(string)) + })) + function_association = optional(map(object({ + event_type = optional(string) + function_arn = optional(string) + function_key = optional(string) + }))) + grpc_config = optional(object({ + enabled = optional(bool) + })) + lambda_function_association = optional(map(object({ + event_type = optional(string) + include_body = optional(bool) + lambda_arn = string + }))) + max_ttl = optional(number) + min_ttl = optional(number) + origin_request_policy_id = optional(string) + origin_request_policy_name = optional(string) + realtime_log_config_arn = optional(string) + response_headers_policy_id = optional(string) + response_headers_policy_name = optional(string) + smooth_streaming = optional(bool) + target_origin_id = string + trusted_key_groups = optional(list(string)) + trusted_signers = optional(list(string)) + viewer_protocol_policy = optional(string, "https-only") + }) + nullable = false +} + variable "default_root_object" { description = "The object that you want CloudFront to return (for example, index.html) when an end user requests the root URL" type = string @@ -47,119 +118,192 @@ variable "http_version" { variable "is_ipv6_enabled" { description = "Whether the IPv6 is enabled for the distribution" type = bool - default = null -} - -variable "price_class" { - description = "The price class for this distribution. One of PriceClass_All, PriceClass_200, PriceClass_100" - type = string - default = null -} - -variable "retain_on_delete" { - description = "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" - type = bool - default = false -} - -variable "wait_for_deployment" { - description = "If enabled, the resource will wait for the distribution status to change from InProgress to Deployed. Setting this to false will skip the process" - type = bool default = true } -variable "web_acl_id" { - description = "If you're using AWS WAF to filter CloudFront requests, the Id of the AWS WAF web ACL that is associated with the distribution. The WAF Web ACL must exist in the WAF Global (CloudFront) region and the credentials configuring this argument must have waf:GetWebACL permissions assigned. If using WAFv2, provide the ARN of the web ACL" - type = string - default = null +variable "logging_config" { + description = "The logging configuration that controls how logs are written to your distribution (maximum one)" + type = object({ + bucket = optional(string) + include_cookies = optional(bool) + prefix = optional(string) + }) + default = null } -variable "staging" { - description = "Whether the distribution is a staging distribution" - type = bool - default = false +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 = list(object({ + allowed_methods = optional(list(string), ["GET", "HEAD", "OPTIONS"]) + cached_methods = optional(list(string), ["GET", "HEAD"]) + cache_policy_id = optional(string) + catch_policy_name = optional(string) + compress = optional(bool, true) + default_ttl = optional(number) + field_level_encryption_id = optional(string) + forwarded_values = optional(object({ + cookies = object({ + forward = optional(string, "none") + whitelisted_names = optional(list(string)) + }) + headers = optional(list(string)) + query_string = optional(bool, false) + query_string_cache_keys = optional(list(string)) + })) + function_association = optional(map(object({ + event_type = optional(string) + function_arn = optional(string) + function_key = optional(string) + }))) + grpc_config = optional(object({ + enabled = optional(bool) + })) + lambda_function_association = optional(map(object({ + event_type = optional(string) + include_body = optional(bool) + lambda_arn = string + }))) + max_ttl = optional(number) + min_ttl = optional(number) + origin_request_policy_id = optional(string) + origin_request_policy_name = optional(string) + path_pattern = string + realtime_log_config_arn = optional(string) + response_headers_policy_id = optional(string) + response_headers_policy_name = optional(string) + smooth_streaming = optional(bool) + target_origin_id = string + trusted_key_groups = optional(list(string)) + trusted_signers = optional(list(string)) + viewer_protocol_policy = string + })) + default = null } -variable "tags" { - description = "A map of tags to assign to the resource" - type = map(string) - default = null +variable "origin_group" { + description = "One or more origin_group for this distribution (multiples allowed)" + type = map(object({ + failover_criteria = object({ + status_codes = list(number) + }) + member = list(object({ + origin_id = string + })) + origin_id = optional(string) + })) + default = null } variable "origin" { description = "One or more origins for this distribution (multiples allowed)" - type = any - default = null + type = map(object({ + connection_attempts = optional(number) + connection_timeout = optional(number) + custom_header = optional(map(string)) + custom_origin_config = optional(object({ + http_port = number + https_port = number + ip_address_type = optional(string) + origin_keepalive_timeout = optional(number) + origin_read_timeout = optional(number) + origin_protocol_policy = string + origin_ssl_protocols = list(string) + })) + domain_name = string + origin_access_control_key = optional(string) + origin_access_control_id = optional(string) + origin_id = optional(string) + origin_path = optional(string) + origin_shield = optional(object({ + enabled = bool + origin_shield_region = optional(string) + })) + response_completion_timeout = optional(number) + vpc_origin_config = optional(object({ + origin_keepalive_timeout = optional(number) + origin_read_timeout = optional(number) + vpc_origin_id = optional(string) + vpc_origin_key = optional(string) + })) + })) + default = {} + nullable = false } -variable "origin_group" { - description = "One or more origin_group for this distribution (multiples allowed)" - type = any - default = {} +variable "price_class" { + description = "The price class for this distribution. One of PriceClass_All, PriceClass_200, PriceClass_100" + type = string + default = null } -variable "viewer_certificate" { - description = "The SSL configuration for this distribution" - type = any +variable "restrictions" { + description = "The restrictions configuration for this distribution" + type = object({ + geo_restriction = object({ + locations = optional(list(string)) + restriction_type = optional(string, "none") + }) + }) default = { - cloudfront_default_certificate = true - minimum_protocol_version = "TLSv1" + geo_restriction = { + restriction_type = "none" + } } + nullable = false } -variable "geo_restriction" { - description = "The restriction configuration for this distribution (geo_restrictions)" - type = any - default = {} +variable "retain_on_delete" { + description = "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" + type = bool + default = null } -variable "logging_config" { - description = "The logging configuration that controls how logs are written to your distribution (maximum one)" - type = any - default = {} +variable "staging" { + description = "Whether the distribution is a staging distribution" + type = bool + default = null } -variable "custom_error_response" { - description = "One or more custom error response elements" - type = any - default = {} +variable "viewer_certificate" { + description = "The SSL configuration for this distribution" + type = object({ + acm_certificate_arn = optional(string) + cloudfront_default_certificate = optional(bool, true) + iam_certificate_id = optional(string) + minimum_protocol_version = optional(string, "TLSv1.2_2025") + ssl_support_method = optional(string) + }) + default = {} + nullable = false } -variable "default_cache_behavior" { - description = "The default cache behavior for this distribution" - type = any +variable "wait_for_deployment" { + description = "If enabled, the resource will wait for the distribution status to change from InProgress to Deployed. Setting this to false will skip the process" + type = bool default = null } -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 "web_acl_id" { + description = "If you're using AWS WAF to filter CloudFront requests, the Id of the AWS WAF web ACL that is associated with the distribution. The WAF Web ACL must exist in the WAF Global (CloudFront) region and the credentials configuring this argument must have waf:GetWebACL permissions assigned. If using WAFv2, provide the ARN of the web ACL" + type = string + default = null } ################################################################################ # Origin Access Control ################################################################################ -variable "create_origin_access_control" { - description = "Controls if CloudFront origin access control should be created" - type = bool - default = false -} - variable "origin_access_control" { description = "Map of CloudFront origin access control" type = map(object({ + description = optional(string) name = optional(string) - description = string origin_type = string signing_behavior = string signing_protocol = string })) - default = { s3 = { - description = "", origin_type = "s3", signing_behavior = "always", signing_protocol = "sigv4" @@ -171,44 +315,32 @@ variable "origin_access_control" { # VPC Origin ################################################################################ -variable "create_vpc_origin" { - description = "If enabled, the resource for VPC origin will be created" - type = bool - default = false -} - variable "vpc_origin" { - description = "Map of CloudFront VPC origin" + description = "Map of CloudFront VPC origins" type = map(object({ - name = string arn = string http_port = number https_port = number + name = optional(string) origin_protocol_policy = string origin_ssl_protocols = object({ items = list(string) quantity = number }) + timeouts = optional(object({ + create = optional(string) + update = optional(string) + delete = optional(string) + })) + tags = optional(map(string), {}) })) - default = {} -} - -variable "vpc_origin_timeouts" { - description = "Create, update, and delete timeout configurations for vpc origin" - type = map(string) - default = {} + default = null } ################################################################################ # Response Headers Policy ################################################################################ -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({ @@ -284,12 +416,6 @@ variable "response_headers_policies" { # Function(s) ################################################################################ -variable "create_cloudfront_function" { - description = "Controls if CloudFront Functions should be created" - type = bool - default = false -} - variable "cloudfront_functions" { description = "Map of CloudFront Function configurations. Key is used as default function name if 'name' not specified" type = map(object({ diff --git a/versions.tf b/versions.tf index 879fe5a..d39a842 100644 --- a/versions.tf +++ b/versions.tf @@ -4,7 +4,7 @@ terraform { required_providers { aws = { source = "hashicorp/aws" - version = ">= 5.100" + version = ">= 6.20" } } } diff --git a/wrappers/main.tf b/wrappers/main.tf index 01c2bf1..0379e31 100644 --- a/wrappers/main.tf +++ b/wrappers/main.tf @@ -4,46 +4,42 @@ module "wrapper" { for_each = var.items aliases = try(each.value.aliases, var.defaults.aliases, null) + anycast_ip_list_id = try(each.value.anycast_ip_list_id, var.defaults.anycast_ip_list_id, null) cloudfront_functions = try(each.value.cloudfront_functions, var.defaults.cloudfront_functions, null) comment = try(each.value.comment, var.defaults.comment, null) continuous_deployment_policy_id = try(each.value.continuous_deployment_policy_id, var.defaults.continuous_deployment_policy_id, null) - create_cloudfront_function = try(each.value.create_cloudfront_function, var.defaults.create_cloudfront_function, false) - create_distribution = try(each.value.create_distribution, var.defaults.create_distribution, true) + create = try(each.value.create, var.defaults.create, true) 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_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) + custom_error_response = try(each.value.custom_error_response, var.defaults.custom_error_response, null) + default_cache_behavior = try(each.value.default_cache_behavior, var.defaults.default_cache_behavior) default_root_object = try(each.value.default_root_object, var.defaults.default_root_object, null) enabled = try(each.value.enabled, var.defaults.enabled, true) - geo_restriction = try(each.value.geo_restriction, var.defaults.geo_restriction, {}) http_version = try(each.value.http_version, var.defaults.http_version, "http2") - is_ipv6_enabled = try(each.value.is_ipv6_enabled, var.defaults.is_ipv6_enabled, null) - logging_config = try(each.value.logging_config, var.defaults.logging_config, {}) - ordered_cache_behavior = try(each.value.ordered_cache_behavior, var.defaults.ordered_cache_behavior, []) - origin = try(each.value.origin, var.defaults.origin, null) + is_ipv6_enabled = try(each.value.is_ipv6_enabled, var.defaults.is_ipv6_enabled, true) + logging_config = try(each.value.logging_config, var.defaults.logging_config, null) + ordered_cache_behavior = try(each.value.ordered_cache_behavior, var.defaults.ordered_cache_behavior, null) + origin = try(each.value.origin, var.defaults.origin, {}) origin_access_control = try(each.value.origin_access_control, var.defaults.origin_access_control, { s3 = { - description = "", origin_type = "s3", signing_behavior = "always", signing_protocol = "sigv4" } }) - origin_group = try(each.value.origin_group, var.defaults.origin_group, {}) + origin_group = try(each.value.origin_group, var.defaults.origin_group, null) 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) - viewer_certificate = try(each.value.viewer_certificate, var.defaults.viewer_certificate, { - cloudfront_default_certificate = true - minimum_protocol_version = "TLSv1" + restrictions = try(each.value.restrictions, var.defaults.restrictions, { + geo_restriction = { + restriction_type = "none" + } }) - vpc_origin = try(each.value.vpc_origin, var.defaults.vpc_origin, {}) - vpc_origin_timeouts = try(each.value.vpc_origin_timeouts, var.defaults.vpc_origin_timeouts, {}) - wait_for_deployment = try(each.value.wait_for_deployment, var.defaults.wait_for_deployment, true) + retain_on_delete = try(each.value.retain_on_delete, var.defaults.retain_on_delete, null) + staging = try(each.value.staging, var.defaults.staging, null) + tags = try(each.value.tags, var.defaults.tags, {}) + viewer_certificate = try(each.value.viewer_certificate, var.defaults.viewer_certificate, {}) + vpc_origin = try(each.value.vpc_origin, var.defaults.vpc_origin, null) + wait_for_deployment = try(each.value.wait_for_deployment, var.defaults.wait_for_deployment, null) web_acl_id = try(each.value.web_acl_id, var.defaults.web_acl_id, null) } diff --git a/wrappers/versions.tf b/wrappers/versions.tf index 879fe5a..d39a842 100644 --- a/wrappers/versions.tf +++ b/wrappers/versions.tf @@ -4,7 +4,7 @@ terraform { required_providers { aws = { source = "hashicorp/aws" - version = ">= 5.100" + version = ">= 6.20" } } } From b3e86c757893588bd1ee42ddc8097e64b86b5518 Mon Sep 17 00:00:00 2001 From: Bryant Biggs Date: Thu, 27 Nov 2025 13:31:33 -0600 Subject: [PATCH 4/4] feat: Update example --- README.md | 62 ++----- docs/UPGRADE-6.0.md | 121 +++++++++++- examples/complete/README.md | 22 +-- examples/complete/main.tf | 338 ++++++++++++++++------------------ examples/complete/versions.tf | 4 - main.tf | 35 ++-- variables.tf | 32 +++- 7 files changed, 332 insertions(+), 282 deletions(-) diff --git a/README.md b/README.md index a43bc76..4a12232 100644 --- a/README.md +++ b/README.md @@ -11,15 +11,8 @@ module "cdn" { source = "terraform-aws-modules/cloudfront/aws" aliases = ["cdn.example.com"] + comment = "My awesome CloudFront" - comment = "My awesome CloudFront" - enabled = true - is_ipv6_enabled = true - price_class = "PriceClass_All" - retain_on_delete = false - wait_for_deployment = false - - create_origin_access_control = true origin_access_control = { s3_oac = { description = "CloudFront access to S3" @@ -40,14 +33,14 @@ module "cdn" { http_port = 80 https_port = 443 origin_protocol_policy = "match-viewer" - origin_ssl_protocols = ["TLSv1", "TLSv1.1", "TLSv1.2"] + origin_ssl_protocols = ["TLSv1.2"] } } } default_cache_behavior = { - target_origin_id = "something" - viewer_protocol_policy = "allow-all" + target_origin_id = "something" + viewer_protocol_policy = "allow-all" allowed_methods = ["GET", "HEAD", "OPTIONS"] cached_methods = ["GET", "HEAD"] @@ -58,7 +51,7 @@ module "cdn" { ordered_cache_behavior = [ { path_pattern = "/static/*" - target_origin_id = "s3_one" + target_origin_id = "s3" viewer_protocol_policy = "redirect-to-https" allowed_methods = ["GET", "HEAD", "OPTIONS"] @@ -82,17 +75,10 @@ module "cdn" { source = "terraform-aws-modules/cloudfront/aws" aliases = ["cdn.example.com"] + comment = "CloudFront with Functions" - comment = "CloudFront with Functions" - enabled = true - is_ipv6_enabled = true - price_class = "PriceClass_All" - retain_on_delete = false - wait_for_deployment = false - - create_origin_access_control = true origin_access_control = { - s3_oac = { + s3 = { description = "CloudFront access to S3" origin_type = "s3" signing_behavior = "always" @@ -101,8 +87,6 @@ module "cdn" { } # Enable CloudFront Functions - create_cloudfront_function = true - cloudfront_functions = { viewer-request-function = { runtime = "cloudfront-js-2.0" @@ -166,26 +150,6 @@ module "cdn" { - [Complete](https://github.com/terraform-aws-modules/terraform-aws-cloudfront/tree/master/examples/complete) - Complete example which creates AWS CloudFront distribution and integrates it with other [terraform-aws-modules](https://github.com/terraform-aws-modules) to create additional resources: S3 buckets, Lambda Functions, CloudFront Functions, VPC Origins, ACM Certificate, Route53 Records. -## Notes - -- `Error: updating CloudFront Distribution (ETXXXXXXXXXXXX): InvalidArgument: The parameter ForwardedValues cannot be used when a cache policy is associated to the cache behavior.` - - When defining a behavior in `ordered_cache_behavior` and `default_cache_behavior` with a cache policy, you must specify `use_forwarded_values = false`. - -```hcl -ordered_cache_behavior = [{ - path_pattern = "/my/path" - target_origin_id = "my-origin" - viewer_protocol_policy = "https-only" - allowed_methods = ["GET", "HEAD"] - use_forwarded_values = false - - # AllViewerAndCloudFrontHeaders-2022-06 - origin_request_policy_id = "33f36d7e-f396-46d9-90e0-52428a34d9dc" - # CachingDisabled - cache_policy_id = "4135ea2d-6df8-44a3-9df3-4b5a84be39ad" -}] -``` - ## Requirements @@ -230,25 +194,25 @@ No modules. | [create](#input\_create) | Controls if resources should be created (affects nearly all resources) | `bool` | `true` | no | | [create\_monitoring\_subscription](#input\_create\_monitoring\_subscription) | If enabled, the resource for monitoring subscription will created | `bool` | `false` | no | | [custom\_error\_response](#input\_custom\_error\_response) | One or more custom error response elements |
list(object({
error_caching_min_ttl = optional(number)
error_code = number
response_code = optional(number)
response_page_path = optional(string)
}))
| `null` | no | -| [default\_cache\_behavior](#input\_default\_cache\_behavior) | The default cache behavior for this distribution |
object({
allowed_methods = optional(list(string), ["GET", "HEAD", "OPTIONS"])
cache_policy_id = optional(string)
catch_policy_name = optional(string)
cached_methods = optional(list(string), ["GET", "HEAD"])
compress = optional(bool, true)
default_ttl = optional(number)
field_level_encryption_id = optional(string)
forwarded_values = optional(object({
cookies = object({
forward = optional(string, "none")
whitelisted_names = optional(list(string))
})
headers = optional(list(string))
query_string = optional(bool, false)
query_string_cache_keys = optional(list(string))
}))
function_association = optional(map(object({
event_type = optional(string)
function_arn = optional(string)
function_key = optional(string)
})))
grpc_config = optional(object({
enabled = optional(bool)
}))
lambda_function_association = optional(map(object({
event_type = optional(string)
include_body = optional(bool)
lambda_arn = string
})))
max_ttl = optional(number)
min_ttl = optional(number)
origin_request_policy_id = optional(string)
origin_request_policy_name = optional(string)
realtime_log_config_arn = optional(string)
response_headers_policy_id = optional(string)
response_headers_policy_name = optional(string)
smooth_streaming = optional(bool)
target_origin_id = string
trusted_key_groups = optional(list(string))
trusted_signers = optional(list(string))
viewer_protocol_policy = optional(string, "https-only")
})
| n/a | yes | +| [default\_cache\_behavior](#input\_default\_cache\_behavior) | The default cache behavior for this distribution |
object({
allowed_methods = optional(list(string), ["GET", "HEAD", "OPTIONS"])
cache_policy_id = optional(string)
cache_policy_name = optional(string)
cached_methods = optional(list(string), ["GET", "HEAD"])
compress = optional(bool, true)
default_ttl = optional(number)
field_level_encryption_id = optional(string)
forwarded_values = optional(object({
cookies = object({
forward = optional(string, "none")
whitelisted_names = optional(list(string))
})
headers = optional(list(string))
query_string = optional(bool, false)
query_string_cache_keys = optional(list(string))
}),
{
cookies = {
forward = "none"
}
query_string = false
}
)
function_association = optional(map(object({
event_type = optional(string)
function_arn = optional(string)
function_key = optional(string)
})))
grpc_config = optional(object({
enabled = optional(bool)
}))
lambda_function_association = optional(map(object({
event_type = optional(string)
include_body = optional(bool)
lambda_arn = string
})))
max_ttl = optional(number)
min_ttl = optional(number)
origin_request_policy_id = optional(string)
origin_request_policy_name = optional(string)
realtime_log_config_arn = optional(string)
response_headers_policy_id = optional(string)
response_headers_policy_name = optional(string)
smooth_streaming = optional(bool)
target_origin_id = string
trusted_key_groups = optional(list(string))
trusted_signers = optional(list(string))
viewer_protocol_policy = optional(string, "https-only")
})
| n/a | yes | | [default\_root\_object](#input\_default\_root\_object) | The object that you want CloudFront to return (for example, index.html) when an end user requests the root URL | `string` | `null` | no | | [enabled](#input\_enabled) | Whether the distribution is enabled to accept end user requests for content | `bool` | `true` | no | | [http\_version](#input\_http\_version) | The maximum HTTP version to support on the distribution. Allowed values are http1.1, http2, http2and3, and http3. The default is http2 | `string` | `"http2"` | no | | [is\_ipv6\_enabled](#input\_is\_ipv6\_enabled) | Whether the IPv6 is enabled for the distribution | `bool` | `true` | no | | [logging\_config](#input\_logging\_config) | The logging configuration that controls how logs are written to your distribution (maximum one) |
object({
bucket = optional(string)
include_cookies = optional(bool)
prefix = optional(string)
})
| `null` | 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 |
list(object({
allowed_methods = optional(list(string), ["GET", "HEAD", "OPTIONS"])
cached_methods = optional(list(string), ["GET", "HEAD"])
cache_policy_id = optional(string)
catch_policy_name = optional(string)
compress = optional(bool, true)
default_ttl = optional(number)
field_level_encryption_id = optional(string)
forwarded_values = optional(object({
cookies = object({
forward = optional(string, "none")
whitelisted_names = optional(list(string))
})
headers = optional(list(string))
query_string = optional(bool, false)
query_string_cache_keys = optional(list(string))
}))
function_association = optional(map(object({
event_type = optional(string)
function_arn = optional(string)
function_key = optional(string)
})))
grpc_config = optional(object({
enabled = optional(bool)
}))
lambda_function_association = optional(map(object({
event_type = optional(string)
include_body = optional(bool)
lambda_arn = string
})))
max_ttl = optional(number)
min_ttl = optional(number)
origin_request_policy_id = optional(string)
origin_request_policy_name = optional(string)
path_pattern = string
realtime_log_config_arn = optional(string)
response_headers_policy_id = optional(string)
response_headers_policy_name = optional(string)
smooth_streaming = optional(bool)
target_origin_id = string
trusted_key_groups = optional(list(string))
trusted_signers = optional(list(string))
viewer_protocol_policy = string
}))
| `null` | no | -| [origin](#input\_origin) | One or more origins for this distribution (multiples allowed) |
map(object({
connection_attempts = optional(number)
connection_timeout = optional(number)
custom_header = optional(map(string))
custom_origin_config = optional(object({
http_port = number
https_port = number
ip_address_type = optional(string)
origin_keepalive_timeout = optional(number)
origin_read_timeout = optional(number)
origin_protocol_policy = string
origin_ssl_protocols = list(string)
}))
domain_name = string
origin_access_control_key = optional(string)
origin_access_control_id = optional(string)
origin_id = optional(string)
origin_path = optional(string)
origin_shield = optional(object({
enabled = bool
origin_shield_region = optional(string)
}))
response_completion_timeout = optional(number)
vpc_origin_config = optional(object({
origin_keepalive_timeout = optional(number)
origin_read_timeout = optional(number)
vpc_origin_id = optional(string)
vpc_origin_key = optional(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 |
list(object({
allowed_methods = optional(list(string), ["GET", "HEAD", "OPTIONS"])
cached_methods = optional(list(string), ["GET", "HEAD"])
cache_policy_id = optional(string)
cache_policy_name = optional(string)
compress = optional(bool, true)
default_ttl = optional(number)
field_level_encryption_id = optional(string)
forwarded_values = optional(object({
cookies = object({
forward = optional(string, "none")
whitelisted_names = optional(list(string))
})
headers = optional(list(string))
query_string = optional(bool, false)
query_string_cache_keys = optional(list(string))
}),
{
cookies = {
forward = "none"
}
query_string = false
}
)
function_association = optional(map(object({
event_type = optional(string)
function_arn = optional(string)
function_key = optional(string)
})))
grpc_config = optional(object({
enabled = optional(bool)
}))
lambda_function_association = optional(map(object({
event_type = optional(string)
include_body = optional(bool)
lambda_arn = string
})))
max_ttl = optional(number)
min_ttl = optional(number)
origin_request_policy_id = optional(string)
origin_request_policy_name = optional(string)
path_pattern = string
realtime_log_config_arn = optional(string)
response_headers_policy_id = optional(string)
response_headers_policy_name = optional(string)
smooth_streaming = optional(bool)
target_origin_id = string
trusted_key_groups = optional(list(string))
trusted_signers = optional(list(string))
viewer_protocol_policy = string
}))
| `null` | no | +| [origin](#input\_origin) | One or more origins for this distribution (multiples allowed) |
map(object({
connection_attempts = optional(number)
connection_timeout = optional(number)
custom_header = optional(map(string))
custom_origin_config = optional(object({
http_port = number
https_port = number
ip_address_type = optional(string)
origin_keepalive_timeout = optional(number)
origin_read_timeout = optional(number)
origin_protocol_policy = string
origin_ssl_protocols = optional(list(string), ["TLSv1.2"])
}))
domain_name = string
origin_access_control_key = optional(string)
origin_access_control_id = optional(string)
origin_id = optional(string)
origin_path = optional(string)
origin_shield = optional(object({
enabled = bool
origin_shield_region = optional(string)
}))
response_completion_timeout = optional(number)
vpc_origin_config = optional(object({
origin_keepalive_timeout = optional(number)
origin_read_timeout = optional(number)
vpc_origin_id = optional(string)
vpc_origin_key = optional(string)
}))
}))
| `{}` | no | | [origin\_access\_control](#input\_origin\_access\_control) | Map of CloudFront origin access control |
map(object({
description = optional(string)
name = optional(string)
origin_type = string
signing_behavior = string
signing_protocol = string
}))
|
{
"s3": {
"origin_type": "s3",
"signing_behavior": "always",
"signing_protocol": "sigv4"
}
}
| no | | [origin\_group](#input\_origin\_group) | One or more origin\_group for this distribution (multiples allowed) |
map(object({
failover_criteria = object({
status_codes = list(number)
})
member = list(object({
origin_id = string
}))
origin_id = optional(string)
}))
| `null` | no | -| [price\_class](#input\_price\_class) | The price class for this distribution. One of PriceClass\_All, PriceClass\_200, PriceClass\_100 | `string` | `null` | 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 | | [restrictions](#input\_restrictions) | The restrictions configuration for this distribution |
object({
geo_restriction = object({
locations = optional(list(string))
restriction_type = optional(string, "none")
})
})
|
{
"geo_restriction": {
"restriction_type": "none"
}
}
| 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` | `null` | no | | [staging](#input\_staging) | Whether the distribution is a staging distribution | `bool` | `null` | no | | [tags](#input\_tags) | A map of tags to add to all resources | `map(string)` | `{}` | no | -| [viewer\_certificate](#input\_viewer\_certificate) | The SSL configuration for this distribution |
object({
acm_certificate_arn = optional(string)
cloudfront_default_certificate = optional(bool, true)
iam_certificate_id = optional(string)
minimum_protocol_version = optional(string, "TLSv1.2_2025")
ssl_support_method = optional(string)
})
| `{}` | no | -| [vpc\_origin](#input\_vpc\_origin) | Map of CloudFront VPC origins |
map(object({
arn = string
http_port = number
https_port = number
name = optional(string)
origin_protocol_policy = string
origin_ssl_protocols = object({
items = list(string)
quantity = number
})
timeouts = optional(object({
create = optional(string)
update = optional(string)
delete = optional(string)
}))
tags = optional(map(string), {})
}))
| `null` | no | +| [viewer\_certificate](#input\_viewer\_certificate) | The SSL configuration for this distribution |
object({
acm_certificate_arn = optional(string)
cloudfront_default_certificate = optional(bool)
iam_certificate_id = optional(string)
minimum_protocol_version = optional(string, "TLSv1.2_2025")
ssl_support_method = optional(string)
})
| `{}` | no | +| [vpc\_origin](#input\_vpc\_origin) | Map of CloudFront VPC origins |
map(object({
arn = string
http_port = number
https_port = number
name = optional(string)
origin_protocol_policy = string
origin_ssl_protocols = object({
items = optional(list(string), ["TLSv1.2"])
quantity = optional(number, 1)
})
timeouts = optional(object({
create = optional(string)
update = optional(string)
delete = optional(string)
}))
tags = optional(map(string), {})
}))
| `null` | no | | [wait\_for\_deployment](#input\_wait\_for\_deployment) | If enabled, the resource will wait for the distribution status to change from InProgress to Deployed. Setting this to false will skip the process | `bool` | `null` | no | | [web\_acl\_id](#input\_web\_acl\_id) | If you're using AWS WAF to filter CloudFront requests, the Id of the AWS WAF web ACL that is associated with the distribution. The WAF Web ACL must exist in the WAF Global (CloudFront) region and the credentials configuring this argument must have waf:GetWebACL permissions assigned. If using WAFv2, provide the ARN of the web ACL | `string` | `null` | no | diff --git a/docs/UPGRADE-6.0.md b/docs/UPGRADE-6.0.md index 97e23da..569e275 100644 --- a/docs/UPGRADE-6.0.md +++ b/docs/UPGRADE-6.0.md @@ -5,19 +5,25 @@ If you find a bug, please open an issue with supporting configuration to reprodu ## List of backwards incompatible changes -- AWS provider `v6.0` is now minimum supported version +- AWS provider `v6.20` is now minimum supported version - Support for `aws_cloudfront_origin_access_identity` has been removed in favor of `aws_cloudfront_origin_access_control` ## Additional changes ### Added -- Support for `region` parameter to specify the AWS region for the resources created if different from the provider region. +- None ### Modified -- Variable definitions now contain detailed `object` types in place of the previously used any type. -- `is_ipv6_enabled` now defaults to `true` if not specified. +- Variable definitions now contain detailed `object` types in place of the previously used any type +- `is_ipv6_enabled` now defaults to `true` if not specified +- `default_cache_behavior.compress` and `ordered_cache_behavior.compress` now default to `true` +- `origin.origin_ssl_protocols` now defaults to `["TLSv1.2"]` +- `vpc_origin.origin_ssl_protocols.items` now defaults to `["TLSv1.2"]` +- `vpc_origin_timeouts` is now embedded under `vpc_origin` +- `viewer_certificate.minimum_protocol_version` now defaults to `"TLSv1.2_2025"` +- See the the `Before vs After` examples below for more details on variable type definition changes ### Variable and output changes @@ -50,7 +56,7 @@ If you find a bug, please open an issue with supporting configuration to reprodu 5. Renamed outputs: - - + - None 6. Added outputs: @@ -67,6 +73,58 @@ module "cloudfront" { # Truncated for brevity ... + create_vpc_origin = true + vpc_origin = { + ec2 = { + arn = module.ec2.arn + http_port = 80 + https_port = 443 + origin_protocol_policy = "http-only" + origin_ssl_protocols = { + items = ["TLSv1.2"] + quantity = 1 + } + } + } + + vpc_origin_timeouts = { + create = "20m" + update = "20m" + delete = "20m" + } + + origin = { + s3 = { + domain_name = module.s3.bucket_regional_domain_name + s3_origin_config = { + origin_access_identity = "s3_bucket_one" + } + + custom_header = [ + { + name = "X-Forwarded-Scheme" + value = "https" + }, + { + name = "X-Frame-Options" + value = "SAMEORIGIN" + } + ] + } + } + + origin_group = { + group_one = { + failover_status_codes = [403, 404, 500, 502] + primary_member_origin_id = "appsync" # Not shown + secondary_member_origin_id = "s3" + } + } + + geo_restriction = { + restriction_type = "whitelist" + locations = ["NO", "UA", "US", "GB"] + } } ``` @@ -79,9 +137,60 @@ module "cloudfront" { # Truncated for brevity ... + vpc_origin = { + ec2 = { + arn = module.ec2.arn + http_port = 80 + https_port = 443 + origin_protocol_policy = "http-only" + origin_ssl_protocols = { + items = ["TLSv1.2"] + quantity = 1 + } + + timeouts = { + create = "20m" + update = "20m" + delete = "20m" + } + } + } + + origin = { + s3 = { + domain_name = module.s3.bucket_regional_domain_name + s3_origin_config = { + origin_access_control_key = "s3_bucket_one" + } + + custom_header = { + "X-Forwarded-Scheme" = "https" + "X-Frame-Options" = "SAMEORIGIN" + } + } + } + + origin_group = { + group-one = { + failover_criteria = { + status_codes = [403, 404, 500, 502] + } + member = [ + { origin_id = "appsync" }, # Not shown + { origin_id = "s3" } + ] + } + } + + restrictions = { + geo_restriction = { + restriction_type = "whitelist" + locations = ["NO", "UA", "US", "GB"] + } + } } ``` ### State Changes -TBD +None diff --git a/examples/complete/README.md b/examples/complete/README.md index b120458..afe8c16 100644 --- a/examples/complete/README.md +++ b/examples/complete/README.md @@ -1,18 +1,6 @@ # Complete CloudFront Distribution -Configuration in this directory creates CloudFront distribution which demos such capabilities: - -- access logging -- origins and origin groups -- caching behaviours -- Origin Access Identities (with S3 bucket policy) -- Origin Access Control (recommended over OAI) -- Lambda@Edge -- CloudFront Functions -- Response Headers Policies -- ACM certificate -- Route53 record -- VPC Origins +Configuration in this directory creates CloudFront distribution which demonstrates nearly all features supported by this module. ## Usage @@ -34,7 +22,6 @@ Note that this example may create resources which cost money. Run `terraform des | [terraform](#requirement\_terraform) | >= 1.5.7 | | [aws](#requirement\_aws) | >= 6.20 | | [null](#requirement\_null) | >= 2.0 | -| [random](#requirement\_random) | >= 2.0 | ## Providers @@ -42,7 +29,6 @@ Note that this example may create resources which cost money. Run `terraform des |------|---------| | [aws](#provider\_aws) | >= 6.20 | | [null](#provider\_null) | >= 2.0 | -| [random](#provider\_random) | >= 2.0 | ## Modules @@ -54,16 +40,16 @@ Note that this example may create resources which cost money. Run `terraform des | [lambda\_function](#module\_lambda\_function) | terraform-aws-modules/lambda/aws | ~> 8.0 | | [log\_bucket](#module\_log\_bucket) | terraform-aws-modules/s3-bucket/aws | ~> 5.0 | | [records](#module\_records) | terraform-aws-modules/route53/aws//modules/records | ~> 5.0 | -| [s3\_one](#module\_s3\_one) | terraform-aws-modules/s3-bucket/aws | ~> 5.0 | +| [s3](#module\_s3) | terraform-aws-modules/s3-bucket/aws | ~> 5.0 | +| [vpc](#module\_vpc) | terraform-aws-modules/vpc/aws | ~> 6.0 | ## Resources | Name | Type | |------|------| | [aws_cloudfront_function.example](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/cloudfront_function) | resource | -| [aws_s3_bucket_policy.bucket_policy](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_policy) | resource | | [null_resource.download_package](https://registry.terraform.io/providers/hashicorp/null/latest/docs/resources/resource) | resource | -| [random_pet.this](https://registry.terraform.io/providers/hashicorp/random/latest/docs/resources/pet) | resource | +| [aws_availability_zones.available](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/availability_zones) | data source | | [aws_canonical_user_id.current](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/canonical_user_id) | data source | | [aws_cloudfront_log_delivery_canonical_user_id.cloudfront](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/cloudfront_log_delivery_canonical_user_id) | data source | | [aws_iam_policy_document.s3_policy](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | diff --git a/examples/complete/main.tf b/examples/complete/main.tf index 4a4ad0c..fb108f4 100644 --- a/examples/complete/main.tf +++ b/examples/complete/main.tf @@ -1,45 +1,56 @@ provider "aws" { region = "us-east-1" # CloudFront expects ACM resources in us-east-1 region only +} - # Make it faster by skipping something - skip_metadata_api_check = true - skip_region_validation = true - skip_credentials_validation = true - - # skip_requesting_account_id should be disabled to generate valid ARN in apigatewayv2_api_execution_arn - skip_requesting_account_id = false +data "aws_availability_zones" "available" { + # Exclude local zones + filter { + name = "opt-in-status" + values = ["opt-in-not-required"] + } } locals { - domain_name = "terraform-aws-modules.modules.tf" # trimsuffix(data.aws_route53_zone.this.name, ".") + # domain_name = "terraform-aws-modules.modules.tf" + domain_name = "sharedservices.clowd.haus" subdomain = "cdn" + + name = "ex-${basename(path.cwd)}" + + vpc_cidr = "10.0.0.0/16" + azs = slice(data.aws_availability_zones.available.names, 0, 3) + + tags = { + Test = local.name + GithubRepo = "terraform-aws-cloudfront" + GithubOrg = "terraform-aws-modules" + } } +################################################################################ +# CloudFront Module +################################################################################ + module "cloudfront" { source = "../../" aliases = ["${local.subdomain}.${local.domain_name}"] - comment = "My awesome CloudFront" - enabled = true - staging = false # If you want to create a staging distribution, set this to true - http_version = "http2and3" - is_ipv6_enabled = true - price_class = "PriceClass_All" - retain_on_delete = false - wait_for_deployment = false - - # If you want to create a primary distribution with a continuous deployment policy, set this to the ID of the policy. - # This argument should only be set on a production distribution. - # ref. `aws_cloudfront_continuous_deployment_policy` resource: https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/cloudfront_continuous_deployment_policy - continuous_deployment_policy_id = null - - # When you enable additional metrics for a distribution, CloudFront sends up to 8 metrics to CloudWatch in the US East (N. Virginia) Region. - # This rate is charged only once per month, per metric (up to 8 metrics per distribution). + comment = "My awesome CloudFront" + enabled = true + http_version = "http2and3" + is_ipv6_enabled = true + price_class = "PriceClass_All" + create_monitoring_subscription = true + logging_config = { + bucket = module.log_bucket.s3_bucket_bucket_domain_name + prefix = "cloudfront" + } + origin_access_control = { - s3_oac = { + s3 = { description = "CloudFront access to S3" origin_type = "s3" signing_behavior = "always" @@ -48,8 +59,7 @@ module "cloudfront" { } vpc_origin = { - ec2_vpc_origin = { - name = random_pet.this.id + ec2 = { arn = module.ec2.arn http_port = 80 https_port = 443 @@ -88,37 +98,29 @@ module "cloudfront" { } } - s3_one = { # with origin access identity (legacy) - domain_name = module.s3_one.s3_bucket_bucket_regional_domain_name - s3_origin_config = { - origin_access_identity = "s3_bucket_one" # key in `origin_access_identities` - # cloudfront_access_identity_path = "origin-access-identity/cloudfront/E5IGQAA1QO48Z" # external OAI resource - } + s3 = { + domain_name = module.s3.s3_bucket_bucket_regional_domain_name + origin_access_control_key = "s3" # key in `origin_access_control` + # origin_access_control_id = "E345SXM82MIOSU" # external OAС resource } - s3_oac = { # with origin access control settings (recommended) - domain_name = module.s3_one.s3_bucket_bucket_regional_domain_name - origin_access_control = "s3_oac" # key in `origin_access_control` - # origin_access_control_id = "E345SXM82MIOSU" # external OAС resource - } - - ec2_vpc_origin = { + ec2 = { domain_name = module.ec2.private_dns vpc_origin_config = { - vpc_origin = "ec2_vpc_origin" # key in `vpc_origin` - # vpc_origin_id = "vo_Cg6A14otX0DB1yyDQ6Nond" # external VPC Origin resource + vpc_origin_key = "ec2" # key in `vpc_origin` + # vpc_origin_id = "vo_Cg6A14otX0DB1yyDQ6Nond" # external VPC Origin resource } } } origin_group = { - group-one = { + group_one = { failover_criteria = { status_codes = [403, 404, 500, 502] } member = [ { origin_id = "appsync" }, - { origin_id = "s3_one" } + { origin_id = "s3" } ] } } @@ -129,13 +131,10 @@ module "cloudfront" { allowed_methods = ["GET", "HEAD", "OPTIONS"] cached_methods = ["GET", "HEAD"] - use_forwarded_values = false - cache_policy_id = "b2884449-e4de-46a7-ac36-70bc7f1ddd6d" response_headers_policy_id = "67f7725c-6f97-4210-82d7-5512b31e9d03" lambda_function_association = { - # Valid keys: viewer-request, origin-request, viewer-response, origin-response viewer-request = { lambda_arn = module.lambda_function.lambda_function_qualified_arn @@ -151,14 +150,12 @@ module "cloudfront" { ordered_cache_behavior = [ { path_pattern = "/static/*" - target_origin_id = "s3_one" + target_origin_id = "s3" viewer_protocol_policy = "redirect-to-https" allowed_methods = ["GET", "HEAD", "OPTIONS"] cached_methods = ["GET", "HEAD"] - use_forwarded_values = false - cache_policy_name = "Managed-CachingOptimized" origin_request_policy_name = "Managed-UserAgentRefererHeaders" response_headers_policy_name = "Managed-SimpleCORS" @@ -191,7 +188,7 @@ module "cloudfront" { }, { path_pattern = "/static-no-policies/*" - target_origin_id = "s3_one" + target_origin_id = "s3" viewer_protocol_policy = "redirect-to-https" allowed_methods = ["GET", "HEAD", "OPTIONS"] @@ -203,7 +200,7 @@ module "cloudfront" { }, { path_pattern = "/vpc-origin/*" - target_origin_id = "ec2_vpc_origin" + target_origin_id = "ec2" viewer_protocol_policy = "redirect-to-https" allowed_methods = ["GET", "HEAD", "OPTIONS"] @@ -271,7 +268,7 @@ module "cloudfront" { comment = "CORS configuration for API" cors_config = { - access_control_allow_credentials = true + access_control_allow_credentials = false origin_override = true access_control_allow_headers = { @@ -336,11 +333,60 @@ module "cloudfront" { } } } + + tags = local.tags +} + +module "records" { + source = "terraform-aws-modules/route53/aws//modules/records" + version = "~> 5.0" + + zone_id = data.aws_route53_zone.this.zone_id + + records = [ + { + name = local.subdomain + type = "A" + alias = { + name = module.cloudfront.cloudfront_distribution_domain_name + zone_id = module.cloudfront.cloudfront_distribution_hosted_zone_id + } + }, + ] +} + +################################################################################ +# Supporting Resources +################################################################################ + +module "vpc" { + source = "terraform-aws-modules/vpc/aws" + version = "~> 6.0" + + name = local.name + cidr = local.vpc_cidr + + azs = local.azs + private_subnets = [for k, v in local.azs : cidrsubnet(local.vpc_cidr, 4, k)] + public_subnets = [for k, v in local.azs : cidrsubnet(local.vpc_cidr, 8, k + 48)] + + enable_nat_gateway = true + single_nat_gateway = true +} + +module "ec2" { + source = "terraform-aws-modules/ec2-instance/aws" + version = "~> 6.0" + + name = local.name + subnet_id = element(module.vpc.private_subnets, 0) } -###### -# ACM -###### +resource "aws_cloudfront_function" "example" { + name = local.name + runtime = "cloudfront-js-1.0" + code = file("./functions/example-function.js") +} data "aws_route53_zone" "this" { name = local.domain_name @@ -353,49 +399,80 @@ module "acm" { domain_name = local.domain_name zone_id = data.aws_route53_zone.this.id subject_alternative_names = ["${local.subdomain}.${local.domain_name}"] -} -############# -# S3 buckets -############# - -data "aws_canonical_user_id" "current" {} -data "aws_cloudfront_log_delivery_canonical_user_id" "cloudfront" {} + tags = local.tags +} -module "s3_one" { +module "s3" { source = "terraform-aws-modules/s3-bucket/aws" version = "~> 5.0" - bucket_prefix = "s3-one-" + bucket_prefix = "${local.name}-" + + # For example only force_destroy = true + + attach_policy = true + policy = data.aws_iam_policy_document.s3_policy.json + + tags = local.tags +} + +data "aws_iam_policy_document" "s3_policy" { + # Origin Access Control + statement { + actions = ["s3:GetObject"] + resources = ["${module.s3.s3_bucket_arn}/*"] + + principals { + type = "Service" + identifiers = ["cloudfront.amazonaws.com"] + } + + condition { + test = "StringEquals" + variable = "aws:SourceArn" + values = [module.cloudfront.cloudfront_distribution_arn] + } + } } +data "aws_canonical_user_id" "current" {} +data "aws_cloudfront_log_delivery_canonical_user_id" "cloudfront" {} + module "log_bucket" { source = "terraform-aws-modules/s3-bucket/aws" version = "~> 5.0" - bucket_prefix = "logs-" + bucket_prefix = "${local.name}-logs-" + + # For example only + force_destroy = true control_object_ownership = true object_ownership = "ObjectWriter" - grant = [{ - type = "CanonicalUser" - permission = "FULL_CONTROL" - id = data.aws_canonical_user_id.current.id - }, { - type = "CanonicalUser" - permission = "FULL_CONTROL" - id = data.aws_cloudfront_log_delivery_canonical_user_id.cloudfront.id - # Ref. https://github.com/terraform-providers/terraform-provider-aws/issues/12512 - # Ref. https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/AccessLogs.html - }] - force_destroy = true + grant = [ + { + type = "CanonicalUser" + permission = "FULL_CONTROL" + id = data.aws_canonical_user_id.current.id + }, + { + type = "CanonicalUser" + permission = "FULL_CONTROL" + id = data.aws_cloudfront_log_delivery_canonical_user_id.cloudfront.id + # Ref. https://github.com/terraform-providers/terraform-provider-aws/issues/12512 + # Ref. https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/AccessLogs.html + } + ] + + tags = local.tags } -############################################# +################################################################################ # Using packaged function from Lambda module -############################################# +################################################################################ locals { package_url = "https://raw.githubusercontent.com/terraform-aws-modules/terraform-aws-lambda/master/examples/fixtures/python-zip/existing_package.zip" @@ -416,7 +493,7 @@ module "lambda_function" { source = "terraform-aws-modules/lambda/aws" version = "~> 8.0" - function_name = "${random_pet.this.id}-lambda" + function_name = local.name description = "My awesome lambda function" handler = "index.lambda_handler" runtime = "python3.11" @@ -426,103 +503,4 @@ module "lambda_function" { create_package = false local_existing_package = local.downloaded - - # @todo: Missing CloudFront as allowed_triggers? - - # allowed_triggers = { - # AllowExecutionFromAPIGateway = { - # service = "apigateway" - # arn = module.api_gateway.apigatewayv2_api_execution_arn - # } - # } -} - -########## -# Route53 -########## - -module "records" { - source = "terraform-aws-modules/route53/aws//modules/records" - version = "~> 5.0" - - zone_id = data.aws_route53_zone.this.zone_id - - records = [ - { - name = local.subdomain - type = "A" - alias = { - name = module.cloudfront.cloudfront_distribution_domain_name - zone_id = module.cloudfront.cloudfront_distribution_hosted_zone_id - } - }, - ] -} - -######################################### -# S3 bucket policy -######################################### - -data "aws_iam_policy_document" "s3_policy" { - # Origin Access Identities - statement { - actions = ["s3:GetObject"] - resources = ["${module.s3_one.s3_bucket_arn}/static/*"] - - principals { - type = "AWS" - identifiers = ["TODO XXX"] - } - } - - # Origin Access Controls - statement { - actions = ["s3:GetObject"] - resources = ["${module.s3_one.s3_bucket_arn}/static/*"] - - principals { - type = "Service" - identifiers = ["cloudfront.amazonaws.com"] - } - - condition { - test = "StringEquals" - variable = "aws:SourceArn" - values = [module.cloudfront.cloudfront_distribution_arn] - } - } -} - -resource "aws_s3_bucket_policy" "bucket_policy" { - bucket = module.s3_one.s3_bucket_id - policy = data.aws_iam_policy_document.s3_policy.json -} - -######################################### -# CloudFront function -######################################### - -resource "aws_cloudfront_function" "example" { - name = "example-${random_pet.this.id}" - runtime = "cloudfront-js-1.0" - code = file("./functions/example-function.js") -} - -######################################### -# EC2 instance for CloudFront VPC origin -######################################### - -module "ec2" { - source = "terraform-aws-modules/ec2-instance/aws" - version = "~> 6.0" - - name = "ec2-vpc-origin-${random_pet.this.id}" -} - -######## -# Extra -######## - -resource "random_pet" "this" { - length = 2 } diff --git a/examples/complete/versions.tf b/examples/complete/versions.tf index e8acfa2..0ec9906 100644 --- a/examples/complete/versions.tf +++ b/examples/complete/versions.tf @@ -6,10 +6,6 @@ terraform { source = "hashicorp/aws" version = ">= 6.20" } - random = { - source = "hashicorp/random" - version = ">= 2.0" - } null = { source = "hashicorp/null" version = ">= 2.0" diff --git a/main.tf b/main.tf index 553da58..8fc52b3 100644 --- a/main.tf +++ b/main.tf @@ -26,22 +26,23 @@ resource "aws_cloudfront_distribution" "this" { content { allowed_methods = default_cache_behavior.value.allowed_methods - cache_policy_id = try(coalesce(default_cache_behavior.value.cache_policy_id, try(data.aws_cloudfront_cache_policy.this[default_cache_behavior.value.cache_policy_name].id, null))) + cache_policy_id = try(coalesce(default_cache_behavior.value.cache_policy_id, try(data.aws_cloudfront_cache_policy.this[default_cache_behavior.value.cache_policy_name].id, null)), null) cached_methods = default_cache_behavior.value.cached_methods compress = default_cache_behavior.value.compress default_ttl = default_cache_behavior.value.default_ttl field_level_encryption_id = default_cache_behavior.value.field_level_encryption_id dynamic "forwarded_values" { - for_each = default_cache_behavior.value.forwarded_values != null ? [default_cache_behavior.value.forwarded_values] : [] + # If a cache policy is specified, then `forwarded_values` must not be set + for_each = default_cache_behavior.value.cache_policy_id == null && default_cache_behavior.value.cache_policy_name == null && default_cache_behavior.value.forwarded_values != null ? [default_cache_behavior.value.forwarded_values] : [] content { dynamic "cookies" { for_each = [forwarded_values.value.cookies] content { - forward = cookies.value.cookies_forward - whitelisted_names = cookies.value.cookies_whitelisted_names + forward = cookies.value.forward + whitelisted_names = cookies.value.whitelisted_names } } @@ -80,9 +81,9 @@ resource "aws_cloudfront_distribution" "this" { max_ttl = default_cache_behavior.value.max_ttl min_ttl = default_cache_behavior.value.min_ttl - origin_request_policy_id = try(coalesce(default_cache_behavior.value.origin_request_policy_id, try(data.aws_cloudfront_origin_request_policy.this[default_cache_behavior.value.origin_request_policy_name].id, null))) + origin_request_policy_id = try(coalesce(default_cache_behavior.value.origin_request_policy_id, try(data.aws_cloudfront_origin_request_policy.this[default_cache_behavior.value.origin_request_policy_name].id, null)), null) realtime_log_config_arn = default_cache_behavior.value.realtime_log_config_arn - response_headers_policy_id = try(coalesce(default_cache_behavior.value.response_headers_policy_id, try(data.aws_cloudfront_response_headers_policy.this[default_cache_behavior.value.response_headers_policy_name].id, null))) + response_headers_policy_id = try(coalesce(default_cache_behavior.value.response_headers_policy_id, try(data.aws_cloudfront_response_headers_policy.this[default_cache_behavior.value.response_headers_policy_name].id, null)), null) smooth_streaming = default_cache_behavior.value.smooth_streaming target_origin_id = default_cache_behavior.value.target_origin_id trusted_key_groups = default_cache_behavior.value.trusted_key_groups @@ -112,21 +113,22 @@ resource "aws_cloudfront_distribution" "this" { content { allowed_methods = ordered_cache_behavior.value.allowed_methods cached_methods = ordered_cache_behavior.value.cached_methods - cache_policy_id = try(coalesce(ordered_cache_behavior.value.cache_policy_id, try(data.aws_cloudfront_cache_policy.this[ordered_cache_behavior.value.cache_policy_name].id, null))) + cache_policy_id = try(coalesce(ordered_cache_behavior.value.cache_policy_id, try(data.aws_cloudfront_cache_policy.this[ordered_cache_behavior.value.cache_policy_name].id, null)), null) compress = ordered_cache_behavior.value.compress default_ttl = ordered_cache_behavior.value.default_ttl field_level_encryption_id = ordered_cache_behavior.value.field_level_encryption_id dynamic "forwarded_values" { - for_each = ordered_cache_behavior.value.forwarded_values != null ? [ordered_cache_behavior.value.forwarded_values] : [] + # If a cache policy is specified, then `forwarded_values` must not be set + for_each = ordered_cache_behavior.value.cache_policy_id == null && ordered_cache_behavior.value.cache_policy_name == null && ordered_cache_behavior.value.forwarded_values != null ? [ordered_cache_behavior.value.forwarded_values] : [] content { dynamic "cookies" { for_each = [forwarded_values.value.cookies] content { - forward = cookies.value.cookies_forward - whitelisted_names = cookies.value.cookies_whitelisted_names + forward = cookies.value.forward + whitelisted_names = cookies.value.whitelisted_names } } @@ -154,7 +156,7 @@ resource "aws_cloudfront_distribution" "this" { } dynamic "lambda_function_association" { - for_each = ordered_cache_behavior.value.lambda_function_association != null ? ordered_cache_behavior.value.lambda_function_association : [] + for_each = ordered_cache_behavior.value.lambda_function_association != null ? ordered_cache_behavior.value.lambda_function_association : {} content { event_type = try(coalesce(lambda_function_association.value.event_type, lambda_function_association.key)) @@ -165,10 +167,10 @@ resource "aws_cloudfront_distribution" "this" { max_ttl = ordered_cache_behavior.value.max_ttl min_ttl = ordered_cache_behavior.value.min_ttl - origin_request_policy_id = try(coalesce(ordered_cache_behavior.value.origin_request_policy_id, try(data.aws_cloudfront_origin_request_policy.this[ordered_cache_behavior.value.origin_request_policy_name].id, null))) + origin_request_policy_id = try(coalesce(ordered_cache_behavior.value.origin_request_policy_id, try(data.aws_cloudfront_origin_request_policy.this[ordered_cache_behavior.value.origin_request_policy_name].id, null)), null) path_pattern = ordered_cache_behavior.value.path_pattern realtime_log_config_arn = ordered_cache_behavior.value.realtime_log_config_arn - response_headers_policy_id = try(coalesce(ordered_cache_behavior.value.response_headers_policy_id, try(data.aws_cloudfront_response_headers_policy.this[ordered_cache_behavior.value.response_headers_policy_name].id, null))) + response_headers_policy_id = try(coalesce(ordered_cache_behavior.value.response_headers_policy_id, try(data.aws_cloudfront_response_headers_policy.this[ordered_cache_behavior.value.response_headers_policy_name].id, null)), null) smooth_streaming = ordered_cache_behavior.value.smooth_streaming target_origin_id = ordered_cache_behavior.value.target_origin_id trusted_key_groups = ordered_cache_behavior.value.trusted_key_groups @@ -183,8 +185,9 @@ resource "aws_cloudfront_distribution" "this" { content { dynamic "failover_criteria" { for_each = [origin_group.value.failover_criteria] + content { - status_codes = origin_group.value.failover_status_codes + status_codes = failover_criteria.value.status_codes } } @@ -231,7 +234,7 @@ resource "aws_cloudfront_distribution" "this" { } domain_name = origin.value.domain_name - origin_access_control_id = try(coalesce(origin.value.origin_access_control_id, try(aws_cloudfront_origin_access_control.this[origin_access_control_key].id, null), null)) + origin_access_control_id = try(coalesce(origin.value.origin_access_control_id, try(aws_cloudfront_origin_access_control.this[origin.value.origin_access_control_key].id, null)), null) origin_id = try(coalesce(origin.value.origin_id, origin.key)) origin_path = origin.value.origin_path @@ -252,7 +255,7 @@ resource "aws_cloudfront_distribution" "this" { content { origin_keepalive_timeout = vpc_origin_config.value.origin_keepalive_timeout origin_read_timeout = vpc_origin_config.value.origin_read_timeout - vpc_origin_id = try(coalesce(vpc_origin_config.value.vpc_origin_id, try(aws_cloudfront_vpc_origin.this[vpc_origin_key], null), null)) + vpc_origin_id = try(coalesce(vpc_origin_config.value.vpc_origin_id, try(aws_cloudfront_vpc_origin.this[vpc_origin_config.value.vpc_origin_key].id, null)), null) } } } diff --git a/variables.tf b/variables.tf index 3e34c58..d7ac1e3 100644 --- a/variables.tf +++ b/variables.tf @@ -54,7 +54,7 @@ variable "default_cache_behavior" { type = object({ allowed_methods = optional(list(string), ["GET", "HEAD", "OPTIONS"]) cache_policy_id = optional(string) - catch_policy_name = optional(string) + cache_policy_name = optional(string) cached_methods = optional(list(string), ["GET", "HEAD"]) compress = optional(bool, true) default_ttl = optional(number) @@ -67,7 +67,14 @@ variable "default_cache_behavior" { headers = optional(list(string)) query_string = optional(bool, false) query_string_cache_keys = optional(list(string)) - })) + }), + { + cookies = { + forward = "none" + } + query_string = false + } + ) function_association = optional(map(object({ event_type = optional(string) function_arn = optional(string) @@ -137,7 +144,7 @@ variable "ordered_cache_behavior" { allowed_methods = optional(list(string), ["GET", "HEAD", "OPTIONS"]) cached_methods = optional(list(string), ["GET", "HEAD"]) cache_policy_id = optional(string) - catch_policy_name = optional(string) + cache_policy_name = optional(string) compress = optional(bool, true) default_ttl = optional(number) field_level_encryption_id = optional(string) @@ -149,7 +156,14 @@ variable "ordered_cache_behavior" { headers = optional(list(string)) query_string = optional(bool, false) query_string_cache_keys = optional(list(string)) - })) + }), + { + cookies = { + forward = "none" + } + query_string = false + } + ) function_association = optional(map(object({ event_type = optional(string) function_arn = optional(string) @@ -207,7 +221,7 @@ variable "origin" { origin_keepalive_timeout = optional(number) origin_read_timeout = optional(number) origin_protocol_policy = string - origin_ssl_protocols = list(string) + origin_ssl_protocols = optional(list(string), ["TLSv1.2"]) })) domain_name = string origin_access_control_key = optional(string) @@ -231,7 +245,7 @@ variable "origin" { } variable "price_class" { - description = "The price class for this distribution. One of PriceClass_All, PriceClass_200, PriceClass_100" + description = "The price class for this distribution. One of `PriceClass_All`, `PriceClass_200`, `PriceClass_100`" type = string default = null } @@ -268,7 +282,7 @@ variable "viewer_certificate" { description = "The SSL configuration for this distribution" type = object({ acm_certificate_arn = optional(string) - cloudfront_default_certificate = optional(bool, true) + cloudfront_default_certificate = optional(bool) iam_certificate_id = optional(string) minimum_protocol_version = optional(string, "TLSv1.2_2025") ssl_support_method = optional(string) @@ -324,8 +338,8 @@ variable "vpc_origin" { name = optional(string) origin_protocol_policy = string origin_ssl_protocols = object({ - items = list(string) - quantity = number + items = optional(list(string), ["TLSv1.2"]) + quantity = optional(number, 1) }) timeouts = optional(object({ create = optional(string)