CloudFormation で OAI を使った CloudFront + S3 の静的コンテンツ配信インフラを作る

よく訓練されたアップル信者、都元です。年末に Fitbit Alta HR を買いました。 睡眠や心拍がほぼ24時間体制でトラッキングされるって面白いですね。え? アップルウォッうわなにをする

はじめに

さて、S3 + CloudFront で静的コンテンツ配信をしよう、というのはもう今さら言うまでもない鉄板構成だと思います。かつてAWSにおける静的コンテンツ配信パターンカタログ(アンチパターン含む)でご紹介した「横綱」パターンですね。

公開サイトとして、この構成を雑に作る場合

CloudFront を介して配信するということは基本的に公開サイトとして配信するユースケースが多いと思います。

つまり、原則としてはみんな CloudFront を介してコンテンツにアクセスして欲しいのですが、最悪 S3 に直接アクセスしてコンテンツを読まれたとしてもセキュリティ上は問題ない、ということです。

このような要件であれば、S3 の設定を Public にしてしまい、CloudFront からも(どこからでも) 自由にコンテンツを読み出せるように設定してしまうのが楽だと思います。

プライベートコンテンツを配信したい場合

一方で、CloudFront の署名付きURL(下記のリンクを参照)などの機能を使ってプライベートコンテンツを配信したい時は、前述のように S3 バケットを Public にしてしまうわけにはいきません。

このような時は、CloudFront の Origin Access Identity (以下、OAI) という機能を使います。 要するに S3 を公開状態にすることなく、S3 へのアクセスを CloudFront からのリクエストに絞るための仕組みです。詳細は次のエントリーを参照してください。

[CloudFront + S3]特定バケットに特定ディストリビューションのみからアクセスできるよう設定する

要するに次のような作業をします。

  1. S3 バケットを作成
  2. CloudFront の OAI を作成
  3. S3 バケットに、作成した OAI からのアクセスを許可するバケットポリシーを設定
  4. オリジンとして S3 バケットを設定、そしてオリジンアクセスの際にこの OAI を利用するように設定して、CloudFront distribution を作成

CloudFront + S3 with OAI の構成を CloudFormation で。

しかし、この構成の手動構築はそこそこ面倒です。こういう時は、CloudFormation でテンプレート化しておくのが吉でしょう。

ただ、私の古い記憶によると、CloudFormation はステップ 2 の Origin Access Identity 作成をサポートしていませんでした。当時やる気を失い、テンプレートを作らないでいた気がします。カスタムリソースを使うという手もありますが、いや、まぁ……ねぇ…w

しかし、先日あらためて調べてみたところ、いつの間にかサポートがありました。え、これいつの間にリリースされたの! 誰もブログ書いてないんじゃ…。やべえ、ということでいま筆を取っている次第です。

AWS::CloudFront::CloudFrontOriginAccessIdentity リソース

はい、テンプレート置いておきます。

AWSTemplateFormatVersion: 2010-09-09
Description: Static contents distribution using S3 and CloudFront.

Resources:
  # S3 bucket contains static contents
  AssetsBucket:
    Type: AWS::S3::Bucket
    DeletionPolicy: Retain
  # S3 bucket policy to allow access from CloudFront OAI
  AssetsBucketPolicy:
    Type: AWS::S3::BucketPolicy
    Properties:
      Bucket: !Ref AssetsBucket
      PolicyDocument:
        Statement:
          - Action: s3:GetObject
            Effect: Allow
            Resource: !Sub arn:aws:s3:::${AssetsBucket}/*
            Principal:
              AWS: !Sub arn:aws:iam::cloudfront:user/CloudFront Origin Access Identity ${CloudFrontOriginAccessIdentity}

  # CloudFront Distribution for contents delivery
  AssetsDistribution:
    Type: AWS::CloudFront::Distribution
    Properties:
      DistributionConfig:
        Origins:
        - Id: S3Origin
          DomainName: !GetAtt AssetsBucket.DomainName
          S3OriginConfig:
            OriginAccessIdentity: !Sub origin-access-identity/cloudfront/${CloudFrontOriginAccessIdentity}
        Enabled: true
        DefaultRootObject: index.html
        Comment: !Sub ${AWS::StackName} distribution
        DefaultCacheBehavior:
          TargetOriginId: S3Origin
          ForwardedValues:
            QueryString: false
          ViewerProtocolPolicy: redirect-to-https
  CloudFrontOriginAccessIdentity:
    Type: AWS::CloudFront::CloudFrontOriginAccessIdentity
    Properties:
      CloudFrontOriginAccessIdentityConfig:
        Comment: !Ref AWS::StackName

Outputs:
  URL:
    Value: !Join [ "", [ "https://", !GetAtt [ AssetsDistribution, DomainName ]]]

ご自由にご利用ください!