Terraform で Route53 + ACM + ALB + EC2 な HTTPS 環境を構築

概要

やりたいこと

過去に OWASP Juice shop を EC2 で作成した。これを HTTPS 化し、 Terraform で管理したい。

以下のような通信の流れになる。

  • ブラウザ -> https(443) -> AlB -> http(80) -> EC2 -> port_forwading(3000) -> owasp-shop

Route53

ドメインの管理を行うサービス。

今回は HTTPS でアクセスしたいため、ドメインの取得を行った。

ACM

SSL 証明書の管理を行うサービス。

ALB

HTTPS の終端。EC2 は今回2つ作成し、バランシングする。

実践

準備

過去の記事を参照。

作業ディレクトリを作成し、プロバイダーと tfstate ファイルを S3 バケットに保存する設定をする。

mkdir alb-ec2
cd alb-ec2
vi main.tf
provider "aws" {
  profile = "terraform"
  region  = "ap-northeast-1"
}

terraform {
  required_version = ">= 0.12.0"
  backend "s3" {
    profile = "terraform"
    region  = "ap-northeast-1"
    bucket  = "terraform-tfstate-runble1"
    key     = "alb-ec2/terraform.tfstate"
    encrypt = true
  }
}

初期化し、ワークスペース名をつける。

terraform init
terraform workspace new alb-ec2-acm

Route53

AWSコンソールより、適当なドメインを登録する。

VPC + ルーティング + セキュリティグループ

最初に、土台となるネットワークを作る。

# ====================
#
# VPC
#
# ====================
resource "aws_vpc" "example" {
  cidr_block           = "10.0.0.0/16"
  instance_tenancy     = "default"
  enable_dns_hostnames = true
  enable_dns_support   = true
}

# ====================
#
# Subnet
#
# ====================
resource "aws_subnet" "public_a" {
  vpc_id                  = aws_vpc.example.id
  cidr_block              = "10.0.1.0/24"
  map_public_ip_on_launch = true
  availability_zone       = "ap-northeast-1a"
}
resource "aws_subnet" "public_c" {
  vpc_id                  = aws_vpc.example.id
  cidr_block              = "10.0.2.0/24"
  map_public_ip_on_launch = true
  availability_zone       = "ap-northeast-1c"
}
resource "aws_subnet" "public_a_web" {
  vpc_id                  = aws_vpc.example.id
  cidr_block              = "10.0.3.0/24"
  map_public_ip_on_launch = true
  availability_zone       = "ap-northeast-1a"
}
resource "aws_subnet" "public_c_web" {
  vpc_id                  = aws_vpc.example.id
  cidr_block              = "10.0.4.0/24"
  map_public_ip_on_launch = true
  availability_zone       = "ap-northeast-1c"
}
resource "aws_subnet" "private_a" {
  vpc_id                  = aws_vpc.example.id
  cidr_block              = "10.0.5.0/24"
  map_public_ip_on_launch = false
  availability_zone       = "ap-northeast-1a"
}
resource "aws_subnet" "private_c" {
  vpc_id                  = aws_vpc.example.id
  cidr_block              = "10.0.6.0/24"
  map_public_ip_on_launch = false
  availability_zone       = "ap-northeast-1c"
}

# ====================
#
# Internet Gateway
#
# ====================
resource "aws_internet_gateway" "example" {
  vpc_id = aws_vpc.example.id
}

# ====================
#
# Route Table
#
# ====================
resource "aws_route_table" "public" {
  vpc_id = aws_vpc.example.id
}
resource "aws_route" "public" {
  route_table_id         = aws_route_table.public.id
  gateway_id             = aws_internet_gateway.example.id
  destination_cidr_block = "0.0.0.0/0"
}
resource "aws_route_table_association" "public_a" {
  subnet_id      = aws_subnet.public_a.id
  route_table_id = aws_route_table.public.id
}
resource "aws_route_table_association" "public_c" {
  subnet_id      = aws_subnet.public_c.id
  route_table_id = aws_route_table.public.id
}

ALB

# ====================
#
# ALB
#
# ====================
resource "aws_lb" "for_webserver" {
  name               = "webserver-alb"
  internal           = false
  load_balancer_type = "application"

  security_groups = [
    aws_security_group.alb.id
  ]

  subnets = [
    aws_subnet.public_a.id,
    aws_subnet.public_c.id,
  ]
}

# ====================
#
# Security Group
#
# ====================
resource "aws_security_group" "alb" {
  name   = "alb"
  vpc_id = aws_vpc.example.id
  ingress {
    from_port   = 443
    to_port     = 443
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }
  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }
}

