個人のAWS環境でTerraformを使ってHTTPS化したサブドメインを定義した。
普段なかなかしないことで忘れてしまうので手順をまとめおく。
TL;DR
- Terraformを使ってAWS上でHTTPS化したサブドメインを構成したい
- ルートドメインのホストゾーンをTerrformで作っても登録済みドメインのネームサーバの設定は手動になるので注意する
- ワイルドカード付きの証明書をTerffaformで生成するときは少しテクニックが必要になる
- ハマりどころを解決できれば少々の定義で構築できた
なお、本記事で利用しているTerraformとAWS Providerのバージョンは以下となる。
terraform {
required_version = ">= 0.13.4"
}
provider "aws" {
version = "~> 3.6.0"
}
HTTPS化したサブドメインを作成したい
この記事の前提とやりたいことは次の通り。
- 前提条件
- example.com というドメインをRoute53の登録済みドメインとして登録済み
- example.comまたは*.example.comをHTTPS化するSSL証明書を発行する
- https://subdomain.example.com というようなHTTPS化されたサブドメインを定義する。
本記事で利用するAWSのサービスはRoute53とAWS Certificate Manager(ACM)だ。
Route53はご存知の通りAWS上でドメインを使ってルーティングを構成するDNSサービスだ。
ACMはSSL/TLS証明書を管理するサービスで、今回はHTTPS通信に利用する証明書を自動管理させる。
Route53でホストゾーンを定義する
まずRoute53でsubdomain.example.com(サブドメイン)をホストゾーンで定義する。
なお、Route53でexample.comを登録済みドメインにしている場合、すでにホストゾーンが作成されているはずである。
Terraform上では、既成のexample.com(ルートドメイン)に対応するホストゾーンをデータソースで参照する。
data aws_route53_zone example_com {
name = "example.com"
}
resource aws_route53_zone subdomain_example_com {
name = "subdomain.example.com"
}
ルートドメインをTerraformで定義しようとしないこと。
ルートドメインのホストゾーンはTerraformで作成しないこと
AWS Provider 3.6.0系でもRoute53の「登録済みドメイン」をTerraformで管理することはできない。
Terraformでルートドメインを定義した場合、定義したルートドメインに割り当てられるNSコード(ネームサーバ)のIPと、登録済みドメインのネームサーバが一致しなくなるので証明書のDNS認証などができなくなる。
- Route 53 に登録されているドメインのホストゾーンの置き換え
ホストゾーンを作成する場合、Route 53 はホストゾーンに一連の 4 つのネームサーバーを割り当てます。ホストゾーンを削除して新しいゾーンを作成すると、Route 53 は別の一連の 4 つのネームサーバーを割り当てます。通常、新しいホストゾーンのネームサーバーは、以前のホストゾーンのネームサーバーと一致しません。新しいホストゾーンのネームサーバーを使用するようにドメイン設定を更新しないと、ドメインはインターネット上で利用できなくなります。
サブドメインとルートドメインのホストゾーンを関連付ける
次に、作成するサブドメインをルートドメインに関連付ける。
具体的には、ルートドメイン (example.com) のホストゾーンにサブドメインのホストゾーン割り当てられる4つのネームサーバーをNSレコードとして登録する。
- サブドメインのトラフィックのルーティング
TerraforrmでNSレコードを作成するコードは次の通り。
resource aws_route53_record ns_record_for_subdomain {
name = aws_route53_zone.example_com.name
zone_id = data.aws_route53_zone.example_com.id
records = [
aws_route53_zone.subdomain_example_com.name_servers[0],
aws_route53_zone.subdomain_example_com.name_servers[1],
aws_route53_zone.subdomain_example_com.name_servers[2],
aws_route53_zone.subdomain_example_com.name_servers[3]
]
ttl = 300
type = "NS"
}
これでリクエストの流れはできた。
ACMでHTTPS化用のSSL証明書を作成する
次にHTTPS化の準備をする。冒頭で述べた通りACMを使って証明書を発行する。
AWSでHTTPS用の証明書を発行する場合、DNS認証を使っておけば証明書の自動更新が実現できる。
当然今回もこれを利用する設定を行う。
- DNSを使用したドメイン所有権の検証
- 新しいドメインの DNS ルーティングの設定
ワイルドカード付きの証明書を発行する
ルートドメイン、サブドメインすべてをHTTPS化したいため、ワイルドカードを含んだSSL証明書を発行する必要がある。
まず、SSL証明書はの定義は次の通り。subject_alternative_names
を使うことでサブドメインもSSL証明書の対象に含めることができる。
resource aws_acm_certificate example_com {
domain_name = data.aws_route53_zone.example_com.name
subject_alternative_names = [format("*.%s", data.aws_route53_zone.example_com.name)]
validation_method = "DNS"
lifecycle {
create_before_destroy = true
}
}
format
関数はTerraformの標準関数のひとつで、ざっくりいうとGoのfmt.Printf
のように文字列を生成できる。
証明書の検証につかうレコードはこのように定義する。
resource aws_route53_record certificate {
for_each = {
for dvo in aws_acm_certificate.example_com.domain_validation_options : dvo.domain_name => {
name = dvo.resource_record_name
record = dvo.resource_record_value
type = dvo.resource_record_type
}
}
name = each.value.name
records = [each.value.record]
ttl = 60
type = each.value.type
zone_id = data.aws_route53_zone.example_com.id
allow_overwrite = true
}
最後にapply時SSL証明書の検証が完了するまで待機する設定を書いておく。
resource aws_acm_certificate_validation cert {
certificate_arn = aws_acm_certificate.example_com.arn
validation_record_fqdns = [for record in aws_route53_record.certificate : record.fqdn]
}
terraform apply中に「Tried to create resource record set….but it already exists」が出て失敗する
ワイルドカードを使った証明書を作成しようとすると、ワイルドカードとルートドメインの検証用レコードが同じ内容になるためエラーが発生する。
これを回避するにはallow_overwrite
をtrue
にしておくのを忘れないこと。
本記事に記載した先ほどのaws_route53_record certificate
はAWS Provider v3の定義方法だ。
AWS Provider v2などで同様の現象を回避したい場合は次のブログが参考になる。
- SANsにワイルドカードが入ったACMのDNS認証なSSL証明書をTerraformで作るときのハマりどころ
DNS認証が終わらない
正しく設定できているように見えるのにDNS認証が通らない場合はネームサーバの設定がおかしく、検証用リクエストがさばけていない可能性がある。
前述したとおり、ルートドメインをTerraformで新しく作った場合などに発生する。
ルートドメインのホストゾーンのNSレコードに記載されているネームサーバのIPを登録済みドメインのネームサーバのIPとして登録すること。
- ドメインのネームサーバーおよびグルーレコードの追加あるいは変更
ALBへ通信を流す
ここまでできればあとはサブドメインのホストゾーンからALBへ通信を流し込むAレコードを作成すればよい。
resource "aws_route53_record" "subdomain_example_com" {
zone_id = aws_route53_zone.subdomain_example_com.zone_id
name = aws_route53_zone.subdomain_example_com.name
type = "A"
# 実際に通信をさばくALBの情報
alias {
name = aws_lb.xxx.dns_name
zone_id = aws_lb.xxx.zone_id
evaluate_target_health = true
}
}
これで、 https://subdomain.example.com
の通信がALBへ流し込まれるようになる。
AレコードはAWS独自拡張DNSレコードタイプのエイリアスレコードのことだ(よくわかっていないが速いらしい)。
- サポートされる DNS レコードタイプ
- エイリアスレコードと非エイリアスレコードの選択
TerraformのApplyが終わればローカルからhttps://subdomain.example.com
への通信が可能になっているはずだ(当然ALB以降に何かしらレスポンスする定義がないとダメだが)。
最後に
私はアプリケーションエンジニアなので、普段ALBらへんまではTerraformで書いたり、覗いたことがあった。
今回いつもSREの方々に任せているところを自分ではじめてRoute53やACMを自分で設定してみてネットワークについて少し理解が深まった。
ただ、アプリケーションを作ってObservabilityの勉強をしたいのが目的なので、ゴール(スタート?)はまだまだ遠い…
最後に今回使ったサンプルコードをまとめておく。
terraform {
required_version = ">= 0.13.4"
}
provider "aws" {
version = "~> 3.6.0"
}
data aws_route53_zone example_com {
name = "example.com"
}
resource aws_route53_zone subdomain_example_com {
name = "subdomain.example.com"
}
resource aws_route53_record ns_record_for_subdomain {
name = aws_route53_zone.example_com.name
zone_id = data.aws_route53_zone.example_com.id
records = [
aws_route53_zone.subdomain_example_com.name_servers[0],
aws_route53_zone.subdomain_example_com.name_servers[1],
aws_route53_zone.subdomain_example_com.name_servers[2],
aws_route53_zone.subdomain_example_com.name_servers[3]
]
ttl = 300
type = "NS"
}
resource aws_acm_certificate example_com {
domain_name = data.aws_route53_zone.example_com.name
subject_alternative_names = [format("*.%s", data.aws_route53_zone.example_com.name)]
validation_method = "DNS"
lifecycle {
create_before_destroy = true
}
}
resource aws_route53_record certificate {
for_each = {
for dvo in aws_acm_certificate.example_com.domain_validation_options : dvo.domain_name => {
name = dvo.resource_record_name
record = dvo.resource_record_value
type = dvo.resource_record_type
}
}
name = each.value.name
records = [each.value.record]
ttl = 60
type = each.value.type
zone_id = data.aws_route53_zone.example_com.id
allow_overwrite = true
}
resource aws_acm_certificate_validation cert {
certificate_arn = aws_acm_certificate.example_com.arn
validation_record_fqdns = [for record in aws_route53_record.certificate : record.fqdn]
}
resource "aws_route53_record" "subdomain_example_com" {
zone_id = aws_route53_zone.subdomain_example_com.zone_id
name = aws_route53_zone.subdomain_example_com.name
type = "A"
# 実際に通信をさばくALBの情報
alias {
name = aws_lb.xxx.dns_name
zone_id = aws_lb.xxx.zone_id
evaluate_target_health = true
}
}
参考
- Amazon Route 53
- AWS Certificate Manager
- route53_zone
- route53_record
- acm_certificate
- format Function
- acm_certificate_validation
- SANsにワイルドカードが入ったACMのDNS認証なSSL証明書をTerraformで作るときのハマりどころ
- Route 53 に登録されているドメインのホストゾーンの置き換え
- サブドメインのトラフィックのルーティング
- ドメインのネームサーバーおよびグルーレコードの追加あるいは変更
- サポートされる DNS レコードタイプ
- エイリアスレコードと非エイリアスレコードの選択
- 新しいドメインの DNS ルーティングの設定
- DNSを使用したドメイン所有権の検証