CloudFormation から OriginAccessControl を設定する

# 前置き
2022年8月末ごろにAWSより OriginAccessControl がリリースされました。
これに伴い、旧来の OriginAccessIdentity が非推奨と記載されるようになりました。

https://aws.amazon.com/jp/about-aws/whats-new/2022/08/amazon-cloudfront-origin-access-control/

https://aws.amazon.com/jp/blogs/news/amazon-cloudfront-introduces-origin-access-control-oac/

よし、では手元の cloudformation yaml をドキュメントに従って書き換えて OAC を使用するようにしよう。

1. [Amazon S3 オリジンへのアクセスの制限](https://docs.aws.amazon.com/ja_jp/AmazonCloudFront/latest/DeveloperGuide/private-content-restricting-access-to-s3.html)
2. [AWS::CloudFront::OriginAccessControl](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-cloudfront-originaccesscontrol.html)

おや？CloufFormation での OAC の作成方法は書いてあるが CloudFront に付ける方法が書いてないぞ。
Console, CLI, API は CloudFront へのアタッチ方も書いてあるのに。
不思議なこともあるもんだ。

まぁ誰か記事くらい書いてるやろ。
と思ったが無いんだなこれが。

誰も・・・
CloudFormation での OAC アタッチ方を書いていないのである!!!

というわけで CloufFormation で OAC の作成から CloudFormation へのアタッチまでをやったので記事を書きました。

# OAC の強化点
折角なのでこれから使う OAC についても少し見ておきましょう。

AWS は OAC の注目すべき(OAIからの)強化点を以下としています。

>- セキュリティ – OAC は、短期間のクレデンシャル、頻繁なクレデンシャルのローテーション、およびリソースベースのポリシーのような強化されたセキュリティプラクティスで実装されています。これらは、ディストリビューションのセキュリティポスチャを強化し、混乱する代理のような攻撃に対してより良い保護を提供します。
>- 包括的な HTTP メソッドのサポート – OAC は GET、PUT、POST、PATCH、DELETE、OPTIONS、および HEAD をサポートしています。
>- SSE-KMS – OAC は、SSE-KMS で暗号化された S3 オブジェクトのダウンロードとアップロードをサポートしています。
>- すべての AWS リージョンでの S3 にアクセス – OAC は、既存のリージョンと将来のすべてのリージョンを含む、すべての AWS リージョンでの S3 へのアクセスをサポートしています。一方、OAI は既存の AWS リージョンと 2022 年 12 月までに開始されるリージョンでのみサポートされます。

逆に、現在の OAI でできないことは [Amazon S3 オリジンへのアクセスの制限 - オリジンアクセスアイデンティティの使用 (レガシー、非推奨)](https://docs.aws.amazon.com/ja_jp/AmazonCloudFront/latest/DeveloperGuide/private-content-restricting-access-to-s3.html#private-content-restricting-access-to-s3-oai) で確認できます。


従来は cloudfront に OAI を設定して、S3 バケットポリシーにてこの OAI を許可することで S3 バケットやオブジェクトをパブリックにすることなく cloudfront からのアクセスのみを許可するようにアクセス制限を設けられました。

OAC ではこれに加えて S3 オブジェクトの SSE-KMS による暗号化に対応するようです。
(OAI では SSE-KMS で暗号化したオブジェクトを利用できなかった。([バケット内のオブジェクトが AWS Key Management Service (AWS KMS) を使用して暗号化されていないことを確認します](https://aws.amazon.com/jp/premiumsupport/knowledge-center/s3-website-cloudfront-error-403/#Confirm_that_the_objects_in_the_bucket_aren.27t_encrypted_by_AWS_Key_Management_Service_.28AWS_KMS.29)))

# 実際の yaml
```yaml
---
AWSTemplateFormatVersion: '2010-09-09'
Description: origin access control test.

Resources:
  S3Bucket:
    Type: AWS::S3::Bucket
    Properties:
      BucketName: {bucket name}
      PublicAccessBlockConfiguration:
        BlockPublicAcls: true
        BlockPublicPolicy: true
        IgnorePublicAcls: true
        RestrictPublicBuckets: true

  OriginAccessControl:
    Type: AWS::CloudFront::OriginAccessControl
    Properties:
      OriginAccessControlConfig:
        Description: ''
        Name: {origin access contlor name}
        OriginAccessControlOriginType: s3
        SigningBehavior: always
        SigningProtocol: sigv4

  CloudFrontDistribution:
    Type: AWS::CloudFront::Distribution
    Properties:
      DistributionConfig:
        Origins:
          - DomainName: !GetAtt S3Bucket.RegionalDomainName
            Id: oac-test-bucket
            OriginAccessControlId: !GetAtt OriginAccessControl.Id
            S3OriginConfig: {}
        DefaultCacheBehavior:
          ForwardedValues:
              QueryString: 'false'
              Cookies:
                Forward: none
          TargetOriginId: oac-test-bucket
          ViewerProtocolPolicy: allow-all
        Enabled: true

  BucketPolicy:
    Type: "AWS::S3::BucketPolicy"
    Properties:
      Bucket: !Ref S3Bucket
      PolicyDocument:
        Statement:
        - Action: "s3:GetObject"
          Effect: Allow
          Resource: !Join
            - ''
            - - 'arn:aws:s3:::'
              - !Ref S3Bucket
              - /*
          Principal:
            Service: cloudfront.amazonaws.com
          Condition:
            StringEquals:
              AWS:SourceArn: !Join
                - ''
                - - 'arn:aws:cloudfront::{your AWS account ID}:distribution/'
                  - !Ref CloudFrontDistribution
```

あまり OAC と関係のない項目は適当に [Amazon CloudFront テンプレートスニペット](https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/quickref-cloudfront.html) の 「Amazon S3 オリジンを使用した Amazon CloudFront Distribution リソース」 あたりから値を拝借している。

private な s3 bucket を作り、同時に作成する cloudfront からのアクセスのみを許可した。

それぞれの項目に軽く触れていく

## S3Bucket
```yaml
  S3Bucket:
    Type: AWS::S3::Bucket
    Properties:
      BucketName: {bucket name}
      PublicAccessBlockConfiguration:
        BlockPublicAcls: true
        BlockPublicPolicy: true
        IgnorePublicAcls: true
        RestrictPublicBuckets: true
```
とりあえず `PublicAccessBlockConfiguration` はすべて `true` にしておいた。

- BlockPublicAcls - 新たに、バケットやオブジェクトの ACL をパブリックにすることや、パブリックなオブジェクトを配置することを禁止する。(= 作成も更新も禁止)
- IgnorePublicAcls - バケットとオブジェクトのパブリック ACL を無視する。
- BlockPublicPolicy - パブリックなバケットポリシー、アクセスポイントポリシーへの変更を禁止する。
- RestrictPublicBuckets - パブリックポリシーを持つバケットやアクセスポイントへのアクセスをバケット所有者のAWSサービスと承認されたユーザーのみに制限する。

総合して、現在のパブリック設定を無視し、新規にパブリック設定を作ることを禁止する。

## OriginAccessControl
```yaml
  OriginAccessControl:
    Type: AWS::CloudFront::OriginAccessControl
    Properties:
      OriginAccessControlConfig:
        Description: ''
        Name: {origin access contlor name}
        OriginAccessControlOriginType: s3
        SigningBehavior: always
        SigningProtocol: sigv4
```

これはほとんど [Amazon S3 オリジンへのアクセスの制限](https://docs.aws.amazon.com/ja_jp/AmazonCloudFront/latest/DeveloperGuide/private-content-restricting-access-to-s3.html) の例のまま。
詳細は　[AWS::CloudFront::OriginAccessControl](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-cloudfront-originaccesscontrol.html)

注意点
1. 例では `DisplayName` と記載があるが、cloudformation ドキュメントでは `Name` となっているのでそっちに従った
1. `Description` は非必須項目だが、空文字を許容するだけで項目自体が存在しないとエラーが出るようだ。

## CloudFrontDistribution
```yaml
  CloudFrontDistribution:
    Type: AWS::CloudFront::Distribution
    Properties:
      DistributionConfig:
        Origins:
          - DomainName: !GetAtt S3Bucket.RegionalDomainName
            Id: oac-test-bucket
            OriginAccessControlId: !GetAtt OriginAccessControl.Id
            S3OriginConfig: {}
        DefaultCacheBehavior:
          ForwardedValues:
              QueryString: 'false'
              Cookies:
                Forward: none
          TargetOriginId: oac-test-bucket
          ViewerProtocolPolicy: allow-all
        Enabled: true
```

とにかく、ここまでで作成した s3 origin へ oac を使ってアクセスする簡単な cloudfront を作成した。

S3 origin への OAC のアタッチだが、[AWS::CloudFront::Distribution Origin](https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/aws-properties-cloudfront-distribution-origin.html) に `OriginAccessControlId` という項目が存在し、[AWS::CloudFront::OriginAccessControl
](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-cloudfront-originaccesscontrol.html) にも `Fn::GetAtt - Id` が存在したので与えてみたところうまくいった。

## BucketPolicy
```yaml
  BucketPolicy:
    Type: "AWS::S3::BucketPolicy"
    Properties:
      Bucket: !Ref S3Bucket
      PolicyDocument:
        Statement:
        - Action: "s3:GetObject"
          Effect: Allow
          Resource: !Join
            - ''
            - - 'arn:aws:s3:::'
              - !Ref S3Bucket
              - /*
          Principal:
            Service: cloudfront.amazonaws.com
          Condition:
            StringEquals:
              AWS:SourceArn: !Join
                - ''
                - - 'arn:aws:cloudfront::{your AWS account ID}:distribution/'
                  - !Ref CloudFrontDistribution
```

OAI では `Principal` に OAI を指定して、OAI を実行元サービスとして許容していたが、OAC では実行元サービスは cloudfront となる。
`Condition` に [Amazon S3 オリジンへのアクセスの制限](https://docs.aws.amazon.com/ja_jp/AmazonCloudFront/latest/DeveloperGuide/private-content-restricting-access-to-s3.html) の「S3 バケットへのアクセス許可をオリジンアクセスコントロールに付与する」 を参考に distribution を指定。

# アクセスしてみる
作成された s3 へ適当なファイルを置いて、cloudfront のディストリビューションドメイン名を確認してブラウザからアクセスしてみます。

デフォルトルートオブジェクトは指定しなかったので適当にパスを付けて、

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/315470/4b648ae0-e788-dfe0-18b8-d30264007821.png)

やったぜ。

念のため S3 オブジェクトへ直接アクセスもしておきましょう。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/315470/b2a5420c-ffd5-7e8e-7484-8a499cfb0917.png)

以上。
cloudformation での OAC 作成から cloudfront distribution へのアタッチまででした。


# 参考情報
1. [Amazon CloudFront でオリジンアクセスコントロール (OAC) をリリース](https://aws.amazon.com/jp/about-aws/whats-new/2022/08/amazon-cloudfront-origin-access-control/)
1. [Amazon CloudFront オリジンアクセスコントロール（OAC）のご紹介](https://aws.amazon.com/jp/blogs/news/amazon-cloudfront-introduces-origin-access-control-oac/)
1. [Amazon S3 オリジンへのアクセスの制限](https://docs.aws.amazon.com/ja_jp/AmazonCloudFront/latest/DeveloperGuide/private-content-restricting-access-to-s3.html)
1. [Amazon CloudFront テンプレートスニペット](https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/quickref-cloudfront.html)
1. [AWS::CloudFront::Distribution Origin](https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/aws-properties-cloudfront-distribution-origin.html)
1. [AWS::CloudFront::OriginAccessControl](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-cloudfront-originaccesscontrol.html)
1. [CloudFront ディストリビューションのオリジンとして S3 ウェブサイトのエンドポイントを使用しています。403 Access Denied エラーが発生するのはなぜですか?](https://aws.amazon.com/jp/premiumsupport/knowledge-center/s3-website-cloudfront-error-403/)