S3オリジンCloudFrontのIP制限をやめてBasic認証にした話 with CloudFormation
最近よく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