😊

AWS CDK x CloudFront x S3 x Basic認証 x index.html対応 x 独自ドメイン

2023/05/16に公開

概要

AWS CDKを用いて、CloudFront + S3による静的サイトの作成を行いました。合わせて、CloudFront Functionを用いて、Basic認証とURLにファイル名や拡張子を含まないリクエストにindex.htmlを追加する処理を加えています。さらに、独自ドメインの追加も行いましたので、その備忘録です。

色々と不完全ですが、以下のリポジトリでソースコードを公開しています。

https://github.com/nakamura196/staticBasic

以下のような.envファイルを用意してcdk deployを実行する想定です。

CERT_ARN=arn:aws:acm:xxxx
RECORD_NAME=aaa.bbb.com
BUCKET_NAME=aaa.bbb.com
REGION=us-east-1
ACCOUNT=yyyy
DOMAIN_NAME=bbb.com

それぞれの説明は以下のとおりです。

項目 説明
CERT_ARN 証明書のARN arn:aws:acm:xxxx
RECORD_NAME 設定したいドメイン名 aaa.bbb.com
BUCKET_NAME ファイルを格納するS3バケット名 aaa.bbb.com
REGION リージョン名 us-east-1
ACCOUNT AWSのアカウント名(12 桁の数値 ) 123456789012
DOMAIN_NAME ホストゾーン名 bbb.com

Stack

以下のStackを作成しました。

import {
  Stack,
  StackProps,
  RemovalPolicy,
  aws_cloudfront,
  aws_cloudfront_origins,
  aws_iam,
} from "aws-cdk-lib";
import { Construct } from "constructs";
import * as s3 from "aws-cdk-lib/aws-s3";
import * as iam from "aws-cdk-lib/aws-iam";
import * as cloudfront from "aws-cdk-lib/aws-cloudfront";
import * as route53 from "aws-cdk-lib/aws-route53";
import * as targets from "aws-cdk-lib/aws-route53-targets";
import { Certificate } from "aws-cdk-lib/aws-certificatemanager";
import * as dotenv from "dotenv";

dotenv.config();

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

    const recordName = process.env.RECORD_NAME || "";
    const domainName = process.env.DOMAIN_NAME || "";
    const bucketName = process.env.BUCKET_NAME || "";
    const cert = process.env.CERT_ARN || "";

    // ホストゾーンIDを取得
    const hostedZoneId = route53.HostedZone.fromLookup(this, "HostedZoneId", {
      domainName,
    });

    // S3バケットを作成
    const websiteBucket = new s3.Bucket(this, "WebsiteBucket", {
      removalPolicy: RemovalPolicy.DESTROY,
      autoDeleteObjects: true,
      bucketName,
    });

    // CloudFront用のOrigin Access Identityを作成
    const originAccessIdentity = new cloudfront.OriginAccessIdentity(
      this,
      "OriginAccessIdentity",
      {
        comment: `${bucketName}-identity`,
      }
    );

    // S3バケットポリシーを設定
    const webSiteBucketPolicyStatement = new iam.PolicyStatement({
      actions: ["s3:GetObject"],
      effect: iam.Effect.ALLOW,
      principals: [
        new aws_iam.CanonicalUserPrincipal(
          originAccessIdentity.cloudFrontOriginAccessIdentityS3CanonicalUserId
        ),
      ],
      resources: [`${websiteBucket.bucketArn}/*`],
    });

    websiteBucket.addToResourcePolicy(webSiteBucketPolicyStatement);

    // CloudFront Functionの設定
    const cfFunction = new aws_cloudfront.Function(this, "CloudFrontFunction", {
      code: aws_cloudfront.FunctionCode.fromFile({
        filePath: "assets/redirect.js",
      }),
    });

    // 証明書を取得
    const certificate = Certificate.fromCertificateArn(
      this,
      "Certificate",
      cert
    );

    // CloudFrontの設定
    const distribution = new aws_cloudfront.Distribution(this, "distribution", {
      domainNames: [recordName ],
      certificate,
      comment: `${bucketName}-cloudfront`,
      defaultRootObject: "index.html",
      defaultBehavior: {
        allowedMethods: aws_cloudfront.AllowedMethods.ALLOW_GET_HEAD,
        cachedMethods: aws_cloudfront.CachedMethods.CACHE_GET_HEAD,
        cachePolicy: aws_cloudfront.CachePolicy.CACHING_OPTIMIZED,
        viewerProtocolPolicy:
          aws_cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS,
        origin: new aws_cloudfront_origins.S3Origin(websiteBucket, {
          originAccessIdentity,
        }),
        functionAssociations: [
          {
            function: cfFunction,
            eventType: aws_cloudfront.FunctionEventType.VIEWER_REQUEST,
          },
        ],
      },
      priceClass: aws_cloudfront.PriceClass.PRICE_CLASS_ALL,
    });

    // Route53レコード設定
    new route53.ARecord(this, "Route53RecordSet", {
      // ドメイン指定
      recordName,
      // ホストゾーンID指定
      zone: hostedZoneId,
      // エイリアスターゲット設定
      target: route53.RecordTarget.fromAlias(
        new targets.CloudFrontTarget(distribution)
      ),
    });
  }
}

まとめ

色々と考慮不足の点があるかと思いますが、AWS CDKの便利さを体感することができました。他の方の参考になる部分がありましたら幸いです。

Discussion