Skip to content

Commit 75518ec

Browse files
committed
feat: add support for CloudFront Standard Logging
Add support for CloudFront Standard Logging using CloudWatch Log Delivery resources. This replaces the legacy logging_config and allows logging to S3, CloudWatch Logs, or Firehose as destinations. Standard logging is automatically enabled when std_logging_destination or std_logging_destination_arn is provided (no separate enable flag needed). New variables: - std_logging_region: region for logging resources (default: us-east-1) - std_logging_source_name: optional custom name for delivery source - std_logging_destination_arn: use existing destination (skip creation) - std_logging_destination: destination configuration (name, output_format, destination_arn) - std_logging_delivery: delivery configuration (field_delimiter, record_fields, s3_delivery_configuration) New outputs: - cloudfront_std_logging_source_arn - cloudfront_std_logging_source_name - cloudfront_std_logging_destination_arn - cloudfront_std_logging_delivery_id - cloudfront_std_logging_delivery_arn
1 parent f7e8814 commit 75518ec

File tree

7 files changed

+228
-30
lines changed

7 files changed

+228
-30
lines changed

README.md

Lines changed: 13 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 |
@@ -210,6 +213,11 @@ No modules.
210213
| <a name="input_restrictions"></a> [restrictions](#input\_restrictions) | The restrictions configuration for this distribution | <pre>object({<br/> geo_restriction = object({<br/> locations = optional(list(string))<br/> restriction_type = optional(string, "none")<br/> })<br/> })</pre> | <pre>{<br/> "geo_restriction": {<br/> "restriction_type": "none"<br/> }<br/>}</pre> | no |
211214
| <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 |
212215
| <a name="input_staging"></a> [staging](#input\_staging) | Whether the distribution is a staging distribution | `bool` | `null` | no |
216+
| <a name="input_std_logging_delivery"></a> [std\_logging\_delivery](#input\_std\_logging\_delivery) | Configuration for the standard logging delivery | <pre>object({<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/> tags = optional(map(string), {})<br/> })</pre> | `null` | no |
217+
| <a name="input_std_logging_destination"></a> [std\_logging\_destination](#input\_std\_logging\_destination) | Configuration for creating a new standard logging destination. Ignored if std\_logging\_destination\_arn is set | <pre>object({<br/> name = string<br/> output_format = optional(string, "json")<br/> destination_arn = string<br/> tags = optional(map(string), {})<br/> })</pre> | `null` | no |
218+
| <a name="input_std_logging_destination_arn"></a> [std\_logging\_destination\_arn](#input\_std\_logging\_destination\_arn) | ARN of an existing CloudWatch Log Delivery Destination to use. If set, std\_logging\_destination is ignored | `string` | `null` | no |
219+
| <a name="input_std_logging_region"></a> [std\_logging\_region](#input\_std\_logging\_region) | Region for standard logging resources. Required for CloudFront (must be us-east-1 for the source) | `string` | `"us-east-1"` | no |
220+
| <a name="input_std_logging_source_name"></a> [std\_logging\_source\_name](#input\_std\_logging\_source\_name) | Name for the CloudWatch Log Delivery Source. Defaults to 'cloudfront-<distribution\_id>' | `string` | `null` | no |
213221
| <a name="input_tags"></a> [tags](#input\_tags) | A map of tags to add to all resources | `map(string)` | `{}` | no |
214222
| <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 |
215223
| <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 |
@@ -234,6 +242,11 @@ No modules.
234242
| <a name="output_cloudfront_monitoring_subscription_id"></a> [cloudfront\_monitoring\_subscription\_id](#output\_cloudfront\_monitoring\_subscription\_id) | The ID of the CloudFront monitoring subscription, which corresponds to the `distribution_id`. |
235243
| <a name="output_cloudfront_origin_access_controls"></a> [cloudfront\_origin\_access\_controls](#output\_cloudfront\_origin\_access\_controls) | The origin access controls created |
236244
| <a name="output_cloudfront_response_headers_policies"></a> [cloudfront\_response\_headers\_policies](#output\_cloudfront\_response\_headers\_policies) | The response headers policies created |
245+
| <a name="output_cloudfront_std_logging_delivery_arn"></a> [cloudfront\_std\_logging\_delivery\_arn](#output\_cloudfront\_std\_logging\_delivery\_arn) | The ARN of the CloudWatch Log Delivery for standard logging |
246+
| <a name="output_cloudfront_std_logging_delivery_id"></a> [cloudfront\_std\_logging\_delivery\_id](#output\_cloudfront\_std\_logging\_delivery\_id) | The ID of the CloudWatch Log Delivery for standard logging |
247+
| <a name="output_cloudfront_std_logging_destination_arn"></a> [cloudfront\_std\_logging\_destination\_arn](#output\_cloudfront\_std\_logging\_destination\_arn) | The ARN of the CloudWatch Log Delivery Destination for standard logging |
248+
| <a name="output_cloudfront_std_logging_source_arn"></a> [cloudfront\_std\_logging\_source\_arn](#output\_cloudfront\_std\_logging\_source\_arn) | The ARN of the CloudWatch Log Delivery Source for standard logging |
249+
| <a name="output_cloudfront_std_logging_source_name"></a> [cloudfront\_std\_logging\_source\_name](#output\_cloudfront\_std\_logging\_source\_name) | The name of the CloudWatch Log Delivery Source for standard logging |
237250
| <a name="output_cloudfront_vpc_origins"></a> [cloudfront\_vpc\_origins](#output\_cloudfront\_vpc\_origins) | The IDS of the VPC origin created |
238251
<!-- END_TF_DOCS -->
239252

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: 63 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -42,9 +42,18 @@ module "cloudfront" {
4242

4343
create_monitoring_subscription = true
4444

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

5059
origin_access_control = {
@@ -435,9 +444,10 @@ data "aws_iam_policy_document" "s3_policy" {
435444
}
436445
}
437446

438-
data "aws_canonical_user_id" "current" {}
439-
data "aws_cloudfront_log_delivery_canonical_user_id" "cloudfront" {}
447+
data "aws_caller_identity" "current" {}
440448

449+
# S3 bucket for CloudFront Standard Logging
450+
# Standard logging uses CloudWatch Log Delivery service and requires a bucket policy
441451
module "log_bucket" {
442452
source = "terraform-aws-modules/s3-bucket/aws"
443453
version = "~> 5.0"
@@ -447,25 +457,57 @@ module "log_bucket" {
447457
# For example only
448458
force_destroy = true
449459

450-
control_object_ownership = true
451-
object_ownership = "ObjectWriter"
460+
# Standard logging requires a bucket policy for the log delivery service
461+
attach_policy = true
462+
policy = data.aws_iam_policy_document.log_bucket_policy.json
452463

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
464+
tags = local.tags
465+
}
466+
467+
data "aws_iam_policy_document" "log_bucket_policy" {
468+
statement {
469+
sid = "AWSLogDeliveryWrite"
470+
actions = ["s3:PutObject"]
471+
resources = [
472+
"${module.log_bucket.s3_bucket_arn}/*"
473+
]
474+
475+
principals {
476+
type = "Service"
477+
identifiers = ["delivery.logs.amazonaws.com"]
465478
}
466-
]
467479

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

471513
################################################################################

main.tf

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

532+
################################################################################
533+
# Standard Logging (replaces legacy logging_config)
534+
################################################################################
535+
536+
locals {
537+
# Standard logging is enabled when either destination_arn or destination config is provided
538+
create_std_logging = var.create && (var.std_logging_destination_arn != null || var.std_logging_destination != null)
539+
}
540+
541+
resource "aws_cloudwatch_log_delivery_source" "this" {
542+
count = local.create_std_logging ? 1 : 0
543+
544+
region = var.std_logging_region
545+
546+
name = coalesce(var.std_logging_source_name, "cloudfront-${aws_cloudfront_distribution.this[0].id}")
547+
log_type = "ACCESS_LOGS"
548+
resource_arn = aws_cloudfront_distribution.this[0].arn
549+
}
550+
551+
resource "aws_cloudwatch_log_delivery_destination" "this" {
552+
count = local.create_std_logging && var.std_logging_destination_arn == null ? 1 : 0
553+
554+
region = var.std_logging_region
555+
556+
name = var.std_logging_destination.name
557+
output_format = var.std_logging_destination.output_format
558+
559+
delivery_destination_configuration {
560+
destination_resource_arn = var.std_logging_destination.destination_arn
561+
}
562+
563+
tags = var.std_logging_destination.tags
564+
}
565+
566+
locals {
567+
# Use computed ARN for the actual resource reference
568+
std_logging_destination_arn = var.std_logging_destination_arn != null ? var.std_logging_destination_arn : try(aws_cloudwatch_log_delivery_destination.this[0].arn, null)
569+
}
570+
571+
resource "aws_cloudwatch_log_delivery" "this" {
572+
count = local.create_std_logging ? 1 : 0
573+
574+
region = var.std_logging_region
575+
576+
delivery_source_name = aws_cloudwatch_log_delivery_source.this[0].name
577+
delivery_destination_arn = local.std_logging_destination_arn
578+
579+
field_delimiter = try(var.std_logging_delivery.field_delimiter, null)
580+
record_fields = try(var.std_logging_delivery.record_fields, null)
581+
582+
dynamic "s3_delivery_configuration" {
583+
for_each = try(var.std_logging_delivery.s3_delivery_configuration, null) != null ? [var.std_logging_delivery.s3_delivery_configuration] : []
584+
585+
content {
586+
enable_hive_compatible_path = s3_delivery_configuration.value.enable_hive_compatible_path
587+
suffix_path = s3_delivery_configuration.value.suffix_path
588+
}
589+
}
590+
591+
tags = try(var.std_logging_delivery.tags, {})
592+
}
593+
532594
################################################################################
533595
# Data source reverse lookup by name
534596
# These are used to refer to resources by name instead of ID

outputs.tf

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,3 +96,32 @@ output "cloudfront_monitoring_subscription_id" {
9696
description = " The ID of the CloudFront monitoring subscription, which corresponds to the `distribution_id`."
9797
value = try(aws_cloudfront_monitoring_subscription.this[0].id, null)
9898
}
99+
100+
################################################################################
101+
# Standard Logging (replaces legacy logging_config)
102+
################################################################################
103+
104+
output "cloudfront_std_logging_source_arn" {
105+
description = "The ARN of the CloudWatch Log Delivery Source for standard logging"
106+
value = try(aws_cloudwatch_log_delivery_source.this[0].arn, null)
107+
}
108+
109+
output "cloudfront_std_logging_source_name" {
110+
description = "The name of the CloudWatch Log Delivery Source for standard logging"
111+
value = try(aws_cloudwatch_log_delivery_source.this[0].name, null)
112+
}
113+
114+
output "cloudfront_std_logging_destination_arn" {
115+
description = "The ARN of the CloudWatch Log Delivery Destination for standard logging"
116+
value = try(aws_cloudwatch_log_delivery_destination.this[0].arn, null)
117+
}
118+
119+
output "cloudfront_std_logging_delivery_id" {
120+
description = "The ID of the CloudWatch Log Delivery for standard logging"
121+
value = try(aws_cloudwatch_log_delivery.this[0].id, null)
122+
}
123+
124+
output "cloudfront_std_logging_delivery_arn" {
125+
description = "The ARN of the CloudWatch Log Delivery for standard logging"
126+
value = try(aws_cloudwatch_log_delivery.this[0].arn, null)
127+
}

variables.tf

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -459,3 +459,50 @@ variable "realtime_metrics_subscription_status" {
459459
type = string
460460
default = "Enabled"
461461
}
462+
463+
################################################################################
464+
# Standard Logging (replaces legacy logging_config)
465+
################################################################################
466+
467+
variable "std_logging_region" {
468+
description = "Region for standard logging resources. Required for CloudFront (must be us-east-1 for the source)"
469+
type = string
470+
default = "us-east-1"
471+
}
472+
473+
variable "std_logging_source_name" {
474+
description = "Name for the CloudWatch Log Delivery Source. Defaults to 'cloudfront-<distribution_id>'"
475+
type = string
476+
default = null
477+
}
478+
479+
variable "std_logging_destination_arn" {
480+
description = "ARN of an existing CloudWatch Log Delivery Destination to use. If set, std_logging_destination is ignored"
481+
type = string
482+
default = null
483+
}
484+
485+
variable "std_logging_destination" {
486+
description = "Configuration for creating a new standard logging destination. Ignored if std_logging_destination_arn is set"
487+
type = object({
488+
name = string
489+
output_format = optional(string, "json")
490+
destination_arn = string
491+
tags = optional(map(string), {})
492+
})
493+
default = null
494+
}
495+
496+
variable "std_logging_delivery" {
497+
description = "Configuration for the standard logging delivery"
498+
type = object({
499+
field_delimiter = optional(string)
500+
record_fields = optional(list(string))
501+
s3_delivery_configuration = optional(object({
502+
enable_hive_compatible_path = optional(bool)
503+
suffix_path = optional(string)
504+
}))
505+
tags = optional(map(string), {})
506+
})
507+
default = null
508+
}

wrappers/main.tf

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -35,11 +35,16 @@ module "wrapper" {
3535
restriction_type = "none"
3636
}
3737
})
38-
retain_on_delete = try(each.value.retain_on_delete, var.defaults.retain_on_delete, null)
39-
staging = try(each.value.staging, var.defaults.staging, null)
40-
tags = try(each.value.tags, var.defaults.tags, {})
41-
viewer_certificate = try(each.value.viewer_certificate, var.defaults.viewer_certificate, {})
42-
vpc_origin = try(each.value.vpc_origin, var.defaults.vpc_origin, null)
43-
wait_for_deployment = try(each.value.wait_for_deployment, var.defaults.wait_for_deployment, null)
44-
web_acl_id = try(each.value.web_acl_id, var.defaults.web_acl_id, null)
38+
retain_on_delete = try(each.value.retain_on_delete, var.defaults.retain_on_delete, null)
39+
staging = try(each.value.staging, var.defaults.staging, null)
40+
std_logging_delivery = try(each.value.std_logging_delivery, var.defaults.std_logging_delivery, null)
41+
std_logging_destination = try(each.value.std_logging_destination, var.defaults.std_logging_destination, null)
42+
std_logging_destination_arn = try(each.value.std_logging_destination_arn, var.defaults.std_logging_destination_arn, null)
43+
std_logging_region = try(each.value.std_logging_region, var.defaults.std_logging_region, "us-east-1")
44+
std_logging_source_name = try(each.value.std_logging_source_name, var.defaults.std_logging_source_name, null)
45+
tags = try(each.value.tags, var.defaults.tags, {})
46+
viewer_certificate = try(each.value.viewer_certificate, var.defaults.viewer_certificate, {})
47+
vpc_origin = try(each.value.vpc_origin, var.defaults.vpc_origin, null)
48+
wait_for_deployment = try(each.value.wait_for_deployment, var.defaults.wait_for_deployment, null)
49+
web_acl_id = try(each.value.web_acl_id, var.defaults.web_acl_id, null)
4550
}

0 commit comments

Comments
 (0)