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

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

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

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

あわせて読みたい
Refererヘッダーを使用してCloudFront経由でS3コンテンツを配信する 【CloudFrontからS3コンテンツを配信する際に、オリジンへのアクセスをReferer ヘッダーで制限する】 以下のページで、静的ウェブサイトホスティング機能を有効化したS3...

ただし上記の構成には1つの課題があります。
それはRefererヘッダーに適切な値を設定されてしまうと、直接オリジンにアクセスすることができてしまうという点です。

これを解決するために、今回はOAI(Origin Access Identity)を使用します。

オリジンアクセスアイデンティティ (OAI) と呼ばれる特別な CloudFront ユーザーを作成し、ディストリビューションに関連付けます。

CloudFront が OAI を使用してバケット内のファイルにアクセスしてユーザーに提供できるように、S3 バケットのアクセス許可を設定します。

オリジンアクセスアイデンティティ (OAI) を使用して Amazon S3 コンテンツへのアクセスを制限する

OAIを使用する方法と、Refererヘッダーを使用する方法を簡単に比較します。

項目OAIRefererヘッダー
静的ウェブサイトホスティング無効有効
オリジンに指定するエンドポイントREST APIエンドポイントウェブサイトエンドポイント
ドキュメントルートオブジェクト必須不要
アクセス制限方法(バケットポリシー)プリンシパルにOAIユーザーを設定Refererヘッダーの値をチェックするように設定

構成する環境

Diagram of S3 content delivery via CloudFront - OAI ver

全体的な構成は以前と同様です。
CloudFrontにはOAIを作成し、S3バケットポリシーにはOAIユーザーからのアクセスを許可する設定を行います。

CloudFormationテンプレートファイル

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

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

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

本ページでは、OAIを使用したオリジンへのアクセス制限方法を中心に取り上げます。
それ以外の内容については、冒頭でご紹介したページをご確認ください。

静的ウェブサイトホスティング機能は不要

Resources:
  Bucket:
    Type: AWS::S3::Bucket
    Properties:
      BucketName: !Ref Prefix
      AccessControl: Private
      #WebsiteConfiguration:
      #  IndexDocument: index.html
Code language: YAML (yaml)

OAIを使用するためには、静的ウェブサイトホスティング機能を有効化する必要はありません。

この設定では、バケットで静的ウェブサイトホスティングを有効にする必要はありません。

CloudFront を使用して、Amazon S3 でホストされた静的ウェブサイトを公開するにはどうすればよいですか?

ですから該当のコードをコメントアウトしています。

S3バケットのエンドポイント整理

ここでS3バケットの2つのエンドポイントの特徴を確認しておきます。

項目REST APIエンドポイントウェブサイトエンドポイント
フォーマットDOC-EXAMPLE-BUCKET.s3.region.amazonaws.comDOC-EXAMPLE-BUCKET.s3.amazonaws.comDOC-EXAMPLE-BUCKET.s3-website-region.amazonaws.com
HTTP/HTTPSHTTPSHTTP
CFNでの取得方法!GetAtt Bucket.RegionalDomainName!GetAtt Bucket.DomainName!GetAtt Bucket.WebsiteURL

冒頭で述べた通り、OAIを使用する場合は、CloudFrontのオリジンとしてREST APIエンドポイントを指定します。

この設定では、静的ウェブサイトホスティング機能のウェブサイトエンドポイントではなく、バケットの REST API エンドポイントを使用します。

CloudFront を使用して、Amazon S3 でホストされた静的ウェブサイトを公開するにはどうすればよいですか?

次にREST APIエンドポイントに注目します。
REST APIエンドポイントには2つのフォーマットが用意されていますが、リージョン情報が含まれているものの方が望ましいとされています。

CloudFront のオリジンとして Amazon S3 バケットを指定する場合は、次の形式を使用することをお勧めします。

bucket-name.s3.region.amazonaws.com

CloudFront ディストリビューションでのさまざまなオリジンの使用

上記に従い、今回はこのフォーマットを使用することにします。

このフォーマットの値を取得する方法を確認します。
この値はCloudFormationの組み込み関数Fn::GetAttを使用して、「!GetAtt Bucket.RegionalDomainName」で取得することができます。

OAI

Resources:
  OAI:
    Type: AWS::CloudFront::CloudFrontOriginAccessIdentity
    Properties:
      CloudFrontOriginAccessIdentityConfig:
        Comment: !Ref Prefix
Code language: YAML (yaml)

OAIを定義します。
設定項目は限られており、Commentプロパティのみです。

ここでOAIの概念について確認しておきます。
OAIは一種のIAMユーザーと捉えることができます。
CloudFrontディストリビューションに紐づく特殊なユーザーであり、エンドユーザーからリクエストを受けると、OAIがユーザーの代理でS3バケットにアクセスするという流れになります。

Diagram of users accessing S3 buckets when using OAI.

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
            S3OriginConfig:
              OriginAccessIdentity: !Sub "origin-access-identity/cloudfront/${OAI}"
            #CustomOriginConfig:
        PriceClass: PriceClass_All
