こんにちは。Kaneyasuです。
最近はIoTまわりをやったり、フロントエンドやったりと幅広く頑張らせていただいています。
今回はLambda@EdgeによるBASIC認証付きのCloudFrontを作るCloudFormationテンプレートを書いてみました。
フロントエンドのお仕事でデプロイ先をサクッと用意するために作ったものです。
CloudFormationテンプレート
ポイント
バージニアリージョンで実行する
上側のCloudFormationテンプレートはバージニアリージョンで実行します。
Lambda@Edgeとして使えるLambdaはバージニアのみだからです。
US-East-1 (バージニア北部) リージョン (us-east-1) にいることを確認します。Lambda@Edge 関数を作成するには、このリージョンに設定されている必要があります。
BASIC認証用のID・パスワードをコードに埋め込む
Code:
ZipFile: !Sub |
import base64
from typing import Dict, Any, List, Optional
BASIC_AUTH_USER = "${BasicAuthUser}"
BASIC_AUTH_PASSWORD = "${BasicAuthPassword}"
LambdaのコードはPythonで書いて、CloudFormationテンプレート内に埋め込んでいます。
BASIC認証のパスワードは、CloudFormationのパラメータとしました。
環境変数にしておこうと思いましたが、環境変数を用いたLambdaはLambda@Edgeとして使用できないようです。
環境変数を使ったLambdaでLambda@Edgeを設定するCloudFormationテンプレートを書いてみましたが、実行時にエラーになることを確認しました。
というわけで、CloudFormationパラメータから持ってきた値を埋め込む形にしました。
Lambdaのバージョンを作っておく
LambdaEdgeFunctionVersion:
Type: AWS::Lambda::Version
Properties:
FunctionName: !Ref LambdaEdgeFunction
Description: v1
Lambda@Edgeとして使う場合、バージョンを作っておくことが必要なので作成しています。
CloudFormationテンプレートのパラメータでLambdaのバージョンのARNを渡す
LambdaEdgeFunctionVersionArn:
Type: String
Description: Lambda Edge Function Version ARN
下側のCloudFormationテンプレートは、LambdaのバージョンのARNをCloudFormationテンプレートのパラメータで受け取り、それを後述のビューワーリクエストにセットしています。
下側のCloudFormationテンプレートは、S3・CloudFront、Lambda@Edgeの設定をしています。
こちらのCloudFormationテンプレートは、東京リージョンで実行しそうなので、Lambda関数のバージョンのARNはOutputsではなくパラメータで渡す形にしています。
リージョンをまたぐと、Outputsを経由した渡し方は機能しないのが理由です。
リージョン間でクロススタック参照を作成することはできません。組み込み関数 Fn::ImportValue を使用して、同じリージョン内にエクスポートされた値のみをインポートできます。
S3と、CloudFront+Lambda@Edgeで分けたらこの問題は解決しそうな気もしますが、あまり分けるのも煩雑なのでこのままでいいかなと思っています。
バケットポリシーの設定を行う
CloudFrontディストリビューションからのみアクセスを許可するよう指定しています。
これがないと、デプロイ後にアクセスしても権限エラーが出ます。
S3BucketPolicyForWebsite:
Type: AWS::S3::BucketPolicy
Properties:
Bucket: !Ref S3BucketForWebsite
PolicyDocument:
Version: '2012-10-17'
Statement:
- Sid: AllowCloudFrontServicePrincipal
Effect: Allow
Principal:
Service: 'cloudfront.amazonaws.com'
Action:
- s3:GetObject
Resource: !Sub arn:aws:s3:::${SystemName}-${Environment}-s3-bucket-for-website/*
Condition:
StringEquals:
'aws:SourceArn': !Sub arn:aws:cloudfront::${AWS::AccountId}:distribution/${CloudFrontDistribution}
OACの設定を行う
OriginAccessControl:
Type: AWS::CloudFront::OriginAccessControl
Properties:
OriginAccessControlConfig:
Description: ''
Name: !Sub ${SystemName}-${Environment}-origin-access-control-for-website
OriginAccessControlOriginType: s3
SigningBehavior: always
SigningProtocol: sigv4
CloudFrontDistribution:
Type: AWS::CloudFront::Distribution
Properties:
DistributionConfig:
Enabled: true
DefaultRootObject: 'index.html'
Origins:
- DomainName: !GetAtt S3BucketForWebsite.RegionalDomainName
Id: !Sub ${SystemName}-${Environment}-s3-bucket-for-website
OriginAccessControlId: !GetAtt OriginAccessControl.Id
S3OriginConfig: {}
CloudFrontの画面で言う、ディストリビューション>オリジンのこの部分を設定しています。
DefaultRootObjectの設定
DefaultRootObject: 'index.html'
これがないと、ファイル名を指定せずにアクセスすると何も表示されるエラーとなります。
ただし、この設定マネジメントコンソール側に見当たらないんですがどこにあるんですかね?
ビューワーリクエストにLambdaのバージョンのARNをセット
LambdaFunctionAssociations:
- EventType: viewer-request
LambdaFunctionARN: !Ref LambdaEdgeFunctionVersionArn
Lambda@Edgeを設定しているのはここです。
ビューワーリクエストにLambdaのバージョンのARNをセットします。
まとめ
Lambda@Edgeの設定は手動で行うと、なかなか成功しないので今回テンプレートにしてみました。
BASIC認証ID・パスをLambdaにハードコーディングしてる部分は、他にやりようがある気がするのでまた調べてみようと思います。