CloudFront DistributionのCDK Constructの新しいクラスを使って静的サイトホスティング(Amazon S3)の配信を構築してみた

2022.02.22

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

こんにちは、CX事業本部 IoT事業部の若槻です。

最近知ったのですが、CloudFront DistributionのCDK Constructとして使用できるクラスとして、既存の「CloudFrontWebDistribution」に加えて、新しく「Distribution」というものがリリースされていました。

aws-cdk/packages/@aws-cdk/aws-cloudfront/lib/distribution.tsのHistoryを見ると、2020年にはすでにリリースされていたようです。

「CloudFrontWebDistribution」と「Distribution」を使い比べてみたところ、直近のCloudFrontの新規機能は「Distribution」にのみ追加されており、今後は「Distribution」の利用が推奨されるようになると思われます。

例えば下記のレスポンスヘッダーの設定は、CDKバージョン1.145.0で確認したところ、「Distribution」でしか行えませんでした。

そこで今回は、CloudFront Distributionの新しいクラスである「Distribution」を使って、静的サイトホスティング(Amazon S3)を構築してみました。

やってみた

環境

  • aws-cdk@1.145.0
  • typescript@3.9.10

スタック定義

CDKスタックの定義は次のようになります。Originリソースの定義は@aws-cdk/aws-cloudfront-originsのモジュールを使用して行います。

lib/aws-cdk-app-stack.ts

import * as cdk from '@aws-cdk/core';
import * as cloudfront from '@aws-cdk/aws-cloudfront';
import * as iam from '@aws-cdk/aws-iam';
import * as s3 from '@aws-cdk/aws-s3';
import * as s3deploy from '@aws-cdk/aws-s3-deployment';
import * as cloudfront_origins from '@aws-cdk/aws-cloudfront-origins';

export class AwsCdkAppStack extends cdk.Stack {
  constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    const websiteBucket = new s3.Bucket(this, 'WebsiteBucket', {
      removalPolicy: cdk.RemovalPolicy.DESTROY,
    });

    const originAccessIdentity = new cloudfront.OriginAccessIdentity(
      this,
      'OriginAccessIdentity',
      {
        comment: 'website-distribution-originAccessIdentity',
      }
    );

    const webSiteBucketPolicyStatement = new iam.PolicyStatement({
      actions: ['s3:GetObject'],
      effect: iam.Effect.ALLOW,
      principals: [
        new iam.CanonicalUserPrincipal(
          originAccessIdentity.cloudFrontOriginAccessIdentityS3CanonicalUserId
        ),
      ],
      resources: [`${websiteBucket.bucketArn}/*`],
    });

    websiteBucket.addToResourcePolicy(webSiteBucketPolicyStatement);

    const distribution = new cloudfront.Distribution(this, 'distribution', {
      comment: 'website-distribution',
      defaultRootObject: 'index.html',
      errorResponses: [
        {
          ttl: cdk.Duration.seconds(300),
          httpStatus: 403,
          responseHttpStatus: 403,
          responsePagePath: '/error.html',
        },
        {
          ttl: cdk.Duration.seconds(300),
          httpStatus: 404,
          responseHttpStatus: 404,
          responsePagePath: '/error.html',
        },
      ],
      defaultBehavior: {
        allowedMethods: cloudfront.AllowedMethods.ALLOW_GET_HEAD,
        cachedMethods: cloudfront.CachedMethods.CACHE_GET_HEAD,
        cachePolicy: cloudfront.CachePolicy.CACHING_OPTIMIZED,
        viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS,
        origin: new cloudfront_origins.S3Origin(websiteBucket, {
          originAccessIdentity,
        }),
      },
      priceClass: cloudfront.PriceClass.PRICE_CLASS_ALL,
    });

    new s3deploy.BucketDeployment(this, 'WebsiteDeploy', {
      sources: [
        s3deploy.Source.data(
          '/index.html',
          '<html><body><h1>Hello World</h1></body></html>'
        ),
        s3deploy.Source.data(
          '/error.html',
          '<html><body><h1>Error!!!!!!!!!!!!!</h1></body></html>'
        ),
      ],
      destinationBucket: websiteBucket,
      distribution: distribution,
      distributionPaths: ['/*'],
    });
  }
}

cdk deployでスタックをデプロイします。

動作確認

ブラウザでDistributionのドメイン名にアクセスすると、Webサイトを開けました!

存在しないリソースのパスにアクセスした時は、ちゃんと403になります。