# ====================
#
# Listner
#
# ====================
resource "aws_lb_listener" "for_webserver" {
  load_balancer_arn = aws_lb.for_webserver.arn
  port              = "443"
  protocol          = "HTTPS"
  certificate_arn   = aws_acm_certificate.example.arn #証明書
  ssl_policy        = "ELBSecurityPolicy-2016-08"

  default_action {
    type             = "forward"
    target_group_arn = aws_lb_target_group.for_webserver.arn
  }
}

resource "aws_lb_listener_rule" "forward" {
  listener_arn = aws_lb_listener.for_webserver.arn
  priority     = 99

  action {
    type             = "forward"
    target_group_arn = aws_lb_target_group.for_webserver.arn
  }

  condition {
    path_pattern {
      values = ["/*"]
    }
  }
}

# ====================
#
# Target Group
#
# ====================
resource "aws_lb_target_group" "for_webserver" {
  name     = "for-webserver-lb-tg"
  port     = 80
  protocol = "HTTP"
  vpc_id   = aws_vpc.example.id

  health_check {
    path = "/index.html"
  }
}
resource "aws_lb_target_group_attachment" "for_webserver_a" {
  target_group_arn = aws_lb_target_group.for_webserver.arn
  target_id        = aws_instance.a.id
  port             = 80
}
resource "aws_lb_target_group_attachment" "for_webserver_c" {
  target_group_arn = aws_lb_target_group.for_webserver.arn
  target_id        = aws_instance.c.id
  port             = 80
}

Route53 + ACM

# ====================
#
# Route53
#
# ====================
data "aws_route53_zone" "example" {
  name = "登録したドメイン"
}

resource "aws_route53_record" "example" {
  zone_id = data.aws_route53_zone.example.zone_id
  name    = data.aws_route53_zone.example.name
  type    = "A"

  alias {
    name                   = aws_lb.for_webserver.dns_name
    zone_id                = aws_lb.for_webserver.zone_id
    evaluate_target_health = true
  }
}

# ====================
#
# ACM
#
# ====================
resource "aws_acm_certificate" "example" {
  domain_name               = aws_route53_record.example.name
  subject_alternative_names = []
  validation_method         = "DNS"

  lifecycle {
    create_before_destroy = true
  }
}

# 検証用DNSレコード
resource "aws_route53_record" "example_certificate" {
  name    = aws_acm_certificate.example.domain_validation_options[0].resource_record_name
  type    = aws_acm_certificate.example.domain_validation_options[0].resource_record_type
  records = [aws_acm_certificate.example.domain_validation_options[0].resource_record_value]
  zone_id = data.aws_route53_zone.example.id
  ttl     = 60
}

# SSL証明書の検証
resource "aws_acm_certificate_validation" "example" {
  certificate_arn         = aws_acm_certificate.example.arn
  validation_record_fqdns = [aws_route53_record.example_certificate.fqdn]
}

# ====================
#
# Output
#
# ====================
output "domain_name" {
  value = aws_route53_record.example.name
}

EC2

owasp-juice-shop というやられアプリを建てる。

# ====================
#
# EC2
#
# ====================
resource "aws_instance" "a" {
  ami                    = "ami-0c3fd0f5d33134a76"
  vpc_security_group_ids = [aws_security_group.for_webserver_ec2.id]
  instance_type          = "t2.micro"
  subnet_id              = aws_subnet.public_a.id
  user_data              = <<EOF
#!/bin/bash
yum update -y
yum install -y docker
service docker start
docker pull bkimminich/juice-shop
docker run -d -p 80:3000 bkimminich/juice-shop
  EOF
}

resource "aws_instance" "c" {
  ami                    = "ami-0c3fd0f5d33134a76"
  vpc_security_group_ids = [aws_security_group.for_webserver_ec2.id]
  instance_type          = "t2.micro"
  subnet_id              = aws_subnet.public_c.id
  user_data              = <<EOF
#!/bin/bash
yum update -y
yum install -y docker
service docker start
docker pull bkimminich/juice-shop
docker run -d -p 80:3000 bkimminich/juice-shop
  EOF
}

# ====================
#
# Security Group
#
# ====================
resource "aws_security_group" "for_webserver_ec2" {
  name   = "for-ec2"
  vpc_id = aws_vpc.example.id
  ingress {
    from_port       = 80
    to_port         = 80
    protocol        = "tcp"
    security_groups = [aws_security_group.alb.id]
  }
  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }
}

登録したドメインに HTTPS でアクセスし閲覧できればOK。

コードは github に。

参考

実践Terraform AWSにおけるシステム設計とベストプラクティス

TerraformでELBとEC2とRDSのよくありそうなWebパターンを作ってみる。

AWS, Terraform

Posted by さいき