OACを使用してCloudFront経由でS3コンテンツを配信する

OACを使用してCloudFront経由でS3コンテンツを配信する

CloudFrontからS3コンテンツを配信する際に、OACでオリジンへのアクセスを制限する

以下のページで、S3バケットのコンテンツをCloudFrontから配信する際に、OAIを使用することで、オリジンへのアクセスを制限する方法をご紹介しました。

https://awstut.com/2022/05/16/s3-content-delivery-via-cloudfront-oai

2022年8月にOAC(Origin Access Control)が発表されました。
AWS公式としては、OAIよりもOACを使用することを推奨しています。

OAC は以下をサポートしているため、OAC の使用をお勧めします。

すべての AWS リージョンのすべての Amazon S3 バケット (2022 年 12 月以降に開始されたオプトインリージョンを含む)

AWS KMS による Amazon S3 サーバー側の暗号化 (SSE-KMS)

Amazon S3 に対する動的なリクエスト (POST、PUT など)

Amazon S3 オリジンへのアクセスの制限

今回はOACを使用して、CloudFront経由でS3バケットにアクセスする方法を確認します。

構築する環境

Diagram of delivering S3 content via CloudFront using OAC

全体的な構成は冒頭のページと同様です。
CloudFrontのOACを作成し、S3バケットポリシーには、このOACからのアクセスを許可する設定を行います。

CloudFormationテンプレートファイル

上記の構成をCloudFormationで構築します。
以下のURLにCloudFormationテンプレートを配置しています。

https://github.com/awstut-an-r/awstut-fa/tree/main/096

テンプレートファイルのポイント解説

S3バケット

Resources:
  Bucket:
    Type: AWS::S3::Bucket
    Properties:
      BucketName: !Ref Prefix
      AccessControl: Private
Code language: YAML (yaml)

コンテンツを配置するバケットです。
OACを使用する上で、S3バケット側は特別な設定は不要です。

OAC

Resources:
  OAC:
    Type: AWS::CloudFront::OriginAccessControl
    Properties:
      OriginAccessControlConfig:
        Name: !Ref Prefix
        OriginAccessControlOriginType: s3
        SigningBehavior: always
        SigningProtocol: sigv4
Code language: YAML (yaml)

OACを作成する上で、重要なパラメータは以下の3つです。

1つ目はOriginAccessControlOriginTypeプロパティです。
本パラメータは「s3」のみ取り得ます。

2つ目はSigningBehaviorプロパティです。
本パラメータはCloudFrontがオリジン(S3)にアクセスする際に、署名するかに関するものです。
AWS公式では、以下の通り言及されています。

CloudFront が常に着信リクエストに署名するので、アプリケーションが常に動作することを保証するため、ほとんどのお客様は “ 署名リクエスト ” オプションを使用することをお勧めします。さらに、CloudFront がリクエストに署名することで、クライアントと CloudFront 間のデータ転送が少なくなり、アプリケーションのパフォーマンスが向上します。

Amazon CloudFront オリジンアクセスコントロール(OAC)のご紹介

上記に従い、常時署名リクエストするように「always」に設定します。

3つ目はSigningProtocolプロパティです。
本パラメータは「sigv4」のみ取り得ます。

CloudFront

Resources:
  Distribution:
    Type: AWS::CloudFront::Distribution
    Properties:
      DistributionConfig:
        DefaultCacheBehavior:
          AllowedMethods:
            - GET
            - HEAD
          CachedMethods:
            - GET
            - HEAD
          Compress: true
          ForwardedValues:
            Cookies:
              Forward: none
            QueryString: false
          TargetOriginId: !Ref BucketName
          ViewerProtocolPolicy: allow-all
        DefaultRootObject: index.html
        Enabled: true
        Origins:
          - DomainName: !Ref BucketRegionalDomainName
            Id: !Ref BucketName
            #CustomOriginConfig:
            OriginAccessControlId: !GetAtt OAC.Id
            S3OriginConfig:
              OriginAccessIdentity: ""
        PriceClass: PriceClass_All
Code language: YAML (yaml)

OACを使用する上で、CloudFrontの設定で重要な項目はOriginsプロパティです。
先ほど定義したOACのIDをOriginAccessControlIdプロパティに設定します。

S3OriginConfigプロパティもポイントです。
内部のOriginAccessIdentityプロパティに空文字列を指定します。
仮に上記を設定しない場合、CloudFormationスタック作成時に、以下のエラーが発生します。

Invalid request provided: Exactly one of CustomOriginConfig and S3OriginConfig must be specified

ちなみにCustomOriginConfigプロパティは、静的Webサイトホスティングを有効化したS3バケット用のパラメータですから、こちらは設定を行いません。

バケットポリシー

