AWS Cloud Development Kit (AWS CDK)を使いこなすため、勉強したことを残していきます。
今回はAmazon S3のバケットを作成して、静的サイトを公開するところまで実施。(S3の静的ウェブホスティングでの公開と、CloudFrontでの公開の2パターンで検討)
S3バケットを作成する部分については以前の記事に記載。
シンプルな静的サイトの作成
構成的に良くないですが、バケットポリシーで公開する形をまず取ります。
実際のpythonコードは以下の通り。CDKのディレクトリにサンプルのindex.htmlも入れて、アップロードするようにします。
以下のようなディレクトリの構成です。
MakeStaticSite/
├── app.py
├── make_static_site/
│ └── make_static_site_stack.py ← これを編集
├── assets/
│ └── index.html ← ここに格納
編集するStack部分のコードは以下の通り。
from aws_cdk import (
Stack,
RemovalPolicy,
CfnOutput,
aws_s3 as s3,
aws_s3_deployment as s3deploy,
)
from constructs import Construct
class MakeStaticSiteStack(Stack):
def __init__(self, scope: Construct, construct_id: str, **kwargs) -> None:
super().__init__(scope, construct_id, **kwargs)
# S3 Bucket(静的ホスティング用)
bucket = s3.Bucket(
self,
"StaticSiteBucket",
website_index_document="index.html",
public_read_access=True,
block_public_access=s3.BlockPublicAccess(
block_public_acls=True,
ignore_public_acls=True,
block_public_policy=False,
restrict_public_buckets=False,
),
removal_policy=RemovalPolicy.DESTROY,
auto_delete_objects=True,
)
# HTMLをS3にアップロード
s3deploy.BucketDeployment(
self,
"DeployWebsite",
sources=[
s3deploy.Source.asset("assets")
],
destination_bucket=bucket,
)
# URL出力
CfnOutput(
self,
"StaticSiteURL",
value=bucket.bucket_website_url,
)
静的サイトを公開する必要があるので、普通にBucketを作るよりPublic Access周りの設定が多くなっています。
public_read_accessでPubulicからの読み取りを許可し、block_public_accessで細かい設定をします。
(Policy関連をFalseにして、ACL関連はTrueでブロックしたまま公開する形)
上記の内容でcdk deployすれば、コマンドの出力に以下のようなURLが出てくるので、index.htmlの中身が見られるか確認します。
...省略
Outputs:
MakeStaticSiteStack.StaticSiteURL = {実際のURL}
...省略
無事に公開はできましたが、セキュリティ的によろしくありません。
S3は非公開にして、配信はCloudFrontに任せる方式で実装します。
CloudFrontでの配信
次はOAC (Origin Access Control)を設定して、CloudFrontから署名付きURLでPrivateのS3にアクセスする形を実装します。Stack部分の実装は以下の通り。
from aws_cdk import (
Stack,
RemovalPolicy,
CfnOutput,
aws_s3 as s3,
aws_s3_deployment as s3deploy,
aws_cloudfront as cloudfront,
aws_cloudfront_origins as origins,
)
from constructs import Construct
class MakeStaticSiteStack(Stack):
def __init__(self, scope: Construct, construct_id: str, **kwargs):
super().__init__(scope, construct_id, **kwargs)
# S3 Bucket
bucket = s3.Bucket(
self,
"PrivateBucket",
block_public_access=s3.BlockPublicAccess.BLOCK_ALL,
removal_policy=RemovalPolicy.DESTROY,
auto_delete_objects=True,
)
# CloudFront Distribution
distribution = cloudfront.Distribution(
self,
"Distribution",
default_behavior=cloudfront.BehaviorOptions(
origin=origins.S3BucketOrigin.with_origin_access_control(bucket), # OACが自動で使われる
viewer_protocol_policy=cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS,
response_headers_policy=cloudfront.ResponseHeadersPolicy.SECURITY_HEADERS, # AWS標準のセキュリティヘッダ付与
),
default_root_object="index.html",
# ここに必要ならerror_responsesの設定を入れる (403/404対応)
)
# HTMLをS3にアップロード
s3deploy.BucketDeployment(
self,
"DeployWebsite",
sources=[s3deploy.Source.asset("assets")],
destination_bucket=bucket,
distribution=distribution,
distribution_paths=["/*"], # キャッシュ削除
)
# URL出力
CfnOutput(
self,
"CloudFrontURL",
value=f"https://{distribution.domain_name}",
)
こちらもdeploy時にURLが出力されるので、CloudFront経由の配信ができているかを確認。
origins.S3BucketOrigin.with_origin_access_controlによって、OACや対応するBucketポリシーが作成されるので楽でした。
CloudFrontのセキュリティヘッダ付与について
CloudFrontにはマネージドのレスポンスヘッダポリシーがあります。
カスタムでヘッダを設定していれば、その設定を使いますが、ない場合はポリシーに従ったヘッダを付与してくれるので、とりあえず追加しておくのが良さそうです。
403/404対応について
今回の実装ではS3の存在しないパスを指定すると403エラーが発生したりします。({CloudFrontのURL}/aboutとかにするとエラーになる)
必要であれば、以下のような内容を追加するとよいかもしれません。
# CloudFront Distribution
distribution = cloudfront.Distribution(
self,
"Distribution",
default_behavior=cloudfront.BehaviorOptions(
origin=origins.S3BucketOrigin.with_origin_access_control(bucket), # OACが自動で使われる
viewer_protocol_policy=cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS,
response_headers_policy=cloudfront.ResponseHeadersPolicy.SECURITY_HEADERS, # AWS標準のセキュリティヘッダ付与
),
default_root_object="index.html",
# ここに必要ならerror_responsesの設定を入れる (403/404対応)
error_responses=[
cloudfront.ErrorResponse(
http_status=403,
response_http_status=200,
response_page_path="/index.html",
),
cloudfront.ErrorResponse(
http_status=404,
response_http_status=200,
response_page_path="/index.html",
),
],
)
)
CloudFrontのInvalidationについて
上記Stack内のdistribution_paths=["/*"]は、指定したパスのキャッシュを捨てる(無効化する)設定のようです。
("/index.html"とすれば、トップページのみを無効化)
通常、CloudFrontはキャッシュを優先して配信しますが、キャッシュを削除することで最新ファイルを取りに行きます。
"/*"だと全てのファイルのキャッシュを削除しますが、本来は"/index.html"など、更新頻度の高いものだけを無効化するなどの調整が望ましいようです。
まとめ
CDKでの静的サイト配信を行いました。
- S3だけでの配信も可能だが、非推奨
- CloudFront + OACでの配信が推奨
-
origins.S3BucketOrigin.with_origin_access_controlがOACやポリシー設定をしてくれる - AWSマネージドのセキュリティポリシーを使えば、ヘッダを移動で追加してくれる
- 必要なら403/404の対応として、
error_responsesの設定を追加する -
distribution_pathsで更新頻度などを元にキャッシュ無効化の設定をする