小西秀和です。
「AWS LambdaカスタムリソースでAWS Cloudformationスタックを別リージョンにデプロイする」の記事でAWS Cloudformationスタックを別リージョンにデプロイするAWS Lambdaカスタムリソースの実装例を説明しました。
また、その記事の続編である「AWS LambdaカスタムリソースでACM証明書を作成するAWS Cloudformationスタックを別リージョンにデプロイする」でカスタムリソースを使用して実際にAWS Certificate Manager(ACM)証明書をデプロイする方法を紹介しました。
今回はさらにその続編という位置づけでカスタムリソースを使用してAmazon CloudFrontで基本認証を行うLambda@Edgeをデプロイする方法を紹介します。
AWS Cloudformationスタックを別リージョンにデプロイするAWS Lambdaカスタムリソースについては元記事を参照してください。
※本記事および当執筆者のその他の記事で掲載されているソースコードは自主研究活動の一貫として作成したものであり、動作を保証するものではありません。使用する場合は自己責任でお願い致します。また、予告なく修正することもありますのでご了承ください。
本記事で試す内容の概要図
今回は次の内容をAWS Cloudformationとカスタムリソースを中心に試しています。
- 呼出元Cloudformationスタックはカスタムリソースを呼び出し、基本認証をするLambda@EdgeのバージョンをCloudformationスタックでus-east-1に作成してARNを受け取る
- 呼出元CloudformationスタックはAmazon S3をオリジンとするAmazon CloudFrontをLambda@EdgeバージョンのARNと関連付けて作成し、カスタムリソースで設定したIDとパスワードで基本認証できるようにする
各ファイル(テンプレート、関数)の名称設定
AWS CloudformationテンプレートやAWS Lambdaカスタムリソースそれぞれが一意に必要なものを識別して連携できれば各ファイルの名称は任意ですが、説明のために次のように各ファイルの名称を設定します。
- 呼出元AWS Cloudformationテンプレート
⇒ OrgSourceOfRunCfnBasicAuthLambdaEdge.yml - Amazon CloudFront用のLambda@Edgeを作成するAWS CloudformationスタックをデプロイするAWS Lambdaカスタムリソース
⇒ CustomResourceToDeployCloudformationStack - Amazon CloudFront用のLambda@Edgeを作成するAWS Cloudformationテンプレート
⇒ CfnBasicAuthLambdaEdge.yml
各ファイルやパラメータの例
各ファイルやパラメータの例を記載していきます。
特に入力パラメータに関しては入力形式を知っていただくための例なので、使用する場合は各パラメータを要件に合わせて設定する必要があります。
呼出元AWS Cloudformationテンプレートへの入力パラメータ例
後述するカスタムリソースの呼出元となるAWS Cloudformationテンプレートに入力するパラメータの例です。
入力値は例なので使用する場合は各パラメータを要件に合わせて設定する必要があります。
次にYAML形式にコメントする形で説明しています。
これらのパラメータはAWSマネジメントコンソールから呼出元AWS Cloudformationテンプレートを実行する場合は、各パラメータを手動入力する必要があります。
#作成するS3バケットのサフィックスに追加する環境名 env: dev #静的ウェブサイトホスティングに使用するS3バケット名 bucketName: cfnlambdaedge4cloudfront-20210822000000-hostingbucket #Lambda@Edgeを作成するスタックをus-east1にデプロイするLambdaカスタムリソースのARN runCfnInUsEast1LambdaARN: arn:aws:lambda:ap-northeast-1:XXXXXXXXXXXX:function:CustomResourceToDeployCloudformationStack #Lambda@Edgeを作成するためにus-east1にデプロイするCloudformationスタック名 runCfnInUsEast1StackName4LambdaEdge: CfnLambdaEdge4CloudFrontDemo #Lambda@Edgeを作成するCloudformationテンプレートを保存しているバケット名 cfnTplS3Bucket4LambdaEdge: h-o2k #Lambda@Edgeを作成するCloudformationテンプレートを保存しているバケット以降のオブジェクトキー cfnTplS3Key4LambdaEdge: CfnBasicAuthLambdaEdge.yml #基本認証をするLambda@Edgeの関数名 basicAuthFuncName4LambdaEdge: BasicAuthWithLambdaEdge #基本認証をするLambda@Edgeを関連付けるAmazon CloudFrontのDistribution ID。AWS Secrets Managerシークレットの一意識別で使用する。 #※スタックの初回Create処理でAmazon CloudFrontを作成してから、2回目のUpdate処理で入力する basicAuthCloudFrontDistId4LambdaEdge: XXXXXXXXXXXXX #基本認証をするLambda@Edgeで認証するID。AWS Secrets Managerシークレットとして保管する。 basicAuthID4LambdaEdge: Iam #基本認証をするLambda@Edgeで認証するパスワード。AWS Secrets Managerシークレットとして保管する。 basicAuthPW4LambdaEdge: Nobody
呼出元AWS Cloudformationテンプレート
ファイル名:OrgSourceOfRunCfnBasicAuthLambdaEdge.yml
カスタムリソースの呼出元となるAWS Cloudformationテンプレートの例を記載します。
このテンプレートはAWS Amplify CLIでAmazon S3+Amazon CloudFrontの静的ウェブサイトホスティング環境を作成したときに生成されるJSONテンプレートをYAMLに変換し、カスタムリソースによる基本認証をするLambda@Edgeバージョン作成処理とCloudFrontへの関連付け処理を追加したものです。
このYAMLファイルに追加した処理やポイントとなる箇所についてコメントを追記しておきます。
AWS Amplify CLIによるAmazon S3+Amazon CloudFrontの作成については次の記事を参照してください。
AWSの静的ウェブサイトホスティングで入門するAWS Amplify(Console、CLI) - 構築編(Amplify CLI)
AWSTemplateFormatVersion: '2010-09-09' Description: 'Hosting and Lambda@Edge resource stack creation' Parameters: env: #作成するS3バケットのサフィックスに追加する環境名 Type: String bucketName: #静的ウェブサイトホスティングに使用するS3バケット名 Type: String runCfnInUsEast1LambdaARN: #Lambda@Edgeを作成するスタックをus-east1にデプロイするLambdaカスタムリソースのARN Type: String runCfnInUsEast1StackName4LambdaEdge: #Lambda@Edgeを作成するためにus-east1にデプロイするCloudformationスタック名 Type: String cfnTplS3Bucket4LambdaEdge: #Lambda@Edgeを作成するCloudformationテンプレートを保存しているバケット名 Type: String cfnTplS3Key4LambdaEdge: #Lambda@Edgeを作成するCloudformationテンプレートを保存しているバケット以降のオブジェクトキー Type: String basicAuthFuncName4LambdaEdge: #基本認証をするLambda@Edgeの関数名 Type: String basicAuthCloudFrontDistId4LambdaEdge: #基本認証をするLambda@Edgeを関連付けるAmazon CloudFrontのDistribution ID。AWS Secrets Managerシークレットの一意識別で使用する。 Type: String #※スタックの初回Create処理でAmazon CloudFrontを作成してから、2回目のUpdate処理で入力する basicAuthID4LambdaEdge: #基本認証をするLambda@Edgeで認証するID。AWS Secrets Managerシークレットとして保管する。 Type: String basicAuthPW4LambdaEdge: #基本認証をするLambda@Edgeで認証するパスワード。AWS Secrets Managerシークレットとして保管する。 Type: String Conditions: ShouldNotCreateEnvResources: 'Fn::Equals': - Ref: env - NONE ShouldCreateBasicAuthLambdaEdge: #入力パラメータにAmazon CloudFrontのDistribution IDがある場合はTrueでLambda@Edgeを作成、ない場合はFalseでLambda@Edge作成をスキップ。 'Fn::Not': ['Fn::Equals':[{Ref: basicAuthCloudFrontDistId4LambdaEdge}, '']] Resources: S3Bucket: Type: 'AWS::S3::Bucket' #DeletionPolicy: Retain Properties: BucketName: 'Fn::If': - ShouldNotCreateEnvResources - {Ref: bucketName} - {'Fn::Join': ["", [{Ref: bucketName}, '-', {Ref: env}]]} WebsiteConfiguration: IndexDocument: index.html ErrorDocument: index.html CorsConfiguration: CorsRules: - {AllowedHeaders: [Authorization, Content-Length], AllowedMethods: [GET], AllowedOrigins: ['*'], MaxAge: 3000} PrivateBucketPolicy: Type: 'AWS::S3::BucketPolicy' DependsOn: OriginAccessIdentity Properties: PolicyDocument: Id: MyPolicy Version: '2012-10-17' Statement: - {Sid: APIReadForGetBucketObjects, Effect: Allow, Principal: {CanonicalUser: {'Fn::GetAtt': [OriginAccessIdentity, S3CanonicalUserId]}}, Action: 's3:GetObject', Resource: {'Fn::Join': ["", ['arn:aws:s3:::', {Ref: S3Bucket}, '/*']]}} Bucket: Ref: S3Bucket OriginAccessIdentity: Type: 'AWS::CloudFront::CloudFrontOriginAccessIdentity' Properties: CloudFrontOriginAccessIdentityConfig: Comment: CloudFrontOriginAccessIdentityConfig #Lambda@Edgeを作成するために呼び出すカスタムリソース RunCfnInUsEast1Func4LambdaEdge: Type: 'Custom::RunCfnInUsEast1Func4LambdaEdge' Properties: ServiceToken: Ref: runCfnInUsEast1LambdaARN StackName: Ref: runCfnInUsEast1StackName4LambdaEdge CfnTplS3Bucket: Ref: cfnTplS3Bucket4LambdaEdge CfnTplS3Key: Ref: cfnTplS3Key4LambdaEdge CloudFrontDistId: Ref: basicAuthCloudFrontDistId4LambdaEdge BasicAuthFuncName: Ref: basicAuthFuncName4LambdaEdge BasicAuthID: Ref: basicAuthID4LambdaEdge BasicAuthPW: Ref: basicAuthPW4LambdaEdge IsSkip: ##入力パラメータにAmazon CloudFrontのDistribution IDがない場合はカスタムリソースにIsSkip:trueを送信し、Lambda@Edge作成処理をスキップする。 'Fn::If': - ShouldCreateBasicAuthLambdaEdge - "false" - "true" CloudFrontDistribution: Type: 'AWS::CloudFront::Distribution' DependsOn: - S3Bucket - OriginAccessIdentity - RunCfnInUsEast1Func4LambdaEdge Properties: DistributionConfig: HttpVersion: http2 Origins: - {DomainName: {'Fn::GetAtt': [S3Bucket, RegionalDomainName]}, Id: hostingS3Bucket, S3OriginConfig: {OriginAccessIdentity: {'Fn::Join': ["", [origin-access-identity/cloudfront/, {Ref: OriginAccessIdentity}]]}}} Enabled: 'true' DefaultCacheBehavior: AllowedMethods: [DELETE, GET, HEAD, OPTIONS, PATCH, POST, PUT] TargetOriginId: hostingS3Bucket ForwardedValues: {QueryString: 'false'} ViewerProtocolPolicy: redirect-to-https DefaultTTL: 86400 MaxTTL: 31536000 MinTTL: 60 Compress: true LambdaFunctionAssociations: 'Fn::If': - ShouldCreateBasicAuthLambdaEdge - [{EventType: viewer-request, IncludeBody: 'true', LambdaFunctionARN: {'Fn::GetAtt': [RunCfnInUsEast1Func4LambdaEdge, LambdaFunctionVersionArn]}}] - { Ref: 'AWS::NoValue' } DefaultRootObject: index.html CustomErrorResponses: - {ErrorCachingMinTTL: 300, ErrorCode: 400, ResponseCode: 200, ResponsePagePath: /} - {ErrorCachingMinTTL: 300, ErrorCode: 403, ResponseCode: 200, ResponsePagePath: /} - {ErrorCachingMinTTL: 300, ErrorCode: 404, ResponseCode: 200, ResponsePagePath: /} Outputs: Region: Value: Ref: 'AWS::Region' HostingBucketName: Description: 'Hosting bucket name' Value: Ref: S3Bucket WebsiteURL: Value: 'Fn::GetAtt': - S3Bucket - WebsiteURL Description: 'URL for website hosted on S3' S3BucketSecureURL: Value: 'Fn::Join': - "" - - 'https://' - {'Fn::GetAtt': [S3Bucket, DomainName]} Description: 'Name of S3 bucket to hold website content' CloudFrontDistributionID: Value: Ref: CloudFrontDistribution CloudFrontDomainName: Value: 'Fn::GetAtt': - CloudFrontDistribution - DomainName CloudFrontSecureURL: Value: 'Fn::Join': - "" - - 'https://' - {'Fn::GetAtt': [CloudFrontDistribution, DomainName]} CloudFrontOriginAccessIdentity: Value: Ref: OriginAccessIdentity CustomResourceSkiped: Value: 'Fn::If': - ShouldCreateBasicAuthLambdaEdge - "false" - "true"
呼出元AWS Cloudformationテンプレートからカスタムリソースに渡される引数
前述の呼出元AWS Cloudformationテンプレートのカスタムリソース(RunCfnInUsEast1Func4LambdaEdge)から渡される引数は以下になります。
StackName、cfnTplS3Bucket、cfnTplS3Key、CloudFrontDistId、BasicAuthFuncName、BasicAuthID、BasicAuthPW、IsSkip
StackName:基本認証用Lambda@Edgeなどリソースを作成するCloudformationスタックの名称
CfnTplS3Bucket:基本認証用Lambda@Edgeなどリソースを作成するCloudformationテンプレートがあるS3バケット
CfnTplS3Key:基本認証用Lambda@Edgeなどリソースを作成するCloudformationテンプレートがあるS3バケット配下のオブジェクトキー
CloudFrontDistId:認証情報を格納するAWS Secrets Managerシークレットに使用する基本認証用Lambda@Edgeバージョンと関連付けるAmazon CloudFrontのDistribution ID
BasicAuthFuncName:基本認証用Lambda@Edgeの関数名
BasicAuthID:基本認証用Lambda@Edgeでの認証に使用するID。AWS Secrets Managerシークレットとして保管する。
BasicAuthPW:基本認証用Lambda@Edgeでの認証に使用するパスワード。AWS Secrets Managerシークレットとして保管する。
IsSkip:Amazon CloudFrontのDistribution IDが発行されていない初回Createの場合に基本認証用Lambda@Edge作成をスキップするために使用する。trueの場合は処理を実行せずスキップする。
これらを設定した上で呼出元AWS Cloudformationテンプレートからカスタムリソースに渡るイベントのフォーマット例を次に示します。
{ "RequestType": "Create", "ResponseURL": "http://pre-signed-S3-url-for-response", "StackId": "arn:aws:cloudformation:ap-northeast-1:123456789012:stack/stack-name/guid", "RequestId": "unique id for this create request", "ResourceType": "Custom::TestResource", "LogicalResourceId": "MyTestResource", "ResourceProperties": { "ServiceToken": "arn:aws:lambda:ap-northeast-1:XXXXXXXXXXXX:function:CustomResourceToDeployCloudformationStack", "StackName": "CfnAcm4CloudFrontDemo", "CfnTplS3Bucket": "h-o2k", "CfnTplS3Key": "CfnACMCertificate.yml", "CloudFrontDistId": "XXXXXXXXXXXXX", "BasicAuthFuncName": "BasicAuthWithLambdaEdge", "BasicAuthID": "Iam", "BasicAuthPW": "Nobody", "IsSkip": "false" } }
カスタムリソースではこのイベントを受けて処理を実行します。
基本認証用Lambda@Edgeバージョンを作成するAWS Cloudformationスタックをus-east-1にデプロイするAWS Lambdaカスタムリソース
関数名:CustomResourceToDeployCloudformationStack
カスタムリソースは「AWS LambdaカスタムリソースでAWS Cloudformationスタックを別リージョンにデプロイする」で作成したものを使用します。
このカスタムリソース内でAmazon S3バケットに保存されている基本認証用Lambda@Edgeバージョンを作成するCloudformationテンプレートを読み込み、呼出元Cloudformationスタックから渡された引数を使用してus-east-1に基本認証用Lambda@Edgeバージョンを作成するCloudformationスタックをデプロイします。
実行したスタックが完了し、基本認証用Lambda@Edgeバージョンが作成されるとそのARNを呼出元に返却して呼出元で作成しているAWS CloudFrontに関連付けます。
基本認証用Lambda@Edgeを作成するAWS Cloudformationテンプレート
ファイル名:CfnBasicAuthLambdaEdge.yml
認証情報を保管するAWS Secrets Managerと基本認証用Lambda@Edgeを作成し、呼出元のAmazon CloudFrontに関連付けるLambdaバージョンを発行するまでを実施するAWS Cloudformationテンプレートは次のようになります。
これをS3バケットに保存しておき、カスタムリソースから読み込んでAWS Cloudformationスタックをus-east-1にデプロイします。
AWSTemplateFormatVersion: '2010-09-09' Description: 'Stack creation of Lambda@Edge Version and Secrets Manager Secret' Parameters: CloudFrontDistId: #基本認証用Lambda@Edgeを呼び出すAmazon CloudFrontのDistribution ID。AWS Secrets ManagerのシークレットIDに使用する。 Type: String #※このCloudFormationテンプレートではAmazon CloudFrontとLambda@Edgeバージョンの関連付けはしないため注意。関連付けは呼出元で実施する想定。 BasicAuthFuncName: #基本認証用Lambda@Edgeの関数名 Type: String BasicAuthID: #AWS Secrets Managerシークレットに保管し、基本認証で使用するID。 Type: String BasicAuthPW: #AWS Secrets Managerシークレットに保管し、基本認証で使用するパスワード。 Type: String Resources: LambdaEdgeBasicAuth: Type: AWS::Lambda::Function DependsOn: - SecretsManagerSecret - LambdaEdgeBasicAuthRole Properties: FunctionName: {Ref: BasicAuthFuncName} Runtime: python3.8 MemorySize: 128 #Lambda@Edgeのクォータ最大値を設定 Timeout: 5 #Lambda@Edgeのクォータ最大値を設定 Role: !GetAtt LambdaEdgeBasicAuthRole.Arn Handler: index.lambda_handler Code: ZipFile: | # 基本認証を実施するLambda@Edgeの関数 import json import boto3 import base64 # ID、パスワードを保管し、認証に使用するAWS Secrets Managerを扱うクライアントを作成 asm_client = boto3.client('secretsmanager', region_name='us-east-1') # エラーの場合のレスポンスを定義 err_response = { 'status': '401', 'statusDescription': 'Unauthorized', 'body': 'Authentication Failed.', 'headers': { 'www-authenticate': [ { 'key': 'WWW-Authenticate', 'value': 'Basic realm="Basic Authentication"' } ] } } def lambda_handler(event, context): try: print('event:') print(event) # イベントからCloudFrontのリクエストを取得 request = event['Records'][0]['cf']['request'] # イベントからCloudFrontのDistribution IDを取得 cf_dist_id = event['Records'][0]['cf']['config']['distributionId'] # リクエストからヘッダーを取得 headers = request['headers'] if (headers.get('authorization') != None): # ヘッダーにauthorizationがあれば認証を試行する # headers['authorization'][0]['value']の中身は「Basic <base64でエンコードされた[ID:Password]の文字列>」 # authorizationの内容を分解してID、パスワードを取り出す target_credentials_str = headers['authorization'][0]['value'].split( " ") target_credentials = base64.b64decode( target_credentials_str[1]).decode().split(":") target_id = target_credentials[0] target_pw = target_credentials[1] # 取得したCloudFrontのDistribution IDおよび基本認証で入力されたIDでシークレットを取得する response = asm_client.get_secret_value( SecretId='CloudFrontBasicAuth/' + cf_dist_id + '/' + str(target_id) ) # シークレットが取得でき、格納されている文字列が基本認証で入力されたパスワードと一致すれば認証成功としてリクエストを返却する。 # それ以外の場合はエラーレスポンスを返す。 if (response.get('SecretString') != None): secret_string = json.loads(response['SecretString']) if (secret_string.get('Password') == target_pw): return request else: return err_response else: return err_response else: return err_response except Exception as e: print("Exception:") print(e) return err_response LambdaEdgeBasicAuthVersion: #呼出元スタックでAmazon CloudFrontと関連付けるためのLambdaEdgeバージョンを作成する Type: AWS::Lambda::Version DependsOn: - LambdaEdgeBasicAuth Properties: FunctionName: !Ref LambdaEdgeBasicAuth Description: 'Basic Auth Lambda Edge for Amazon CloudFront' LambdaEdgeBasicAuthRole: #基本認証用Lambda@Edgeに適用するIAMロールとIAMポリシーの設定 Type: AWS::IAM::Role Properties: RoleName: !Sub 'LambdaEdgeBasicAuthRole4${BasicAuthFuncName}' Path: / AssumeRolePolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Principal: Service: - edgelambda.amazonaws.com - lambda.amazonaws.com Action: - sts:AssumeRole ManagedPolicyArns: - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole Policies: - PolicyName: LambdaEdgePolicy PolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Action: - iam:CreateServiceLinkedRole Resource: - '*' - Effect: Allow Action: - lambda:GetFunction - lambda:EnableReplication Resource: - !Sub 'arn:aws:lambda:us-east-1:${AWS::AccountId}:function:${BasicAuthFuncName}' - Effect: Allow Action: - cloudfront:UpdateDistribution Resource: - !Sub 'arn:aws:cloudfront::${AWS::AccountId}:distribution/${CloudFrontDistId}' - PolicyName: SecretsManagerGetSecretValue PolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Action: - secretsmanager:GetSecretValue Resource: - !Sub 'arn:aws:secretsmanager:us-east-1:${AWS::AccountId}:secret:CloudFrontBasicAuth/${CloudFrontDistId}/*' SecretsManagerSecret: #基本認証に使用するID、パスワードを格納するAWS Secrets Managerシークレットの作成 Type: 'AWS::SecretsManager::Secret' Properties: Name: !Sub CloudFrontBasicAuth/${CloudFrontDistId}/${BasicAuthID} SecretString: !Sub '{"Password":"${BasicAuthPW}"}' Description: "SecretsManagerSecret of LambdaEdgeBasicAuth" Outputs: Region: Value: Ref: 'AWS::Region' LambdaFunctionArn: Value: Ref: LambdaEdgeBasicAuth #基本認証用Lambda@EdgeのARN LambdaFunctionVersionArn: Value: Ref: LambdaEdgeBasicAuthVersion #基本認証用Lambda@EdgeバージョンのARN SecretsManagerSecretArn: Value: Ref: SecretsManagerSecret #基本認証情報を格納したAWS Secrets ManagerシークレットのARN
基本認証をするLambda@Edgeの概要
上記のCloudformationテンプレートのうち「ZipFile: |」の下から「LambdaEdgeBasicAuthVersion: 」の上までが基本認証をするLambda@Edgeの関数部分です。
CloudformationテンプレートではデプロイするAWS Lambda関数のコードは、ZipFileプロパティによるテンプレートへの直書き、S3バケットのオブジェクト(バージョン含む)、Amazon ECRのコンテナイメージのいずれかの方法で指定することができます。
この記事の例ではブログ上での説明を簡略化するためZipFileプロパティによる直書きを選択しました。
今回の基本認証をするLambda@Edgeの特徴には次のような点があります。
- 基本認証用のIDとパスワードはAWS Secrets Managerから取得する
-
認証に使用するAWS Secrets ManagerのシークレットIDを次のフォーマットで取得を試みる
CloudFrontBasicAuth/<Lambda@Edgeを関連付けたCloudFrontのDistribution ID>/<ブラウザから入力された基本認証用のID>
-
シークレットIDが存在し、シークレット内に次のように格納されている基本認証用のパスワードがブラウザから入力されたパスワードと一致するかを確認する
{ "Password": "<基本認証用のパスワード>" }
構築手順
-
AWS Lambda関数「CustomResourceToDeployCloudformationStack」を用意しておく
基本認証用Lambda@Edgeバージョンを作成するAWS Cloudformationスタックをus-east-1にデプロイするAWS Lambdaカスタムリソースを予め準備しておく -
基本認証用Lambda@Edgeバージョンを作成するCloudformationテンプレートファイル「CfnBasicAuthLambdaEdge.yml」をS3バケットに置く
基本認証用Lambda@Edgeバージョンを作成するAWS CloudformationテンプレートをAmazon S3バケット(前述のパラメータ例では「h-o2k」)に予め配置しておく -
呼出元Cloudformationテンプレートファイル「OrgSourceOfRunCfnBasicAuthLambdaEdge.yml 」をAWS Cloudformationで実行する
呼出元CloudformationテンプレートをAWSマネジメントコンソールなどからパラメータを入力の上でAWS Cloudformationスタックとして実行する -
静的ウェブサイトホスティングに使用するS3バケットにコンテンツを追加する
静的ウェブサイトホスティング用S3バケット(前述のパラメータ例では「cfnlambdaedge4cloudfront-20210822000000-hostingbucket-dev」)にindex.htmlなどコンテンツを追加する。
(今回の例では「CfnLambdaEdge4CloudFrontDemo」とだけ表示されるindex.htmlを追加しました。) -
初回実行(Create処理でAmazon CloudFrontが作成され、Distribution IDが発行される。カスタムリソースによる基本認証用Lambda@Edgeバージョン作成は実行しない。)
最初の呼出元AWS Cloudformationスタックの実行では「basicAuthCloudFrontDistId4LambdaEdge」にAmazon CloudFrontのDistribution IDを入力しないことで、基本認証用Lambda@Edgeを作成するAWS Cloudformationスタックの作成処理はスキップして、それ以外のリソースを作成します。
■初回実行時(Create)のパラメータ入力例
env: dev bucketName: cfnlambdaedge4cloudfront-20210822000000-hostingbucket runCfnInUsEast1LambdaARN: arn:aws:lambda:ap-northeast-1:XXXXXXXXXXXX:function:CustomResourceToDeployCloudformationStack runCfnInUsEast1StackName4LambdaEdge: CfnLambdaEdge4CloudFrontDemo cfnTplS3Bucket4LambdaEdge: h-o2k cfnTplS3Key4LambdaEdge: CfnBasicAuthLambdaEdge.yml basicAuthFuncName4LambdaEdge: BasicAuthWithLambdaEdge basicAuthCloudFrontDistId4LambdaEdge: #初回時は入力しない(Distribution IDが発行されていないので入力できない) basicAuthID4LambdaEdge: Iam basicAuthPW4LambdaEdge: Nobody
-
2回目実行(Update処理でAmazon CloudFrontのDistribution IDをカスタムリソースに渡し、基本認証用Lambda@Edgeバージョンを作成する。)
最初の呼出元AWS Cloudformationスタックの実行では「basicAuthCloudFrontDistId4LambdaEdge」に初回実行で発行されたAmazon CloudFrontのDistribution IDを入力することで、基本認証用Lambda@Edgeを作成するAWS Cloudformationスタックの作成処理を実行して、基本認証用Lambda@Edgeのバージョンと呼出元のAmazon CloudFrontを関連付けます。
■2回目実行時(Update)のパラメータ入力例
#2回目実行時(Update)はすべてのパラメータを入力 env: dev bucketName: cfnlambdaedge4cloudfront-20210822000000-hostingbucket runCfnInUsEast1LambdaARN: arn:aws:lambda:ap-northeast-1:XXXXXXXXXXXX:function:CustomResourceToDeployCloudformationStack runCfnInUsEast1StackName4LambdaEdge: CfnLambdaEdge4CloudFrontDemo cfnTplS3Bucket4LambdaEdge: h-o2k cfnTplS3Key4LambdaEdge: CfnBasicAuthLambdaEdge.yml basicAuthFuncName4LambdaEdge: BasicAuthWithLambdaEdge basicAuthCloudFrontDistId4LambdaEdge: XXXXXXXXXXXXX basicAuthID4LambdaEdge: Iam basicAuthPW4LambdaEdge: Nobody
-
結果確認
正しく実行されていればAmazon CloudFrontのURLにアクセスすると基本認証のダイアログが表示され、パラメータで指定した基本認証のIDとパスワードで認証を通過します。
正しく動作しない場合は呼出元AWS Cloudformationスタックのイベント内容、カスタムリソースでデプロイしたスタックのイベント内容、AWS LambdaカスタムリソースのAmazon CloudWatch Logsの内容、AWS CloudTrailのログから原因を特定して不具合を修正します。
削除手順
今回のAWS CloudformationテンプレートではLambda@EdgeとAmazon CloudFrontの関連付け解除とカスタムリソースの削除の順序制御をしていないため、一度にすべてのスタックを削除することができません。
そのため、削除する場合は次の手順のように関連付けを解除した後、呼出元AWS Cloudformationスタックと基本認証用Lambda@Edgeバージョンを作成したスタックをそれぞれ削除する必要があります。
- 呼出元AWS CloudformationスタックのパラメータでAmazon CloudFrontのDistribution IDを空にしてUpdate処理をする
呼出元AWS Cloudformationスタックのパラメータ「basicAuthCloudFrontDistId4LambdaEdge」を空にしてスタックを更新する。
us-east-1の基本認証用Lambda@Edgeのスタックの削除はCloudFrontとの関連付けがあるので失敗するが、呼び出し元のスタックではAmazon CloudFrontとLambda@Edgeの関連付けが削除される。 - 呼出元AWS Cloudformationスタックを削除する
呼出元AWS Cloudformationスタックと基本認証用Lambda@Edgeのスタックの関連付けが解除されているため、呼出元AWS Cloudformationスタックが削除できる。 - us-east-1で基本認証用Lambda@Edgeバージョンを作成したCloudformationスタックを削除する
呼出元AWS Cloudformationスタックと基本認証用Lambda@Edgeのスタックの関連付けが解除されているため、基本認証用Lambda@Edgeバージョンを作成したCloudformationスタックを単独で削除する。
参考:
What is AWS CloudFormation? - AWS CloudFormation
Tech Blog with related articles referenced
AWS CloudFormation Templates and AWS Lambda Custom Resources for Associating AWS Certificate Manager, Lambda@Edge, and AWS WAF with a Website on Amazon S3 and Amazon CloudFront Cross-Region
Deploy AWS Cloudformation Stack Cross-Region with AWS Lambda Custom Resources
まとめ
今回は「AWS LambdaカスタムリソースでAWS Cloudformationスタックを別リージョンにデプロイする」の記事で説明したAWS Cloudformationスタックを別リージョンにデプロイするAWS Lambdaカスタムリソースを使用し、基本認証をするLambda@Edgeバージョンと認証情報を格納するAWS Secrets Managerをus-east-1で作成して呼出元AWS Cloudformationスタックで管理しているAmazon CloudFrontに関連付ける例を紹介しました。
次回は別リージョンにAmazon S3バケットを作成し、呼出元リージョンのAmazon S3バケットとクロスリージョンレプリケーションおよびAmazon CloudFrontオリジンフェイルオーバーを構成する例を紹介しようと考えています。