ALBでSSLを終端し、EC2間をHTTPSで暗号化してみる
Posted On 2025-01-07
ACM証明書を利用してALBでSSLを終端し、自己署名証明書を使ってALBからEC2までの通信もHTTPS化する手順をTerraformで解説。Route 53の独自ドメイン設定を組み合わせることで、ブラウザからのアクセスからEC2間まで全て暗号化された安全な構成を実現する。
目次
はじめに
- SCSを勉強していてよく出てきたSSLのTermination
- ALBを一旦SSLの末端として、そこからEC2までの通信をHTTPではなくHTTPSとしてサードパーティのSSL証明書をEC2に接続するというもの
- SSL TerminationをすることでEC2までの接続がオールHTTPSになり、セキュアになるよというもの
SSL Termination とは
SSL Terminationとは、クライアントとサーバー間のSSL/TLS暗号化通信を特定のポイント(この場合はALB)で終端し、その後の通信を平文または再暗号化された状態で内部ネットワークに転送するプロセス。
やりかた
- ALBをHTTPSにするためにACMで発行したSSL証明書が必要。これにはカスタムドメインが必要
- Route 53で別途ドメインを発行し(
.click
ドメインが3ドルなので、テスト用にはいい)、パブリックホストゾーンを作っておく - ACMから証明書を発行し、Route 53にCNAMEレコードを登録しておく
- Route 53で別途ドメインを発行し(
- EC2側は外部認証したサードパーティの証明書だが、今回は面倒なので自己署名証明書(オレオレ証明書)で代用する
その他前提
- VPCとパブリックサブネット、プライベートサブネットは作成済みとする
- プライベートサブネットには、NAT(ゲートウェイ)、実際はfck-natを使ったNATインスタンスを用意しておく
- Route 53のドメインと、ACMの証明書は作成済みとする
Terraformのコード
コード構成は以下の通り
.
├── main.tf
├── alb.tf
└── ec2.tf
main.tf
以下の変数を定義する
variable "public_subnet_ids" {
description = "ALBを配置するパブリックサブネットのID"
type = list(string)
}
variable "private_subnet_ids" {
description = "ALBを配置するプライベートサブネットのID"
type = list(string)
}
variable "vpc_id" {
description = "VPCのID"
type = string
}
variable "certificate_arn" {
description = "ALBで使用するACM証明書のARN"
type = string
}
variable "domain_name" {
description = "証明書に紐づけるドメイン名。例: example.com"
type = string
}
variable "hosted_zone_id" {
description = "Route 53ホストゾーンのID"
type = string
}
ドメイン名はwww.example.com
だったら、example.com
とする。これはEC2内の自己署名証明書の作成で使用する(ここはユーザースクリプトで実装)
ec2.tf
- 2つのプライベートサブネットにまたがるEC2インスタンス(Webサーバー)を作成
- OSはAmazon Linux2023を使用
- セキュリティグループは、ALBのセキュリティグループからのインバウンド443のみ許可。よって、ALBからはHTTPS接続のみ許可され、HTTP接続(80)は弾かれる
- EC2のユーザースクリプト内で、nginx(Webサーバー)と、OpenSSLをインストール。OpenSSLは自己署名証明書の発行に作成。
- ちゃんとした開発では自己署名証明書ではなく、外部認証された証明書を使うこと
resource "aws_security_group" "ec2_sg" {
name = "ec2_sg"
description = "A security group for EC2 instances"
vpc_id = var.vpc_id
ingress {
description = "Allow HTTPS from ALB"
from_port = 443
to_port = 443
protocol = "tcp"
security_groups = [aws_security_group.alb_sg.id]
}
egress {
description = "Allow all outbound traffic"
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
}
# Amazon Linux 2023
data "aws_ami" "amazon_linux" {
most_recent = true
owners = ["137112412989"] # AmazonのAMI所有者ID
filter {
name = "name"
# Amazon Linux 2023 AMIの名前パターン。minimumを除外する
values = ["al2023-ami-2023*-kernel-*-x86_64"]
}
filter {
name = "architecture"
values = ["x86_64"]
}
filter {
name = "virtualization-type"
values = ["hvm"]
}
}
# WebServerのEC2
resource "aws_instance" "web_server" {
count = 2
ami = data.aws_ami.amazon_linux.id
instance_type = "t3.micro"
subnet_id = element(var.private_subnet_ids, count.index % length(var.public_subnet_ids))
vpc_security_group_ids = [aws_security_group.ec2_sg.id]
user_data = <<-EOF
#!/bin/bash
dnf update -y
dnf install -y nginx openssl
mkdir /etc/nginx/ssl
openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
-keyout /etc/nginx/ssl/selfsigned.key \
-out /etc/nginx/ssl/selfsigned.crt \
-subj "/C=JP/ST=Tokyo/L=Chiyoda/O=Example Company/OU=IT Department/CN=${var.domain_name}"
cat > /etc/nginx/conf.d/default.conf <<EOL
server {
listen 443 ssl;
server_name ${var.domain_name};
ssl_certificate /etc/nginx/ssl/selfsigned.crt;
ssl_certificate_key /etc/nginx/ssl/selfsigned.key;
location / {
root /usr/share/nginx/html;
index index.html index.htm;
}
}
EOL
systemctl enable nginx
systemctl start nginx
EOF
tags = {
Name = "WebServer-${count.index + 1}"
}
}
alb.tf
- HTTPS接続(443)のみ許可するセキュリティグループをALBにアタッチ
- ターゲットグループはHTTPSとして、作成した2つのEC2インスタンスをアタッチする
- リスナールールはHTTPS(443)として、ここにACMの証明書をアタッチする
- 独自ドメインと紐づけるために、Route 53にエイリアス(A)レコードを追加する
resource "aws_security_group" "alb_sg" {
name = "alb_sg"
description = "A security group for ALB"
vpc_id = var.vpc_id
ingress {
description = "Allow HTTPS inbound"
from_port = 443
to_port = 443
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
ipv6_cidr_blocks = ["::/0"]
}
egress {
description = "Allow all outbound traffic"
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
}
resource "aws_lb" "application_lb" {
name = "application-lb"
internal = false
load_balancer_type = "application"
security_groups = [aws_security_group.alb_sg.id]
subnets = var.public_subnet_ids
enable_deletion_protection = false
}
resource "aws_lb_target_group" "alb_target_group" {
name = "alb-target-group"
port = 443
protocol = "HTTPS"
vpc_id = var.vpc_id
health_check {
protocol = "HTTPS"
port = "443"
path = "/"
interval = 30
timeout = 5
healthy_threshold = 5
unhealthy_threshold = 2
matcher = "200-299"
}
# 証明書の指定はリスナーで行うため不要
}
resource "aws_lb_listener" "https_listener" {
load_balancer_arn = aws_lb.application_lb.arn
port = "443"
protocol = "HTTPS"
ssl_policy = "ELBSecurityPolicy-2016-08"
certificate_arn = var.certificate_arn
default_action {
type = "forward"
target_group_arn = aws_lb_target_group.alb_target_group.arn
}
}
resource "aws_lb_target_group_attachment" "tg_attachment" {
count = 2
target_group_arn = aws_lb_target_group.alb_target_group.arn
target_id = aws_instance.web_server[count.index].id
port = 443
}
resource "aws_route53_record" "alb_alias" {
zone_id = var.hosted_zone_id
name = "www.${var.domain_name}"
type = "A"
alias {
name = aws_lb.application_lb.dns_name
zone_id = aws_lb.application_lb.zone_id
evaluate_target_health = true
}
}
結果
ブラウザからアクセスすると自己署名証明書の警告は出るものの、独自ドメインとHTTPSで接続できた(nginxの画面が出るのが想定された挙動)。
EC2のセキュリティグループを見ると、HTTP(80)のインバウンドが許可されていないので、ALB→EC2間も暗号化されている。
所感
- 難しそうかと思ったけど意外と簡単だった。一回やってみるものだね
- NLBでTCPパススルーを構成し、EC2にHTTPSで接続するのは試してなかったけどやってみたくなったらで!
Shikoan's ML Blogの中の人が運営しているサークル「じゅ~しぃ~すくりぷと」の本のご案内
技術書コーナー
北海道の駅巡りコーナー