[AWS CDK] 静的サイトホスティング(CloudFront + S3)でfavicon.icoの読み込みエラーを抑制する

2022.02.25

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

デモや検証のために Amazon CloudFront + Amazon S3 構成の静的サイトホスティングの配信を構築する場合は、配信するコンテンツはHello Worldなどの適当な文字列表示で済ませれば良い場合が多いと思います。例えばデプロイの部分の検証が目的の場合などです。

しかしその際に、ファビコン(favicon.ico)のリソースを設定しない場合は、下記のような読み取りエラー(403)が発生します。見栄えが良くないですし、検証の妨げにもなるので出来れば抑制をしたいですね。

そこで今回は、AWS CDKによる静的サイトホスティング(CloudFront + S3)の構築で、favicon.icoの読み込みエラーを抑制してみました。

やってみた

環境

  • aws-cdk@1.145.0
  • typescript@3.9.10

とりあえずエラーを抑制したい

CDKスタックのコードです。まずはとりあえずエラーを抑制したい場合。

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';
import * as acm from '@aws-cdk/aws-certificatemanager';

interface AwsCdkAppStackProps extends cdk.StackProps {
  certArn: string;
  customDomain: string;
}

export class AwsCdkAppStack extends cdk.Stack {
  constructor(scope: cdk.Construct, id: string, props: AwsCdkAppStackProps) {
    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,
      certificate: acm.Certificate.fromCertificateArn(
        this,
        'CustomDomainCertificate',
        props.certArn
      ),
      domainNames: [props.customDomain],
      minimumProtocolVersion: cloudfront.SecurityPolicyProtocol.TLS_V1_2_2021,
    });

    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>'
        ),
        s3deploy.Source.data('/favicon.ico', ''),
      ],
      destinationBucket: websiteBucket,
      distribution: distribution,
      distributionPaths: ['/*'],
    });
  }
}

BucketDeploymentsources/favicon.icoリソースを設定します。この時リソースのデータ内容は空文字とします。

これにより読み取りエラーを抑制することができます。読み込んだデータの内容は空のため、ファビコンはブラウザ既定のものとなっていますが、見栄えを気にしなければこれでも良さそうですね。

見栄えも気にしたい

ブラウザ既定のものでなく何かしら見栄えの良いファビコンを表示したい場合は、<header>を使用して外部から読み込んでしまうのも手です。ただし無闇に迷惑を掛けないようになるべく自分の管理下にあるものから拝借しましょう。今回は自社サイトのファビコンのURLを指定しています。

lib/aws-cdk-app-stack.ts

    const headerElement =
      '<head><link rel="icon" type="image/x-icon" href="https://dev.classmethod.jp/favicon.ico"></head>';

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

これにより参照先のファビコンを表示できました。

おわりに

AWS CDKによる静的サイトホスティング(CloudFront + S3)の構築で、favicon.icoの読み込みエラーを抑制してみました。

ファビコン用の画像データを使用しなくても簡単にファビコンが設定できました。横着をしたい際に便利ですね。

以上