🍈

S3オリジンCloudFrontのIP制限をやめてBasic認証にした話 with CloudFormation

2024/07/27に公開

最近よくLPをAWSでデプロイしてるのですが、そこでIP制限について考え直す機会がありました。

先日、新幹線でテザリングしながらデプロイしたWebサイトを見ていたのですが、私のスマホの回線のIPアドレスがコロコロ変わるので、IP制限に弾かれまくってしまいました。

ここで選択としてはVPNを契約するというものがあるのですが、できればあまりそこにコストをかけたくないです。
そこで、別の方法はないかなと考えたところ、超基本的なところに行きつきました。

Basic認証です。

CloudForamtionでBasic認証作ってみる

CloudFrontにはCloudFront Functionsというものがあります。
Webフレームワークのミドルウェアっぽい感じで動きます。
こちらでBasic認証をやってみます。

設定画面はこんな感じです。

key valueのちょっとしたストアがあるので、そちらも使ってみてもいいかもしれません。

コードはjavascriptで書きます。
といっても全く大したことはしていません。

で、これのCloudFormationテンプレートを作るとこんな感じです。

AWSTemplateFormatVersion: 2010-09-09
Description: CloudFront Functions

Parameters:
  Username:
    Type: String
    Description: Username for Basic Auth
  Password:
    Type: String
    Description: Password for Basic Auth
    NoEcho: true

Resources
  BasicAuthFunction:
    Type: "AWS::CloudFront::Function"
    Properties:
      Name: "BasicAuthFunction"
      FunctionConfig:
        Comment: "Basic authentication"
        Runtime: "cloudfront-js-2.0"
      AutoPublish: true
      FunctionCode: !Sub 
        - | 
          function handler(event) {
            const request = event.request
            const headers = request.headers

            if (!headers.authorization || headers.authorization.value !== "Basic ${AuthString}") {
              return {
                statusCode: 401,
                statusDescription: 'Unauthorized',
                headers: {
                  'www-authenticate': { value: 'Basic' }
                }
              };
            }

            return request;
          }
        - AuthString: !Base64 {Fn::Sub: "${Username}:${Password}"}

↓この部分なんですが、これはBASIC認証の仕様に合わせたものです。

AuthString: !Base64 {Fn::Sub: "${Username}:${Password}"}

BASIC認証ではusernameとpasswordをコロンで繋いだものをBase64エンコードした文字列で認証を行います。

以下は例です。

ユーザーネーム
q8ApMofcutLA

パスワード
bTGR0YrFf1G9

これをコロンでくっ付けてbase64エンコードしたものです。

q8ApMofcutLA:bTGR0YrFf1G9

cThBcE1vZmN1dExBOmJUR1IwWXJGZjFHOQ==

パラメータでusernameとpasswordを入力してそれをBase64エンコードするところまでをCloudFormationでやっています。

こうすることでパスワードがそのままコードにのらないです。
ハッシュと違ってデコードできますが、今回の用途ではコンソールに入れる人はもちろんユーザ名とパスワードはすでに知っているというような状況下だと思うので、画面にパスワードが丸見えになってないだけでひとまずOKかなと思います。

作った後にすること

CloudFront Functionsにはステージという概念があります。
DEVとLIVEというステージです。
DEVでコードを書いてテストして、OKだったら関数の発行というボタンを押してLIVEというステージにあげます。
LIVEになったあと、ディストリビューションに関数を紐づける必要があります。

今回共有したCloudFormationテンプレートはLIVEというステージで関数が最初から発行されるようにしていますが、ディストリビューションとの紐付けはどうするか分からなかったためできてません。
ご存知の方いらっしゃったら教えてください!

追記: AIに聞いたらわかりました。

  CloudFrontDistribution:
    Type: "AWS::CloudFront::Distribution"
    Properties:
      DistributionConfig:
        Comment: !Sub "${ProjectName}-cloudfront"
        Origins:
          - Id: "S3Origin"
            DomainName: { Fn::ImportValue: !Sub "${ProjectName}-S3OriginBucketDomainName" }
            OriginAccessControlId: !GetAtt Oac.Id
            S3OriginConfig:
              OriginAccessIdentity: ""
        DefaultCacheBehavior:
          TargetOriginId: "S3Origin"
          AllowedMethods: 
            - "GET"
            - "HEAD"
          CachedMethods:
            - "GET"
            - "HEAD"
          ViewerProtocolPolicy: "redirect-to-https"
          CachePolicyId: "658327ea-f89d-4fab-a63d-7e88639e58f6" #CachingOptimized
          Compress: true
          FunctionAssociations:
            - EventType: viewer-request
              FunctionARN: !GetAtt BasicAuthFunction.FunctionARN

デフォルトキャッシュビヘイビアのところに紐付きを書くようです。

          FunctionAssociations:
            - EventType: viewer-request
              FunctionARN: !GetAtt BasicAuthFunction.FunctionARN

以下は手動で行う場合も書きます。

方法は簡単で、右上の関連付けを追加というところからディストリビューションとキャッシュビヘイビアを選択してください。

イベントタイプはViewer requestで問題ないです。

イメージ的にはwebフレームワークのルート定義のファイルでミドルウェアを差し込んでる感じです。

まとめ

今まで、会社の業務でAWSを触ることが多かったので、IP制限がファーストチョイスとして上がってきたんですが個人用だとBasic認証の方が良いかもですね。
LPでコンタクトフォームとかなくて、GETリクエストしか許可しないみたいな状況だったら、いっそCloudFrontからWAFを外して経費節約みたいなこともできるなーと。

これからCloudFront Functions積極的に使ってみようと思います。

Discussion