Code language: YAML (yaml)

ポイントは2点です。

1点目はデフォルトルートオブジェクトです。
この設定は必須です。

ディストリビューションにデフォルトのルートオブジェクトが定義されておらず、リクエスタが s3:ListBucket アクセス許可を持っていない場合、リクエスタはアクセス拒否エラーを受け取ります。

CloudFront ディストリビューションのオリジンとして S3 REST API エンドポイントを使用しています。403 Access Denied エラーが発生するのはなぜですか?

上記に従い、index.htmlというファイルをルートオブジェクトとして設定します。
なおこのファイルはCloudFormationカスタムリソースでLambda関数を実行し、自動生成します。
詳細は以下のページをご確認ください。

あわせて読みたい
CFNカスタムリソースでS3オブジェクトを作成・削除する 【CloudFormationカスタムリソースを使って、スタック生成/削除時にS3オブジェクトを作成/削除する方法】 CloudFormationカスタムリソースはスタック操作(作成、更新、...

2点目はオリジンに関する設定です。
まずオリジンの指定ですが、Originsプロパティの内部で行います。
DomainNameプロパティで、先述のリージョン情報を含むフォーマットのREST APIエンドポイントを指定します。
次にオリジンの詳細設定として、S3OriginConfigプロパティに設定を行います。
Refererヘッダーを使用する際は、CustomOriginConfigプロパティに設定を行いましたが、今回はそうではありません。

Use S3OriginConfig to specify an Amazon S3 bucket that is not configured with static website hosting.

Use CustomOriginConfig to specify all other kinds of origins

AWS::CloudFront::Distribution Origin

上記に従い、S3OriginConfigプロパティを選択します。
このプロパティには、OriginAccessIdentityプロパティというOAIを設定するプロパティが用意されています。
記載方法は以下の通りに説明されています。

The format of the value is:

origin-access-identity/cloudfront/ID-of-origin-access-identity

AWS::CloudFront::Distribution S3OriginConfig

上記に従い、組み込み関数Fn::Subを使用して、文字列を作成します。

バケットポリシーのプリンシパルにOAIを指定する

Resources:
  BucketPolicy:
    Type: AWS::S3::BucketPolicy
    Properties:
      Bucket: !Ref BucketName
      PolicyDocument:
        Statement:
          Action:
            - s3:GetObject
          Effect: Allow
          Principal:
            AWS: !Sub "arn:aws:iam::cloudfront:user/CloudFront Origin Access Identity ${OAI}"
          Resource: !Sub "arn:aws:s3:::${BucketName}/*"
          #Condition:
Code language: YAML (yaml)

バケットポリシーにて、バケットにアクセスできるユーザーを制限します。
今回はConditionではなく、Principalプロパティを使用します。
このプロパティにユーザーとしてOAIを設定することになります。
具体的には以下の通りに設定を行います。

arn:aws:iam::cloudfront:user/CloudFront Origin Access Identity [OAI-id]

オリジンアクセスアイデンティティ (OAI) を使用して Amazon S3 コンテンツへのアクセスを制限する

環境構築

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

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

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

あわせて読みたい
CloudFormationのネストされたスタックで環境を構築する 【CloudFormationのネストされたスタックで環境を構築する方法】 CloudFormationにおけるネストされたスタックを検証します。 CloudFormationでは、スタックをネストす...

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

  • S3バケット:fa-050
  • CloudFrontディストリビューションのドメイン:https://d38ixdp3esygfv.cloudfront.net/
  • OAIの名前:fa-050
  • OAIのID:E3A4FYJVN7KUC8

AWS Management Consoleからもリソースを確認します。
まずCloudFrontです。

CloudFront settings.

S3バケットがオリジンとして設定されています。
リージョンを含むタイプのREST APIエンドポイントを指定しています。
またバケットへのアクセス時は、OAI(fa-050)を使用する設定となっていることも確認できます。

Specify index.html as Default root object.

デフォルトルートオブジェクトにindex.htmlを設定しています。
つまり「https://d38ixdp3esygfv.cloudfront.net/」にアクセスすると、「https://d38ixdp3esygfv.cloudfront.net/index.html」にアクセスしたものとしてして動作するという意味です。

次にS3バケットポリシーを確認します。

Specify OAI as the principal of the bucket policy.

先ほどとOAIのIDを指定しています。
このOAIからのアクセスの場合は、オブジェクトへのアクセスを許可するという内容です。

Static website hosting functionality is disabled.

ちなみに静的ウェブサイトホスティング機能は有効化していません。

動作確認

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

Result when accessing S via CloudFront.

S3バケットのコンテンツが表示されました。
CloudFrontを経由して、オリジンであるS3バケットのREST APIエンドポイントにアクセスしているということです。

当然、S3バケットに直接アクセスすることはできません。

Result of direct S3 bucket access.

今回のOAIを使用する方法は、ヘッダー情報に依存した方法ではないため、よりセキュアな方法と言えます。

まとめ

S3バケットのコンテンツを配信する際に、OAIを使用することによって、CloudFront経由でアクセスすることを強制する方法をご紹介しました。