注意点

注意点として、「Distribution」を使用する場合は、S3 Bucket側でwebsiteIndexDocumentを設定して静的ウェブサイトホスティングを有効化すると、Distribution側でカスタムオリジンが優先して設定されてしまうようです。

lib/aws-cdk-app-stack.ts

    const websiteBucket = new s3.Bucket(this, 'WebsiteBucket', {
      removalPolicy: cdk.RemovalPolicy.DESTROY,
      websiteIndexDocument: 'index.html', //設定したらだめ
      websiteErrorDocument: 'error.html', //設定したらだめ
    });

下記は、S3 Bucket側で静的ウェブサイトホスティングを有効化した場合のDistribution側のオリジンの設定です。Origin Typeが本来ならS3 Originとなるべき所がCustom Originとなってしまっています。よってOrigin Access Identityも設定されていません。これによりDistributionへ正常なアクセスができなくなってしまいます。

$ aws cloudfront get-distribution \
  --id ${distribution_id} \
  --query "Distribution.DistributionConfig.Origins"

{
    "Quantity": 1,
    "Items": [
        {
            "Id": "AwsCdkAppStackdistributionOrigin13DA6F167",
            "DomainName": "awscdkappstack-websitebucket75c24d94-xxxxxxxx.s3-website-us-east-1.amazonaws.com",
            "OriginPath": "",
            "CustomHeaders": {
                "Quantity": 0
            },
            "CustomOriginConfig": {
                "HTTPPort": 80,
                "HTTPSPort": 443,
                "OriginProtocolPolicy": "http-only",
                "OriginSslProtocols": {
                    "Quantity": 1,
                    "Items": [
                        "TLSv1.2"
                    ]
                },
                "OriginReadTimeout": 30,
                "OriginKeepaliveTimeout": 5
            },
            "ConnectionAttempts": 3,
            "ConnectionTimeout": 10,
            "OriginShield": {
                "Enabled": false
            }
        }
    ]
}

一方で、従来の「CloudFrontWebDistribution」の場合は、S3 Bucket側で静的ウェブサイトホスティングを有効化してもカスタムオリジンとはなりません。ハマりやすいので注意しましょう。私はこれで丸1日溶かしました。

まあでもよく考えたらOrigin Access Identityにのみアクセスを制限する構成で、S3 Bucket側で静的ウェブサイトホスティングを有効化するのは意味がありませんよね。

カスタムドメインを使う場合

カスタムドメインのエイリアスを使う場合は、次のようにDistributionを設定すればOKです。

lib/aws-cdk-app-stack.ts

import * as acm from '@aws-cdk/aws-certificatemanager';

lib/aws-cdk-app-stack.ts

    const distribution = new cloudfront.Distribution(this, 'distribution', {
      comment: 'website-distribution',
      defaultRootObject: 'index.html',
      errorResponses: [
        {
          ttl: cdk.Duration.seconds(300),
          httpStatus: 403,
          responseHttpStatus: 403,
          responsePagePath: '/error.html',
        },
        {
          ttl: cdk.Duration.seconds(300),
          httpStatus: 404,
          responseHttpStatus: 404,
          responsePagePath: '/error.html',
        },
      ],
      defaultBehavior: {
        allowedMethods: cloudfront.AllowedMethods.ALLOW_GET_HEAD,
        cachedMethods: cloudfront.CachedMethods.CACHE_GET_HEAD,
        cachePolicy: cloudfront.CachePolicy.CACHING_OPTIMIZED,
        viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS,
        origin: new cloudfront_origins.S3Origin(websiteBucket, {
          originAccessIdentity,
        }),
      },
      priceClass: cloudfront.PriceClass.PRICE_CLASS_ALL,
      certificate: acm.Certificate.fromCertificateArn(
        this,
        'customDomainCertificate',
        'certificateArn'
      ),
      domainNames: ['customDomainName'],
      minimumProtocolVersion: cloudfront.SecurityPolicyProtocol.TLS_V1_2_2021,
    });

おわりに

CloudFront DistributionのCDK Constructの新しいクラスである「Distribution」を使って、静的サイトホスティング(Amazon S3)を構築してみました。

今後の構築では基本的に「Distribution」をメインに使っていきたいと思います。また「Distribution」がリリースされたという情報のキャッチアップが遅すぎたのは反省点です。今後はもっとアンテナをしっかり張ろうと思います。

参考

以上