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を起動させバケットポリシーを書き換えるようにしました。

AWS の IP アドレス範囲の通知

Lambda用スクリプトファイル

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 になりました。よかった。

20191026_s3bucketpolicy_01

20191026_s3bucketpolicy_02

© てっくらのーと/mkr-note 2024