どのサイトでもHTTPS化することが求められる時代になっています。
今回はTerraformとAWSを使用して、ALBにHTTPSアクセスできるようにします!
また、HTTPでアクセスした場合にはHTTPSへリダイレクトするようにもします
現状
- Route53でALBをAliasレコードとして登録している
- ALBはNginxをインストールしているEC2へリクエストを振り分ける
- 1.と2.よりexample.comでアクセスすると、Nginxの画面が表示される
- ALBにSSL証明書は適用していないため、HTTPのみでアクセスできる
コマンドで現状を確認すると
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
// 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にアタッチされたまま削除しようとしてエラーになることを防げます。 |
1 2 3 4 5 6 7 8 9 10 |
# 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にしないとエラーになる |
なぜ、allow_overwrite を true にするのか?は↓をご覧ください!
また、aws_acm_certificate_validationを使用してDNS検証が完了するまで待ちます
aws_route53_record.cert を Set→Listへ変換してvalidation_record_fqdnsに指定します。
このパートの詳しいことは下記の記事をご参考ください
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
# ホストゾーン # 事前に作成されているものとします 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リスナーを作成
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。 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
# 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にして、
リダイレクト先のポート、プロトコル、スタータスコードを指定します
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
# 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ポートへの通信を許可
意外に忘れやすいSecurity Groupで許可すること!
かくいう私も忘れていて慌てて後から設定追加しました(笑)
設定内容はインバウンドルールにインターネットからの443ポートへの通信を許可すること。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
# 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"] } |
完成したコード
ハイライトされている部分が今回追加したコードになります
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 |
# 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でのアクセス
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
$ 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のリダイレクト
1 2 3 4 5 6 7 8 |
$ 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にリダイレクトされています
無事終わりましたー
参考
コメント