CloudFrontの新しいログ出力機能(v2)のCFnテンプレート

# CloudFrontの新しいログ出力機能(v2)のCFn(CloudFormation)テンプレート
2024年11月にAWSのCloudFrontで新しいログ出力機能が登場しました。
詳しくは下記をご覧ください。
- [Amazon CloudFront がアクセスログの追加のログ形式と宛先のサポートを開始 \- AWS](https://aws.amazon.com/jp/about-aws/whats-new/2024/11/amazon-cloudfront-log-formats-destinations-access/)
- [CloudFront 標準ログがアップデート！新機能のパーティションやJSON出力を試してみた \| DevelopersIO](https://dev.classmethod.jp/articles/cloudfront-access-log-update-202411/)
- [CloudFrontの新しいログ形式に対応したAthenaのテーブルを作ってみた](https://zenn.dev/yutaro1985/articles/create_table_for_new_cloudfront_log)

CloudFormationのテンプレートで従来のログ出力（S3 Legacy logging）から、新しいログ出力（S3 logging (v2)）に切り替えるのに苦労したので、以下の投稿が参考になれば幸いです。

## 従来のログ出力設定を削除
### S3 Bucket
アクセスログ出力用のS3バケットで（従来利用していた）ACLが無効化できるようになりました。
- ※ただし無効化する前にバケットのACLで `awslogsdelivery (c4c1ede66af53448b93c283ce9448c4ba468c9432aa01d700d3878632f77d2d0)` の許可を削除してください。
```diff:CloudFormationテンプレートの一部抜粋
   ## S3: Bucket (Access Logs)
   S3BucketAccessLogs:
     Type: AWS::S3::Bucket
     Properties:
       OwnershipControls:
         Rules:
-          - ObjectOwnership: ObjectWriter
+          - ObjectOwnership: BucketOwnerEnforced
```

### CloudFront Distribution
CloudFrontのDistributionで従来のログ出力設定を削除します。
```diff:CloudFormationテンプレートの一部抜粋
   ## CloudFront: Distribution
   Distribution:
     Type: AWS::CloudFront::Distribution
     Properties:
       DistributionConfig:
-        Logging:
-          Bucket: !GetAtt S3BucketAccessLogs.DomainName
-          Prefix: logs/
-          IncludeCookies: true
```

## 新しいログ出力設定を追加
### S3 Bucket Policy
アクセスログ出力用のS3バケットで今度はバケットポリシーを利用することになりました。
※Conditionでバージニア北部リージョン(us-east-1)のリソースARNが指定されていますが、そのリソースは後から作るで問題ありません。
```diff:CloudFormationテンプレートの一部抜粋
+  ## S3: Bucket Policy (Access Logs)
+  S3BucketPolicyAccessLogs:
+    Type: AWS::S3::BucketPolicy
+    Properties:
+      Bucket: !Ref S3BucketAccessLogs
+      PolicyDocument:
+        Version: "2012-10-17"
+        Statement:
+          - Effect: Allow
+            Action: s3:PutObject
+            Principal:
+              Service: delivery.logs.amazonaws.com
+            Resource: !Sub ${S3BucketAccessLogs.Arn}/AWSLogs/${AWS::AccountId}/CloudFront/*
+            Condition:
+              StringEquals:
+                aws:SourceAccount: !Ref AWS::AccountId
+                s3:x-amz-acl: bucket-owner-full-control
+              ArnLike:
+                ## Logs delivery resources (CFn stack) need to be created in the us-east-1 region after this CFn stack is created.
+                aws:SourceArn: !Sub arn:aws:logs:us-east-1:${AWS::AccountId}:delivery-source:${SystemName}-${Environment}-cloudfront-delivery-source
```

### Logs Delivery関連 @バージニア北部リージョン(us-east-1)
新しいログ出力にはLogs Delivery関連のリソースが3つ必要になりますが、CloudFrontの場合はLogs Delivery Sourceが東京リージョン(ap-northeast-1)で作成できなかったので、やむを得ずバージニア北部リージョン(us-east-1)で作成しました。
- ※ログの出力先(S3SuffixPath)はALBと同じように `AWSLogs/${AWS::AccountId}/CloudFront/{yyyy}/{MM}/{dd}/{HH}` の配下にしました。左記以外に `{DistributionId}` も使えます
- ※ログの出力項目(RecordFields)は全て選択しました。各出力項目の意味は公式ドキュメント https://docs.aws.amazon.com/ja_jp/AmazonCloudFront/latest/DeveloperGuide/real-time-logs.html をご覧ください
- ※その他の詳細は公式ドキュメント https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/standard-logging.html をご覧ください
```yaml:バージニア北部リージョンで追加するCloudFormationテンプレートの全体
---
AWSTemplateFormatVersion: "2010-09-09"
Description: Create Logs Delivery for CloudFront etc.

Mappings:
  EnvironmentMap:
    prod:
      ## [Change CloudFront Distribution Id] Logs Delivery Source needs to be created in the us-east-1 region.
      CloudFrontDistributionId: [xxxxxxxxxxxxx]
    stg:
      CloudFrontDistributionId: [xxxxxxxxxxxxx]
    dev:
      CloudFrontDistributionId: [xxxxxxxxxxxxx]

Parameters:
  SystemName:
    Description: System Name
    Type: String
    Default: rubiconlink
  Environment:
    Description: Environment
    Type: String
    Default: prod
    AllowedValues:
      - prod
      - stg
      - dev

Metadata:
  AWS::CloudFormation::Interface:
    ParameterGroups:
      - Label:
          default: "Environment Configuration"
        Parameters:
          - SystemName
          - Environment

Resources:
  ## Logs: Delivery Source
  LogsDeliverySource:
    Type: AWS::Logs::DeliverySource
    Properties:
      Name: !Sub ${SystemName}-${Environment}-cloudfront-delivery-source
      ResourceArn: !Sub
          - arn:aws:cloudfront::${AWS::AccountId}:distribution/${DistributionId}
          - DistributionId:
              !FindInMap [ EnvironmentMap, !Ref Environment, CloudFrontDistributionId ]
      LogType: ACCESS_LOGS

  ## Logs: Delivery Destination
  LogsDeliveryDestination:
    Type: AWS::Logs::DeliveryDestination
    Properties:
      Name: !Sub ${SystemName}-${Environment}-cloudfront-delivery-destination
      DestinationResourceArn: !Sub arn:aws:s3:::${SystemName}-${Environment}-cloudfront-accesslogs-${AWS::AccountId}
      OutputFormat: json

  ## Logs: Delivery
  LogsDelivery:
    Type: AWS::Logs::Delivery
    Properties:
      DeliverySourceName: !Sub ${SystemName}-${Environment}-cloudfront-delivery-source
      DeliveryDestinationArn: !GetAtt LogsDeliveryDestination.Arn
      RecordFields:
        - timestamp
        - DistributionId
        - date
        - time
        - x-edge-location
        - sc-bytes
        - c-ip
        - cs-method
        - cs(Host)
        - cs-uri-stem
        - sc-status
        - cs(Referer)
        - cs(User-Agent)
        - cs-uri-query
        - cs(Cookie)
        - x-edge-result-type
        - x-edge-request-id
        - x-host-header
        - cs-protocol
        - cs-bytes
        - time-taken
        - x-forwarded-for
        - ssl-protocol
        - ssl-cipher
        - x-edge-response-result-type
        - cs-protocol-version
        - fle-status
        - fle-encrypted-fields
        - c-port
        - time-to-first-byte
        - x-edge-detailed-result-type
        - sc-content-type
        - sc-content-len
        - sc-range-start
        - sc-range-end
        - timestamp(ms)
        - origin-fbl
        - origin-lbl
        - asn
      S3SuffixPath: !Sub AWSLogs/${AWS::AccountId}/CloudFront/{yyyy}/{MM}/{dd}/{HH}
      S3EnableHiveCompatiblePath: false

Outputs:
  ## Logs: Delivery Source
  LogsDeliverySourceArn:
    Value: !GetAtt LogsDeliverySource.Arn
    Export:
      Name: !Sub ${AWS::StackName}-LogsDeliverySourceArn

  LogsDeliverySourceResourceArn1:
    Value: !Select [ 0, !GetAtt LogsDeliverySource.ResourceArns ]
    Export:
      Name: !Sub ${AWS::StackName}-LogsDeliverySourceResourceArn1

  LogsDeliverySourceService:
    Value: !GetAtt LogsDeliverySource.Service
    Export:
      Name: !Sub ${AWS::StackName}-LogsDeliverySourceService

  ## Logs: Delivery Destination
  LogsDeliveryDestination:
    Value: !GetAtt LogsDeliveryDestination.Arn
    Export:
      Name: !Sub ${AWS::StackName}-LogsDeliveryDestinationArn

  ## Logs: Delivery
  LogsDeliveryArn:
    Value: !GetAtt LogsDelivery.Arn
    Export:
      Name: !Sub ${AWS::StackName}-LogsDeliveryArn

  LogsDeliveryDestinationType:
    Value: !GetAtt LogsDelivery.DeliveryDestinationType
    Export:
      Name: !Sub ${AWS::StackName}-LogsDeliveryDestinationType

  LogsDeliveryId:
    Value: !GetAtt LogsDelivery.DeliveryId
    Export:
      Name: !Sub ${AWS::StackName}-LogsDeliveryId
```

## 参考記事
CloudFront以外でも、新しいログ出力機能(v2)が利用可能なサービスの一覧が公式ドキュメントにありました。
https://docs.aws.amazon.com/ja_jp/AmazonCloudWatch/latest/logs/AWS-logs-and-resource-policy.html

新しいログ出力機能の料金は料金ページの「その他の機能」タブをご覧ください。
https://aws.amazon.com/jp/cloudfront/pricing/

（再掲）ログ出力できる項目はリアルタイムログと同じようです。
https://docs.aws.amazon.com/ja_jp/AmazonCloudFront/latest/DeveloperGuide/real-time-logs.html

（再掲）その他詳細は公式ドキュメントをご覧ください。
https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/standard-logging.html

## おわりに
参考になれば幸いです！

インフラエンジニア歴17年（AWS利用歴9年）の経験を活かして、私が直接オンラインでAWSを教える塾（現場最強のエンジニアになろう® ルビコン塾）を開講しています。今回のテンプレートもルビコン塾の教材の一部です。

ご興味ある方はX https://x.com/RubiconLink をフォローして、公式LINEにご登録ください！