小西秀和です。
前回、AWS CDKで別リージョンにスタックをデプロイしてパラメータをリージョン間で受け渡す方法 -AWS CDKカスタムリソースの実装例の記事でAWS CDKでスタックをクロスリージョンにデプロイし、パラメータをAWS CDKカスタムリソースでリージョン間の受け渡しをする例を紹介しました。
今回はその記事の続編として、前回記事で作成したAWS CDKカスタムリソースを使用して実際にAWS Certificate Manager(ACM)証明書をデプロイする方法を紹介します。
パラメータをクロスリージョンで扱う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)パラメータストアに保存する
- ap-northeast-1リージョンでAmazon CloudFront+Amazon S3のホスティングを構成するスタックを実行し、SSMパラメータストアからACM証明書を取得してCloudFrontに設定する

AWS CDKプロジェクトの階層構造とファイル概要
AWS CDKのプロジェクトの構造は次のようになります。
主に実装した部分にはコメントを追記しています。
[iam@h-o2k s3cf-acm]# tree ~不要部分は省略しています~ ├── README.md ├── app.py # 各スタックのリージョンや依存関係などを指定する ├── cdk.json # Contextに静的パラメータを定義する ├── cdk.out ├── requirements.txt ├── s3cf_acm │ ├── __init__.py │ ├── certificate_stack.py # us-east-1リージョンでACM証明書を発行するスタック │ ├── s3cloudfront_stack.py # ap-northeast-1リージョンでCloudFront+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.certificate_stack import CertificateStack from s3cf_acm.s3cloudfront_stack import S3CloudfrontStack app = core.App() certificate_stk = CertificateStack(app, "CdkS3CfAcmCertificateStack", env=core.Environment(region='us-east-1')) s3cloudfront_stk = S3CloudfrontStack(app, "CdkS3CfAcmS3CloudfrontStack", env=core.Environment(region='ap-northeast-1')) s3cloudfront_stk.add_dependency(certificate_stk) app.synth()
cdk.jsonではContextに次の静的パラメータを追記してスタック等で呼び出して使用します。
"PREFIX": "<リージョン間で受け渡すパラメータ名の先頭にプロジェクト固有のプレフィックスを付けるために使用>", "HOSTED_ZONE_ID": "<ACM証明書を発行する対象ドメインのホストゾーンID>", "ZONE_NAME": "<ACM証明書を発行する対象ドメインのゾーン名>", "RECORD_NAME": "<ACM証明書を発行する対象ドメインのレコード名>", "DOMAIN_NAME": "<ACM証明書を発行する対象ドメイン名>"
■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_", "HOSTED_ZONE_ID": "XXXXXXXXXXXXXXXXXXXXX", "ZONE_NAME": "h-o2k.com", "RECORD_NAME": "cdk-s3cf-acm", "DOMAIN_NAME": "cdk-s3cf-acm.h-o2k.com" } }
AWS Certificate Manager(ACM)証明書を作成するスタック
us-east-1リージョンにACM証明書を発行するスタックの内容です。
処理のポイントとなる箇所についてコメントを追記しておきます。
■certificate_stack.py
from aws_cdk import core as cdk from x_region_param import XRegionParam from aws_cdk import ( core, aws_certificatemanager as acm, aws_route53 as r53 ) class CertificateStack(core.Stack): def __init__(self, scope: core.Construct, id: str, **kwargs) -> None: super().__init__(scope, id, **kwargs) #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") #ホストゾーンIDとゾーン名からホストゾーンを取得する hosted_zone = r53.HostedZone.from_hosted_zone_attributes(self, 'HostedZone', hosted_zone_id=HOSTED_ZONE_ID, zone_name=ZONE_NAME ) #ドメイン名とホストゾーンを使用してACM証明書の発行とDNS認証を実施する certificate = acm.Certificate(self, 'ACMCertificate', domain_name=DOMAIN_NAME, validation_method=acm.ValidationMethod.DNS, validation=acm.CertificateValidation.from_dns(hosted_zone) ) #リージョン間値送受信クラスでap-northeast-1リージョンにSSMパラメータ(ACM証明書ARN)を保存する resut_put_param = XRegionParam(self, 'ACMCertificateArnParameter', region='ap-northeast-1', service='SSM', action='PUT', key=PREFIX + 'certificate-arn', val=certificate.certificate_arn, description='' )
Amazon CloudFront+Amazon S3を作成し、Amazon CloudFrontにACM証明書を設定するスタック
ap-northeast-1にCloudFront+S3ホスティングを構成するスタックの内容です。
処理のポイントとなる箇所についてコメントを追記しておきます。
■s3cloudfront_stack.py
from aws_cdk import core as cdk from x_region_param import XRegionParam from aws_cdk import ( core, aws_s3 as s3, aws_iam as iam, aws_cloudfront as cf, aws_route53 as r53, aws_route53_targets as r53tgt, ) class S3CloudfrontStack(core.Stack): def __init__(self, scope: core.Construct, id: str, **kwargs) -> None: super().__init__(scope, id, **kwargs) #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") #ホスティング用S3バケットを作成する bucket = s3.Bucket(self, 'BucketPrimary', access_control=s3.BucketAccessControl.PUBLIC_READ, website_index_document='index.html' ) bucket.grant_public_access() #CloudFrontにS3バケットを設定するためのOAIを作成 oai = cf.OriginAccessIdentity(self, 'CfOai'); #S3へのアクセス権限をOAIに限定するようにS3バケットポリシーを設定 bucket_policy = iam.PolicyStatement( effect=iam.Effect.ALLOW, actions=['s3:GetObject'], principals=[ iam.CanonicalUserPrincipal( oai.cloud_front_origin_access_identity_s3_canonical_user_id ) ], resources=[bucket.bucket_arn + '/*'] ) bucket.add_to_resource_policy(bucket_policy); #リージョン間値送受信クラスで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'] #CloudFrontのオリジンにS3バケットを設定し、ACM証明書を関連付ける cf_behavior = cf.Behavior(is_default_behavior=True) 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 ) ) ] ) #ホストゾーン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)) )
AWS CDKカスタムリソースを使用するクラス
次の記事で紹介したAWS CDKカスタムリソースでクロスリージョンのパラメータ受け渡しをするConstructを継承したクラス(x_region_param.py)を各スタックと同じ階層に配置して使用しています。
■x_region_param.py
まとめ
今回は「AWS CDKで別リージョンにスタックをデプロイしてパラメータをリージョン間で受け渡す方法 -AWS CDKカスタムリソースの使い方」の記事で説明したAWS CDKカスタムリソースでリージョン間のパラメータ受け渡しをするConstruct継承クラスを使用して、ACM証明書をus-east-1で発行してap-northeast-1のAmazon CloudFrontに設定し、Route53レコードセットにCloudFrontを関連付ける例を紹介しました。
次回は同様にリージョン間値送受信をするConstruct継承クラスを使用して、us-east-1リージョンにAmazon S3バケットを作成し、ap-northeast-1リージョンのAmazon S3バケットとクロスリージョンレプリケーションおよびAmazon CloudFrontオリジンフェイルオーバーを構成する例を紹介しようと考えています。