S3をオリジンとしてWebリソースを公開するAmazon CloudFront用のCloudFormationテンプレート

# 概要

S3バケットをオリジンとしてCloudFrontで静的Webリソースを配信、というのは典型的なAWSのサーバレス構成の一つかと思いますが、それをサクッと作成したい時のための自分用CloudFormationテンプレートを公開したものです。

数年前に個人的に作成して使っていたものが、内容が少々古くなっていたので更新(※)するついでに自身にも他の方にも参照しやすいようにQiita記事として公開してみました。

# ※更新した部分

「少々古くなっていて更新した」という部分は以下の２点です（古いものは公開していないので比較対象がありませんが）。
- キャッシュポリシーなどを管理ポリシーに変更
- S3バケットへのアクセスをCloudFront経由に制限する設定をOAIからOACに変更

それぞれの更新内容については後述します。

# CloudFormationテンプレート全体

「CloudFormationスタック名＋AWSアカウントID」の名前のS3バケットと、それをオリジンとするCloudFrontディストリビューションが作成されるだけのテンプレートです。  
こちらの[GitHub](https://github.com/chs-k-kinoshita/cfn_tmpl_samples/blob/main/tmpl_s3cf_202303.yaml)からダウンロードもできます。

```yaml:テンプレート全体
AWSTemplateFormatVersion: '2010-09-09'
Description: Sample template for S3Origin CloudFront

Parameters:
  CloudFrontPriceClass:
    Type: String
    Default: PriceClass_200
    AllowedValues: [PriceClass_100, PriceClass_200, PriceClass_All]

Resources:

  OriginS3Bucket:
    Type: AWS::S3::Bucket
    DeletionPolicy: Retain
    UpdateReplacePolicy: Retain
    Properties:
      BucketName: !Sub ${AWS::StackName}-${AWS::AccountId}
      PublicAccessBlockConfiguration:
        BlockPublicAcls: True
        BlockPublicPolicy: True
        IgnorePublicAcls: True
        RestrictPublicBuckets: True

  OriginS3BucketPolicy:
    Type: AWS::S3::BucketPolicy
    Properties:
      Bucket: !Ref OriginS3Bucket
      PolicyDocument:
        Statement:
          - Action: s3:GetObject
            Effect: Allow
            Resource: !Sub arn:${AWS::Partition}:s3:::${OriginS3Bucket}/*
            Principal:
              Service: cloudfront.amazonaws.com
            Condition:
              StringEquals:
                AWS:SourceArn: !Sub arn:${AWS::Partition}:cloudfront::${AWS::AccountId}:distribution/${CloudFrontDistribution}
          - Action: s3:ListBucket
            Effect: Allow
            Resource: !Sub arn:${AWS::Partition}:s3:::${OriginS3Bucket}
            Principal:
              Service: cloudfront.amazonaws.com
            Condition:
              StringEquals:
                AWS:SourceArn: !Sub arn:${AWS::Partition}:cloudfront::${AWS::AccountId}:distribution/${CloudFrontDistribution}

  CloudFrontDistribution:
    Type: AWS::CloudFront::Distribution
    Properties:
      DistributionConfig:
        Comment: !Sub 'Created by ${AWS::StackName}'
        DefaultCacheBehavior:
          TargetOriginId: mainS3Origin
          ForwardedValues:
            QueryString: false
            Cookies:
              Forward: 'none'
          ViewerProtocolPolicy: redirect-to-https
          CachePolicyId: 658327ea-f89d-4fab-a63d-7e88639e58f6 # CachingOptimized
          OriginRequestPolicyId: acba4595-bd28-49b8-b9fe-13317c0390fa # UserAgentRefererHeaders
          Compress: true
        DefaultRootObject: index.html
        Enabled: true
        PriceClass: !Ref CloudFrontPriceClass
        Origins:
          - DomainName: !Sub ${OriginS3Bucket}.s3.amazonaws.com
            Id: mainS3Origin
            S3OriginConfig:
              OriginAccessIdentity: ''
            OriginAccessControlId: !GetAtt OriginAccessControl.Id

  OriginAccessControl:
    Type: AWS::CloudFront::OriginAccessControl
    Properties:
      OriginAccessControlConfig:
        Description: !Sub 'OAC of ${OriginS3Bucket}. Created by ${AWS::StackName}'
        Name: !Sub 'OAC-${OriginS3Bucket}'
        OriginAccessControlOriginType: s3
        SigningBehavior: always
        SigningProtocol: sigv4

Outputs:
  OriginS3BucketName:
    Value: !Ref OriginS3Bucket
    Export:
      Name: !Sub ${AWS::StackName}-OriginS3Bucket
  CloudfrontDomainName:
    Value: !GetAtt CloudFrontDistribution.DomainName
    Export:
      Name: !Sub ${AWS::StackName}-CloudfrontDomainName
```

テンプレートからコマンドでリソースを構築する場合は、下記のようなコマンド実行になります。
ここではスタック名を「sample-s3cf」としています。作成されるS3バケット名に使われます。
```
aws cloudformation deploy --stack-name sample-s3cf --template-file ./tmpl_s3cf_202303.yaml
```

## 更新した部分１つめについて

１つめの更新ポイントである「キャッシュポリシーなどを管理ポリシーに変更」についての補足説明です。

2020年7月頃からCloudFrontのキャッシュとオリジンリクエストの設定がポリシーとして外だしできるようになり、AWSマネージドな管理ポリシーも提供されるようになっていました。
なので、管理ポリシーを使用するようにテンプレートを改善しました。
テンプレートの59～60行目にある下記の設定が該当の部分です。

```yaml:DistributionConfigのDefaultCacheBehavior設定
          CachePolicyId: 658327ea-f89d-4fab-a63d-7e88639e58f6 # CachingOptimized
          OriginRequestPolicyId: acba4595-bd28-49b8-b9fe-13317c0390fa # UserAgentRefererHeaders
```

ここでは CachePolicyとOriginRequestPolicyに、それぞれ「CachingOptimized」「UserAgentRefererHeaders」の管理ポリシーを設定しています。
各管理ポリシーのID値については、下記の公式ガイドに載っています。

- [参考: CloudFront デベロッパーガイド 管理キャッシュポリシーの使用](https://docs.aws.amazon.com/ja_jp/AmazonCloudFront/latest/DeveloperGuide/using-managed-cache-policies.html)
- [参考: CloudFront デベロッパーガイド 管理オリジンリクエストポリシーの使用](https://docs.aws.amazon.com/ja_jp/AmazonCloudFront/latest/DeveloperGuide/using-managed-origin-request-policies.html)


## 更新した部分２つめについて

２つめの更新ポイントでである「S3バケットへのアクセスをCloudFront経由に制限する設定をOAIからOACに変更」についての補足説明です。

以前(2022年8月頃まで)はOAC(OriginAccessControl)ではなくOAI(OriginAccessIdentity)を使って、オリジンとなるS3バケットのアクセスをCloudFront経由に制限する設定を行っていました。
OACの登場により、OAIではなくOACの利用が推奨されるようになったのでテンプレートを改善しました。
下記がテンプレートの関連する部分です。

```yaml:S3バケットポリシーの設定
  OriginS3BucketPolicy:
    Type: AWS::S3::BucketPolicy
    Properties:
      Bucket: !Ref OriginS3Bucket
      PolicyDocument:
        Statement:
          - Action: s3:GetObject
            Effect: Allow
            Resource: !Sub arn:${AWS::Partition}:s3:::${OriginS3Bucket}/*
            Principal:
              Service: cloudfront.amazonaws.com
            Condition:
              StringEquals:
                AWS:SourceArn: !Sub arn:${AWS::Partition}:cloudfront::${AWS::AccountId}:distribution/${CloudFrontDistribution}
          - Action: s3:ListBucket
            Effect: Allow
            Resource: !Sub arn:${AWS::Partition}:s3:::${OriginS3Bucket}
            Principal:
              Service: cloudfront.amazonaws.com
            Condition:
              StringEquals:
                AWS:SourceArn: !Sub arn:${AWS::Partition}:cloudfront::${AWS::AccountId}:distribution/${CloudFrontDistribution}
```

※ ↑で「s3:GetObject」だけでなく「s3:ListBucket」の設定も行っているのは、存在しないファイルのURLが参照された場合に403(Access Denied)ではなく404(Not Found)のステータコードのレスポンスを返したいためです(ListBucketの設定がないと403になります)。

```yaml:DistributionConfigのOrigins設定
        Origins:
          - DomainName: !Sub ${OriginS3Bucket}.s3.amazonaws.com
            Id: mainS3Origin
            S3OriginConfig:
              OriginAccessIdentity: ''
            OriginAccessControlId: !GetAtt OriginAccessControl.Id
    
  OriginAccessControl:
    Type: AWS::CloudFront::OriginAccessControl
    Properties:
      OriginAccessControlConfig:
        Description: !Sub 'OAC of ${OriginS3Bucket}. Created by ${AWS::StackName}'
        Name: !Sub 'OAC-${OriginS3Bucket}'
        OriginAccessControlOriginType: s3
        SigningBehavior: always
        SigningProtocol: sigv4
```

※現状では、使わないとしてもテンプレート上は"OriginAccessIdentity"の設定が必須になっているようで、エラー回避のための空文字を設定しています。

# 備考（おまけ）

別記事でCodePipelineを使用して、gitリポジトリ(CodeCommit)へのpushをトリガーにSPA(React)のビルドとS3バケットへのデプロイを自動化するための構成用のCloudFormationテンプレートを↓の記事で公開しています。

[SPA(React)のビルドとデプロイを自動化するAWS CodePipeline用のCloudFormationテンプレート](https://qiita.com/Keiichi_Kinoshita/items/9a6c2a104bd8cd3635fd)

当記事で作成できるS3＋CloudFrontと、↑の記事で作成できるCodePipelineを組み合わせることで、SPAの公開およびビルドとデプロイの自動化を行える構成が簡単に構築できます。

ご参考になれば幸いです。

# 参考にさせて頂いた他記事様

- [【CloudFormation】CloudFront の OAI を OAC に移行する](https://qiita.com/hayao_k/items/8558042d52be4f29c6e6)
- [[NEW] CloudFrontからS3への新たなアクセス制御方法としてOrigin Access Control (OAC)が発表されました！](https://dev.classmethod.jp/articles/amazon-cloudfront-origin-access-control/)
- [[アップデート] Amazon CloudFront でキャッシュキーとオリジンリクエストポリシーによる管理が可能となりました](https://dev.classmethod.jp/articles/cloudfront-cache-key-policy/)
- [CloudFront で Cache Policy と Origin Request Policy が使えるようになったみたいなので調べてみた](https://michimani.net/post/aws-cloudfront-cache-key-and-origin-request-policy/)