CloudFrontとS3バケットのCloudFormationスタック
AWS CDKとCloudFormation StackによるCloudFrontディストリビューションとS3バケットのデプロイ
これはWagtailを使っている場合によくある使用例だが、他にも応用が利く。
Wagtailを使用する場合、これは一般的なユースケースです。推奨は、すべての画像を保存するS3の "ウェブサイト "と、そのバケットへのファイルの読み書きを許可するIAMユーザー(APIアクセスのみ)を作成することです。
手作業でこれを行うのは面倒だし、多少ミスが起こりやすいので、このプロセスを自動化したかったのだ。この記事で使用したコードは、このGitHubリポジトリにもある。
AWS CDKは素晴らしいツールで、CloudFormationテンプレートで直接作業するのに比べてはるかに理解しやすいフォーマットで、作成したいリソースをプログラムで作成することができます。CDKはAWSアカウントにこれらのリソースをデプロイすることもできるが、私は従来のCloudFormationテンプレートを生成するためにCDKを使っている。
CDK入力パラメーター
このスタックを再利用可能にするために、DNSドメイン名とIPアドレスの2つのパラメーターを要求する。
- DNS Domain Nameは、明らかにCloudFront Distributionを構成するDomainの名前です。そのDNS名を管理し、Route53 Domainとして登録されていないことを前提としているので、AWS Certificate ManagerでSSL証明書を作成するためにDNS検証を使用します。
Tags.ofは、CDKの実にすてきな構成要素で、指定された名前と値でデプロイされたすべてのリソースにタグを作成するようCDKに指示します。
site_domain_name = CfnParameter(self,
id='domain_name',
description="Domain Name",
default="static.example.com",
type="String",
).value_as_string
stack_name = self.stack_name
Tags.of(self).add('Project', "Wagtail Images");
Tags.of(self).add('stackName', stack_name);
Tags.of(self).add('Domain', site_domain_name);
S3バケット
そして、AWSの最新のベストプラクティスに従って、デフォルトの暗号化で構成されたS3バケットを作成し、公開ウェブアクセスをブロックします。CloudFrontディストリビューションだけがそのバケットから読み取ることができ、公開ウェブサイト経由でファイルを提供することができる。
つまり、CloudFormationデプロイメントを削除しても、S3バケットとオブジェクトは削除されず、データを失うことはありません。テスト中で、クリーンアップを最小限にしたい場合は、Removal PolicyをDESTROYに変更してください。
また、2つのS3ライフサイクルポリシーを作成する。詳細については、費用対効果の高いS3バケットを参照。
myBucket = aws_s3.Bucket(
self,
's3_bucket',
bucket_name = site_domain_name,
encryption=aws_s3.BucketEncryption.S3_MANAGED,
access_control=aws_s3.BucketAccessControl.PRIVATE,
public_read_access=False,
block_public_access=aws_s3.BlockPublicAccess.BLOCK_ALL,
# removal_policy=RemovalPolicy.DESTROY,
removal_policy=RemovalPolicy.RETAIN,
auto_delete_objects=False,
lifecycle_rules = [
aws_s3.LifecycleRule(
id="multipart",
enabled=True,
abort_incomplete_multipart_upload_after=Duration.days(1),
expired_object_delete_marker=True,
),
aws_s3.LifecycleRule(
id="IA",
enabled=True,
transitions=[{
"storageClass": aws_s3.StorageClass.INTELLIGENT_TIERING,
"transitionAfter": Duration.days(0)
}]
)
]
)
AWS IAM
さて、お楽しみのIAMユーザーとアクセス・ポリシーだ。
まずはAWS ConsoleにアクセスできないIAMユーザーを作成し、シークレットアクセスキーを生成します。このシークレットアクセスキーはとりあえずAWS Secrets Managerに保存しておきます、
また、そのユーザーには、自身のアクセスキーを管理するアクセス権(詳細は後述)と、先ほど作成したS3バケット内のファイルを管理するアクセス権を付与する。
user = iam.User(self, "User")
access_key = iam.AccessKey(self, "AccessKey", user=user)
secret = secretsmanager.Secret(self, "Secret",
secret_string_value=access_key.secret_access_key
)
policy = iam.Policy(self, 'myPolicy', statements=[iam.PolicyStatement(
resources=[user.user_arn],
actions=["iam:ListAccessKeys",
"iam:CreateAccessKey",
"iam:DeleteAccessKey"
]
)]
)
policy.attach_to_user(user)
myBucket.add_to_resource_policy(
iam.PolicyStatement(
sid="AllowUserManageBucket",
effect=iam.Effect.ALLOW,
actions=["s3:ListBucket",
"s3:GetBucketLocation",
"s3:ListBucketMultipartUploads",
"s3:ListBucketVersions"],
resources=[myBucket.bucket_arn],
principals=[iam.ArnPrincipal(user.user_arn)],
)
)
myBucket.add_to_resource_policy(
iam.PolicyStatement(
sid="AllowUserManageBucketObjects",
effect=iam.Effect.ALLOW,
actions=["s3:*Object"],
resources=[myBucket.arn_for_objects("*")],
principals=[iam.ArnPrincipal(user.user_arn)],
)
)
SSL証明書
では、AWS Certificate Managerを使用して、CloudFrontディストリビューション用の無料のSSL証明書を作成します。ドメインDNSに新しいCNAME DNSレコードを作成し、このドメインを所有していることをAWSに証明する必要があります。DNSにRoute53を使用している場合は、別のオプションを選択することをお勧めします。
このステップを実行するまでCloudFormationのデプロイは完了しないことに注意してください。
certificate = cm.Certificate(self, "MyCertificate",
domain_name=site_domain_name,
certificate_name=site_domain_name,
validation=cm.CertificateValidation.from_dns()
)
CloudFrontディストリビューション
では、CloudFront Distributionを作成しましょう。S3バケットへのアクセスには、従来のCloudFrontオリジンアクセスID(OAI)ではなく、AWSが推奨する新しいOriginアクセスコントロール設定を使うことに注意してください。これはまだCDKで完全にサポートされていないので、今のところ回避策を実装する必要があります。
また、ディストリビューション内のオブジェクトのキャッシュ、圧縮、デフォルトとエラーのレスポンスなど、いくつかのビヘイビアを設定します。
cfn_origin_access_control = cloudfront.CfnOriginAccessControl(self, "MyCfnOriginAccessControl",
origin_access_control_config=cloudfront.CfnOriginAccessControl.OriginAccessControlConfigProperty(
name=site_domain_name,
origin_access_control_origin_type="s3",
signing_behavior="always",
signing_protocol="sigv4",
description="CF S3 Origin Access Control"
)
)
distribution = cloudfront.Distribution(
self, "CloudFrontDistribution",
default_behavior=cloudfront.BehaviorOptions(
compress=True,
allowed_methods=cloudfront.AllowedMethods.ALLOW_GET_HEAD_OPTIONS,
cached_methods=cloudfront.CachedMethods.CACHE_GET_HEAD,
cache_policy=cloudfront.CachePolicy.CACHING_OPTIMIZED,
origin_request_policy=cloudfront.OriginRequestPolicy.CORS_S3_ORIGIN,
viewer_protocol_policy=cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS,
origin=origins.S3Origin(
myBucket, origin_id='s3-static-frontend',
)
),
domain_names=[site_domain_name],
certificate=certificate,
http_version=cloudfront.HttpVersion.HTTP2,
enabled=True,
default_root_object="index.html",
price_class=cloudfront.PriceClass.PRICE_CLASS_ALL,
error_responses=[
cloudfront.ErrorResponse(
http_status=404, response_page_path="/index.html", response_http_status=200,
ttl=Duration.seconds(60)),
]
)
# Override the default behavior to use OAC and not OAI
# Because CDK does not have full support for this feature yet
# Based on: https://github.com/aws/aws-cdk/issues/21771#issuecomment-1282780627
cf_cfn_distrib = t.cast(
cloudfront.CfnDistribution,
distribution.node.default_child,
)
cf_cfn_distrib.add_property_override(
'DistributionConfig.Origins.0.OriginAccessControlId',
cfn_origin_access_control.get_att('Id'),
)
cf_cfn_distrib.add_property_override(
'DistributionConfig.Origins.0.S3OriginConfig.OriginAccessIdentity',
'',
)
myBucket.add_to_resource_policy(iam.PolicyStatement(
effect=iam.Effect.ALLOW,
actions=['s3:GetObject', 's3:ListBucket'],
resources=[f'{myBucket.bucket_arn}/*', myBucket.bucket_arn],
principals=[iam.ServicePrincipal("cloudfront.amazonaws.com")],
conditions={
"StringEquals": {
"aws:SourceArn": f"arn:aws:cloudfront::{Aws.ACCOUNT_ID}:distribution/{distribution.distribution_id}"
}
}
))
CloudFormationの出力
最後に、CloudFormation Outputでデプロイされた主要コンポーネントの具体的な詳細を出力する。これらの詳細は後で重要になる。
# Add stack outputs
CfnOutput(
self,
"userNAME",
value=user.user_name,
)
CfnOutput(
self,
"accessKeyId",
value=access_key.to_string(),
)
CfnOutput(
self,
"SiteBucketName",
value=myBucket.bucket_name,
)
CfnOutput(
self,
"DistributionId",
value=distribution.distribution_id,
)
CfnOutput(
self,
"distributionDomainName",
value=distribution.distribution_domain_name,
)
CfnOutput(
self,
"CertificateArn",
value=certificate.certificate_arn,
)
CloudFormationのデプロイメント
これらのリソースをアカウントにデプロイするためにAWS CDKを使用することもできるが、私はCloudFormationテンプレートを生成するためにAWS CDKを使用することを好む。
全てのPython要件とAWS CLIをインストールした後、以下のコマンドを使用してこのCDKコード用のCloudFormationテンプレートを合成することができます。
その後、CloudFormation.yamlファイルを使ってこのCloudFormationスタックをデプロイできる。
python3 -m venv .env
source .env/bin/activate
pip install -r requirements.txt
cdk synth --path-metadata false --version-reporting false > CloudFormation.yaml
配備後のステップ
Stackのデプロイに成功したら、いくつかの手動アクションを実行しよう。
- AWSによって作成されたSSL証明書は、DNS検証を使用してDNS名の所有権を確認します。DNSバリデーションが完了するまで、Stackはデプロイを完了しません。
- CloudFrontのDistributionDomainNameのCNAMEとしてDomainのDNS名を作成する必要があります。
- 作成されたIAM Userアカウントのaws_secret_access_keyはAWS Secrets Managerに保存されています。課金され続けたくなければ値を取得し、シークレットを削除してください。
- あなたの環境の.env環境変数ファイルに正しい値を入力してください。
DJANGO_SETTINGS_MODULE=myapp.settings.production
AWS_ACCESS_KEY_ID=<removed>
AWS_SECRET_ACCESS_KEY=<removed>
AWS_REGION=us-east-1
AWS_STORAGE_BUCKET_NAME=static.example.com
AWS_S3_CUSTOM_DOMAIN=static.example.com
DJANGO_SECRET_KEY=<removed>
完全なCloudFormationテンプレートは以下で入手できる:
https://github.com/Christophe-Gauge/cdk-s3-cloudfront/blob/main/CloudFormation.yaml
Tagged with:
AWS Wagtail