【Terraform】ACMを使用してALBにHTTPSでアクセスする
どのサイトでも HTTPS 化することが求められる時代になっています。
今回はTerraformとAWSを使用して、ALB に HTTPS アクセスできるようにします!
また、HTTP でアクセスした場合には HTTPS へリダイレクトするようにもします
現状
- Route53 で ALB を Alias レコードとして登録している
- ALB は Nginx をインストールしている EC2 へリクエストを振り分ける
- 1.と 2.より example.com でアクセスすると、Nginx の画面が表示される
- ALB に SSL 証明書は適用していないため、HTTP のみでアクセスできる
コマンドで現状を確認すると
// HTTPリクエスト
// レスポンスヘッダ
$ curl -I http://example.com
HTTP/1.1 200 OK
Date: Wed, 01 Nov 2023 13:37:21 GMT
Content-Type: text/html
Content-Length: 615
Connection: keep-alive
Server: nginx/1.24.0
Last-Modified: Fri, 13 Oct 2023 13:33:26 GMT
ETag: "65294726-267"
Accept-Ranges: bytes
// レスポンスボディ
$ curl http://example.com
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
----略------------------------------
// HTTPSリクエスト
$ curl https://example.com
^C
// 応答ないので終了する
$ telnet example.com 443
Trying ×××.×××.×××.×××...
Trying ×××.×××.×××.×××...
telnet: Unable to connect to remote host: Connection timed out
ゴール
ACM で SSL 証明書を作成し ALB に適用することで
- HTTPS でアクセス
- HTTP⇒HTTPS のリダイレクト
をできるようにします
Terraform で構築していく
ACM で SSL 証明書を作成
aws_acm_certificate で SSL 証明書を作成します
プロパティ | 説明 |
---|---|
domain_name | 証明書のドメイン名。 |
validation_method | 検証方法。今回は DNS 認証としています。 後ほど Route53 に検証用レコードを作成します。 |
subject_alternative_names | SANs の設定。今回はワイルドカードを設定しておきます。 |
create_before_destroy | 旧リソースを削除する前に新リソースを作成するか。 true にしておくと、証明書を作り変えるときに 旧証明書が ALB にアタッチされたまま削除しようとしてエラーになることを防げます。 |
# ACMでSSL証明書を作成
resource "aws_acm_certificate" "cert" {
domain_name = "example.com"
validation_method = "DNS"
subject_alternative_names = ["*.example.com"]
lifecycle {
create_before_destroy = true
}
}
SSL 証明書を Route53 で検証
このパートはハマりポイントが多いので注意してください!
aws_route53_record でDNS 検証用レコードを Route53 に作成します
aws_acm_certificate.cert.domain_validation_options は Set 型のため、
for 文で map 型へ変換し、for_each で 1 つずつ取り出しながらレコードを作成しています。
プロパティ | 説明 |
---|---|
name | レコード名。map 型の name プロパティに入っている |
type | レコードタイプ。type プロパティに入っている |
records | レコード値。List 型。record プロパティに入っている |
zone_id | ホストゾーンの ID。 |
ttl | TTL。 |
allow_overwrite | レコードの上書きを許すか。 証明書にワイルドカードを含めた場合には true にしないとエラーになる |
また、aws_acm_certificate_validation を使用して DNS 検証が完了するまで待ちます
aws_route53_record.cert を Set→List へ変換して validation_record_fqdns に指定します
# ホストゾーン
# 事前に作成されているものとします
resource "aws_route53_zone" "hostzone" {
name = "example.com"
}
# SSL証明書のDNS検証用レコード
resource "aws_route53_record" "cert" {
for_each = {
for dvo in aws_acm_certificate.cert.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
type = each.value.type
records = [each.value.record]
zone_id = aws_route53_zone.hostzone.zone_id
ttl = 60
allow_overwrite = true
}
# SSL証明書の検証が終わるまで待つ
resource "aws_acm_certificate_validation" "cert" {
certificate_arn = aws_acm_certificate.cert.arn
validation_record_fqdns = flatten([values(aws_route53_record.cert)[*].fqdn])
}
ALB の HTTPS リスナーを作成
ALB の 443 ポートへ来たアクセスをターゲットグループの 80 ポートへながします
プロパティ | 説明 |
---|---|
load_balancer_arn | ALB の ARN。 |
port | リクエストを受け取るリスナーのポート。443 とします |
protocol | リスナーが受け取るプロトコル。HTTPS とします |
ssl_policy | セキュリティポリシー。 AWSによると ELBSecurityPolicy-TLS13-1-0-2021-06 が推奨。 |
certificate_arn | SSL 証明書の ARN。 aws_acm_certificate で作成したものを指定。 |
default_action | リクエストが来た時の ALB の挙動を設定。 |
type | デフォルトアクションのタイプ。 ターゲットグループにリクエストを流すのでforward を指定 |
target_group_arn | リクエストを流すターゲットグループの ARN。 |
# ALB
# すでに作成済みとします
resource "aws_lb" "alb" {
name = "alb"
internal = false
load_balancer_type = "application"
security_groups = [aws_security_group.alb_sg.id]
subnets = [
aws_subnet.subnet_1a.public_subnet_id,
aws_subnet.subnet_1c.public_subnet_id
]
# 削除保護
enable_deletion_protection = true
}
# HTTPSリスナー
# ALBは443ポートで受けてEC2の80ポートへリクエストをながす
resource "aws_lb_listener" "alb_https" {
load_balancer_arn = aws_lb.alb.arn
port = "443"
protocol = "HTTPS"
ssl_policy = "ELBSecurityPolicy-TLS13-1-2-2021-06"
certificate_arn = aws_acm_certificate.cert.arn
default_action {
type = "forward"
target_group_arn = aws_lb_target_group.tg.arn
}
}
# ターゲットグループ
# 80ポートにHTTPプロトコルでリクエストをながす
# すでに作成済みとします
resource "aws_lb_target_group" "tg" {
name = "alb-tg"
port = 80
protocol = "HTTP"
vpc_id = aws_vpc.main.id
}
HTTP リスナーのアクションをリダイレクトへ変更
HTTP リスナーを修正して、HTTPS へリダイレクトするようにします
具体的にはdefault_action の type を redirectにして、
リダイレクト先のポート、プロトコル、スタータスコードを指定します
# HTTPリスナー
# HTTPSへリダイレクトする
resource "aws_lb_listener" "alb_http" {
load_balancer_arn = aws_lb.alb.arn
port = "80"
protocol = "HTTP"
default_action {
type = "redirect"
redirect {
port = "443"
protocol = "HTTPS"
status_code = "HTTP_301"
}
}
}
(必要に応じて)ALB の 443 ポートへの通信を許可
設定内容はインバウンドルールにインターネットからの 443 ポートへの通信を許可すること。
# ALBのセキュリティグループ
# すでに作成済みとします
resource "aws_security_group" "alb_sg" {
name = "alb_sg"
vpc_id = aws_vpc.main.id
}
# インターネットからの443ポートへの通信を許可
resource "aws_security_group_rule" "alb_sg_ingress_https" {
security_group_id = aws_security_group.alb_sg.id
type = "ingress"
from_port = "443"
to_port = "443"
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
完成したコード
# ACMでSSL証明書を作成
resource "aws_acm_certificate" "cert" {
domain_name = "example.com"
validation_method = "DNS"
subject_alternative_names = ["*.example.com"]
lifecycle {
create_before_destroy = true
}
}
# ホストゾーン
# 事前に作成されているものとします
resource "aws_route53_zone" "hostzone" {
name = "example.com"
}
# SSL証明書のDNS検証用レコード
resource "aws_route53_record" "cert" {
for_each = {
for dvo in aws_acm_certificate.cert.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
type = each.value.type
records = [each.value.record]
zone_id = aws_route53_zone.hostzone.zone_id
ttl = 60
allow_overwrite = true
}
# SSL証明書の検証
resource "aws_acm_certificate_validation" "cert" {
certificate_arn = aws_acm_certificate.cert.arn
validation_record_fqdns = flatten([values(aws_route53_record.cert)[*].fqdn])
}
# ALB
# すでに作成済みとします
resource "aws_lb" "alb" {
name = "alb"
internal = false
load_balancer_type = "application"
security_groups = [aws_security_group.alb_sg.id]
subnets = [
aws_subnet.subnet_1a.public_subnet_id,
aws_subnet.subnet_1c.public_subnet_id
]
# 削除保護
enable_deletion_protection = true
}
# HTTPリスナー
# HTTPSへリダイレクトする
resource "aws_lb_listener" "alb_http" {
load_balancer_arn = aws_lb.alb.arn
port = "80"
protocol = "HTTP"
default_action {
type = "redirect"
redirect {
port = "443"
protocol = "HTTPS"
status_code = "HTTP_301"
}
}
}
# HTTPSリスナー
# ALBは443ポートで受けてEC2の80ポートへリクエストをながす
resource "aws_lb_listener" "alb_https" {
load_balancer_arn = aws_lb.alb.arn
port = "443"
protocol = "HTTPS"
ssl_policy = "ELBSecurityPolicy-TLS13-1-2-2021-06"
certificate_arn = aws_acm_certificate.cert.arn
default_action {
type = "forward"
target_group_arn = aws_lb_target_group.tg.arn
}
}
# ターゲットグループ
# 80ポートにHTTPプロトコルでリクエストをながす
# すでに作成済みとします
resource "aws_lb_target_group" "tg" {
name = "alb-tg"
port = 80
protocol = "HTTP"
vpc_id = aws_vpc.main.id
}
# ALBのセキュリティグループ
# すでに作成済みとします
resource "aws_security_group" "alb_sg" {
name = "alb_sg"
vpc_id = aws_vpc.main.id
}
# インターネットからの443ポートへの通信を許可
resource "aws_security_group_rule" "alb_sg_ingress_https" {
security_group_id = aws_security_group.alb_sg.id
type = "ingress"
from_port = "443"
to_port = "443"
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
動作確認
terraform apply を実行して正常終了したら、動作確認をしていきます
HTTPS でのアクセス
$ curl -I https://example.com
HTTP/2 200
date: Wed, 01 Nov 2023 15:32:10 GMT
content-type: text/html
content-length: 615
server: nginx/1.24.0
last-modified: Fri, 13 Oct 2023 13:33:26 GMT
etag: "65294726-267"
accept-ranges: bytes
$ curl https://example.com
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
html { color-scheme: light dark; }
body { width: 35em; margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif; }
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>
<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>
<p><em>Thank you for using nginx.</em></p>
</body>
</html>
HTTPS に対して 200 と Nginx の画面も返ってきています
やったー
HTTP⇒HTTPS のリダイレクト
$ curl -I http://example.com
HTTP/1.1 301 Moved Permanently
Server: awselb/2.0
Date: Wed, 01 Nov 2023 15:32:24 GMT
Content-Type: text/html
Content-Length: 134
Connection: keep-alive
Location: https://example.com:443/
HTTP に curl を投げると、301 でhttps://example.comにリダイレクトされています
無事終わりましたー
参考
Discussion