From b2430b30c2abbc70366b2aad85415162985adf36 Mon Sep 17 00:00:00 2001 From: David Stone Date: Sun, 28 Jun 2026 11:22:48 -0700 Subject: [PATCH] feat(cdk): manage CloudFront access logs with a 90-day lifecycle The CloudFront distribution had no logging configured in CDK, yet an out-of-band `trashcal-access-logs` S3 bucket was accumulating logs with no lifecycle policy. Add a CDK-managed log bucket (ACLs enabled and SSE-S3, as required by CloudFront standard logging) with a 90-day expiration rule, and wire the distribution to deliver logs into it under a `cloudfront/` prefix. The old orphaned bucket can be decommissioned once logs are landing here. Co-Authored-By: Claude Opus 4.8 (1M context) --- cdk/lib/trashcal-cdk-stack.ts | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/cdk/lib/trashcal-cdk-stack.ts b/cdk/lib/trashcal-cdk-stack.ts index d536663..bfaf875 100644 --- a/cdk/lib/trashcal-cdk-stack.ts +++ b/cdk/lib/trashcal-cdk-stack.ts @@ -19,6 +19,7 @@ import { } from "aws-cdk-github-oidc"; import * as cloudfront from "aws-cdk-lib/aws-cloudfront"; import * as cloudfrontOrigins from "aws-cdk-lib/aws-cloudfront-origins"; +import * as s3 from "aws-cdk-lib/aws-s3"; export interface TrashcalCdkStackProps extends cdk.StackProps { domainName: string; @@ -100,8 +101,30 @@ export class TrashcalCdkStack extends cdk.Stack { } ); + // CloudFront access logs bucket. Standard (legacy) CloudFront logging + // delivers log files using bucket ACLs, so ACLs must stay enabled + // (BUCKET_OWNER_PREFERRED) and the bucket can't use SSE-KMS — hence + // SSE-S3. The 90-day lifecycle rule caps growth. This replaces the old + // out-of-band `trashcal-access-logs` bucket, which can be decommissioned + // once this distribution is delivering logs here. + const accessLogsBucket = new s3.Bucket(this, "trashcal-access-logs", { + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + objectOwnership: s3.ObjectOwnership.BUCKET_OWNER_PREFERRED, + encryption: s3.BucketEncryption.S3_MANAGED, + enforceSSL: true, + lifecycleRules: [ + { + id: "expire-access-logs", + expiration: cdk.Duration.days(90), + }, + ], + }); + new cloudfront.Distribution(this, "cloudfront-api", { domainNames: [props.domainName], + enableLogging: true, + logBucket: accessLogsBucket, + logFilePrefix: "cloudfront/", defaultBehavior: { origin: new cloudfrontOrigins.HttpOrigin( `${api.apiId}.execute-api.${cdk.Stack.of(this).region}.amazonaws.com`