前書き
- 間違った情報もあると思うので、話半分で聞いてね☆
前提
- 独自ドメインを取得済み
ながーれ
- route53に静的ウェブサイトを公開したいレコードを登録
- acmにてssl証明書を取得
- S3に静的ファイルを配置
- Cloud Frontディストリビューションを作成
レッツTerraform
まずはゾーンの登録とレコードの追加です。
取得済みドメインは heigineer.jp
の体にします。
余談ですが、ゾーンはレコードを管理するためのコンテナです。
同じvpc内であればひとつのゾーン下で色々なサブドメインを切り、さまざまなトラフィックを制御することもできます。
route53.tf
// zoneの登録
resource "aws_route53_zone" "main" {
name = "heigineer.jp"
}
resource "aws_route53_record" "info" {
zone_id = aws_route53_zone.main.zone_id
name = "www.heigineer.jp"
type = "A"
alias {
name = aws_cloudfront_distribution.info.domain_name
zone_id = aws_cloudfront_distribution.info.hosted_zone_id
evaluate_target_health = false
}
}
ipv4でipとドメインを紐づけるAレコードとして登録します。
aliasについてはのちほど作成するCloud Frontディストリビューションを指定しています。
次にssl通信を実現するためにacmのサービスを利用し、ssl証明書を発行します。
acm.tf
resource "aws_acm_certificate" "info" {
domain_name = "www.heigineer.jp"
validation_method = "DNS"
provider = aws.virginia
}
resource "aws_route53_record" "info_validation" {
for_each = {
for dvo in aws_acm_certificate.info.domain_validation_options : dvo.domain_name => {
name = dvo.resource_record_name
record = dvo.resource_record_value
type = dvo.resource_record_type
}
}
allow_overwrite = true
name = each.value.name
records = [each.value.record]
ttl = 60
type = each.value.type
zone_id = aws_route53_zone.main.zone_id
}
resource "aws_acm_certificate_validation" "example" {
certificate_arn = aws_acm_certificate.info.arn
validation_record_fqdns = [for record in aws_route53_record.info_validation : record.fqdn]
}
ポイントとして、Cloud Frontでacmの証明書を利用する場合はバージニア北部のリージョンを指定する必要があるので、providerをそれに対応させます。
あらかじめ以下のようにproviderのaliasを使って構成を用意しておくと良いかと思います。
config.tf
provider "aws" {
region = "us-east-1"
alias = "virginia"
}
その他証明書周りの詳細な挙動については深く把握できていないので、割愛させてください。
基本的にterraformの公式からコピッたコードになってます。
上記の手順で、ゾーン、レコード、ssl証明書を手配するリソースができたので、続いて静的ページ用のコンテンツをS3に準備します。
s3.tf
resource "aws_s3_bucket" "info" {
bucket = "www.heigineer.jp"
acl = "public-read"
website {
index_document = "index.html"
}
}
resource "aws_s3_bucket" "cloudfront_logs" {
bucket = "logs"
acl = "private"
}
data "aws_iam_policy_document" "info" {
statement {
actions = [
"s3:GetObject"
]
resources = [
"${aws_s3_bucket.info.arn}/*"
]
condition {
test = "StringLike"
variable = "aws:referer"
values = ["https://www.heigineer.jp"]
}
principals {
identifiers = ["*"]
type = "*"
}
}
}
resource "aws_s3_bucket_policy" "info" {
bucket = aws_s3_bucket.info.id
policy = data.aws_iam_policy_document.info.json
}
resource "aws_s3_bucket_object" "object" {
bucket = aws_s3_bucket.info.id
key = "index.html"
source = "./index.html"
etag = filemd5("./index.html")
}
index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>My test page</title>
</head>
<body>
<p>This is my page</p>
</body>
</html>
いくつかポイントを補足していきます。
公開用ファイルのバケット名ですが、これはroute53に登録したレコードと一致している必要があるっぽいです。
websiteブロックで静的ウェブサイトオブジェクトとして取り扱う定義をし、
index_documentでルートドメインアクセス時に返すファイルを指定しています。
ポリシードキュメントなのですが、conditionブロックで、ポリシーのキーバリューとリクエストコンテキストの一致を検証し、Cloud Frontからのアクセスのみを許容するための記述を施しています。aws:refererでアクセス元のURLがvaluesの値と部分一致しているかを見ています。
またCloud Frontログ用バケットも作成しました。
そして次にCloud Frontディストリビューションの作成です。
cloudfront.tf
resource "aws_cloudfront_distribution" "info" {
// コンテンツの保存場所
origin {
// ウェブサイトオブジェクトとして取り扱うのでwebsite_endpoint
domain_name = aws_s3_bucket.info.website_endpoint
origin_id = aws_s3_bucket.info.id
// s3で指定したポリシーに合わせ、nameとvalueのheaderを指定
custom_header {
name = "referer"
value = "https://www.heigineer.jp"
}
// customオリジンとして取り扱うための定義
custom_origin_config {
http_port = "80"
https_port = "443"
origin_protocol_policy = "http-only"
origin_ssl_protocols = ["TLSv1", "TLSv1.1", "TLSv1.2"]
}
}
// Cloud Frontディストリビューションの有効化の可否
enabled = true
// ipv6の対応可否
is_ipv6_enabled = true
// ロギング設定
logging_config {
include_cookies = false
bucket = aws_s3_bucket.cloudfront_logs.bucket_domain_name
prefix = "info_pages/"
}
// Cloud Frontの代替ドメイン(CNAME)
aliases = ["www.heigineer.jp"]
default_cache_behavior {
allowed_methods = ["GET", "HEAD"]
cached_methods = ["GET", "HEAD"]
target_origin_id = aws_s3_bucket.info.id
// クエリ文字列やcookieのoriginへの転送設定
forwarded_values {
query_string = false
cookies {
forward = "none"
}
}
// originへのアクセスプロトコルの指定
viewer_protocol_policy = "redirect-to-https"
min_ttl = 0
default_ttl = 3600
max_ttl = 86400
}
// コンテンツ配信の地理別制限
restrictions {
geo_restriction {
restriction_type = "none"
}
}
// ssl構成設定
viewer_certificate {
cloudfront_default_certificate = false
acm_certificate_arn = aws_acm_certificate.info.arn
ssl_support_method = "sni-only"
minimum_protocol_version = "TLSv1.2_2021"
}
}
originブロックでコンテンツの保存場所を指定しています。
今回、静的ウェブサイトホスティングを使用するので、domain_nameはwebsite_endpointを指定します。
custom_headerブロックなのですが、バケットポリシーに指定したconditionの条件を満たすためにcustom_headerとして、refererとurlのname、valueを指定しています。もしかするとこれはこなくても良いのかもしれませんが、外すと403でアクセスできなかったので、定義しています。
このあたりちょっとよくわかりません。。。
静的ウェブサイトホスティングを利用する場合は、カスタムオリジンとして取り扱いわなければならないそうなので、custom_origin_configを定義しています。ちなみにカスタムオリジンとはS3などで配置する単純な静的ファイル等ではなく、一般的なWebサーバーとしてコンテンツを取り扱うオリジンのことみたいです。
aliasesはCloud Frontの代替ドメインを指定しています、これは作成したレコードとリンクさせておく必要があるみたいです。
cacheの挙動は以下の記事がわかりやすかった気がします。
viewer_protocol_policyはredirect-to-httpsを選んでおくのが無難な気がします。
viewer_certificateはあらかじめ取得したacmの証明書リソースを指定しています。
あとは色々とよしなに手を入れてもらってapplyしてもらえればアクセスできるんじゃないかなーと。
(できなかったらごめんなさい、誤りや不足等があったのだと。。。)
疑問に思ったこと
- Cloud Frontディストリビューションにて、custom_headerでrefererを指定しなければバケットポリシーで定義したIAM条件演算子をパスできなかったこと。(Cloud Frontからのアクセスのみ許容するという要件が満たせなかったこと)
- aws_route53_recordリソースを作った際のaliasでCloud Frontディストリビューションを指定しているのに、なぜCloud Front側のリソースで再度aliasesを定義してあげないとダメなのか?
- 静的ウェブサイトホスティングの場合オリジンをカスタムオリジンとして取り扱わなければならない性質上、OAIによるアクセス制限ができない。公式にはカスタムヘッダーの利用が推奨されていたので、
X-なんちゃら
のようなnameとランダム文字列のvalueでヘッダーを送り、バケットポリシー側で指定した文字列がリクエストとして送られてきているかというようなconditionで制限をかけようと思ったのですが、うまくいかず。。。refererを使用することにしました。カスタムオリジンにおいて、「Cloud Frontからのアクセスのみ許可」という要件を満たすのには何がベスプラなのだろうか。。。
おわりに
Cloud Front難しい。