【AWS】Terraformでアクセスを分散、WebサーバーをHTTPS化する その3(ACM)

## 概要
TerraformでAWSサービスを使用して、

- 負荷分散
- 独自ドメインの設定
- SSL証明書を作成してHTTPS化

上記の3つを含めた構成を作成する手順をまとめました。

## 目次
本記事は「3. AWS Certificate Manager(ACM)でSSL証明書の作成」について記述。
「1. アプリケーションロードバランサー(ALB)の作成」はこちら →[【AWS】Terraformでアクセスを分散、WebサーバーをHTTPS化する その1(ALB)](https://qiita.com/ramunauna/items/250d1dfc16468c5b8522)
「2. Route53でDNS設定」はこちら → [【AWS】Terraformでアクセスを分散、WebサーバーをHTTPS化する その2(Route53)](https://qiita.com/ramunauna/items/cc85201fb99ef3e6eb95)

1. アプリケーションロードバランサー(ALB)の作成
  - ALBの作成
  - ターゲットグループの作成 / ターゲットの登録
  - リスナーを設定
  - ALBのDNS名からHTTPアクセス確認
1. Route53でDNS設定
  - ドメインの取得（例はfreenomの無料ドメイン）
  - ホストゾーンの作成
  - DNSレコードの定義
  - ネームサーバーの設定を加える
  - 取得したドメインへHTTPアクセス確認
1. AWS Certificate Manager(ACM)でSSL証明書の作成　__←本記事はここから__
  - SSL証明書の作成
  - SSL証明書の検証
  - HTTPSリスナーの定義
  - HTTPSアクセス確認

ここまでの前提条件として
DNS設定済みで設定したドメインにhttpでアクセスできる状態であること。

## 構成図
目標構成図はこちら。
![ALB_Route53_ACM.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1813991/ac0da77b-d9fa-f1cc-5fd0-63f304929274.png)

## 3. AWS Certificate Manager(ACM)でSSL証明書の作成

### SSL証明書の作成
HTTPS化に必要なSSL証明書をACMで作成する。
ACMでSSL証明書を作成すれば証明書の有効期限日をむかえても自動更新されるため、期限切れによる失効や稼働停止の防止に役立つ。

ACMを使ってパブリックなSSL/TLS証明書を発行できるのは下記のサービス。

- ELB
- CloudFront
- Elastic Beanstalk
- API Gateway
- CloudFormation

acm.tfを作成。
下記にてSSL証明書を定義。

```terraform:acm.tf
#SSL証明書の定義
resource "aws_acm_certificate" "cert" {
  domain_name               = aws_route53_record.dns_record.name
  subject_alternative_names = []
  validation_method         = "DNS"
  lifecycle {
    create_before_destroy = true
  }
}
```

- `subject_alternative_names`
ドメイン名を追加したい場合、`subject_alternative_names = [リスト]`を設定。
["blahblahblah.example.com"]と設定した場合、「example.com」と「blahblahblah.example.com」のSSL証明書を作成する。
追加しない場合は[ ]（空リスト）を渡す。

- `validation_method = "DNS"`
ドメインの検証方法を設定。DNSかEMAILかを設定できる。
SSL証明書を自動更新したい場合は、DNS検証を選択。  

- `lifecycle { create_before_destroy = true }`
`create_before_destroy`を設定することで、「リソースの作成→リソースの削除」という順序でリソースの再生成を実行できる。（通常は逆の削除→作成の順で実施される）
これにより、リソースの依存関係によって発生しうる不要なリソースの再生成を避けることができる。
注意点として、一時的に2つのリソースが作成されるため、ネーミングにユニーク制約があるリソースは作成に失敗する恐れがある。

### SSL証明書の検証
続いてDNS検証用のDNSレコードを追加する。
また`aws_acm_certification_validation`リソースを定義することで、apply時にSSL証明書の検証が完了するまで待機する設定を加える。

```terraform:acm.tf
# 検証用のレコード定義
resource "aws_route53_record" "cert_validation" {
  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
    }
  }
  allow_overwrite = true
  name            = each.value.name
  records         = [each.value.record]
  ttl             = 60
  type            = each.value.type
  zone_id         = aws_route53_zone.hostzone.zone_id
}

# 検証完了までの待機
resource "aws_acm_certificate_validation" "cert" {
  certificate_arn         = aws_acm_certificate.cert.arn
  validation_record_fqdns = [for record in aws_route53_record.cert_validation : record.fqdn]
}
```
### HTTPSリスナーの定義
続いてALBへHTTPSアクセスできるよう、HTTPSリスナーを作成する。
また、HTTPSのインバウンドルールを既存のセキュリティグループに追加する。

```terraform:acm.tf
# HTTPSリスナーを追記
resource "aws_lb_listener" "https" {
  load_balancer_arn = aws_lb.alb.arn
  port              = "443"
  protocol          = "HTTPS"
  certificate_arn   = aws_acm_certificate.cert.arn
  ssl_policy        = "ELBSecurityPolicy-2016-08"

  default_action {
    type             = "forward" # リクエストをターゲットグループに転送
    target_group_arn = aws_lb_target_group.alb_target_group.id
  }

  depends_on = [
    aws_acm_certificate_validation.cert
  ]
}
```

```terraform:acm.tf
# HTTPSインバウンドルールを追記
resource "aws_security_group_rule" "ingress_https" {
  security_group_id = aws_security_group.sg_for_ec2.id
  type              = "ingress"
  from_port         = 443
  to_port           = 443
  protocol          = "tcp"
  cidr_blocks       = ["0.0.0.0/0"]
}
```
- `certificate_arn`  
SSL証明書発行リソースを指定。
- `ssl_policy`  
ELBはプロトコルと暗号の組み合わせであるセキュリティポリシーを使用して、クライアントとSSL接続を確立している。  
ALBは`ELBSecurityPolicy-2016-08`をデフォルトのセキュリティポリシーとしている。
- `depends_on`  
ここではSSL証明書の検証完了してから、HTTPSリスナーを作成するように明示的に依存関係を記している。

### HTTPSアクセス確認
ここまででHTTPSアクセス確認の準備は整ったが、まだHTTPでもアクセスできる状態なので、HTTPでアクセスした際にHTTPSへリダイレクトする設定を加える。

`alb.tf`のHTTPリスナーの`default_action`を編集しておく。

```terraform:alb.tf
#HTTPリスナー
resource "aws_lb_listener" "http" {
  load_balancer_arn = aws_lb.alb.arn
  port              = "80"
  protocol          = "HTTP"

  # default_action {
  #   type             = "forward"
  #   target_group_arn = aws_lb_target_group.alb_target_group.id
  # }
  
  # ↑ 上記内容を
  # ↓ 下記に書き換え　　

  default_action {
    type = "redirect"

    redirect {
      port        = "443"
      protocol    = "HTTPS"
      status_code = "HTTP_301"
    }
  }
}
```
- `default_action`  
`type = "redirect"`を設定し、後はリダイレクト先プロトコルの情報を加えるだけ。

ここまでで`terraform apply`を実行する。

__HTTPSにアクセス確認__
`https://取得したドメイン`にアクセスし、問題なくページが表示されたら、HTTPS化が成功！

__HTTPでアクセスしてHTTPSへリダイレクトされるか確認__
`http://取得したドメイン`にアクセスし、HTTPSへリダイレクトされたら、リダイレクトも成功！

## 終わりに
今回まででALBでの負荷分散、Route53でDNS設定、ACMでSSL証明書作成をまとめました。
1つテンプレを持っておくと、色々カスタマイズして使えそうです。

:::note warn
ここまでのコードを全て合わせてまとめて実行しようとすると、SSL証明書の検証完了待ちあたりで処理が終わらないという壁にぶち当たりました。
※ 今回は全て同ディレクトリでalb.tf、route53.tf、acm.tfと順々に追加したので、問題なくデプロイできました。
ファイルの分割、実施順序やリソースデプロイのタイミングなど細かく詰めていく必要がありそうです。
:::
↓↓↓
並行してディレクトリ構造整理をまとめました。
[【AWS】Terraformディレクトリ構造の整理（tfファイル配置方式の見直し）] (https://qiita.com/ramunauna/items/286a47f2d68b302f2e7d)