CloudFrontでファイル名を含まないURLや末尾スラッシュ付与などの加工処理をCloudFormationから設定してみる

2022.01.26

いわさです。

CloudFront+S3の静的サイト構成をよく組むと思います。
その時、S3の静的サイトホスティングを有効にするかしないかの選択肢が出てきます。

軽くまとめると以下のような感じでしょうか。

  • 静的サイトホスティングなS3のメリット
    • リダイレクトルールが設定出来る
  • 非静的サイトホスティングなS3のメリット
    • CloudFrontとS3の間をHTTPSに出来る
    • OAIを使ってS3への直接アクセスを抑制出来る

もし、どちらのメリットも享受したい場合は、現時点では非静的サイトホスティングなS3をオリジンとしつつ、エッジでリダイレクトやオリジンURLの調整をしてやるのが、ケースとしては多いのかなと思っています。

そこで、本日は一番よく使いそうな設定をCloudFormationにしておきたかったので記事にまとめておきました。

リポジトリ

以下のリポジトリにテンプレートを置いておきます。

設定している内容

CloudFront Functionsを使って、ビューワーリクエストで以下を設定しています。
Lambda@EdgeとCloudFront Functionsのどちらで実装すべきかはこの記事では言及しませんが、AWSのサンプルにあわせてCloudFront Functionsで実装しています。

index.html を追加してファイル名を含まない URL をリクエストする - Amazon CloudFront

  ##########################################
  # CloudFront Functions
  ##########################################
  ModifyRequestUrlFunction:
    Type: AWS::CloudFront::Function
    Properties: 
      Name: modify-request-url-cf2
      AutoPublish: true
      FunctionConfig:
        Comment: https://docs.aws.amazon.com/ja_jp/AmazonCloudFront/latest/DeveloperGuide/example-function-add-index.html
        Runtime: cloudfront-js-1.0
      FunctionCode: |
        function handler(event) {
            var request = event.request;
            var uri = request.uri;
            // Check whether the URI is missing a file name.
            if (uri.endsWith('/')) {
                request.uri += 'index.html';
            }
            // Check whether the URI is missing a file extension.
            else if (!uri.includes('.')) {
                request.uri += '/index.html';
            }
            return request;
        }

  ##########################################
  # CloudFront
  ##########################################
  AssetsDistribution:
    Type: AWS::CloudFront::Distribution
    Properties:
      DistributionConfig:
        PriceClass: PriceClass_All
        Origins:
        - Id: !Sub ${AWS::StackName}-origin-primary
          DomainName: !GetAtt OriginBucket.RegionalDomainName
          S3OriginConfig:
            OriginAccessIdentity: !Sub origin-access-identity/cloudfront/${CloudFrontOriginAccessIdentity}
        Enabled: true
        DefaultRootObject: index.html
        DefaultCacheBehavior:

          ...

          FunctionAssociations:
            - EventType: viewer-request
              FunctionARN: !GetAtt ModifyRequestUrlFunction.FunctionMetadata.FunctionARN
        HttpVersion: http2

オリジンS3バケットには、hogeディレクトリとその中にindex.htmlが存在しています。
デプロイ後、HTTPクライアントでアクセス確認してみましょう。

iwasa.takahito@hoge aws-cfn-cloudfront-viewer-url % curl -I https://d1yqpd6yikj21d.cloudfront.net/hoge/
HTTP/2 200 
content-type: text/html
content-length: 284
last-modified: Tue, 25 Jan 2022 01:55:06 GMT
x-amz-server-side-encryption: AES256
accept-ranges: bytes
server: AmazonS3
date: Wed, 26 Jan 2022 00:20:18 GMT
etag: "6a7f3791753d07bdd2c884213521c2a4"
x-cache: RefreshHit from cloudfront
via: 1.1 0d3f96f58ac3ef451aa652616a3206fc.cloudfront.net (CloudFront)
x-amz-cf-pop: NRT51-C4
x-amz-cf-id: _ALe2uoVmbwcCIrmi0uLX-3fDEouv3TfTILUPh7q_enHlXsCXX0JBg==

iwasa.takahito@hoge aws-cfn-cloudfront-viewer-url % curl -I https://d1yqpd6yikj21d.cloudfront.net/hoge 
HTTP/2 200 
content-type: text/html
content-length: 284
last-modified: Tue, 25 Jan 2022 01:55:06 GMT
x-amz-server-side-encryption: AES256
accept-ranges: bytes
server: AmazonS3
date: Wed, 26 Jan 2022 00:20:18 GMT
etag: "6a7f3791753d07bdd2c884213521c2a4"
x-cache: Hit from cloudfront
via: 1.1 27366235f7cfef185b99df4aa8a4c352.cloudfront.net (CloudFront)
x-amz-cf-pop: NRT51-C4
x-amz-cf-id: SBP_zC2eTpOdbjzSZwhoukVP8CY7TFdnqW87w4aPc9i4hSa9j1-Cgw==
age: 3

