Skip to content

Commit 5f12dd4

Browse files
bryantbiggsclark42
andauthored
feat: Add support for CloudFront standard logging v2 (#192)
Co-authored-by: Jean 'clark' EYMERT <jean@kamorion.com>
1 parent 2d7d47b commit 5f12dd4

File tree

6 files changed

+158
-24
lines changed

6 files changed

+158
-24
lines changed

README.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,9 @@ No modules.
178178
| [aws_cloudfront_origin_access_control.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/cloudfront_origin_access_control) | resource |
179179
| [aws_cloudfront_response_headers_policy.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/cloudfront_response_headers_policy) | resource |
180180
| [aws_cloudfront_vpc_origin.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/cloudfront_vpc_origin) | resource |
181+
| [aws_cloudwatch_log_delivery.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/cloudwatch_log_delivery) | resource |
182+
| [aws_cloudwatch_log_delivery_destination.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/cloudwatch_log_delivery_destination) | resource |
183+
| [aws_cloudwatch_log_delivery_source.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/cloudwatch_log_delivery_source) | resource |
181184
| [aws_cloudfront_cache_policy.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/cloudfront_cache_policy) | data source |
182185
| [aws_cloudfront_origin_request_policy.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/cloudfront_origin_request_policy) | data source |
183186
| [aws_cloudfront_response_headers_policy.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/cloudfront_response_headers_policy) | data source |
@@ -196,6 +199,7 @@ No modules.
196199
| <a name="input_custom_error_response"></a> [custom\_error\_response](#input\_custom\_error\_response) | One or more custom error response elements | <pre>list(object({<br/> error_caching_min_ttl = optional(number)<br/> error_code = number<br/> response_code = optional(number)<br/> response_page_path = optional(string)<br/> }))</pre> | `null` | no |
197200
| <a name="input_default_cache_behavior"></a> [default\_cache\_behavior](#input\_default\_cache\_behavior) | The default cache behavior for this distribution | <pre>object({<br/> allowed_methods = optional(list(string), ["GET", "HEAD", "OPTIONS"])<br/> cache_policy_id = optional(string)<br/> cache_policy_name = optional(string)<br/> cached_methods = optional(list(string), ["GET", "HEAD"])<br/> compress = optional(bool, true)<br/> default_ttl = optional(number)<br/> field_level_encryption_id = optional(string)<br/> forwarded_values = optional(object({<br/> cookies = object({<br/> forward = optional(string, "none")<br/> whitelisted_names = optional(list(string))<br/> })<br/> headers = optional(list(string))<br/> query_string = optional(bool, false)<br/> query_string_cache_keys = optional(list(string))<br/> }),<br/> {<br/> cookies = {<br/> forward = "none"<br/> }<br/> query_string = false<br/> }<br/> )<br/> function_association = optional(map(object({<br/> event_type = optional(string)<br/> function_arn = optional(string)<br/> function_key = optional(string)<br/> })))<br/> grpc_config = optional(object({<br/> enabled = optional(bool)<br/> }))<br/> lambda_function_association = optional(map(object({<br/> event_type = optional(string)<br/> include_body = optional(bool)<br/> lambda_arn = string<br/> })))<br/> max_ttl = optional(number)<br/> min_ttl = optional(number)<br/> origin_request_policy_id = optional(string)<br/> origin_request_policy_name = optional(string)<br/> realtime_log_config_arn = optional(string)<br/> response_headers_policy_id = optional(string)<br/> response_headers_policy_key = optional(string)<br/> response_headers_policy_name = optional(string)<br/> smooth_streaming = optional(bool)<br/> target_origin_id = string<br/> trusted_key_groups = optional(list(string))<br/> trusted_signers = optional(list(string))<br/> viewer_protocol_policy = optional(string, "https-only")<br/> })</pre> | n/a | yes |
198201
| <a name="input_default_root_object"></a> [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 |
202+
| <a name="input_enable_v2_logging"></a> [enable\_v2\_logging](#input\_enable\_v2\_logging) | Whether to enable v2 logging for the CloudFront distribution | `bool` | `false` | no |
199203
| <a name="input_enabled"></a> [enabled](#input\_enabled) | Whether the distribution is enabled to accept end user requests for content | `bool` | `true` | no |
200204
| <a name="input_http_version"></a> [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 |
201205
| <a name="input_is_ipv6_enabled"></a> [is\_ipv6\_enabled](#input\_is\_ipv6\_enabled) | Whether the IPv6 is enabled for the distribution | `bool` | `true` | no |
@@ -211,6 +215,7 @@ No modules.
211215
| <a name="input_retain_on_delete"></a> [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 |
212216
| <a name="input_staging"></a> [staging](#input\_staging) | Whether the distribution is a staging distribution | `bool` | `null` | no |
213217
| <a name="input_tags"></a> [tags](#input\_tags) | A map of tags to add to all resources | `map(string)` | `{}` | no |
218+
| <a name="input_v2_logging"></a> [v2\_logging](#input\_v2\_logging) | Configuration block for v2 logging destination | <pre>object({<br/> # Destination<br/> delivery_destination_configuration = optional(object({<br/> destination_resource_arn = optional(string)<br/> }))<br/> delivery_destination_type = optional(string)<br/> name = string<br/> output_format = optional(string)<br/> # Delivery<br/> field_delimiter = optional(string)<br/> record_fields = optional(list(string))<br/> s3_delivery_configuration = optional(object({<br/> enable_hive_compatible_path = optional(bool)<br/> suffix_path = optional(string)<br/> }))<br/> })</pre> | `null` | no |
214219
| <a name="input_viewer_certificate"></a> [viewer\_certificate](#input\_viewer\_certificate) | The SSL configuration for this distribution | <pre>object({<br/> acm_certificate_arn = optional(string)<br/> cloudfront_default_certificate = optional(bool)<br/> iam_certificate_id = optional(string)<br/> minimum_protocol_version = optional(string, "TLSv1.2_2025")<br/> ssl_support_method = optional(string)<br/> })</pre> | `{}` | no |
215220
| <a name="input_vpc_origin"></a> [vpc\_origin](#input\_vpc\_origin) | Map of CloudFront VPC origins | <pre>map(object({<br/> arn = string<br/> http_port = number<br/> https_port = number<br/> name = optional(string)<br/> origin_protocol_policy = string<br/> origin_ssl_protocols = object({<br/> items = optional(list(string), ["TLSv1.2"])<br/> quantity = optional(number, 1)<br/> })<br/> timeouts = optional(object({<br/> create = optional(string)<br/> update = optional(string)<br/> delete = optional(string)<br/> }))<br/> tags = optional(map(string), {})<br/> }))</pre> | `null` | no |
216221
| <a name="input_wait_for_deployment"></a> [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 |

examples/complete/README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,8 +50,8 @@ Note that this example may create resources which cost money. Run `terraform des
5050
| [aws_cloudfront_function.example](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/cloudfront_function) | resource |
5151
| [null_resource.download_package](https://registry.terraform.io/providers/hashicorp/null/latest/docs/resources/resource) | resource |
5252
| [aws_availability_zones.available](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/availability_zones) | data source |
53-
| [aws_canonical_user_id.current](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/canonical_user_id) | data source |
54-
| [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 |
53+
| [aws_caller_identity.current](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/caller_identity) | data source |
54+
| [aws_iam_policy_document.log_bucket_policy](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source |
5555
| [aws_iam_policy_document.s3_policy](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source |
5656
| [aws_route53_zone.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/route53_zone) | data source |
5757

examples/complete/main.tf

Lines changed: 60 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ data "aws_availability_zones" "available" {
1010
}
1111
}
1212

13+
data "aws_caller_identity" "current" {}
14+
1315
locals {
1416
subdomain = "cdn"
1517

@@ -42,9 +44,17 @@ module "cloudfront" {
4244

4345
create_monitoring_subscription = true
4446

45-
logging_config = {
46-
bucket = module.log_bucket.s3_bucket_bucket_domain_name
47-
prefix = "cloudfront"
47+
# v2 Logging - logs to S3 with CloudWatch Log Delivery
48+
# Note: This supersedes the legacy logging_config which used S3 ACLs
49+
v2_logging = {
50+
name = "example-v2-logs"
51+
destination_arn = "${module.log_bucket.s3_bucket_arn}/cloudfront"
52+
output_format = "parquet"
53+
54+
s3_delivery_configuration = {
55+
enable_hive_compatible_path = true
56+
suffix_path = "{DistributionId}/{yyyy}/{MM}/{dd}/{HH}"
57+
}
4858
}
4959

5060
origin_access_control = {
@@ -435,9 +445,6 @@ data "aws_iam_policy_document" "s3_policy" {
435445
}
436446
}
437447

438-
data "aws_canonical_user_id" "current" {}
439-
data "aws_cloudfront_log_delivery_canonical_user_id" "cloudfront" {}
440-
441448
module "log_bucket" {
442449
source = "terraform-aws-modules/s3-bucket/aws"
443450
version = "~> 5.0"
@@ -447,25 +454,56 @@ module "log_bucket" {
447454
# For example only
448455
force_destroy = true
449456

450-
control_object_ownership = true
451-
object_ownership = "ObjectWriter"
457+
attach_policy = true
458+
policy = data.aws_iam_policy_document.log_bucket_policy.json
452459

453-
grant = [
454-
{
455-
type = "CanonicalUser"
456-
permission = "FULL_CONTROL"
457-
id = data.aws_canonical_user_id.current.id
458-
},
459-
{
460-
type = "CanonicalUser"
461-
permission = "FULL_CONTROL"
462-
id = data.aws_cloudfront_log_delivery_canonical_user_id.cloudfront.id
463-
# Ref. https://github.com/terraform-providers/terraform-provider-aws/issues/12512
464-
# Ref. https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/AccessLogs.html
460+
tags = local.tags
461+
}
462+
463+
data "aws_iam_policy_document" "log_bucket_policy" {
464+
statement {
465+
sid = "AWSLogDeliveryWrite"
466+
actions = ["s3:PutObject"]
467+
resources = [
468+
"${module.log_bucket.s3_bucket_arn}/*"
469+
]
470+
471+
principals {
472+
type = "Service"
473+
identifiers = ["delivery.logs.amazonaws.com"]
465474
}
466-
]
467475

468-
tags = local.tags
476+
condition {
477+
test = "StringEquals"
478+
variable = "s3:x-amz-acl"
479+
values = ["bucket-owner-full-control"]
480+
}
481+
482+
condition {
483+
test = "StringEquals"
484+
variable = "aws:SourceAccount"
485+
values = [data.aws_caller_identity.current.account_id]
486+
}
487+
}
488+
489+
statement {
490+
sid = "AWSLogDeliveryAclCheck"
491+
actions = ["s3:GetBucketAcl"]
492+
resources = [
493+
module.log_bucket.s3_bucket_arn
494+
]
495+
496+
principals {
497+
type = "Service"
498+
identifiers = ["delivery.logs.amazonaws.com"]
499+
}
500+
501+
condition {
502+
test = "StringEquals"
503+
variable = "aws:SourceAccount"
504+
values = [data.aws_caller_identity.current.account_id]
505+
}
506+
}
469507
}
470508

471509
################################################################################

main.tf

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -529,6 +529,63 @@ resource "aws_cloudfront_monitoring_subscription" "this" {
529529
}
530530
}
531531

532+
################################################################################
533+
# v2 Logging
534+
# https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/standard-logging.html
535+
################################################################################
536+
537+
locals {
538+
enable_v2_logging = var.create && var.enable_v2_logging
539+
}
540+
541+
resource "aws_cloudwatch_log_delivery_source" "this" {
542+
count = local.enable_v2_logging ? 1 : 0
543+
544+
log_type = "ACCESS_LOGS"
545+
name = "cloudfront-${aws_cloudfront_distribution.this[0].id}"
546+
resource_arn = aws_cloudfront_distribution.this[0].arn
547+
548+
tags = var.tags
549+
}
550+
551+
resource "aws_cloudwatch_log_delivery_destination" "this" {
552+
count = local.enable_v2_logging ? 1 : 0
553+
554+
dynamic "delivery_destination_configuration" {
555+
for_each = var.v2_logging.delivery_destination_configuration != null ? [var.v2_logging.delivery_destination_configuration] : []
556+
557+
content {
558+
destination_resource_arn = delivery_destination_configuration.value.destination_resource_arn
559+
}
560+
}
561+
562+
delivery_destination_type = var.v2_logging.delivery_destination_type
563+
name = var.v2_logging.name
564+
output_format = var.v2_logging.output_format
565+
566+
tags = var.tags
567+
}
568+
569+
resource "aws_cloudwatch_log_delivery" "this" {
570+
count = local.enable_v2_logging ? 1 : 0
571+
572+
delivery_destination_arn = aws_cloudwatch_log_delivery_destination.this[0].arn
573+
delivery_source_name = aws_cloudwatch_log_delivery_source.this[0].name
574+
field_delimiter = var.v2_logging.field_delimiter
575+
record_fields = var.v2_logging.record_fields
576+
577+
dynamic "s3_delivery_configuration" {
578+
for_each = var.v2_logging.s3_delivery_configuration != null ? [var.v2_logging.s3_delivery_configuration] : []
579+
580+
content {
581+
enable_hive_compatible_path = s3_delivery_configuration.value.enable_hive_compatible_path
582+
suffix_path = s3_delivery_configuration.value.suffix_path
583+
}
584+
}
585+
586+
tags = var.tags
587+
}
588+
532589
################################################################################
533590
# Data source reverse lookup by name
534591
# These are used to refer to resources by name instead of ID

variables.tf

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -461,3 +461,35 @@ variable "realtime_metrics_subscription_status" {
461461
type = string
462462
default = "Enabled"
463463
}
464+
465+
################################################################################
466+
# v2 Logging
467+
# https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/standard-logging.html
468+
################################################################################
469+
470+
variable "enable_v2_logging" {
471+
description = "Whether to enable v2 logging for the CloudFront distribution"
472+
type = bool
473+
default = false
474+
}
475+
476+
variable "v2_logging" {
477+
description = "Configuration block for v2 logging destination"
478+
type = object({
479+
# Destination
480+
delivery_destination_configuration = optional(object({
481+
destination_resource_arn = optional(string)
482+
}))
483+
delivery_destination_type = optional(string)
484+
name = string
485+
output_format = optional(string)
486+
# Delivery
487+
field_delimiter = optional(string)
488+
record_fields = optional(list(string))
489+
s3_delivery_configuration = optional(object({
490+
enable_hive_compatible_path = optional(bool)
491+
suffix_path = optional(string)
492+
}))
493+
})
494+
default = null
495+
}

wrappers/main.tf

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ module "wrapper" {
1313
custom_error_response = try(each.value.custom_error_response, var.defaults.custom_error_response, null)
1414
default_cache_behavior = try(each.value.default_cache_behavior, var.defaults.default_cache_behavior)
1515
default_root_object = try(each.value.default_root_object, var.defaults.default_root_object, null)
16+
enable_v2_logging = try(each.value.enable_v2_logging, var.defaults.enable_v2_logging, false)
1617
enabled = try(each.value.enabled, var.defaults.enabled, true)
1718
http_version = try(each.value.http_version, var.defaults.http_version, "http2")
1819
is_ipv6_enabled = try(each.value.is_ipv6_enabled, var.defaults.is_ipv6_enabled, true)
@@ -38,6 +39,7 @@ module "wrapper" {
3839
retain_on_delete = try(each.value.retain_on_delete, var.defaults.retain_on_delete, null)
3940
staging = try(each.value.staging, var.defaults.staging, null)
4041
tags = try(each.value.tags, var.defaults.tags, {})
42+
v2_logging = try(each.value.v2_logging, var.defaults.v2_logging, null)
4143
viewer_certificate = try(each.value.viewer_certificate, var.defaults.viewer_certificate, {})
4244
vpc_origin = try(each.value.vpc_origin, var.defaults.vpc_origin, null)
4345
wait_for_deployment = try(each.value.wait_for_deployment, var.defaults.wait_for_deployment, null)

0 commit comments

Comments
 (0)