From 26317d8447332489ee07d83cdb3db0e67950743e Mon Sep 17 00:00:00 2001 From: vijay-stephen Date: Mon, 10 Nov 2025 12:05:16 +0530 Subject: [PATCH 1/3] support for origin group --- README.md | 19 +++++ examples/origin-group-dr/.terraform.lock.hcl | 45 ++++++++++ examples/origin-group-dr/README.md | 41 +++++++++ examples/origin-group-dr/main.tf | 88 ++++++++++++++++++++ examples/origin-group-dr/outputs.tf | 9 ++ examples/origin-group-dr/provider.tf | 18 ++++ examples/origin-group-dr/variables.tf | 23 +++++ main.tf | 25 ++++++ variables.tf | 14 ++++ version.tf | 2 +- 10 files changed, 283 insertions(+), 1 deletion(-) create mode 100644 examples/origin-group-dr/.terraform.lock.hcl create mode 100644 examples/origin-group-dr/README.md create mode 100644 examples/origin-group-dr/main.tf create mode 100644 examples/origin-group-dr/outputs.tf create mode 100644 examples/origin-group-dr/provider.tf create mode 100644 examples/origin-group-dr/variables.tf diff --git a/README.md b/README.md index 79cb05c..1202ea0 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,8 @@ For more information about this repository and its usage, please see [Terraform ## Usage +**Important Note**: When using custom ACM certificates (`acm_details.domain_name` is set), the certificate must be created in the `us-east-1` region as required by CloudFront. If you're deploying in a different region, you'll need to configure a provider alias for `us-east-1`. + To see a full example, check out the [main.tf](https://github.com/sourcefuse/terraform-aws-arc-cloudfront/blob/main/example/main.tf) file in the example folder. ``` tcl @@ -162,6 +164,22 @@ module "cloudfront" { response_page_path = "/custom_404.html" }] + # Origin Groups for Disaster Recovery + origin_groups = [{ + origin_id = "failover-group" + failover_criteria = { + status_codes = [403, 404, 500, 502, 503, 504] + } + members = [ + { + origin_id = "primary-origin" + }, + { + origin_id = "secondary-origin" + } + ] + }] + s3_kms_details = { s3_bucket_encryption_type = "SSE-S3", //Encryption for S3 bucket , options : `SSE-S3` , `SSE-KMS` kms_key_administrators = [], @@ -236,6 +254,7 @@ module "cloudfront" { | [geo\_restriction](#input\_geo\_restriction) | Geographic restriction |
object({
restriction_type = optional(string, "blacklist")
locations = optional(list(string), ["KP", "RU"])
})
|
{
"locations": [],
"restriction_type": "none"
}
| no | | [logging\_bucket](#input\_logging\_bucket) | S3 bucket used for storing logs | `string` | `null` | no | | [namespace](#input\_namespace) | Namespace for the resources. | `string` | `null` | no | +| [origin\_groups](#input\_origin\_groups) | List of Origin Groups for failover support |
list(object({
origin_id = string
failover_criteria = object({
status_codes = list(number)
})
members = list(object({
origin_id = string
}))
}))
| `[]` | no | | [origin\_request\_policies](#input\_origin\_request\_policies) | Origin request policies,
eg. {
"origin-req-policy" = {
cookies\_config = {
cookie\_behavior = "none",
items = []
},
headers\_config = {
header\_behavior = "whitelist",
items = ["Accept", "Accept-Charset", "Accept-Datetime", "Accept-Language",
"Access-Control-Request-Method", "Access-Control-Request-Headers", "CloudFront-Forwarded-Proto", "CloudFront-Is-Android-Viewer",
"CloudFront-Is-Desktop-Viewer", "CloudFront-Is-IOS-Viewer"]
},
query\_strings\_config = {
query\_string\_behavior = "none",
items = []
}
} } |
map(object({
cookies_config = object({
cookie_behavior = string
items = list(string)
}),
headers_config = object({
header_behavior = string
items = list(string)
}),
query_strings_config = object({
query_string_behavior = string
items = list(string)
})
}))
| `{}` | no | | [origins](#input\_origins) | List of Origins for Cloudfront |
list(object({
origin_type = string // S3 or custom origin
origin_id = string
origin_path = optional(string)
domain_name = string
bucket_name = optional(string) // required of origin is S3
create_bucket = bool // required of origin is S3
connection_attempts = optional(number, 3)
connection_timeout = optional(number, 10)
cors_configuration = optional(any) // cors for S3
origin_shield = optional(object({
enabled = bool
origin_shield_region = string
}), {
enabled = false
origin_shield_region = null
})
custom_origin_config = optional(object({
http_port = number
https_port = number
origin_protocol_policy = string
origin_ssl_protocols = list(string)
origin_keepalive_timeout = optional(number, 5)
origin_read_timeout = optional(number, 30)
}))
}))
| `[]` | no | | [price\_class](#input\_price\_class) | Price class for this distribution. One of PriceClass\_All, PriceClass\_200, PriceClass\_100. | `string` | `"PriceClass_All"` | no | diff --git a/examples/origin-group-dr/.terraform.lock.hcl b/examples/origin-group-dr/.terraform.lock.hcl new file mode 100644 index 0000000..ca514f2 --- /dev/null +++ b/examples/origin-group-dr/.terraform.lock.hcl @@ -0,0 +1,45 @@ +# This file is maintained automatically by "terraform init". +# Manual edits may be lost in future updates. + +provider "registry.terraform.io/hashicorp/aws" { + version = "4.67.0" + constraints = ">= 2.0.0, >= 4.0.0, ~> 4.0, >= 4.9.0, < 6.0.0" + hashes = [ + "h1:5Zfo3GfRSWBaXs4TGQNOflr1XaYj6pRnVJLX5VAjFX4=", + "zh:0843017ecc24385f2b45f2c5fce79dc25b258e50d516877b3affee3bef34f060", + "zh:19876066cfa60de91834ec569a6448dab8c2518b8a71b5ca870b2444febddac6", + "zh:24995686b2ad88c1ffaa242e36eee791fc6070e6144f418048c4ce24d0ba5183", + "zh:4a002990b9f4d6d225d82cb2fb8805789ffef791999ee5d9cb1fef579aeff8f1", + "zh:559a2b5ace06b878c6de3ecf19b94fbae3512562f7a51e930674b16c2f606e29", + "zh:6a07da13b86b9753b95d4d8218f6dae874cf34699bca1470d6effbb4dee7f4b7", + "zh:768b3bfd126c3b77dc975c7c0e5db3207e4f9997cf41aa3385c63206242ba043", + "zh:7be5177e698d4b547083cc738b977742d70ed68487ce6f49ecd0c94dbf9d1362", + "zh:8b562a818915fb0d85959257095251a05c76f3467caa3ba95c583ba5fe043f9b", + "zh:9b12af85486a96aedd8d7984b0ff811a4b42e3d88dad1a3fb4c0b580d04fa425", + "zh:9c385d03a958b54e2afd5279cd8c7cbdd2d6ca5c7d6a333e61092331f38af7cf", + "zh:b3ca45f2821a89af417787df8289cb4314b273d29555ad3b2a5ab98bb4816b3b", + "zh:da3c317f1db2469615ab40aa6baba63b5643bae7110ff855277a1fb9d8eb4f2c", + "zh:dc6430622a8dc5cdab359a8704aec81d3825ea1d305bbb3bbd032b1c6adfae0c", + "zh:fac0d2ddeadf9ec53da87922f666e1e73a603a611c57bcbc4b86ac2821619b1d", + ] +} + +provider "registry.terraform.io/hashicorp/time" { + version = "0.13.1" + constraints = ">= 0.7.0" + hashes = [ + "h1:ZT5ppCNIModqk3iOkVt5my8b8yBHmDpl663JtXAIRqM=", + "zh:02cb9aab1002f0f2a94a4f85acec8893297dc75915f7404c165983f720a54b74", + "zh:04429b2b31a492d19e5ecf999b116d396dac0b24bba0d0fb19ecaefe193fdb8f", + "zh:26f8e51bb7c275c404ba6028c1b530312066009194db721a8427a7bc5cdbc83a", + "zh:772ff8dbdbef968651ab3ae76d04afd355c32f8a868d03244db3f8496e462690", + "zh:78d5eefdd9e494defcb3c68d282b8f96630502cac21d1ea161f53cfe9bb483b3", + "zh:898db5d2b6bd6ca5457dccb52eedbc7c5b1a71e4a4658381bcbb38cedbbda328", + "zh:8de913bf09a3fa7bedc29fec18c47c571d0c7a3d0644322c46f3aa648cf30cd8", + "zh:9402102c86a87bdfe7e501ffbb9c685c32bbcefcfcf897fd7d53df414c36877b", + "zh:b18b9bb1726bb8cfbefc0a29cf3657c82578001f514bcf4c079839b6776c47f0", + "zh:b9d31fdc4faecb909d7c5ce41d2479dd0536862a963df434be4b16e8e4edc94d", + "zh:c951e9f39cca3446c060bd63933ebb89cedde9523904813973fbc3d11863ba75", + "zh:e5b773c0d07e962291be0e9b413c7a22c044b8c7b58c76e8aa91d1659990dfb5", + ] +} diff --git a/examples/origin-group-dr/README.md b/examples/origin-group-dr/README.md new file mode 100644 index 0000000..1ec3c9b --- /dev/null +++ b/examples/origin-group-dr/README.md @@ -0,0 +1,41 @@ +# CloudFront Origin Group for Disaster Recovery + +This example demonstrates how to configure CloudFront with origin groups for disaster recovery scenarios. + +## Features + +- **Primary and Secondary Origins**: Configure multiple origins for failover +- **Automatic Failover**: CloudFront automatically routes traffic to secondary origin when primary fails +- **Configurable Status Codes**: Define which HTTP status codes trigger failover +- **High Availability**: Ensures continuous service availability during outages + +## Usage + +```hcl +origin_groups = [ + { + origin_id = "failover-group" + failover_criteria = { + status_codes = [403, 404, 500, 502, 503, 504] + } + members = [ + { + origin_id = "primary-origin" + }, + { + origin_id = "secondary-origin" + } + ] + } +] +``` + +## Deployment + +```bash +terraform init +terraform plan +terraform apply +``` + +The distribution will automatically failover to the secondary origin when the primary returns any of the configured status codes. diff --git a/examples/origin-group-dr/main.tf b/examples/origin-group-dr/main.tf new file mode 100644 index 0000000..28059a2 --- /dev/null +++ b/examples/origin-group-dr/main.tf @@ -0,0 +1,88 @@ +################################################################################ +## Tags Module +################################################################################ + +module "tags" { + source = "sourcefuse/arc-tags/aws" + version = "1.2.3" + + environment = var.environment + project = var.project_name + + extra_tags = { + RepoName = "terraform-aws-arc-cloudfront" + } +} + +################################################################################ +## Module Cloudfront with Origin Groups for DR +################################################################################ + +module "cloudfront" { + source = "../../" + + providers = { + aws.acm = aws.acm + } + + origins = [ + { + origin_type = "s3" + origin_id = "primary-origin" + domain_name = "" + bucket_name = "arc-poc-primary-bucket" + create_bucket = true + }, + { + origin_type = "s3" + origin_id = "secondary-origin" + domain_name = "" + bucket_name = "arc-pocsecondary-bucket" + create_bucket = true + } + ] + + origin_groups = [ + { + origin_id = "failover-group" + failover_criteria = { + status_codes = [403, 404, 500, 502, 503, 504] + } + members = [ + { + origin_id = "primary-origin" + }, + { + origin_id = "secondary-origin" + } + ] + } + ] + + namespace = "dr-example" + description = "CloudFront distribution with origin group for DR" + route53_root_domain = var.route53_root_domain + create_route53_records = var.create_route53_records + aliases = [] + enable_logging = false + + default_cache_behavior = { + origin_id = "failover-group" + allowed_methods = ["GET", "HEAD", "OPTIONS"] + cached_methods = ["GET", "HEAD"] + compress = true + viewer_protocol_policy = "redirect-to-https" + use_aws_managed_cache_policy = true + cache_policy_name = "CachingOptimized" + use_aws_managed_origin_request_policy = true + origin_request_policy_name = "CORS-S3Origin" + } + + viewer_certificate = { + cloudfront_default_certificate = true + minimum_protocol_version = "TLSv1.2_2018" + ssl_support_method = "sni-only" + } + + tags = module.tags.tags +} diff --git a/examples/origin-group-dr/outputs.tf b/examples/origin-group-dr/outputs.tf new file mode 100644 index 0000000..c2bd250 --- /dev/null +++ b/examples/origin-group-dr/outputs.tf @@ -0,0 +1,9 @@ +output "cloudfront_id" { + value = module.cloudfront.cloudfront_id + description = "CloudFront distribution ID" +} + +output "cloudfront_domain_name" { + value = module.cloudfront.cloudfront_domain_name + description = "CloudFront domain name" +} diff --git a/examples/origin-group-dr/provider.tf b/examples/origin-group-dr/provider.tf new file mode 100644 index 0000000..a28ec82 --- /dev/null +++ b/examples/origin-group-dr/provider.tf @@ -0,0 +1,18 @@ +terraform { + required_version = ">= 1.5" + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 4.0, < 6.0" + } + } +} + +provider "aws" { + region = "us-east-1" +} + +provider "aws" { + alias = "acm" + region = "us-east-1" +} diff --git a/examples/origin-group-dr/variables.tf b/examples/origin-group-dr/variables.tf new file mode 100644 index 0000000..50c52a9 --- /dev/null +++ b/examples/origin-group-dr/variables.tf @@ -0,0 +1,23 @@ +variable "environment" { + description = "Environment name" + type = string + default = "dev" +} + +variable "project_name" { + description = "Project name" + type = string + default = "arc-cloudfront-dr" +} + +variable "route53_root_domain" { + description = "Route53 root domain" + type = string + default = null +} + +variable "create_route53_records" { + description = "Create Route53 records" + type = bool + default = false +} diff --git a/main.tf b/main.tf index aab22fc..30ae5ef 100755 --- a/main.tf +++ b/main.tf @@ -264,6 +264,31 @@ resource "aws_cloudfront_distribution" "this" { } } + dynamic "origin_group" { + for_each = { + for index, group in var.origin_groups : + group.origin_id => group + } + iterator = g + + content { + origin_id = g.value.origin_id + + failover_criteria { + status_codes = g.value.failover_criteria.status_codes + } + + dynamic "member" { + for_each = g.value.members + iterator = m + + content { + origin_id = m.value.origin_id + } + } + } + } + comment = var.description enabled = true is_ipv6_enabled = true diff --git a/variables.tf b/variables.tf index 13d7304..6d90f98 100755 --- a/variables.tf +++ b/variables.tf @@ -40,6 +40,20 @@ variable "origins" { default = [] } +variable "origin_groups" { + type = list(object({ + origin_id = string + failover_criteria = object({ + status_codes = list(number) + }) + members = list(object({ + origin_id = string + })) + })) + description = "List of Origin Groups for failover support" + default = [] +} + variable "default_root_object" { type = string default = "index.html" diff --git a/version.tf b/version.tf index d1b0fbe..9d247f8 100644 --- a/version.tf +++ b/version.tf @@ -5,7 +5,7 @@ terraform { aws = { source = "hashicorp/aws" version = ">= 4.0, < 6.0" - configuration_aliases = [aws.acm] + configuration_aliases = [aws.acm] // Curremtly we cannot make it as optional : https://github.com/hashicorp/terraform/issues/30461 } } } From f8f9c89e25793df64a805099ffc9701f7257a495 Mon Sep 17 00:00:00 2001 From: vijay-stephen Date: Mon, 10 Nov 2025 14:34:23 +0530 Subject: [PATCH 2/3] working version of origin group, removed external depency --- README.md | 10 +- examples/origin-group-dr/.terraform.lock.hcl | 64 +++++----- examples/origin-group-dr/main.tf | 38 +++++- examples/origin-group-dr/provider.tf | 6 +- main.tf | 2 - s3-bucket.tf | 127 ++++++------------- variables.tf | 13 -- version.tf | 4 +- 8 files changed, 118 insertions(+), 146 deletions(-) diff --git a/README.md b/README.md index 1202ea0..a86f9b5 100644 --- a/README.md +++ b/README.md @@ -200,7 +200,7 @@ module "cloudfront" { | Name | Version | |------|---------| | [terraform](#requirement\_terraform) | >= 1.5, < 2.0.0 | -| [aws](#requirement\_aws) | >= 4.0, < 6.0 | +| [aws](#requirement\_aws) | >= 6.5.0, < 7.0.0 | ## Providers @@ -213,9 +213,9 @@ module "cloudfront" { | Name | Source | Version | |------|--------|---------| -| [kms](#module\_kms) | ./modules/kms | n/a | -| [s3\_bucket](#module\_s3\_bucket) | git::https://github.com/cloudposse/terraform-aws-s3-bucket | 3.1.2 | -| [s3\_bucket\_logs](#module\_s3\_bucket\_logs) | git::https://github.com/cloudposse/terraform-aws-s3-bucket | 3.1.2 | +| [kms](#module\_kms) | sourcefuse/arc-kms/aws | 1.0.11 | +| [s3\_bucket](#module\_s3\_bucket) | sourcefuse/arc-s3/aws | 0.0.5 | +| [s3\_bucket\_logs](#module\_s3\_bucket\_logs) | sourcefuse/arc-s3/aws | 0.0.5 | ## Resources @@ -232,7 +232,6 @@ module "cloudfront" { | [aws_route53_record.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/route53_record) | resource | | [aws_s3_bucket_policy.cdn_bucket_policy](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_policy) | resource | | [aws_caller_identity.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/caller_identity) | data source | -| [aws_partition.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/partition) | data source | | [aws_route53_zone.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/route53_zone) | data source | | [aws_s3_bucket.origin](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/s3_bucket) | data source | @@ -244,7 +243,6 @@ module "cloudfront" { | [aliases](#input\_aliases) | Fully qualified domain name for site being hosted | `list(string)` | n/a | yes | | [cache\_behaviors](#input\_cache\_behaviors) | Set the cache behaviors for the distribution , Note:- You cannot use an origin request policy in a cache behavior without a cache policy. |
list(object({
origin_id = string // should be same as what is given in origins
path_pattern = string
allowed_methods = list(string)
cached_methods = list(string)
response_headers_policy_name = optional(string, null)
use_aws_managed_response_headers_policy = optional(bool, false)
function_association = optional(list(object({ // Specific event to trigger this function. Valid values: viewer-request or viewer-response.
event_type = string,
function_arn = string
})))
lambda_function_association = optional(list(object({ // A config block that triggers a lambda function with specific actions (maximum 4).
event_type = string,
lambda_arn = string,
include_body = bool // When set to true it exposes the request body to the lambda function.
})))
use_aws_managed_cache_policy = bool,
cache_policy_name = string, // It can be custom or aws managed policy name , if custom cache_policies variable key should match
use_aws_managed_origin_request_policy = optional(bool),
origin_request_policy_name = optional(string), // It can be custom or aws managed policy name , if custom origin_request_policies variable key should match
compress = bool,
viewer_protocol_policy = string
}))
| `[]` | no | | [cache\_policies](#input\_cache\_policies) | Cache policies,
eg. {
"cache-policy-1" = {
default\_ttl = 86400,
max\_ttl = 31536000,
min\_ttl = 0,
cookies\_config = {
cookie\_behavior = "none",
items = []
},
headers\_config = {
header\_behavior = "whitelist",
items = ["Authorization", "Origin", "Accept", "Access-Control-Request-Method", "Access-Control-Request-Headers", "Referer"]
},
query\_string\_behavior = {
header\_behavior = "none",
items = []
},
query\_strings\_config = {
query\_string\_behavior = "none",
items = []
}
} } |
map(object(
{
default_ttl = number,
max_ttl = number,
min_ttl = number,
cookies_config = object({
cookie_behavior = string
items = list(string)
}),
headers_config = object({
header_behavior = string
items = list(string)
}),
query_strings_config = object({
query_string_behavior = string
items = list(string)
})
}
))
| `{}` | no | -| [cors\_configuration](#input\_cors\_configuration) | Specifies the allowed headers, methods, origins and exposed headers when using CORS on this bucket |
list(object({
allowed_headers = list(string)
allowed_methods = list(string)
allowed_origins = list(string)
expose_headers = list(string)
max_age_seconds = number
}))
| `null` | no | | [create\_route53\_records](#input\_create\_route53\_records) | made optional route53 | `bool` | `false` | no | | [custom\_error\_responses](#input\_custom\_error\_responses) | One or more custom error response elements |
list(object({
error_caching_min_ttl = optional(number),
error_code = string,
response_code = optional(string),
response_page_path = optional(string) // eg: /custom_404.html
}))
| `[]` | no | | [default\_cache\_behavior](#input\_default\_cache\_behavior) | Default cache behavior for the distribution |
object({
origin_id = string // should be same as what is given in origins
allowed_methods = list(string)
cached_methods = list(string)
response_headers_policy_name = optional(string, null)
use_aws_managed_response_headers_policy = optional(bool, false)
function_association = optional(list(object({ // A config block that triggers a lambda function with specific actions (maximum 4).
event_type = string, // Specific event to trigger this function. Valid values: viewer-request or viewer-response.
function_arn = string
})))
lambda_function_association = optional(list(object({ // A config block that triggers a lambda function with specific actions (maximum 4).
event_type = string,
lambda_arn = string,
include_body = bool // When set to true it exposes the request body to the lambda function.
})))
use_aws_managed_cache_policy = bool,
cache_policy_name = string, // It can be custom or aws managed policy name , if custom cache_policies variable key should match
use_aws_managed_origin_request_policy = optional(bool),
origin_request_policy_name = optional(string), // It can be custom or aws managed policy name , if custom origin_request_policies variable key should match
compress = bool
viewer_protocol_policy = optional(string, "redirect-to-https")
})
| n/a | yes | diff --git a/examples/origin-group-dr/.terraform.lock.hcl b/examples/origin-group-dr/.terraform.lock.hcl index ca514f2..497f399 100644 --- a/examples/origin-group-dr/.terraform.lock.hcl +++ b/examples/origin-group-dr/.terraform.lock.hcl @@ -2,44 +2,44 @@ # Manual edits may be lost in future updates. provider "registry.terraform.io/hashicorp/aws" { - version = "4.67.0" - constraints = ">= 2.0.0, >= 4.0.0, ~> 4.0, >= 4.9.0, < 6.0.0" + version = "6.20.0" + constraints = ">= 5.0.0, >= 6.5.0, < 7.0.0" hashes = [ - "h1:5Zfo3GfRSWBaXs4TGQNOflr1XaYj6pRnVJLX5VAjFX4=", - "zh:0843017ecc24385f2b45f2c5fce79dc25b258e50d516877b3affee3bef34f060", - "zh:19876066cfa60de91834ec569a6448dab8c2518b8a71b5ca870b2444febddac6", - "zh:24995686b2ad88c1ffaa242e36eee791fc6070e6144f418048c4ce24d0ba5183", - "zh:4a002990b9f4d6d225d82cb2fb8805789ffef791999ee5d9cb1fef579aeff8f1", - "zh:559a2b5ace06b878c6de3ecf19b94fbae3512562f7a51e930674b16c2f606e29", - "zh:6a07da13b86b9753b95d4d8218f6dae874cf34699bca1470d6effbb4dee7f4b7", - "zh:768b3bfd126c3b77dc975c7c0e5db3207e4f9997cf41aa3385c63206242ba043", - "zh:7be5177e698d4b547083cc738b977742d70ed68487ce6f49ecd0c94dbf9d1362", - "zh:8b562a818915fb0d85959257095251a05c76f3467caa3ba95c583ba5fe043f9b", + "h1:x9OpnfcX0zYkPdJ38K5vSNe78QZReatBzk/pIlSDU1I=", + "zh:05173910d3031e6ba7b985188cff620bbe48cc6bb35ffc30238b9dcd9201c0d4", + "zh:0fd36a822e15c473593b5ca826ddfabe248a89833721960781aac65f05a55bc1", + "zh:13f478b843ef1fb52780e2771098ae42d6aaceab50d56bc31fdb02ddce6f4621", + "zh:16c5a17af7d0fc5329957bc48b5d10b987240076203453a108190132622d40bf", + "zh:290342af2da821383b8b5a897920cde2ae01f25768fe5e2df80b80bcc34421cb", + "zh:4cfe46a2ed6324467b896aa0dd7fbcf74a94a046d963c8a8fb865e817bcfb955", + "zh:78c243a5ec47f6d7066104191480fb13eb85995000cc7c80bc92383baea286a5", + "zh:88004fb58967b32061120d1d668e1af1f988da694994cddd11d2d4f7726f5054", + "zh:94184d7f00ec5e30d572d56979110e6cf6d5071583713b0e357cca72785e5365", "zh:9b12af85486a96aedd8d7984b0ff811a4b42e3d88dad1a3fb4c0b580d04fa425", - "zh:9c385d03a958b54e2afd5279cd8c7cbdd2d6ca5c7d6a333e61092331f38af7cf", - "zh:b3ca45f2821a89af417787df8289cb4314b273d29555ad3b2a5ab98bb4816b3b", - "zh:da3c317f1db2469615ab40aa6baba63b5643bae7110ff855277a1fb9d8eb4f2c", - "zh:dc6430622a8dc5cdab359a8704aec81d3825ea1d305bbb3bbd032b1c6adfae0c", - "zh:fac0d2ddeadf9ec53da87922f666e1e73a603a611c57bcbc4b86ac2821619b1d", + "zh:b219d9c012db507a761c3f3a9147573f25d81d524a076e7c13099f87a50ae386", + "zh:beee1b49f414d1d8c03c9c4264725e288d77a676c093e3017de7d10a8ac45582", + "zh:e2e76be18ff4c2bf7cfcea9898475ec320b5574c01181581d76d047a2ee7be5c", + "zh:fa9afad49b0b2586032afcda2a5813da6e4ba9e56a8a25167b8f659b68e3983d", + "zh:fd284feb9f95a20ba848a9027f3c4298d75be6e882df40f075d60955f7bcf170", ] } -provider "registry.terraform.io/hashicorp/time" { - version = "0.13.1" - constraints = ">= 0.7.0" +provider "registry.terraform.io/hashicorp/random" { + version = "3.7.2" + constraints = "~> 3.0" hashes = [ - "h1:ZT5ppCNIModqk3iOkVt5my8b8yBHmDpl663JtXAIRqM=", - "zh:02cb9aab1002f0f2a94a4f85acec8893297dc75915f7404c165983f720a54b74", - "zh:04429b2b31a492d19e5ecf999b116d396dac0b24bba0d0fb19ecaefe193fdb8f", - "zh:26f8e51bb7c275c404ba6028c1b530312066009194db721a8427a7bc5cdbc83a", - "zh:772ff8dbdbef968651ab3ae76d04afd355c32f8a868d03244db3f8496e462690", + "h1:KG4NuIBl1mRWU0KD/BGfCi1YN/j3F7H4YgeeM7iSdNs=", + "zh:14829603a32e4bc4d05062f059e545a91e27ff033756b48afbae6b3c835f508f", + "zh:1527fb07d9fea400d70e9e6eb4a2b918d5060d604749b6f1c361518e7da546dc", + "zh:1e86bcd7ebec85ba336b423ba1db046aeaa3c0e5f921039b3f1a6fc2f978feab", + "zh:24536dec8bde66753f4b4030b8f3ef43c196d69cccbea1c382d01b222478c7a3", + "zh:29f1786486759fad9b0ce4fdfbbfece9343ad47cd50119045075e05afe49d212", + "zh:4d701e978c2dd8604ba1ce962b047607701e65c078cb22e97171513e9e57491f", "zh:78d5eefdd9e494defcb3c68d282b8f96630502cac21d1ea161f53cfe9bb483b3", - "zh:898db5d2b6bd6ca5457dccb52eedbc7c5b1a71e4a4658381bcbb38cedbbda328", - "zh:8de913bf09a3fa7bedc29fec18c47c571d0c7a3d0644322c46f3aa648cf30cd8", - "zh:9402102c86a87bdfe7e501ffbb9c685c32bbcefcfcf897fd7d53df414c36877b", - "zh:b18b9bb1726bb8cfbefc0a29cf3657c82578001f514bcf4c079839b6776c47f0", - "zh:b9d31fdc4faecb909d7c5ce41d2479dd0536862a963df434be4b16e8e4edc94d", - "zh:c951e9f39cca3446c060bd63933ebb89cedde9523904813973fbc3d11863ba75", - "zh:e5b773c0d07e962291be0e9b413c7a22c044b8c7b58c76e8aa91d1659990dfb5", + "zh:7b8434212eef0f8c83f5a90c6d76feaf850f6502b61b53c329e85b3b281cba34", + "zh:ac8a23c212258b7976e1621275e3af7099e7e4a3d4478cf8d5d2a27f3bc3e967", + "zh:b516ca74431f3df4c6cf90ddcdb4042c626e026317a33c53f0b445a3d93b720d", + "zh:dc76e4326aec2490c1600d6871a95e78f9050f9ce427c71707ea412a2f2f1a62", + "zh:eac7b63e86c749c7d48f527671c7aee5b4e26c10be6ad7232d6860167f99dbb0", ] } diff --git a/examples/origin-group-dr/main.tf b/examples/origin-group-dr/main.tf index 28059a2..b06882e 100644 --- a/examples/origin-group-dr/main.tf +++ b/examples/origin-group-dr/main.tf @@ -10,10 +10,38 @@ module "tags" { project = var.project_name extra_tags = { - RepoName = "terraform-aws-arc-cloudfront" + RepoName = "terraform-aws-refarch-cloudfront" } } +################################################################################ +## S3 Buckets for Origins +################################################################################ + +module "primary_bucket" { + source = "sourcefuse/arc-s3/aws" + version = "0.0.5" + + name = "${var.project_name}-primary-${random_id.bucket_suffix.hex}" + acl = "private" + + tags = module.tags.tags +} + +module "secondary_bucket" { + source = "sourcefuse/arc-s3/aws" + version = "0.0.5" + + name = "${var.project_name}-secondary-${random_id.bucket_suffix.hex}" + acl = "private" + + tags = module.tags.tags +} + +resource "random_id" "bucket_suffix" { + byte_length = 4 +} + ################################################################################ ## Module Cloudfront with Origin Groups for DR ################################################################################ @@ -30,15 +58,15 @@ module "cloudfront" { origin_type = "s3" origin_id = "primary-origin" domain_name = "" - bucket_name = "arc-poc-primary-bucket" - create_bucket = true + bucket_name = module.primary_bucket.bucket_id + create_bucket = false }, { origin_type = "s3" origin_id = "secondary-origin" domain_name = "" - bucket_name = "arc-pocsecondary-bucket" - create_bucket = true + bucket_name = module.secondary_bucket.bucket_id + create_bucket = false } ] diff --git a/examples/origin-group-dr/provider.tf b/examples/origin-group-dr/provider.tf index a28ec82..4092ca9 100644 --- a/examples/origin-group-dr/provider.tf +++ b/examples/origin-group-dr/provider.tf @@ -3,7 +3,11 @@ terraform { required_providers { aws = { source = "hashicorp/aws" - version = ">= 4.0, < 6.0" + version = ">= 5.0, < 7.0" + } + random = { + source = "hashicorp/random" + version = "3.7.2" } } } diff --git a/main.tf b/main.tf index 30ae5ef..79b1768 100755 --- a/main.tf +++ b/main.tf @@ -1,5 +1,3 @@ -data "aws_partition" "this" {} - data "aws_caller_identity" "this" {} resource "aws_cloudfront_cache_policy" "this" { diff --git a/s3-bucket.tf b/s3-bucket.tf index 7d3cb41..49eb1c7 100644 --- a/s3-bucket.tf +++ b/s3-bucket.tf @@ -4,16 +4,40 @@ // Module creates KMS and its related resources module "kms" { - count = var.s3_kms_details.s3_bucket_encryption_type == "SSE-KMS" && var.s3_kms_details.kms_key_arn == null ? 1 : 0 + source = "sourcefuse/arc-kms/aws" + version = "1.0.11" + count = var.s3_kms_details.s3_bucket_encryption_type == "SSE-KMS" && var.s3_kms_details.kms_key_arn == null ? 1 : 0 - source = "./modules/kms" - environment = local.environment - alias = "${local.environment}/s3/${var.logging_bucket}" - kms_key_administrators = var.s3_kms_details.kms_key_administrators - kms_key_users = var.s3_kms_details.kms_key_users + alias = "${local.environment}-s3-${var.logging_bucket}" deletion_window_in_days = 7 - aws_services = ["s3.amazonaws.com", "cloudfront.amazonaws.com"] - tags = var.tags + policy = jsonencode({ + Version = "2012-10-17" + Statement = [ + { + Sid = "Enable IAM User Permissions" + Effect = "Allow" + Principal = { + AWS = "arn:aws:iam::${data.aws_caller_identity.this.account_id}:root" + } + Action = "kms:*" + Resource = "*" + }, + { + Sid = "Allow S3 Service" + Effect = "Allow" + Principal = { + Service = "s3.amazonaws.com" + } + Action = [ + "kms:Decrypt", + "kms:GenerateDataKey" + ] + Resource = "*" + } + ] + }) + + tags = var.tags } data "aws_s3_bucket" "origin" { @@ -25,95 +49,28 @@ data "aws_s3_bucket" "origin" { } module "s3_bucket" { - source = "git::https://github.com/cloudposse/terraform-aws-s3-bucket?ref=3.1.2" + source = "sourcefuse/arc-s3/aws" + version = "0.0.5" for_each = { for index, origin in var.origins : origin.origin_id => origin if origin.create_bucket == true && origin.origin_type == "s3" } - //count = var.create_bucket ? 1 : 0 - - bucket_name = each.value.bucket_name - environment = local.environment - namespace = var.namespace - - enabled = true - acl = "private" - versioning_enabled = true - bucket_key_enabled = var.s3_kms_details.s3_bucket_encryption_type == "SSE-KMS" ? true : false - kms_master_key_arn = var.s3_kms_details.s3_bucket_encryption_type == "SSE-S3" ? "" : (var.s3_kms_details.kms_key_arn == null ? module.kms[0].key_arn : var.s3_kms_details.kms_key_arn) - sse_algorithm = var.s3_kms_details.s3_bucket_encryption_type == "SSE-S3" ? "AES256" : "aws:kms" - - policy = jsonencode({ - Version = "2012-10-17", - Statement = [ - { - Effect = "Allow", - Principal = { - AWS = "arn:${data.aws_partition.this.partition}:iam::${data.aws_caller_identity.this.account_id}:root" - }, - Action = [ - "s3:GetObjectAttributes", - "s3:GetObject", - "s3:PutObject", - "s3:ListMultipartUploadParts", - "s3:AbortMultipartUpload" - ], - Resource = "arn:${data.aws_partition.this.partition}:s3:::${var.namespace}-${terraform.workspace}-deployment/*" - } - ] - }) + name = each.value.bucket_name + acl = "private" - privileged_principal_actions = [ - "s3:GetObject", - "s3:ListBucket", - "s3:GetBucketLocation" - ] - cors_configuration = var.cors_configuration - tags = var.tags + tags = var.tags } module "s3_bucket_logs" { - source = "git::https://github.com/cloudposse/terraform-aws-s3-bucket?ref=3.1.2" + source = "sourcefuse/arc-s3/aws" + version = "0.0.5" count = var.enable_logging ? 1 : 0 - bucket_name = "${var.logging_bucket}-logging" - environment = local.environment - namespace = var.namespace - - acl = "log-delivery-write" - versioning_enabled = false - bucket_key_enabled = var.s3_kms_details.s3_bucket_encryption_type == "SSE-KMS" ? true : false - kms_master_key_arn = var.s3_kms_details.s3_bucket_encryption_type == "SSE-S3" ? "" : (var.s3_kms_details.kms_key_arn == null ? module.kms[0].key_arn : var.s3_kms_details.kms_key_arn) - sse_algorithm = var.s3_kms_details.s3_bucket_encryption_type == "SSE-S3" ? "AES256" : "aws:kms" - - policy = jsonencode({ - Version = "2012-10-17", - Statement = [ - { - Effect = "Allow", - Principal = { - AWS = "arn:${data.aws_partition.this.partition}:iam::${data.aws_caller_identity.this.account_id}:root" - }, - Action = [ - "s3:GetObjectAttributes", - "s3:GetObject", - "s3:PutObject", - "s3:ListMultipartUploadParts", - "s3:AbortMultipartUpload" - ], - Resource = "arn:${data.aws_partition.this.partition}:s3:::${var.namespace}-${terraform.workspace}-deployment/*" - } - ] - }) + name = "${var.logging_bucket}-logging" + acl = "private" - privileged_principal_actions = [ - "s3:GetObject", - "s3:ListBucket", - "s3:GetBucketLocation" - ] - cors_configuration = var.cors_configuration - tags = var.tags + tags = var.tags } diff --git a/variables.tf b/variables.tf index 6d90f98..6780afd 100755 --- a/variables.tf +++ b/variables.tf @@ -126,19 +126,6 @@ variable "cache_behaviors" { default = [] } -variable "cors_configuration" { - type = list(object({ - allowed_headers = list(string) - allowed_methods = list(string) - allowed_origins = list(string) - expose_headers = list(string) - max_age_seconds = number - })) - default = null - - description = "Specifies the allowed headers, methods, origins and exposed headers when using CORS on this bucket" -} - variable "tags" { type = map(string) description = "Tags for AWS resources" diff --git a/version.tf b/version.tf index 9d247f8..964b8a6 100644 --- a/version.tf +++ b/version.tf @@ -4,8 +4,8 @@ terraform { required_providers { aws = { source = "hashicorp/aws" - version = ">= 4.0, < 6.0" - configuration_aliases = [aws.acm] // Curremtly we cannot make it as optional : https://github.com/hashicorp/terraform/issues/30461 + version = ">= 6.5.0, < 7.0.0" + configuration_aliases = [aws.acm] // Currently we cannot make it as optional : https://github.com/hashicorp/terraform/issues/30461 } } } From d8e9adb35d7c58bf92432152a9d05363d5432b13 Mon Sep 17 00:00:00 2001 From: vijay-stephen Date: Mon, 10 Nov 2025 17:24:22 +0530 Subject: [PATCH 3/3] enabled logging --- README.md | 8 +- examples/cf-edge-function/README.md | 1 - examples/cf-edge-function/main.tf | 1 - examples/cf-edge-function/variables.tf | 6 - examples/custom-origin/main.tf | 1 - examples/origin-group-dr/main.tf | 1 - examples/response-headers/.terraform.lock.hcl | 45 +++++++ examples/response-headers/README.md | 42 ++++++ examples/response-headers/locals.tf | 125 ++++++++++++++++++ examples/response-headers/main.tf | 43 ++++++ examples/response-headers/outputs.tf | 9 ++ examples/response-headers/provider.tf | 24 ++++ examples/response-headers/variables.tf | 17 +++ examples/s3-origin-external-domain/main.tf | 1 - examples/s3-origin/main.tf | 1 - main.tf | 51 ++++--- outputs.tf | 2 +- s3-bucket.tf | 10 +- variables.tf | 31 ++--- 19 files changed, 355 insertions(+), 64 deletions(-) create mode 100644 examples/response-headers/.terraform.lock.hcl create mode 100644 examples/response-headers/README.md create mode 100644 examples/response-headers/locals.tf create mode 100644 examples/response-headers/main.tf create mode 100644 examples/response-headers/outputs.tf create mode 100644 examples/response-headers/provider.tf create mode 100644 examples/response-headers/variables.tf diff --git a/README.md b/README.md index a86f9b5..5c66d45 100644 --- a/README.md +++ b/README.md @@ -214,8 +214,8 @@ module "cloudfront" { | Name | Source | Version | |------|--------|---------| | [kms](#module\_kms) | sourcefuse/arc-kms/aws | 1.0.11 | -| [s3\_bucket](#module\_s3\_bucket) | sourcefuse/arc-s3/aws | 0.0.5 | -| [s3\_bucket\_logs](#module\_s3\_bucket\_logs) | sourcefuse/arc-s3/aws | 0.0.5 | +| [s3\_bucket](#module\_s3\_bucket) | sourcefuse/arc-s3/aws | 0.0.7 | +| [s3\_bucket\_logs](#module\_s3\_bucket\_logs) | sourcefuse/arc-s3/aws | 0.0.7 | ## Resources @@ -248,10 +248,8 @@ module "cloudfront" { | [default\_cache\_behavior](#input\_default\_cache\_behavior) | Default cache behavior for the distribution |
object({
origin_id = string // should be same as what is given in origins
allowed_methods = list(string)
cached_methods = list(string)
response_headers_policy_name = optional(string, null)
use_aws_managed_response_headers_policy = optional(bool, false)
function_association = optional(list(object({ // A config block that triggers a lambda function with specific actions (maximum 4).
event_type = string, // Specific event to trigger this function. Valid values: viewer-request or viewer-response.
function_arn = string
})))
lambda_function_association = optional(list(object({ // A config block that triggers a lambda function with specific actions (maximum 4).
event_type = string,
lambda_arn = string,
include_body = bool // When set to true it exposes the request body to the lambda function.
})))
use_aws_managed_cache_policy = bool,
cache_policy_name = string, // It can be custom or aws managed policy name , if custom cache_policies variable key should match
use_aws_managed_origin_request_policy = optional(bool),
origin_request_policy_name = optional(string), // It can be custom or aws managed policy name , if custom origin_request_policies variable key should match
compress = bool
viewer_protocol_policy = optional(string, "redirect-to-https")
})
| n/a | yes | | [default\_root\_object](#input\_default\_root\_object) | Object that you want CloudFront to return (for example, index.html) when an end user requests the root URL. | `string` | `"index.html"` | no | | [description](#input\_description) | CloudFron destribution description | `string` | n/a | yes | -| [enable\_logging](#input\_enable\_logging) | Enable logging for Clouffront destribution, this will create new S3 bucket | `bool` | `false` | no | | [geo\_restriction](#input\_geo\_restriction) | Geographic restriction |
object({
restriction_type = optional(string, "blacklist")
locations = optional(list(string), ["KP", "RU"])
})
|
{
"locations": [],
"restriction_type": "none"
}
| no | -| [logging\_bucket](#input\_logging\_bucket) | S3 bucket used for storing logs | `string` | `null` | no | -| [namespace](#input\_namespace) | Namespace for the resources. | `string` | `null` | no | +| [logging\_config](#input\_logging\_config) | CloudFront logging configuration |
object({
enabled = optional(bool, false)
bucket = optional(string)
})
|
{
"bucket": null,
"enabled": false
}
| no | | [origin\_groups](#input\_origin\_groups) | List of Origin Groups for failover support |
list(object({
origin_id = string
failover_criteria = object({
status_codes = list(number)
})
members = list(object({
origin_id = string
}))
}))
| `[]` | no | | [origin\_request\_policies](#input\_origin\_request\_policies) | Origin request policies,
eg. {
"origin-req-policy" = {
cookies\_config = {
cookie\_behavior = "none",
items = []
},
headers\_config = {
header\_behavior = "whitelist",
items = ["Accept", "Accept-Charset", "Accept-Datetime", "Accept-Language",
"Access-Control-Request-Method", "Access-Control-Request-Headers", "CloudFront-Forwarded-Proto", "CloudFront-Is-Android-Viewer",
"CloudFront-Is-Desktop-Viewer", "CloudFront-Is-IOS-Viewer"]
},
query\_strings\_config = {
query\_string\_behavior = "none",
items = []
}
} } |
map(object({
cookies_config = object({
cookie_behavior = string
items = list(string)
}),
headers_config = object({
header_behavior = string
items = list(string)
}),
query_strings_config = object({
query_string_behavior = string
items = list(string)
})
}))
| `{}` | no | | [origins](#input\_origins) | List of Origins for Cloudfront |
list(object({
origin_type = string // S3 or custom origin
origin_id = string
origin_path = optional(string)
domain_name = string
bucket_name = optional(string) // required of origin is S3
create_bucket = bool // required of origin is S3
connection_attempts = optional(number, 3)
connection_timeout = optional(number, 10)
cors_configuration = optional(any) // cors for S3
origin_shield = optional(object({
enabled = bool
origin_shield_region = string
}), {
enabled = false
origin_shield_region = null
})
custom_origin_config = optional(object({
http_port = number
https_port = number
origin_protocol_policy = string
origin_ssl_protocols = list(string)
origin_keepalive_timeout = optional(number, 5)
origin_read_timeout = optional(number, 30)
}))
}))
| `[]` | no | diff --git a/examples/cf-edge-function/README.md b/examples/cf-edge-function/README.md index 62cdbd9..682a3b1 100644 --- a/examples/cf-edge-function/README.md +++ b/examples/cf-edge-function/README.md @@ -41,7 +41,6 @@ | Name | Description | Type | Default | Required | |------|-------------|------|---------|:--------:| | [create\_route53\_records](#input\_create\_route53\_records) | made optional route53 | `bool` | `true` | no | -| [enable\_logging](#input\_enable\_logging) | Enable logging for Cloudfront destribution, this will create new S3 bucket | `bool` | `false` | no | | [region](#input\_region) | AWS region | `string` | `"us-east-1"` | no | ## Outputs diff --git a/examples/cf-edge-function/main.tf b/examples/cf-edge-function/main.tf index 6dd7456..09402cd 100644 --- a/examples/cf-edge-function/main.tf +++ b/examples/cf-edge-function/main.tf @@ -38,7 +38,6 @@ module "cloudfront" { route53_root_domain = "arc-poc.link" // Used to fetch the Hosted Zone create_route53_records = var.create_route53_records aliases = ["cf.arc-poc.link", "www.cf.arc-poc.link", "test.arc-poc.link", "*.arc-poc.link", "test1.arc-poc.link"] - enable_logging = var.enable_logging // Create a new S3 bucket for storing Cloudfront logs default_cache_behavior = { origin_id = "cloudfront-arc", diff --git a/examples/cf-edge-function/variables.tf b/examples/cf-edge-function/variables.tf index 4606565..e917892 100644 --- a/examples/cf-edge-function/variables.tf +++ b/examples/cf-edge-function/variables.tf @@ -4,12 +4,6 @@ variable "region" { default = "us-east-1" } -variable "enable_logging" { - type = bool - description = "Enable logging for Cloudfront destribution, this will create new S3 bucket" - default = false -} - variable "create_route53_records" { type = bool description = "made optional route53" diff --git a/examples/custom-origin/main.tf b/examples/custom-origin/main.tf index c2b44db..5eb9882 100644 --- a/examples/custom-origin/main.tf +++ b/examples/custom-origin/main.tf @@ -29,7 +29,6 @@ module "cloudfront" { route53_root_domain = local.cloudfront_config.route53_root_domain create_route53_records = local.cloudfront_config.create_route53_records aliases = local.cloudfront_config.aliases - enable_logging = local.cloudfront_config.enable_logging default_cache_behavior = local.cloudfront_config.default_cache_behavior viewer_certificate = local.cloudfront_config.viewer_certificate acm_details = local.cloudfront_config.acm_details diff --git a/examples/origin-group-dr/main.tf b/examples/origin-group-dr/main.tf index b06882e..5f29017 100644 --- a/examples/origin-group-dr/main.tf +++ b/examples/origin-group-dr/main.tf @@ -92,7 +92,6 @@ module "cloudfront" { route53_root_domain = var.route53_root_domain create_route53_records = var.create_route53_records aliases = [] - enable_logging = false default_cache_behavior = { origin_id = "failover-group" diff --git a/examples/response-headers/.terraform.lock.hcl b/examples/response-headers/.terraform.lock.hcl new file mode 100644 index 0000000..497f399 --- /dev/null +++ b/examples/response-headers/.terraform.lock.hcl @@ -0,0 +1,45 @@ +# This file is maintained automatically by "terraform init". +# Manual edits may be lost in future updates. + +provider "registry.terraform.io/hashicorp/aws" { + version = "6.20.0" + constraints = ">= 5.0.0, >= 6.5.0, < 7.0.0" + hashes = [ + "h1:x9OpnfcX0zYkPdJ38K5vSNe78QZReatBzk/pIlSDU1I=", + "zh:05173910d3031e6ba7b985188cff620bbe48cc6bb35ffc30238b9dcd9201c0d4", + "zh:0fd36a822e15c473593b5ca826ddfabe248a89833721960781aac65f05a55bc1", + "zh:13f478b843ef1fb52780e2771098ae42d6aaceab50d56bc31fdb02ddce6f4621", + "zh:16c5a17af7d0fc5329957bc48b5d10b987240076203453a108190132622d40bf", + "zh:290342af2da821383b8b5a897920cde2ae01f25768fe5e2df80b80bcc34421cb", + "zh:4cfe46a2ed6324467b896aa0dd7fbcf74a94a046d963c8a8fb865e817bcfb955", + "zh:78c243a5ec47f6d7066104191480fb13eb85995000cc7c80bc92383baea286a5", + "zh:88004fb58967b32061120d1d668e1af1f988da694994cddd11d2d4f7726f5054", + "zh:94184d7f00ec5e30d572d56979110e6cf6d5071583713b0e357cca72785e5365", + "zh:9b12af85486a96aedd8d7984b0ff811a4b42e3d88dad1a3fb4c0b580d04fa425", + "zh:b219d9c012db507a761c3f3a9147573f25d81d524a076e7c13099f87a50ae386", + "zh:beee1b49f414d1d8c03c9c4264725e288d77a676c093e3017de7d10a8ac45582", + "zh:e2e76be18ff4c2bf7cfcea9898475ec320b5574c01181581d76d047a2ee7be5c", + "zh:fa9afad49b0b2586032afcda2a5813da6e4ba9e56a8a25167b8f659b68e3983d", + "zh:fd284feb9f95a20ba848a9027f3c4298d75be6e882df40f075d60955f7bcf170", + ] +} + +provider "registry.terraform.io/hashicorp/random" { + version = "3.7.2" + constraints = "~> 3.0" + hashes = [ + "h1:KG4NuIBl1mRWU0KD/BGfCi1YN/j3F7H4YgeeM7iSdNs=", + "zh:14829603a32e4bc4d05062f059e545a91e27ff033756b48afbae6b3c835f508f", + "zh:1527fb07d9fea400d70e9e6eb4a2b918d5060d604749b6f1c361518e7da546dc", + "zh:1e86bcd7ebec85ba336b423ba1db046aeaa3c0e5f921039b3f1a6fc2f978feab", + "zh:24536dec8bde66753f4b4030b8f3ef43c196d69cccbea1c382d01b222478c7a3", + "zh:29f1786486759fad9b0ce4fdfbbfece9343ad47cd50119045075e05afe49d212", + "zh:4d701e978c2dd8604ba1ce962b047607701e65c078cb22e97171513e9e57491f", + "zh:78d5eefdd9e494defcb3c68d282b8f96630502cac21d1ea161f53cfe9bb483b3", + "zh:7b8434212eef0f8c83f5a90c6d76feaf850f6502b61b53c329e85b3b281cba34", + "zh:ac8a23c212258b7976e1621275e3af7099e7e4a3d4478cf8d5d2a27f3bc3e967", + "zh:b516ca74431f3df4c6cf90ddcdb4042c626e026317a33c53f0b445a3d93b720d", + "zh:dc76e4326aec2490c1600d6871a95e78f9050f9ce427c71707ea412a2f2f1a62", + "zh:eac7b63e86c749c7d48f527671c7aee5b4e26c10be6ad7232d6860167f99dbb0", + ] +} diff --git a/examples/response-headers/README.md b/examples/response-headers/README.md new file mode 100644 index 0000000..b866cc5 --- /dev/null +++ b/examples/response-headers/README.md @@ -0,0 +1,42 @@ +# CloudFront Response Headers Policy Example + +This example demonstrates how to configure CloudFront with custom response headers policies for enhanced security and CORS handling. + +## Features + +- **Security Headers**: Content Security Policy, X-Frame-Options, X-Content-Type-Options, etc. +- **CORS Configuration**: Cross-Origin Resource Sharing headers +- **Custom Headers**: Add custom headers to responses +- **S3 Origin**: Uses S3 bucket as origin + +## Response Headers Policy Configuration + +The example includes: + +### Security Headers +- `X-Content-Type-Options: nosniff` +- `X-Frame-Options: DENY` +- `Referrer-Policy: strict-origin-when-cross-origin` +- `X-XSS-Protection: 1; mode=block` +- `Strict-Transport-Security: max-age=31536000; includeSubDomains` +- `Content-Security-Policy: default-src 'self'; script-src 'self' 'unsafe-inline'` + +### CORS Headers +- `Access-Control-Allow-Origin: *` +- `Access-Control-Allow-Methods: GET, HEAD, OPTIONS` +- `Access-Control-Allow-Headers: *` +- `Access-Control-Expose-Headers: ETag` +- `Access-Control-Max-Age: 86400` + +### Custom Headers +- `X-Custom-Header: CustomValue` + +## Usage + +```bash +terraform init +terraform plan +terraform apply +``` + +The distribution will automatically add the configured response headers to all responses from the origin. diff --git a/examples/response-headers/locals.tf b/examples/response-headers/locals.tf new file mode 100644 index 0000000..89b57ee --- /dev/null +++ b/examples/response-headers/locals.tf @@ -0,0 +1,125 @@ +locals { + cloudfront_config = { + origins = [{ + origin_type = "s3", + origin_id = "arc-dev-s3-origin", + domain_name = "", + bucket_name = "arc-dev-s3-origin-6781", + create_bucket = true + }] + + namespace = "dev" + description = "Distribution with custom response headers policy" + default_root_object = "index.html" + route53_root_domain = null + create_route53_records = false + aliases = [] + logging_config = { + bucket = "arc-dev-s3-origin-6718" + enabled = true + } + + + default_cache_behavior = { + origin_id = "arc-dev-s3-origin" + allowed_methods = ["GET", "HEAD"] + cached_methods = ["GET", "HEAD"] + compress = false + viewer_protocol_policy = "redirect-to-https" + use_aws_managed_cache_policy = true + cache_policy_name = "CachingOptimized" + use_aws_managed_origin_request_policy = true + origin_request_policy_name = "CORS-S3Origin" + use_aws_managed_response_headers_policy = false + response_headers_policy_name = "security-headers-policy" + } + + viewer_certificate = { + cloudfront_default_certificate = true + minimum_protocol_version = "TLSv1.2_2021" + ssl_support_method = "sni-only" + } + + response_headers_policy = { + "security-headers-policy" = { + name = "security-headers-policy" + comment = "Security headers policy for CloudFront" + + cors_config = { + access_control_allow_credentials = false + access_control_allow_headers = { + items = ["*"] + } + access_control_allow_methods = { + items = ["GET", "HEAD", "OPTIONS"] + } + access_control_allow_origins = { + items = ["*"] + } + access_control_expose_headers = { + items = ["ETag"] + } + access_control_max_age_sec = 86400 + origin_override = false + } + + security_headers_config = { + content_type_options = { + override = true + } + frame_options = { + frame_option = "DENY" + override = true + } + referrer_policy = { + referrer_policy = "strict-origin-when-cross-origin" + override = true + } + xss_protection = { + mode_block = true + protection = true + override = true + report_uri = "" + } + strict_transport_security = { + access_control_max_age_sec = "31536000" + include_subdomains = true + preload = false + override = true + } + content_security_policy = { + content_security_policy = "default-src 'self'; script-src 'self' 'unsafe-inline'" + override = true + } + } + + custom_headers_config = { + items = [ + { + header = "X-Custom-Header" + override = true + value = "CustomValue" + } + ] + } + } + } + + custom_error_responses = [ + { + error_caching_min_ttl = 300 + error_code = "403" + response_code = "200" + response_page_path = "/index.html" + }, + { + error_caching_min_ttl = 10 + error_code = "404" + response_code = "200" + response_page_path = "/index.html" + } + ] + + price_class = "PriceClass_All" + } +} diff --git a/examples/response-headers/main.tf b/examples/response-headers/main.tf new file mode 100644 index 0000000..7ed86b8 --- /dev/null +++ b/examples/response-headers/main.tf @@ -0,0 +1,43 @@ +################################################################################ +## Tags Module +################################################################################ + +module "tags" { + source = "sourcefuse/arc-tags/aws" + version = "1.2.3" + + environment = var.environment + project = var.project_name + + extra_tags = { + RepoName = "terraform-aws-refarch-cloudfront" + } +} + +################################################################################ +## Module Cloudfront with Response Headers Policy +################################################################################ + +module "cloudfront" { + source = "../../" + + origins = local.cloudfront_config.origins + namespace = local.cloudfront_config.namespace + description = local.cloudfront_config.description + default_root_object = local.cloudfront_config.default_root_object + route53_root_domain = local.cloudfront_config.route53_root_domain + create_route53_records = local.cloudfront_config.create_route53_records + aliases = local.cloudfront_config.aliases + logging_config = local.cloudfront_config.logging_config + default_cache_behavior = local.cloudfront_config.default_cache_behavior + viewer_certificate = local.cloudfront_config.viewer_certificate + custom_error_responses = local.cloudfront_config.custom_error_responses + price_class = local.cloudfront_config.price_class + response_headers_policy = local.cloudfront_config.response_headers_policy + + providers = { + aws.acm = aws.acm + } + + tags = module.tags.tags +} diff --git a/examples/response-headers/outputs.tf b/examples/response-headers/outputs.tf new file mode 100644 index 0000000..b9f72fe --- /dev/null +++ b/examples/response-headers/outputs.tf @@ -0,0 +1,9 @@ +output "cloudfront_domain_name" { + value = module.cloudfront.cloudfront_domain_name + description = "CloudFront Domain name" +} + +output "acm_certificate_arn" { + value = module.cloudfront.acm_certificate_arn + description = "Certificate ARN" +} diff --git a/examples/response-headers/provider.tf b/examples/response-headers/provider.tf new file mode 100644 index 0000000..4590416 --- /dev/null +++ b/examples/response-headers/provider.tf @@ -0,0 +1,24 @@ +terraform { + required_version = "~> 1.3" + + required_providers { + aws = { + version = ">= 5.0, < 7.0" + source = "hashicorp/aws" + } + } + + # backend "s3" {} +} + +provider "aws" { + alias = "acm" + region = "us-east-1" +} + +provider "aws" { + region = var.region + default_tags { + tags = module.tags.tags + } +} diff --git a/examples/response-headers/variables.tf b/examples/response-headers/variables.tf new file mode 100644 index 0000000..dcd1291 --- /dev/null +++ b/examples/response-headers/variables.tf @@ -0,0 +1,17 @@ +variable "project_name" { + type = string + description = "Name of the project." + default = "arc" +} + +variable "region" { + type = string + description = "AWS region" + default = "us-east-1" +} + +variable "environment" { + type = string + description = "ENV for the resource" + default = "dev" +} diff --git a/examples/s3-origin-external-domain/main.tf b/examples/s3-origin-external-domain/main.tf index f7f4100..3d9d8cf 100644 --- a/examples/s3-origin-external-domain/main.tf +++ b/examples/s3-origin-external-domain/main.tf @@ -29,7 +29,6 @@ module "cloudfront" { route53_root_domain = local.cloudfront_config.route53_root_domain create_route53_records = local.cloudfront_config.create_route53_records aliases = local.cloudfront_config.aliases - enable_logging = local.cloudfront_config.enable_logging default_cache_behavior = local.cloudfront_config.default_cache_behavior viewer_certificate = local.cloudfront_config.viewer_certificate acm_details = local.cloudfront_config.acm_details diff --git a/examples/s3-origin/main.tf b/examples/s3-origin/main.tf index b4a13a0..dd62f20 100644 --- a/examples/s3-origin/main.tf +++ b/examples/s3-origin/main.tf @@ -29,7 +29,6 @@ module "cloudfront" { route53_root_domain = local.cloudfront_config.route53_root_domain create_route53_records = local.cloudfront_config.create_route53_records aliases = local.cloudfront_config.aliases - enable_logging = local.cloudfront_config.enable_logging default_cache_behavior = local.cloudfront_config.default_cache_behavior viewer_certificate = local.cloudfront_config.viewer_certificate acm_details = local.cloudfront_config.acm_details diff --git a/main.tf b/main.tf index 79b1768..f776173 100755 --- a/main.tf +++ b/main.tf @@ -142,37 +142,36 @@ resource "aws_cloudfront_response_headers_policy" "this" { } # TODO: Fix issue in setting below configs - # dynamic "remove_headers_config" { - # for_each = each.value.remove_headers_config == null ? [] : [each.value.remove_headers_config] + 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 - - # content { - # header = items - # } - # } - # } - # } + content { + dynamic "items" { + for_each = remove_headers_config + content { + header = 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 + dynamic "custom_headers_config" { + for_each = each.value.custom_headers_config == null ? [] : [each.value.custom_headers_config] - # content { - # header = items.header - # override = try(items.override, false) - # value = try(items.value, "none") - # } - # } + content { + dynamic "items" { + for_each = custom_headers_config.value.items - # } - # } + content { + header = items.value.header + override = items.value.override + value = items.value.value + } + } + } + } } @@ -404,7 +403,7 @@ resource "aws_cloudfront_distribution" "this" { } dynamic "logging_config" { - for_each = var.enable_logging ? [1] : [] + for_each = var.logging_config.enabled ? [1] : [] content { include_cookies = false diff --git a/outputs.tf b/outputs.tf index 22d9aaf..568ecb5 100755 --- a/outputs.tf +++ b/outputs.tf @@ -6,7 +6,7 @@ output "origin_s3_bucket" { } output "logging_s3_bucket" { - value = var.enable_logging ? module.s3_bucket_logs[0].bucket_id : null + value = var.logging_config.enabled ? module.s3_bucket_logs[0].bucket_id : null description = "Logging bucket name" } diff --git a/s3-bucket.tf b/s3-bucket.tf index 49eb1c7..2802b5a 100644 --- a/s3-bucket.tf +++ b/s3-bucket.tf @@ -8,7 +8,7 @@ module "kms" { version = "1.0.11" count = var.s3_kms_details.s3_bucket_encryption_type == "SSE-KMS" && var.s3_kms_details.kms_key_arn == null ? 1 : 0 - alias = "${local.environment}-s3-${var.logging_bucket}" + alias = "${local.environment}-s3-${var.logging_config.bucket}" deletion_window_in_days = 7 policy = jsonencode({ Version = "2012-10-17" @@ -50,7 +50,7 @@ data "aws_s3_bucket" "origin" { module "s3_bucket" { source = "sourcefuse/arc-s3/aws" - version = "0.0.5" + version = "0.0.7" for_each = { for index, origin in var.origins : origin.origin_id => origin @@ -65,11 +65,11 @@ module "s3_bucket" { module "s3_bucket_logs" { source = "sourcefuse/arc-s3/aws" - version = "0.0.5" + version = "0.0.7" - count = var.enable_logging ? 1 : 0 + count = var.logging_config.enabled && var.logging_config.bucket != null ? 1 : 0 - name = "${var.logging_bucket}-logging" + name = "${var.logging_config.bucket}-logging" acl = "private" tags = var.tags diff --git a/variables.tf b/variables.tf index 6780afd..b228d18 100755 --- a/variables.tf +++ b/variables.tf @@ -3,10 +3,21 @@ variable "aliases" { type = list(string) } -variable "logging_bucket" { - description = "S3 bucket used for storing logs" - type = string - default = null +variable "logging_config" { + description = "CloudFront logging configuration" + type = object({ + enabled = optional(bool, false) + bucket = optional(string) + }) + default = { + enabled = false + bucket = null + } + + validation { + condition = !var.logging_config.enabled || var.logging_config.bucket != null + error_message = "bucket must be provided when enabled is true." + } } variable "origins" { @@ -132,12 +143,6 @@ variable "tags" { default = {} } -variable "namespace" { - type = string - description = "Namespace for the resources." - default = null -} - variable "create_route53_records" { type = bool description = "made optional route53" @@ -376,11 +381,7 @@ variable "s3_kms_details" { } } -variable "enable_logging" { - type = bool - description = "Enable logging for Clouffront destribution, this will create new S3 bucket" - default = false -} + variable "acm_details" { type = object({