Resources:
  BucketPolicy:
    Type: AWS::S3::BucketPolicy
    Properties:
      Bucket: !Ref BucketName
      PolicyDocument:
        Statement:
          Action:
            - s3:GetObject
          Effect: Allow
          Principal:
            Service:
              - cloudfront.amazonaws.com
          Resource: !Sub "arn:aws:s3:::${BucketName}/*"
          Condition:
            StringEquals:
              AWS:SourceArn: !Sub "arn:aws:cloudfront::${AWS::AccountId}:distribution/${Distribution}"
Code language: YAML (yaml)

以下のAWS公式ページを参考に、バケットポリシーを定義します。

https://docs.aws.amazon.com/ja_jp/AmazonCloudFront/latest/DeveloperGuide/private-content-restricting-access-to-s3.html

先述のCloudFrontディストリビューションがプリンシパルの場合、オブジェクトの取得するアクションを許可する内容です。

(参考)CloudFormationカスタムリソース

Resources:
  CustomResource:
    Type: Custom::CustomResource
    Properties:
      ServiceToken: !GetAtt Function.Arn

  Function:
    Type: AWS::Lambda::Function
    Properties:
      Code:
        ZipFile: |
          import boto3
          import cfnresponse
          import os

          bucket_name = os.environ['BUCKET_NAME']

          object_name = 'index.html'
          object_body = """<html>
            <head></head>
            <body>
              <h1>index.html</h1>
              <p>{bucket_name}</p>
            </body>
          </html>""".format(bucket_name=bucket_name)
          content_type = 'text/html'
          char_code= 'utf-8'

          s3_client = boto3.client('s3')

          CREATE = 'Create'
          DELETE = 'Delete'
          response_data = {}

          def lambda_handler(event, context):
            try:
              if event['RequestType'] == CREATE:
                put_response = s3_client.put_object(
                  Bucket=bucket_name,
                  Key=object_name,
                  Body=object_body.encode(char_code),
                  ContentEncoding=char_code,
                  ContentType=content_type)
                print(put_response)

              elif event['RequestType'] == DELETE:
                list_response = s3_client.list_objects_v2(
                  Bucket=bucket_name)
                for obj in list_response['Contents']:
                  delete_response = s3_client.delete_object(
                    Bucket=bucket_name,
                    Key=obj['Key'])
                  print(delete_response)

              cfnresponse.send(event, context, cfnresponse.SUCCESS, response_data)

            except Exception as e:
              print(e)
              cfnresponse.send(event, context, cfnresponse.FAILED, response_data)
      Environment:
        Variables:
          BUCKET_NAME: !Ref BucketName
      FunctionName: !Sub "${Prefix}-function"
      Handler: !Ref Handler
      Runtime: !Ref Runtime
      Role: !GetAtt FunctionRole.Arn
Code language: YAML (yaml)

CloudFormationカスタムリソースを使用して、S3バケットに自動的にオブジェクトを作成します。
今回は先述のS3バケット内にindex.htmlを作成します。

詳細につきましては、以下のページをご確認ください。

https://awstut.com/2022/05/08/create-and-delete-s3-object-by-cfn-custom-resource

環境構築

CloudFormationを使用して、本環境を構築し、実際の挙動を確認します。

CloudFormationスタックを作成し、スタック内のリソースを確認する

CloudFormationスタックを作成します。
スタックの作成および各スタックの確認方法については、以下のページをご確認ください。

https://awstut.com/2021/12/02/cloudformation-nested-stacks

各スタックのリソースを確認した結果、今回作成された主要リソースの情報は以下の通りです。

  • CloudFrontディストリビューション:E3YPSL0CON945
  • CloudFrontドメイン名:d1ujnv40lgk4sa.cloudfront.net
  • OAC:fa-096
  • S3バケット:fa-096

作成されたリソースをAWS Management Consoleから確認します。
OACを確認します。

Detail of CloudFront 1.

OACが作成されています。
SigV4で常に署名を実施するように設定されていることがわかります。

CloudFrontディストリビューションに関連づいていることもわかります。

Detail of CloudFront 2.

CloudFrontディストリビューション側の設定ページを確認しても、OACの設定が有効化されていることがわかります。

またオリジンとしてS3バケットが指定されていることもわかります。

Detail of S3 1 .

バケット内にindex.htmlファイルが設置されています。
CloudFormationカスタムリソースに紐づくLambda関数によって、HTMLファイルが自動的に生成されて設置されました。

Detail of S3 2.

バケットポリシーを確認すると、CloudFrontをプリンシパルとして、S3バケットへのアクセスを許可する内容であることがわかります。

動作確認

準備が整いましたので、CloudFrontディストリビューションにアクセスします。

Detail of CloudFront 3.

S3バケット内のHTMLファイル(index.html)にアクセスすることができました。
本来バケットポリシーによって制限されているバケット内オブジェクトへのアクセスが、OACを使用することによって、CloudFront経由でのアクセスが許可されたということです。

次に直接S3バケットへアクセスします。

Detail of CloudFront 4.

拒否されました。
バケットポリシーが動作しており、直接のアクセスが制限されているということです。

まとめ

OACを使用して、CloudFront経由でS3バケットにアクセスする方法をご紹介しました。