iwasa.takahito@hoge aws-cfn-cloudfront-viewer-url % curl https://d1yqpd6yikj21d.cloudfront.net/hoge/ 
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    iwasa hoge/index.html
</body>
</html>%

設定前は、ステータス403になっていましたが、200でindex.htmlの内容が取得出来ています。
良いですね。

トレイリングスラッシュなしの場合にありでリダイレクトさせる

次はURLの末尾がスラッシュでない場合に、そのままあるいはURL加工のうえオリジン転送するのではなく、一度クライアントでリダイレクト処理をさせてみます。
ビューワーリクエストでリダイレクト方法は以下で紹介されているので、こちらを応用します。

ビューワーを新しい URL にリダイレクトさせる - Amazon CloudFront

ビューワーリクエストにつきひとつの関数しか設定出来ないので、先程の関数の「/で終わっていないURLで.が無い場合」の分岐でリダイレクトを設定してみます。

  ModifyRequestUrlFunction:
    Type: AWS::CloudFront::Function
    Properties: 
      Name: modify-request-url-cf2
      AutoPublish: true
      FunctionConfig:
        Comment: https://docs.aws.amazon.com/ja_jp/AmazonCloudFront/latest/DeveloperGuide/example-function-add-index.html
        Runtime: cloudfront-js-1.0
      FunctionCode: |
        function handler(event) {
            var request = event.request;
            var uri = request.uri;
            // Check whether the URI is missing a file name.
            if (uri.endsWith('/')) {
                request.uri += 'index.html';
            }
            // Check whether the URI is missing a file extension.
            else if (!uri.includes('.')) {
                var response = {
                    statusCode: 302,
                    statusDescription: 'Found',
                    headers:
                      { "location": { "value": request.uri + '/' }}
                  }
                return response;
            }
            return request;
        }

確認してみます。

iwasa.takahito@hoge aws-cfn-cloudfront-viewer-url % curl -I https://d1yqpd6yikj21d.cloudfront.net/hoge/         
HTTP/2 200 
content-type: text/html
content-length: 284
last-modified: Tue, 25 Jan 2022 01:55:06 GMT
x-amz-server-side-encryption: AES256
accept-ranges: bytes
server: AmazonS3
date: Wed, 26 Jan 2022 00:27:24 GMT
etag: "6a7f3791753d07bdd2c884213521c2a4"
x-cache: RefreshHit from cloudfront
via: 1.1 58ef75a5fdb60c073729be8392b4c628.cloudfront.net (CloudFront)
x-amz-cf-pop: NRT51-C4
x-amz-cf-id: Tb-cSXFRUCbLhtYRCZfHX5_LedJZlD92Eh6ejzqOK-sbGlnCi-0opQ==

iwasa.takahito@hoge aws-cfn-cloudfront-viewer-url % curl -I https://d1yqpd6yikj21d.cloudfront.net/hoge          
HTTP/2 302 
server: CloudFront
date: Wed, 26 Jan 2022 00:27:28 GMT
content-length: 0
location: /hoge/
x-cache: FunctionGeneratedResponse from cloudfront
via: 1.1 7bed027509794290f6c6a30b859ffb1a.cloudfront.net (CloudFront)
x-amz-cf-pop: NRT51-C4
x-amz-cf-id: RvjRO6VrsEWEwfGFgG1JG9DLLjQm8LTAq9IVTH7xxtLTdfKiWKSw1w==

先程までhoge/index.htmlの内容が表示されていましたが、一度hoge/へリダイレクト出来ていますね。

上記のコードでは、ステータスを302にしています。
必要に応じてリダイレクト要件にあわせて変更してください。

さいごに

本日はCloudFront+S3(非静的ウェブサイトホスティング)でよく使いそうな2種類のリダイレクト処理とオリジン転送をCloudFormationで構成してみました。
類似の記事はいくつかDevIOでも存在していますが、CloudFormation+CloudFront Functionsで構築するパターンが見当たらなかったので記事にしてみました。

Lambda@Edgeだともう少し導入の手間がありそうですが、CloudFront Functionsはお手軽で良いですね。