🌐

AWS CDK S3+CloudFrontウェブサイト構築 Typescript #2

に公開

はじめに

以前、AWSCDKでS3静的ウェブサイトホスティング構築をしました。

https://zenn.dev/michinoku/articles/42c45f187ac06b

今回はAWSが提供するCDNサービスであるCloudFrontをS3の前に配置してCloudFront経由でS3静的サイトを配信できるようにしていきます。

CloudFrontを使用する理由

グローバルへの高速なコンテンツ配信

AWSのCDN(コンテンツ配信ネットワーク)であるCloudFrontを使用すると、S3に格納されたコンテンツはアクセス元から最も近い場所(エッジロケーション)から配信されます。

これによりコンテンツを高速に配信することが可能になります。

コスト効率性

S3バケットはインターネットへのコンテンツ配信に対して料金が発生します。

コンテンツへのアクセスが増加すればするほど料金が高くなります。

CloudFrontには配信したコンテンツをキャッシュに保持しておく機能がありキャッシュされた内容へのアクセスにはS3への問い合わせを行うことなくレスポンスすることができます。

これによりS3(オリジンサーバー)へのアクセスを削減することができコスト軽減が可能になります。

S3への直接的なアクセスの防止

クライアントから見た場合にCloudFrontがS3の前に配置されることでクライアントがコンテンツにアクセスする場合にはCloudFrontに対してのアクセスを実施することになります。

これにより直接S3バケットのURLを叩いてアクセスされるリスクを回避することができます。

前述の特徴と重複しますが、S3への直接的なアクセス防止は集中アクセスによるS3料金の増幅を回避できます。

(S3単体にはレート制限機能等のDos攻撃への対抗策が無い)

S3単体ではHTTPS通信に対応していない。

CloudFrontを使用することでHTTPSを使用したセキュアな通信が可能になります。

一元的なログ取得の実現

コンテンツへのアクセスをCloudFront経由に限定することでログをCloudFrontに集約することができます。

S3への直接的なアクセスを許可してしまうとCloudFront側のログに記録されないものが出てしまいます。

構築

今回はS3の前にCloudFrontを配置しCloudFrontからのコンテンツ配信を可能とします。

まずはS3バケットを非公開にします。

今回使用するCloudFrontのモジュールをimportに追加しておきます。

前回記述していたwebsiteIndexDocumentがCloudFront側に設定するためS3バケット側では不要となります。

その他修正するポイントは以下コードを参照してください。

s3_cloudfront-stack.ts
import * as cdk from 'aws-cdk-lib';
import { Construct } from 'constructs';
import {
  aws_s3 as s3,
  aws_s3_deployment as s3_deployment,
  aws_cloudfront as cloudfront,
  aws_cloudfront_origins as cloudfront_origins,
 } from 'aws-cdk-lib';

export class S3CloudfrontStack extends cdk.Stack {
  constructor(scope: Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);
    
    const s3Bucket = new s3.Bucket(this, 'WebSiteBucket', {
      bucketName: `s3-bucket-${cdk.Aws.ACCOUNT_ID}-${cdk.Aws.REGION}`,
      autoDeleteObjects: true,
      removalPolicy: cdk.RemovalPolicy.DESTROY,
        // S3バケットのパブリックアクセスを拒否(デフォルト)
      publicReadAccess: false,
      websiteIndexDocument: 'index.html',
        // S3へのパブリックアクセスを全て拒否(デフォルト)
      blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL,
    });

これでS3バケットがパブリックに公開されなりました。

なのでこれ以降はS3の静的ウェブサイトホスティング機能ではなくCloudFrontの機能としてコンテンツを配信していくことになります。

OAC(オリジンアクセスコントロール)という機能を使うことでS3バケットへのアクセスをCloudFront経由に限定することができます。

OACの名前にあるとおりCloudFrontを経由して配信されるコンテンツをオリジンと呼びます。

CloudFrontではオリジンや配信におけるキャッシュポリシーなどの設定をひとまとめにディストリビューションとして扱います。

CDKではOAC、ディストリビューション、オリジンを定義してCloudFrontを構築していきます。

https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_cloudfront.S3OriginAccessControl.html

https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_cloudfront.Distribution.html

https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_cloudfront_origins.S3BucketOrigin.html

s3_cloudfront-stack.ts
    const oac = new cloudfront.S3OriginAccessControl(this, 'OAC', {
      originAccessControlName: `${id}-oac`,
        // 認証の設定
        // SIGV4:認証プロトコル(現状唯一)
        // ALWAYS/NO_OVERRIDE:CloudFrontが認証するリクエストの選定
      signing: cloudfront.Signing.SIGV4_ALWAYS,
    }) 

    const distribution = new cloudfront.Distribution(this, 'Distribution', {
          // CloudFrontディストリビューションのデフォルト動作を各種設定
      defaultBehavior: {
          // オリジンの指定
        origin: cloudfront_origins.S3BucketOrigin.withOriginAccessControl(
          s3Bucket,{
            originAccessLevels: [cloudfront.AccessLevel.READ],
          },
        ),          
      },
          // S3からではなくCloudFrontから配信するのでこちらでrootオブジェクトを指定
      defaultRootObject: 'index.html',
    });

デプロイ後、CloudFrontのディストリビューションドメイン名を使用してサイトにアクセスしてみます。

本来、CloudFront経由でS3のコンテンツを配信する場合はS3バケットポリシーでCloudFrontからのS3バケット接続を許可する必要があります。

本コードではCDK側がよしなにバケットポリシーを記述してくれているので配信ができています。

おわり

今回はS3静的ウェブサイトホスティングで配信していたコンテンツをCloudFront経由でHTTPS配信できるようにしました。

CloudFront経由の配信はAWSがドメインを証明してくれるので独自ドメインを取得せずともHTTPS配信ができます。

次回以降では独自ドメインを取得しRoute53やACMと連携させていきたいと思います。

それでは、ここまでお読みいただきありがとうございました。

ソースコード

https://github.com/michinoku-YoRHa/awscdk-s3-cloudfront/tree/v2-s3-private-with-cloudfront

参考

https://zenn.dev/funteractiveinc/articles/d2a0d96f82bd2f

https://business.ntt-east.co.jp/content/cloudsolution/column-480.html

https://dev.classmethod.jp/articles/cloudfront-s3web/

https://zenn.dev/funteractiveinc/articles/153581a53ef0fa

Discussion