CloudFront + S3で構築した静的なウェブサイト(オリジンにS3を指定してるやつ)での問題点として、
オリジンのバケットがパブリック公開されているので、
バケットのURLを叩くとオリジンに直接アクセスできてしまう
というのが巷で言われてたやつ。
その解決法として、ドキュメントにはCloudFrontからカスタムヘッダーを設定し、 そのヘッダー値が付与されている場合のみアクセスを許可するようにバケットポリシーを書くという方法が紹介されています。 (アクセスが Referer ヘッダーで制限されたオリジンとして、ウェブサイトのエンドポイントを使用するの部分)
上記手順をTerraformで設定していきます。
また、アクセスを制限するにあたって、色々と注意すべき点があったので併せて紹介します。
CloudFrontからカスタムヘッダーを転送する。
まずは、CloudFrontからカスタムヘッダーを転送する設定を追加します。
originブロック内に以下の「custom_header」ブロックを追加。
custom_header { | |
name = "Referer" | |
value = "任意の値(乱数がおすすめ)" # S3側でマッチさせる値 | |
} |
ここのnameはRefererにします。任意の値ではダメみたいです。
AWS グローバル条件コンテキストキーに記載がありますが、aws:Refererでヘッダーにアクセスしにいくとのこと。
オリジンのS3のバケットポリシーを書く。
CloudFrontからのアクセスのみを通すためにバケットポリシーを以下の通り記載します。
- policy.tf
resource "aws_s3_bucket_policy" "policy" { | |
bucket = "バケットのID" | |
policy = "${data.aws_iam_policy_document.pd.json}" | |
} | |
data "aws_iam_policy_document" "pd" { | |
# 許可 | |
statement { | |
sid = "1" | |
effect = "Allow" | |
principals { | |
type = "*" | |
identifiers = ["*"] | |
} | |
actions = [ | |
"s3:GetObject", | |
] | |
resources = [ | |
"バケットのARN/*", # アクセスさせたい部分 | |
] | |
# 条件1(カスタムヘッダーの値が付与されていれば許可) | |
condition { | |
test = "StringEquals" | |
variable = "aws:Referer" | |
values = [ | |
"CloudFrontのカスタムヘッダーで指定した「value」の値", | |
] | |
} | |
} | |
# 拒否 | |
statement { | |
sid = "2" | |
effect = "Deny" | |
principals { | |
type = "*" | |
identifiers = ["*"] | |
} | |
actions = [ | |
"s3:*", # s3の全アクションを拒否 | |
] | |
resources = [ | |
"バケットのARN/*", # 上記で許可したリソースと基本は同じ | |
] | |
# 条件2(カスタムヘッダーの値が異なったら拒否) | |
condition { | |
test = "StringNotEquals" | |
variable = "aws:Referer" | |
values = [ | |
"CloudFrontのカスタムヘッダーで指定した「value」の値", | |
] | |
} | |
# 条件3(使用しているAWSアカウント以外からのアクセスは拒否) | |
condition { | |
test = "StringNotEquals" | |
variable = "aws:PrincipalAccount" | |
values = [ | |
"${data.aws_caller_identity.self.account_id}", | |
] | |
} | |
} | |
} | |
data "aws_caller_identity" "self" {} |
許可と拒否を両方書くことに注意してください。
また、拒否に書いた条件2と3はAND条件になります。
なので、日本語にすると「カスタムヘッダーの値が異なる且つ自身のAWSアカウントではない場合に拒否」になります。
ちなみにstatementブロックで分けた条件はORになります。
自身のAWSアカウント条件を追加しなくても問題ないですが、 例えばWebサイトの更新者用のIAMアカウントがあったとして、そのユーザーもDenyされてしまうため、バケットを運用するユーザーを作っている場合、条件に加えても良いと思います。
まとめ
上記、CloudFrontとS3に付け足していただければ、オリジンの直アクセスを制限できるかと思います。 1点今回の制限方法がカスタムヘッダーに値があるかどうかの判定になるので、 厳密にはヘッダーにその値を含めればアクセスはできてしまいます。
例えば、以下の通りにcurlするとアクセス可能です。
$ curl S3のオリジンURL -H "Referer:設定した値"
なので、できる限りカスタムヘッダーに含める値は乱数などわからないものにしましょう!