[備忘録]HTTPS化の作業を一撃で終わらせるCloudFormationテンプレートについて

自身で作ったテンプレを残しておくための備忘録です。
2020.06.26

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

どーもsutoです。AWSのサービスを駆使してWebサーバ(EC2)のSSL設定を自動化するテンプレートを作成しました。

要件

前提

  • 対象のEC2は既に作成済
  • Route53でドメイン取得、ホストゾーン作成が完了済

テンプレートで自動化する項目

  • ALB、ターゲットグループ、リスナーを新規作成
  • Route53の既存ホストゾーン下にサブドメインを新規作成、エイリアスにALBをアタッチ
  • ALBに紐づけるSGの新規作成
  • AWS Certificate Manager(ACM)で証明書を発行、対象のサブドメインに設定

試してみた

以下のテンプレートにテストパラメータを入力して実行結果を見てみます。

AWSTemplateFormatVersion: '2010-09-09'
Description: Create new ALB, ACM, SG, https
Parameters:
  ServerName:
    Description: Server Name
    Type: String
    Default: 'test'
  VPCID:
    Description: VPC ID
    Type: String
    Default: 'vpc-xxx'
  PrimarySubnet:
    Description: PrimarySubnet ID
    Type: String
    Default: 'subnet-xxx'
  SecondarySubnet:
    Description: SecondarySubnet ID
    Type: String
    Default: 'subnet-xxxx'
  AllowIP:
    Description: IP Address for SG
    Type: String
    Default: ''
    AllowedPattern: "(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})/(\\d{1,2})"
    ConstraintDescription: "Must be a valid IP range of the form x.x.x.x/x"
  EC2Web1:
    Description: EC2 Instance ID
    Type: String
    Default: 'i-xxx'
  HostZoneId:
    Description: FQDN of the hosted zone
    Type: String
    Default: 'Z0xxx'
  DomainName:
    Description: FQDN of the HostZone
    Type: String
    Default: 'example.com'
  SubDomain:
    Description: FQDN of the certificate
    Type: String
    Default: 'test.example.com'
Resources:
  SGloadbalancer:
    Type: 'AWS::EC2::SecurityGroup'
    Properties:
      GroupName: !Sub ${ServerName}-alb-sg
      GroupDescription: !Sub ${ServerName}-alb-sg
      VpcId: !Ref VPCID
      SecurityGroupIngress:
        - IpProtocol: tcp
          FromPort: '443'
          ToPort: '443'
          CidrIp: !Ref AllowIP
      Tags:
        - Key: Name
          Value: !Sub ${ServerName}-alb-sg
  TargetGroup: 
    Type: "AWS::ElasticLoadBalancingV2::TargetGroup"
    Properties: 
      VpcId: !Ref VPCID
      Name: !Sub ${ServerName}-tg
      Protocol: HTTP
      Port: 80
      HealthCheckProtocol: HTTP
      HealthCheckPath: "/"
      HealthCheckPort: "traffic-port"
      HealthyThresholdCount: 5
      UnhealthyThresholdCount: 2
      HealthCheckTimeoutSeconds: 5
      HealthCheckIntervalSeconds: 30
      Matcher: 
        HttpCode: 200
      Targets: 
        - Id: !Ref EC2Web1
          Port: 80
  InternetALB: 
    Type: "AWS::ElasticLoadBalancingV2::LoadBalancer"
    Properties: 
      Name: !Sub ${ServerName}-alb
      Scheme: "internet-facing"
      LoadBalancerAttributes: 
        - Key: "deletion_protection.enabled"
          Value: false
        - Key: "idle_timeout.timeout_seconds"
          Value: 4000
      SecurityGroups:
        - !Ref SGloadbalancer
      Subnets: 
        - !Ref PrimarySubnet
        - !Ref SecondarySubnet
  ALBListenerHTTP: 
    Type: "AWS::ElasticLoadBalancingV2::Listener"
    Properties: 
      Port: 80
      Protocol: HTTP
      DefaultActions: 
        - Type: redirect
          RedirectConfig: 
            Host: '#{host}'
            Path: '/#{path}'
            Port: 443
            Protocol: HTTPS
            Query: '#{query}'
            StatusCode: HTTP_301
      LoadBalancerArn: !Ref InternetALB
  ALBListenerHTTPS:
    Type: AWS::ElasticLoadBalancingV2::Listener
    Properties:
      Port: 443
      Protocol: HTTPS
      Certificates:
        - CertificateArn: !Ref ACM
      DefaultActions:
        - TargetGroupArn: !Ref TargetGroup
          Type: forward
      LoadBalancerArn: !Ref InternetALB
  DnsRecord:
    Type: AWS::Route53::RecordSet
    Properties:
      HostedZoneId: !Sub '${HostZoneId}'
      Comment: "DNS for ALB"
      Name: !Sub '${SubDomain}'
      Type: A
      AliasTarget:
        HostedZoneId: !GetAtt 'InternetALB.CanonicalHostedZoneID'
        DNSName: !GetAtt 'InternetALB.DNSName'
  ACM:
    Type: AWS::CertificateManager::Certificate
    Properties:
      DomainName: !Sub '${SubDomain}'
      DomainValidationOptions:
        - DomainName: !Sub '${SubDomain}'
          HostedZoneId: !Sub '${HostZoneId}'
      ValidationMethod: DNS

約15分程度でスタック処理が完了。無事リソースが出来ていました。

2020年6月17日のアップデートによりACMのDNS検証が自動化されたため、間に手作業をまったく入れずスタックの完了を待つだけで設定できるのはホントに素晴らしい!

時間に余裕があるときにParametersの記述を整理したいなと思います。

作成経緯における個人的な注意点

  • 当初”RecordSet”のホストゾーンの指定を”HostedZoneName”にしていたが、値を「example.com.」(←最後にドットが必要)にしなければならず紛らわしかったので”HostedZoneId”の指定に変えた
  • このスタックを削除しても、DNS検証で自動作成されたRoute53のCNAMEは削除されず残ってしまうため、不必要なら手動で削除すること

以上となります。