小西秀和です。
前回の記事までに、「AWS CDKで別リージョンにスタックをデプロイしてパラメータをリージョン間で受け渡す方法 -AWS CDKカスタムリソースの実装例」の記事で紹介したリージョン間でパラメータを送受信する方法を使って、AWS CDKでACM証明書(SSL証明書)、基本認証用Lambda@Edge、レプリケーション用S3バケットとAmazon CloudFrontオリジンフェイルオーバーといったクロスリージョンのリソースを連携させる例を以下の記事で紹介してきました。
- AWS CDKで別リージョンにAWS Certificate Manager(ACM)証明書スタックをデプロイしてAmazon CloudFrontに設定する
- AWS CDKで別リージョンにレプリケーション用S3バケットを作成するスタックをデプロイしてAmazon CloudFrontオリジンフェイルオーバーを設定する
- AWS CDKで別リージョンに基本認証用Lambda@Edgeを作成するスタックをデプロイしてAmazon CloudFrontに設定する
今回は今までの総集編として、これらの機能をまとめて設定するAWS CDKの例を紹介したいと思います。
パラメータをクロスリージョンで扱うAWS CDKカスタムリソースについては元記事を参照してください。
※本記事および当執筆者のその他の記事で掲載されているソースコードは自主研究活動の一貫として作成したものであり、動作を保証するものではありません。使用する場合は自己責任でお願い致します。また、予告なく修正することもありますのでご了承ください。
本記事で試す内容の概要図
今回は次の内容をAWS Cloud Development Kit(AWS CDK)とAWS CDKカスタムリソースを中心に試しています。
- us-east-1リージョンでAWS Certificate Manager(ACM)証明書を発行するスタックを実行し、ACM証明書のARNをap-northeast-1リージョンのAWS Systems Manager(SSM)パラメータストアに保存する
- us-east-1リージョンでクロスリージョンレプリケーション用のAmazon S3バケットを作成するスタックを実行し、S3バケットARNなどをap-northeast-1リージョンのAWS Systems Manager(SSM)パラメータストアに保存する
- us-east-1リージョンで基本認証用Lambda@Edgeを作成するスタックを実行し、Lambda@EdgeのARNをap-northeast-1リージョンのAWS Systems Manager(SSM)パラメータストアに保存する
- ap-northeast-1リージョンでAmazon CloudFront+Amazon S3のホスティングを構成するスタックを実行し、SSMパラメータストアからACM証明書ARN、Lambda@EdgeのARN、S3バケットARNなどを取得して、S3バケットのクロスリージョンレプリケーションとオリジンフェイルオーバーを構成したCloudFrontに設定する
AWS CDKプロジェクトの階層構造とファイル概要
AWS CDKのプロジェクトの構造は次のようになります。
主に実装した部分にはコメントを追記しています。
[ho2k_com@ho2k-com s3cf-edge]# tree ~不要部分は省略しています~ ├── README.md ├── app.py # 各スタックのリージョンや依存関係などを指定する ├── cdk.json # Contextに静的パラメータを定義する ├── cdk.out ├── lambda │ └── index.py # Lambda@EdgeとしてデプロイするLambda関数 ├── requirements.txt ├── s3cf_edge │ ├── __init__.py │ ├── certificate_stack.py # us-east-1リージョンでACM証明書を発行するスタック │ ├── lambda_edge_stack.py # us-east-1リージョンでLambda@Edgeを作成するスタック │ ├── s3cloudfront_stack.py # ap-northeast-1リージョンでCloudFront+S3ホスティングを構成するスタック │ ├── s3secondary_stack.py # us-east-1リージョンでレプリケーション用S3バケットを作成するスタック │ └── x_region_param.py # クロスリージョンのパラーメータ受け渡しをするConstruct継承クラス ├── setup.py └── source.bat
各スタック共通設定
各スタックのリージョンや依存関係などを指定するapp.pyは次のようになります。
add_dependencyでACM証明書発行スタックを実行してから、CloudFront+S3ホスティングのスタックを実行する依存関係を指定しています。
■app.py
#!/usr/bin/env python3 import os from aws_cdk import core as cdk from aws_cdk import core from s3cf_acm_edge_s3sec.certificate_stack import CertificateStack from s3cf_acm_edge_s3sec.lambda_edge_stack import LambdaEdgeStack from s3cf_acm_edge_s3sec.s3secondary_stack import S3SecondaryStack from s3cf_acm_edge_s3sec.s3cloudfront_stack import S3CloudfrontStack app = core.App() certificate_stk = CertificateStack(app, "CdkS3CfAllCertificateStack", env=core.Environment(region='us-east-1')) lambda_edge_stk = LambdaEdgeStack(app, "CdkS3CfAllLambdaEdgeStack", env=core.Environment(region='us-east-1')) s3secondary_stk = S3SecondaryStack(app, "CdkS3CfAllS3SecondaryStack", env=core.Environment(region='us-east-1')) s3cloudfront_stk = S3CloudfrontStack(app, "CdkS3CfAllS3CloudfrontStack", env=core.Environment(region='ap-northeast-1')) s3cloudfront_stk.add_dependency(certificate_stk) s3cloudfront_stk.add_dependency(s3secondary_stk) s3cloudfront_stk.add_dependency(lambda_edge_stk) app.synth()
cdk.jsonではContextに次の静的パラメータを追記してスタック等で呼び出して使用します。
"PREFIX": "<リージョン間で受け渡すパラメータ名の先頭にプロジェクト固有のプレフィックスを付けるために使用>", "ACTIVATE_BASIC_AUTH": <初回デプロイ以降に基本認証用Lambda@Edgeを有効化するフラグ。true:有効化、false:無効化>, "BASIC_AUTH_FUNC_NAME": "<基本認証用Lambda@Edgeの関数名>", "BASIC_AUTH_ID": "<基本認証ID>", "BASIC_AUTH_PW": "<基本認証パスワード>"
■cdk.json
{ "app": "python3 app.py", "context": { "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true, "@aws-cdk/core:enableStackNameDuplicates": "true", "aws-cdk:enableDiffNoFail": "true", "@aws-cdk/core:stackRelativeExports": "true", "@aws-cdk/aws-ecr-assets:dockerIgnoreSupport": true, "@aws-cdk/aws-secretsmanager:parseOwnedSecretName": true, "@aws-cdk/aws-kms:defaultKeyPolicies": true, "@aws-cdk/aws-s3:grantWriteWithoutAcl": true, "@aws-cdk/aws-ecs-patterns:removeDefaultDesiredCount": true, "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, "@aws-cdk/aws-efs:defaultEncryptionAtRest": true, "@aws-cdk/aws-lambda:recognizeVersionProps": true, "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": true, "PREFIX": "s3cf_acm_edge_s3sec_", "HOSTED_ZONE_ID": "XXXXXXXXXXXXXXXXXXXXX", "ZONE_NAME": "h-o2k.com", "RECORD_NAME": "cdk-s3cf-acm-edge-s3sec", "DOMAIN_NAME": "cdk-s3cf-acm-edge-s3sec.h-o2k.com", "ACTIVATE_BASIC_AUTH": false, "BASIC_AUTH_FUNC_NAME": "CdkS3CfAllLambdaEdgeOfBasicAuth", "BASIC_AUTH_ID": "Iam", "BASIC_AUTH_PW": "Nobody" } }
AWS Certificate Manager(ACM)証明書を作成するスタック
AWS Certificate Manager(ACM)証明書を作成するスタックは次の記事で紹介した「certificate_stack.py」をそのまま使用します。
■certificate_stack.py
クロスリージョンレプリケーション用S3バケットを作成するスタック
クロスリージョンレプリケーション用S3バケットを作成するスタックは次の記事で紹介した「s3secondary_stack.py」をそのまま使用します。
■s3secondary_stack.py
基本認証用Lambda@Edgeを作成するスタック
基本認証用Lambda@Edgeを作成するスタックは次の記事で紹介した「lambda_edge_stack.py」をそのまま使用します。
■lambda_edge_stack.py
Lambda@Edgeとして基本認証を実施するLambda関数の内容
Lambda@Edgeとして基本認証を実際におこなうLambda関数は次の記事で紹介した「index.py」をそのまま使用します。
■index.py
Amazon CloudFront+Amazon S3を作成し、ACM証明書、基本認証用Lambda@Edge、CloudFrontオリジンフェイルオーバーを設定するスタック
ap-northeast-1にCloudFront+S3ホスティングを構成し、上記のスタックで作成されたACM証明書、基本認証用Lambda@Edge、CloudFrontオリジンフェイルオーバーを設定するスタックは次のようになります。
■s3cloudfront_stack.py
from aws_cdk import core as cdk from x_region_param import XRegionParam from aws_cdk.aws_s3 import CfnBucket from aws_cdk import ( core, aws_s3 as s3, aws_cloudfront as cf, aws_route53 as r53, aws_route53_targets as r53tgt, aws_iam as iam, aws_lambda as lmd ) class S3CloudfrontStack(core.Stack): def __init__(self, scope: core.Construct, id: str, **kwargs) -> None: super().__init__(scope, id, **kwargs) stack = core.Stack.of(self); #スタックの環境設定からAWSアカウントIDを取得 ACCOUNT_ID = stack.account #cdk.jsonのContextから静的パラメータを取得 PREFIX = self.node.try_get_context("PREFIX") HOSTED_ZONE_ID = self.node.try_get_context("HOSTED_ZONE_ID") ZONE_NAME = self.node.try_get_context("ZONE_NAME") DOMAIN_NAME = self.node.try_get_context("DOMAIN_NAME") RECORD_NAME = self.node.try_get_context("RECORD_NAME") ACTIVATE_BASIC_AUTH = self.node.try_get_context("ACTIVATE_BASIC_AUTH") #ホスティング用S3バケットを作成する bucket = s3.Bucket(self, 'BucketPrimary', access_control=s3.BucketAccessControl.PUBLIC_READ, website_index_document='index.html', versioned=True ) bucket.grant_public_access() #リージョン間値送受信クラスでap-northeast-1リージョンにあるSSMパラメータ(OAI名)を取得する resut_get_oai_name = XRegionParam(self, 'GetOai', region='ap-northeast-1', service='SSM', action='GET', key=PREFIX + 'oai-name', val='', description='' ) oai_name = resut_get_oai_name.get_result()['result'] #OAI名からOAIを取得する oai = cf.OriginAccessIdentity.from_origin_access_identity_name(self, 'CfOai', origin_access_identity_name=oai_name ) #リージョン間値送受信クラスでap-northeast-1リージョンにあるSSMパラメータ(OAI S3 Canonical User ID)を取得する resut_get_canonical_user_id = XRegionParam(self, 'GetCanonicalUserId', region='ap-northeast-1', service='SSM', action='GET', key=PREFIX + 'canonical-user-id', val='', description='' ) cloud_front_origin_access_identity_s3_canonical_user_id = resut_get_canonical_user_id.get_result()['result'] #S3へのアクセス権限をOAIに限定するようにS3バケットポリシーを設定 bucket_policy = iam.PolicyStatement( effect=iam.Effect.ALLOW, actions=['s3:GetObject'], principals=[ iam.CanonicalUserPrincipal( cloud_front_origin_access_identity_s3_canonical_user_id ) ], resources=[bucket.bucket_arn + '/*'] ); bucket.add_to_resource_policy(bucket_policy); #リージョン間値送受信クラスでap-northeast-1リージョンにあるSSMパラメータ(レプリケーション用S3バケットのARN)を取得する resut_get_bucket_arn = XRegionParam(self, 'GetBucketSecondaryArn', region='ap-northeast-1', service='SSM', action='GET', key=PREFIX + 'bucket-secondary-arn', val='', description='' ) secondary_bucket_arn = resut_get_bucket_arn.get_result()['result'] #S3バケットARNとリージョンからレプリケーション用S3バケットを取得する secondary_bucket = s3.Bucket.from_bucket_attributes(self, 'BucketSecondary', bucket_arn=secondary_bucket_arn, region='us-east-1' ) #S3レプリケーション設定用のIAMロールを作成する replication_role = iam.Role(self, "ReplicationRole", assumed_by=iam.ServicePrincipal("s3.amazonaws.com"), path="/service-role/", ) #S3レプリケーション設定用のIAMロールにポリシーを追加していく replication_role.add_to_policy( iam.PolicyStatement( resources=[bucket.bucket_arn], actions=["s3:GetReplicationConfiguration", "s3:ListBucket"] ) ) replication_role.add_to_policy( iam.PolicyStatement( resources=[bucket.arn_for_objects("*")], actions=[ "s3:GetObjectVersion", "s3:GetObjectVersionAcl", "s3:GetObjectVersionForReplication", "s3:GetObjectLegalHold", "s3:GetObjectVersionTagging", "s3:GetObjectRetention" ], ) ) replication_role.add_to_policy( iam.PolicyStatement( resources=[secondary_bucket.arn_for_objects("*")], actions=[ "s3:ReplicateObject", "s3:ReplicateDelete", "s3:ReplicateTags", "s3:GetObjectVersionTagging", "s3:ObjectOwnerOverrideToBucketOwner" ] ) ) #L1 ConstructのCfnBucketを使用してS3のクロスリージョンレプリケーション設定をする bucket.node.default_child.replication_configuration = CfnBucket.ReplicationConfigurationProperty( role=replication_role.role_arn, rules=[ CfnBucket.ReplicationRuleProperty( destination=CfnBucket.ReplicationDestinationProperty( bucket=secondary_bucket.bucket_arn, account=ACCOUNT_ID ), status="Enabled" ) ] ) #リージョン間値送受信クラスでap-northeast-1リージョンにあるSSMパラメータ(ACM証明書ARN)を取得する resut_get_param = XRegionParam(self, 'GetCertificateArn', region='ap-northeast-1', service='SSM', action='GET', key=PREFIX + 'certificate-arn', val='', description='' ) acm_arn = resut_get_param.get_result()['result'] #ACTIVATE_BASIC_AUTHがtrueだとLambda@Edgeを設定したBehaviorを作成 #ACTIVATE_BASIC_AUTHがfalseだとデフォルトのBehaviorを作成 if ACTIVATE_BASIC_AUTH: #リージョン間値送受信クラスでap-northeast-1リージョンにあるSSMパラメータ(Lambda@EdgeのARN)を取得する resut_get_param = XRegionParam(self, 'GetLambdaEdgeArn', region='ap-northeast-1', service='SSM', action='GET', key= PREFIX + 'lambda-edge-arn', val='', description='' ) lmd_ver_arn = resut_get_param.get_result()['result'] #Lambda@EdgeのARNでCloudFrontのビューワーリクエストに対してlambda@EdgeをBehaviorに設定する cf_behavior = cf.Behavior( is_default_behavior=True, lambda_function_associations=[ cf.LambdaFunctionAssociation( event_type=cf.LambdaEdgeEventType.VIEWER_REQUEST, lambda_function=lmd.Version.from_version_arn(self,"BasicAuthLambdaEdgeVersion4AmazonCloudFront", version_arn=lmd_ver_arn ), include_body=True ) ] ) else: #デフォルトのBehaviorを作成 cf_behavior = cf.Behavior(is_default_behavior=True) #CloudFrontのオリジンにS3バケットを設定し、ACM証明書を関連付ける #CloudFrontのオリジングループを2つのS3バケットで構成し、CloudFrontオリジンフェイルオーバーを設定する #Lambda@Edgeの有効化の有無を反映したBehaviorを含めてCloudFrontを作成する distribution = cf.CloudFrontWebDistribution(self, 'CloudFrontWebDistribution', alias_configuration=cf.AliasConfiguration( names=[DOMAIN_NAME], acm_cert_ref=acm_arn, ssl_method=cf.SSLMethod.SNI, security_policy=cf.SecurityPolicyProtocol.TLS_V1_2_2021 ), origin_configs=[ cf.SourceConfiguration( behaviors=[cf_behavior], s3_origin_source=cf.S3OriginConfig( s3_bucket_source=bucket, origin_access_identity=oai ), failover_s3_origin_source=cf.S3OriginConfig( s3_bucket_source=secondary_bucket, origin_access_identity=oai ) ) ] ) #ホストゾーンIDとゾーン名からホストゾーンを取得する hosted_zone = r53.HostedZone.from_hosted_zone_attributes(self, 'HostedZone', hosted_zone_id=HOSTED_ZONE_ID, zone_name=ZONE_NAME ) #Route53のホストゾーンに対応するAレコードにエイリアスレコードとしてCloudFrontを設定する r53.ARecord(self, 'AliasRecord', zone=hosted_zone, record_name=RECORD_NAME, target=r53.RecordTarget.from_alias(r53tgt.CloudFrontTarget(distribution)) ) #リージョン間値送受信クラスでus-east-1リージョンにSSMパラメータ(CloudFront Distribution ID)を保存する resut_put_param = XRegionParam(self, 'CloudFrontDistIdParameter', region='us-east-1', service='SSM', action='PUT', key=PREFIX + 'cloud-front-dist-id', val=distribution.distribution_id, description='' )
AWS CDKカスタムリソースを使用するクラス
次の記事で紹介したAWS CDKカスタムリソースでクロスリージョンのパラメータ受け渡しをするConstructを継承したクラス(x_region_param.py)を各スタックと同じ階層に配置して使用しています。
■x_region_param.py
実行手順
今回のAWS CDKプロジェクトで基本認証用Lambda@Edgeを有効化する設定は2段階で実行することを前提にしています。
まず、初回実行でS3+CloudFrontのウェブホスティング環境を作成し、2回目実行でCloudFrontディストリビューションIDを使用してLambda@EdgeとSecrets Managerシークレットを作成してCloudFrontに関連付けます。
- 【初回実行】cdk.jsonでcontextを「"ACTIVATE_BASIC_AUTH": false」の状態で実行する。
この段階ではACM証明書(SSL証明書)、レプリケーションS3バケットを使用したCloudFrontオリジンフェイルオーバーが設定されます。 - 【2回目実行】初回実行後、cdk.jsonでcontextを「"ACTIVATE_BASIC_AUTH": true」の状態で実行する。
この段階で基本認証用Lambda@Edgeが有効になります。
参考:
Tech Blog with related articles referenced
まとめ
今回は「AWS CDKで別リージョンにスタックをデプロイしてパラメータをリージョン間で受け渡す方法 -AWS CDKカスタムリソースの使い方」の記事で説明したAWS CDKカスタムリソースでリージョン間のパラメータ受け渡しをするConstruct継承クラスを使用して、別リージョンにACM証明書(SSL証明書)、基本認証用Lambda@Edge、レプリケーション用S3バケットとAmazon CloudFrontオリジンフェイルオーバーといったクロスリージョンのリソースをまとめて設定する例を紹介しました。
これまで、AWS Amplify、AWS CloudFormation、AWS CDKについて静的ウェブサイトホスティングをテーマにして書いてきました。
これらの静的ウェブサイトホスティングに関連する記事は相互に関連している内容を記載しているため、参考までにリンクをまとめておきます。
主にAWS Amplify、AWS CloudFormation、AWS CDKそれぞれの静的ウェブサイトホスティングにACM証明書(SSL証明書)、基本認証用Lambda@Edge、レプリケーション用S3バケットとAmazon CloudFrontオリジンフェイルオーバーを設定する例になります。
<静的ウェブサイトホスティングで入門するAWS Amplifyシリーズ>
- AWSの静的ウェブサイトホスティングで入門するAWS Amplify(Console、CLI) - 概要編
- AWSの静的ウェブサイトホスティングで入門するAWS Amplify(Console、CLI) - 構築編(Amplify Console)
- AWSの静的ウェブサイトホスティングで入門するAWS Amplify(Console、CLI) - 構築編(Amplify CLI)
- AWS Amplify CLIとAWS CloudformationでAmplify Console Hostingと同じ機能の再現を試みる - AWS CloudFormationによるAWS Amplify CLIの拡張
<LambdaカスタムリソースでCloudformationスタックをデプロイするシリーズ>
- AWS LambdaカスタムリソースでAWS Cloudformationスタックを別リージョンにデプロイする
- AWS LambdaカスタムリソースでACM証明書を作成するAWS Cloudformationスタックを別のリージョンにデプロイする
- AWS Lambdaカスタムリソースで基本認証用Lambda@Edgeを作成するAWS Cloudformationスタックを別リージョンにデプロイする
- AWS LambdaカスタムリソースでCloudFrontオリジンフェイルオーバー用S3バケットを作成するAWS Cloudformationスタックを別リージョンにデプロイする
- AWS LambdaカスタムリソースでSSL証明書・基本認証・CloudFrontオリジンフェイルオーバーを作成するAWS Cloudformationスタックを別リージョンにデプロイする
<AWS CDKで別リージョンにスタックをデプロイ・パラメータをリージョン間で受け渡すシリーズ>
- AWS CDKで別リージョンにスタックをデプロイしてパラメータをリージョン間で受け渡す方法 -AWS CDKカスタムリソースの実装例
- AWS CDKで別リージョンにAWS Certificate Manager(ACM)証明書スタックをデプロイしてAmazon CloudFrontに設定する
- AWS CDKで別リージョンにレプリケーション用S3バケットを作成するスタックをデプロイしてAmazon CloudFrontオリジンフェイルオーバーを設定する
- AWS CDKで別リージョンに基本認証用Lambda@Edgeを作成するスタックをデプロイしてAmazon CloudFrontに設定する
- AWS CDKで別リージョンにSSL証明書・基本認証・レプリケーション用S3バケットを作成するスタックをデプロイしてAmazon CloudFrontオリジンフェイルオーバーを設定する