🐳

CloudFormationを使ってCloudFront+S3の静的サイトを構築

2024/12/31に公開

目標

  • 静的サイトをS3に用意し、CloudFrontから公開する
  • S3はCloudFrontからのアクセスのみ許可する
  • アクセスログをS3バケットに格納する
  • 独自ドメインは使用しない
  • CloudFormationで作成する

static-site.png

静的ウェブサイトの設定について

そもそも静的ウェブサイトの設定方法は2020年10月6日時点では、以下の設定があるようです。

  • アクセスがオリジンアクセスアイデンティティ(OAI) で制限されたオリジンとして、REST API エンドポイントを使用する
  • 匿名 (パブリック) アクセスを許可して、ウェブサイトのエンドポイントをオリジンとして使用する
  • アクセスが Referer ヘッダーで制限されたオリジンとして、ウェブサイトのエンドポイントを使用する
  • AWS CloudFormation を使用して REST API エンドポイントをオリジンとしてデプロイし、OAI と CloudFront を指すカスタムドメインによってアクセスを制限する

今回はCloudFormationを使用してOAIで制限されたREST APIエンドポイント使用する感じになります。

実装

AWSTemplateFormatVersion: '2010-09-09'
Description: "aws static site sample"
Resources:
  S3BucketLogs:
    Type: AWS::S3::Bucket
    Properties:
      AccessControl: LogDeliveryWrite
      BucketEncryption:
        ServerSideEncryptionConfiguration:
          - ServerSideEncryptionByDefault:
              SSEAlgorithm: AES256
  S3BucketWebStatic:
    Type: AWS::S3::Bucket
    Properties:
      BucketEncryption:
        ServerSideEncryptionConfiguration:
          - ServerSideEncryptionByDefault:
              SSEAlgorithm: AES256
      LoggingConfiguration:
        DestinationBucketName: !Ref S3BucketLogs
        LogFilePrefix: 'origin/'
  BucketPolicyWebStatic:
    Type: AWS::S3::BucketPolicy
    Properties:
      Bucket: !Ref S3BucketWebStatic
      PolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Action:
              - s3:GetObject
            Effect: Allow
            Resource: !Sub '${S3BucketWebStatic.Arn}/*'
            Principal:
              CanonicalUser: !GetAtt CloudFrontOriginAccessIdentity.S3CanonicalUserId
  CloudFrontOriginAccessIdentity:
    Type: AWS::CloudFront::CloudFrontOriginAccessIdentity
    Properties:
      CloudFrontOriginAccessIdentityConfig:
        Comment: "CloudFront OAI for Static Web Site"
  CloudFrontDistribution:
    Type: AWS::CloudFront::Distribution
    Properties:
      DistributionConfig:
        DefaultCacheBehavior:
          Compress: true
          DefaultTTL: 86400
          ForwardedValues:
            QueryString: true
          MaxTTL: 31536000
          TargetOriginId: 'WebStatic-S3'
          # httpの通信はhttpsにリダイレクトする
          ViewerProtocolPolicy: 'redirect-to-https'
        Enabled: true
        HttpVersion: 'http2'
        DefaultRootObject: 'index.html'
        IPV6Enabled: true
        Logging:
          Bucket: !GetAtt S3BucketLogs.DomainName
          IncludeCookies: false
          Prefix: 'cdn/'
        Origins:
          # 作成直後にStatusCode 307とならないように、Region指定の方式
          # https://aws.amazon.com/jp/premiumsupport/knowledge-center/s3-http-307-response/
          - DomainName: !Join ["", [!Ref S3BucketWebStatic, !Sub ".s3-${AWS::Region}.amazonaws.com"]]
            Id: 'WebStatic-S3'
            S3OriginConfig:
              OriginAccessIdentity:
                !Join ['', ['origin-access-identity/cloudfront/', !Ref CloudFrontOriginAccessIdentity]]
        PriceClass: 'PriceClass_All'
        ViewerCertificate:
          CloudFrontDefaultCertificate: true
Outputs:
  S3BucketLogsName:
    Description: "S3 Bucket for keeping access logs."
    Value: !Ref S3BucketLogs
  S3BucketWebStaticName:
    Description: "S3 Bucket for deploying static site."
    Value: !Ref S3BucketWebStatic
  CloudFrontDomainName:
    Description: "CloudFront DomainName."
    Value: !GetAtt  CloudFrontDistribution.DomainName

aws cliからスタックを作成します。

$ aws cloudformation create-stack --stack-name static-site-sample --template-body file://static-site.yaml

作成完了後にOutputで作成したS3のバケット名とCloudFrontのドメイン名を確認します。

$ aws cloudformation describe-stacks --stack-name static-site-sample                   {
    "Stacks": [
        {
            "StackId": "arn:aws:cloudformation:ap-northeast-1:xxxx:stack/static-site-sample/xxxxx",
            "StackName": "static-site-sample",
            "Description": "aws static site sample",   
...
            "Outputs": [
                {
                    "OutputKey": "S3BucketLogsName",
                    "OutputValue": "static-site-sample-s3bucketlogs-xxxxx",
                    "Description": "S3 Bucket for keeping access logs."
                },
                {
                    "OutputKey": "S3BucketWebStaticName",
                    "OutputValue": "static-site-sample-s3bucketwebstatic-xxxx",
                    "Description": "S3 Bucket for deploying static site."
                },
                {
                    "OutputKey": "CloudFrontDomainName",
                    "OutputValue": "xxxxxxx.cloudfront.net",
                    "Description": "CloudFront DomainName."
                }
            ],
...                           

作成したバケットに静的ファイルをアップロードします。仮にビルドしたファイルのアップロードを想定します。

$ aws s3 cp --recursive ./build s3://static-site-sample-s3bucketwebstatic-xxxx

以上で作成が完了しているはずです。cloudfrontからのアクセスは確認ができるが、S3のオブジェクトURLは403でエラーとなるはずです。

ソースコードはこちらです。
https://github.com/shikazuki/aws-static-site-sample

参考

Discussion