てっくらのーとは、触れた技術のメモと日常の記録が少し合わさった個人のサイトです。
CloudFront で使用するIPアドレスレンジを動的にポリシーへ設定する
AWS S3 の静的ホスティングを CloudFront でCDN配信した時のS3バケットのアクセス性について。
経緯
S3 静的ホスティングをCloudFrontでCDN配信したとき、CloudFrontの生成ドメイン(xxxxxx.cloudfront.net
)でユーザにアクセスいただきたいところを、オリジンである静的ホスティングエンドポイント({bucketname}.s3-website-{region}.amazonaws.com
)でアクセスされてしまう可能性があり、これを防ぎたいねという話です。あと、ホスティングエンドポイントにアクセスできるのは、CloudFrontだけとしたいです。
前提
そもそも、この構成を取る場合はオリジンの設定方法が複数あるようです。
- バケットそのもの
- ホスティングエンドポイント
今回は、S3バケットのサブディレクトリに index.html
がありURLでは解決したくなかった(URLにindex.html
を付与したくなかった)ことから、オリジンにホスティングエンドポイントを指定しました。
方法
いくつか方法はあるようなのですが、パっと見で実装できそうだった SNS + Lambda + S3 バケットポリシー で実装してみました。バケットポリシーでCloudFrontが使用するIPアドレスレンジのみを許可する方針です。
ip-ranges
AWS さんはサービスで使用するIPアドレスレンジが公開されているのでここのIPアドレスレンジを拾って、ポリシーに組み込みます。
このアドレスレンジが変更することもあります。ただ、そのときはSNSによる受信が可能なので受信できるようにして、受信をトリガーとしてLambdaを起動させバケットポリシーを書き換えるようにしました。
Lambda用スクリプトファイル
- lambda_function.py
import boto3
import json
import urllib.request
def lambda_handler(event, context):
addrs = []
req = urllib.request.Request("https://ip-ranges.amazonaws.com/ip-ranges.json")
with urllib.request.urlopen(req) as res:
body = json.load(res)
for i, prefix in enumerate(body["prefixes"]):
if (prefix["service"] == "CLOUDFRONT"):
addrs.append(prefix["ip_prefix"])
for i, prefix in enumerate(body["ipv6_prefixes"]):
if (prefix["service"] == "CLOUDFRONT"):
addrs.append(prefix["ipv6_prefix"])
client = boto3.client("s3")
bucket_policy = {
'Version': '2012-10-17',
'Statement': [{
'Sid': 'AllowS3GetObjectAccess',
'Effect': 'Allow',
'Principal': '*',
'Action': ['s3:GetObject'],
'Resource': "arn:aws:s3:::(バケット名)/*"
},
{
'Sid': 'DenyS3Access',
'Effect': 'Deny',
'Principal': '*',
'Action': ['s3:*'],
'Resource': "arn:aws:s3:::(バケット名)/*",
'Condition': {
'NotIpAddress': {
'aws:SourceIp': addrs
}
}
}]
}
bucket_policy = json.dumps(bucket_policy)
client.put_bucket_policy(
Bucket="(バケット名)",
Policy=bucket_policy
)
lambdaのロールは S3 へのアクセスをフル許可していますが、実際にポリシーを適用したいバケットのみの許可でも問題ないと思います。
ポリシーの内容ですがAllowS3GetObjectAccess
が無くてもOKかと思っていたのだけどダメだった。Lambdaがバケット(とオブジェクト)の情報を得るために必要な許可なのか。
結果
403 になりました。よかった。