【AWS】Route53でドメインを登録してHTTPS対応のnginxサーバーをCloudFrontとCloudFormationで構築するまで
AWSでHTTPS対応のnginxサーバーを構築するまでの手順をまとめました。
HTTPS対応のサーバーを構築する手順は多々あります。今回は「CertificateManager」でサーバ証明書を発行し「CloudFront」でHTTPS対応する構成にしました。
Route53で独自ドメインの登録を行いましたが、別のプロバイダで登録することも可能です。
- [0] 前提条件
- [1] Route53でドメイン登録
- [2] Certificate Managerで証明書発行
- [3] EC2インスタンス構築
- [4] nginxインストール
- [5] セキュリティグループの設定
- [6] CloudFrontの設定
- [7] 動作確認
- [8] スタックの削除
- 終わりに
- 参考資料
[0] 前提条件
nginxインストールなどの操作を行うために、EC2へSSHでログインします。SSHでログインするためのキーペアは作成済みとします。
[前提条件]
- キーペアは作成済み
[1] Route53でドメイン登録
マネージメントコンソールから、Route53で独自ドメインを登録します。
[1-1] ドメインの選択
ドメインの選択欄にドメイン名を入力し[チェック]を押すと、利用可能か否かと料金が表示されます。
[カートに入れる]を押すとカートに料金と登録期間が表示されます。登録期間を過ぎても自動延長できます。長期間使用する予定がなければ1年のままで問題ありません。画面下部の[続行]を押して次に進みます。
[1-2] 連絡先の入力と購入
登録者の連絡先を登録します。電話番号以外は日本語表記で問題ありません。
プライバシーの保護は、個人の場合は[有効化]にしておきます。
[続行]を押すと、次のように表示されますが問題ありませんでした。心配な場合は連絡先に登録したEメールを確認しましょう。AWSからメールが来ているはずです。
ドメインを登録すると自動的にホストゾーンが作成されます。ホストゾーンは12時間以上使用すると、0.5$/月かかります。しばらく使用しない場合は後で手動で削除しましょう。
ドメインの自動更新は個々の目的に応じて選びます。
連絡先に登録したEメールを確認し、メール内のリンクをクリックします。
リンクをクリックした後、[ステータスの更新]を押します。すると、ステータスが検証済みになります。
以上でドメインの登録は完了です。ドメイン登録が完了して使えるようになるまでには多少時間がかかります。私の場合は30分かかりました。
ホストゾーンが自動作成されたかを確認すると、作成されていました。
[2] Certificate Managerで証明書発行
続いて、Certificate Managerでサーバ証明書を発行します。証明書の発行は無料です。
なお、リージョンは「米国東部(バージニア北部)」にします。別のリージョンで発行するとCloudFrontと証明書の関連付けが出来ません。
CloudFront で SSL/TLS 証明書を使用するための要件 - Amazon CloudFront
CloudFront ディストリビューションで使用するために SSL 証明書を米国東部 (バージニア北部) リージョンに移行する
[2-1] 証明書発行
[パブリック証明書のリクエスト]を選択します。
ドメイン名を入力します。登録したドメインがexample.com
であれば、example.com
と*.example.com
を登録します。ワイルドカードを使用してサブドメインも登録します。
検証方法は[DNSの検証]にします。
証明書を多数発行するなど特別な理由がなければ、タグは空欄のままで問題ありません。
内容を確認し[確認とリクエスト]を押します。
[2-2] 検証
「検証保留中」になっています。これを「発行済み」にしていきます。ドメイン名横のドロップダウンを選択します。
[Route53でのレコードの作成]を押下します。
[作成]を押します。
もう一方も同様にDNSレコードを作成します。
以上で証明書発行の手続きは完了です。「検証保留中」になっていますが、数分経つと「発行済み」に変わります。
[3] EC2インスタンス構築
EC2インスタンス構築に当たり、次のものを作成する必要があります。
これらを毎回手動で構築するのは面倒です。なので、CloudFormationで自動化します。
[3-1] CloudFormationのテンプレート
[3-1-1] テンプレートバージョン
テンプレートバージョンは固定です。Description
は任意です。
AWSTemplateFormatVersion: 2010-09-09 Description: >- AWS CloudFormation template for the nginx server.
[3-1-2] パラメータ
次の内容はユーザーが決められるようにします。Default
を指定しておけば毎回入力する必要がなくて楽です。
Parameters: # SSHのキー名称 KeyName: Description: Name of an existing EC2 KeyPair to enable SSH access to the instance Type: 'AWS::EC2::KeyPair::KeyName' ConstraintDescription: Can contain only ASCII characters. # インスタンスタイプをリストから選択 InstanceType: Description: EC2 instance type Type: String Default: t3.micro AllowedValues: - t3.nano - t3.micro - t3.small - t3.medium ConstraintDescription: must be a valid EC2 instance type. # EBSの容量 VolumeSize: Description: 'Volume size of EBS' Type: Number Default: 8 ConstraintDescription: expect size >= 8GB. # CIDR VpcCidr: Description: 'VPC CIDR' Type: String Default: 10.0.0.0/16 SubnetCidr: Description: 'Subnet CIDR' Type: String Default: 10.0.0.0/24 # プライベートIPアドレス PrivateIpAddress: Description: 'Private IP Address.' Type: String Default: '10.0.0.10'
[3-1-3] ネットワーク周り
VPC, サブネット, ルートテーブル, インターネットゲートウェイの設定です。特殊なのはVPCのEnableDnsHostnames
とEnableDnsSupport
をtrueにすることです。デフォルトはfalseですが、CloudFrontの設定時に必要なためtrueにします。
Resources: # VPC Vpc: Type: 'AWS::EC2::VPC' Properties: CidrBlock: !Ref VpcCidr # DNSはCloudFrontのOrigin Domain Name指定のために必要 EnableDnsHostnames: true EnableDnsSupport: true Tags: - Key: Name Value: !Sub '${AWS::StackName}-vpc' # サブネット Subnet: Type: 'AWS::EC2::Subnet' Properties: VpcId: !Ref Vpc CidrBlock: !Ref SubnetCidr Tags: - Key: Name Value: !Sub '${AWS::StackName}-ec2-sb' # インターネットゲートウェイ IGW: Type: 'AWS::EC2::InternetGateway' Properties: Tags: - Key: Name Value: !Sub '${AWS::StackName}-igw' IGW2Vpc: Type: 'AWS::EC2::VPCGatewayAttachment' Properties: InternetGatewayId: !Ref IGW VpcId: !Ref Vpc # ルートテーブル RouteTable: Type: 'AWS::EC2::RouteTable' Properties: VpcId: !Ref Vpc Tags: - Key: Name Value: !Sub '${AWS::StackName}-ec2-rt' RT2Subnet: Type: 'AWS::EC2::SubnetRouteTableAssociation' Properties: SubnetId: !Ref Subnet RouteTableId: !Ref RouteTable Route: Type: 'AWS::EC2::Route' Properties: GatewayId: !Ref IGW RouteTableId: !Ref RouteTable DestinationCidrBlock: 0.0.0.0/0
[3-1-4] セキュリティグループ
セキュリティグループは少々複雑です。4つセキュリティグループを作成するのには理由があります。
セキュリティグループ1は、EC2へのSSHアクセスと、EC2からnginxインストール等の作業を可能にするための設定です。これは普通の設定です。
セキュリティグループ2, 3, 4は、CloudFrontからのHTTPアクセスのみ受け付けるためです。CloudFrontは世界中にエッジサーバを持っており、どのサーバからアクセスされるか分かりません。そのため、全エッジサーバのIPアドレスをセキュリティグループに登録しておく必要があります。
セキュリティグループへの登録は、EC2構築後にシェルスクリプトで行います。CloudFormationでは中身が空のセキュリティグループを準備しておきます。3つ用意するのは、セキュリティグループのルール数に制限があるからです。デフォルトでは60ルールまでですが、CloudFrontのIPアドレスは100以上有るため、複数用意しておきます。
# セキュリティグループ1 SecurityGroup1: Type: 'AWS::EC2::SecurityGroup' Properties: GroupName: !Sub '${AWS::StackName}-ec2-sg1' GroupDescription: !Sub '${AWS::StackName}-ec2-sg1' VpcId: !Ref Vpc Tags: - Key: Name Value: !Sub '${AWS::StackName}-ec2-sg1' # インバウンド: SSH許可 SecurityGroupIngress: - IpProtocol: tcp FromPort: 22 ToPort: 22 CidrIp: 0.0.0.0/0 # アウトバウンド: HTTPとHTTPS許可 SecurityGroupEgress: - IpProtocol: tcp FromPort: 80 ToPort: 80 CidrIp: 0.0.0.0/0 - IpProtocol: tcp FromPort: 443 ToPort: 443 CidrIp: 0.0.0.0/0 # セキュリティグループ2 (for CloudFront) SecurityGroup2: Type: 'AWS::EC2::SecurityGroup' Properties: GroupName: !Sub '${AWS::StackName}-ec2-sg2' GroupDescription: !Sub '${AWS::StackName}-ec2-sg2' VpcId: !Ref Vpc Tags: - Key: Name Value: !Sub '${AWS::StackName}-ec2-sg2' # セキュリティグループ3 (for CloudFront) SecurityGroup3: Type: 'AWS::EC2::SecurityGroup' Properties: GroupName: !Sub '${AWS::StackName}-ec2-sg3' GroupDescription: !Sub '${AWS::StackName}-ec2-sg3' VpcId: !Ref Vpc Tags: - Key: Name Value: !Sub '${AWS::StackName}-ec2-sg3' # セキュリティグループ4 (for CloudFront) SecurityGroup4: Type: 'AWS::EC2::SecurityGroup' Properties: GroupName: !Sub '${AWS::StackName}-ec2-sg4' GroupDescription: !Sub '${AWS::StackName}-ec2-sg4' VpcId: !Ref Vpc Tags: - Key: Name Value: !Sub '${AWS::StackName}-ec2-sg4'
Amazon VPC でセキュリティグループルールの制限を増やす
[3-1-5] IAMロール
IAMロールは通常不要です。わざわざ設定するのは、EC2からCloudFrontのIPアドレスをセキュリティグループに登録するためです。セキュリティグループの変更は、EC2構築後にEC2から行います。EC2にセキュリティグループ操作の権限を付与します。
# IAMロール SecurityGroupAccessRole: Type: "AWS::IAM::Role" Properties: AssumeRolePolicyDocument: Version: "2012-10-17" Statement: - Effect: "Allow" Principal: Service: - "ec2.amazonaws.com" Action: - "sts:AssumeRole" Path: "/" # IAMポリシー SecurityGroupAccessPolicies: Type: AWS::IAM::Policy Properties: PolicyName: SecurityGroupAccess PolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow # 自リージョン・自アカウントのセキュリティグループの変更を許可 Action: - 'ec2:AuthorizeSecurityGroupEgress' - 'ec2:AuthorizeSecurityGroupIngress' - 'ec2:DeleteSecurityGroup' - 'ec2:RevokeSecurityGroupEgress' - 'ec2:RevokeSecurityGroupIngress' Resource: !Sub 'arn:aws:ec2:${AWS::Region}:${AWS::AccountId}:security-group/*' # 作成するVPCのセキュリティグループのみに限定する Condition: ArnEquals: 'ec2:Vpc': !Sub 'arn:aws:ec2:${AWS::Region}:${AWS::AccountId}:vpc/${Vpc}' - Effect: Allow Action: - 'ec2:DescribeSecurityGroups' - 'ec2:DescribeSecurityGroupReferences' - 'ec2:DescribeStaleSecurityGroups' - 'ec2:DescribeVpcs' Resource: '*' Roles: - !Ref SecurityGroupAccessRole # IAMインスタンスプロファイル SecurityGroupAccessInstanceProfile: Type: AWS::IAM::InstanceProfile Properties: Path: "/" Roles: - !Ref SecurityGroupAccessRole
[3-1-6] EC2 (前半)
ここでは、リージョンは東京(ap-northeast-1)、OSはAmazon Linux2で構築する設定にしています。UserData
はEC2構築後にjq, curlを自動インストールし、シェルスクリプトを自動生成するための設定です。
# インスタンス Instance: Type: 'AWS::EC2::Instance' # プロパティ Properties: # ap-northeast-1のAmazon Linux2 ImageId: 'ami-01748a72bed07727c' InstanceType: !Ref InstanceType # ネットワーク周りの設定 NetworkInterfaces: - AssociatePublicIpAddress: true DeviceIndex: 0 GroupSet: - Ref: SecurityGroup1 - Ref: SecurityGroup2 - Ref: SecurityGroup3 - Ref: SecurityGroup4 SubnetId: Ref: Subnet PrivateIpAddress: !Ref PrivateIpAddress # SSHのキー名称 KeyName: !Ref KeyName # EBSの設定 BlockDeviceMappings: - DeviceName: /dev/xvda Ebs: VolumeSize: !Ref VolumeSize VolumeType: gp2 DeleteOnTermination: true # IAM IamInstanceProfile: !Ref SecurityGroupAccessInstanceProfile # タグ Tags: - Key: Application Value: String - Key: Name Value: !Sub '${AWS::StackName}-inst' # ユーザーデータ UserData: !Base64 Fn::Sub: | #!/bin/bash yum -y update yum -y install aws-cfn-bootstrap /opt/aws/bin/cfn-init -v --stack ${AWS::StackName} --resource Instance --region ${AWS::Region} /opt/aws/bin/cfn-signal -e $? --stack ${AWS::StackName} --resource Instance --region ${AWS::Region}
[3-1-7] EC2 (後半)
EC2構築後にjq, curlを自動インストールし、シェルスクリプトを自動生成するための設定です。スクリプトの詳細は後述しますが下記記事を参考にさせていただいています。
EC2のセキュリティグループにCloudFrontからしかアクセスを許可しない設定を追加する(改良版) | 綺麗に死ぬITエンジニア
# メタデータ Metadata: AWS::CloudFormation::Init: config: # jq, curlをインストール packages: yum: jq: [] curl: [] files: # セキュリティグループ変更用のスクリプト '/home/ec2-user/set_security_group.sh': content: Fn::Sub: | #!/bin/bash IP_RANGE=`curl -s https://ip-ranges.amazonaws.com/ip-ranges.json` IP_LIST=`echo $IP_RANGE | jq -r '.prefixes[] | if .service == "CLOUDFRONT" then .ip_prefix else empty end'` count=0 for IP in $IP_LIST; do if [ $count -lt 60 ]; then SG=${SecurityGroup2} elif [ $count -lt 120 ]; then SG=${SecurityGroup3} else SG=${SecurityGroup4} fi aws ec2 authorize-security-group-ingress --region ${AWS::Region} --group-id $SG --protocol tcp --port 80 --cidr $IP echo $IP (( count++ )) done mode: '000644' owner: 'ec2-user' group: 'ec2-user' # nginxインストール用のスクリプト '/home/ec2-user/install_nginx.sh': content: Fn::Sub: | #!/bin/bash sudo amazon-linux-extras install nginx1 -y sudo systemctl start nginx sudo systemctl enable nginx mode: '000644' owner: 'ec2-user' group: 'ec2-user'
[3-1-8] Elastic IP
サーバーを再起動するたびにパブリックIPが変わると面倒なので、Elastic IPを設定しておきます。
# Elastic IP ElasticIp: Type: AWS::EC2::EIP Properties: InstanceId: !Ref Instance Domain: vpc
[3-2] スタックの作成
テンプレートが準備できたらスタックを作成します。
テンプレートファイルはローカルからのアップロードを選択しました。S3にアップロードしたものを参照することも可能です。
スタックの名前は任意の名称を入力します。今回は'stack1'としました。
パラメータはキーペアの名称(KeyName)以外はデフォルトのままにしました。キーペアのみコンボボックスから作成済みのキーペアを選択します。
「スタックオプションの設定」は変更なしで次に進みます。
「レビュー」では末尾の機能のみ注意が必要です。今回作成したテンプレートではIAMロールをEC2に付与しています。IAMリソースを作成しても問題ないか問われるので、チェックボックスにチェックして[スタックの作成]を押下します。
数分待つとスタックの作成が完了します。
インスタンスの状態を確認すると、設定通りに作成されていることが分かります。
[4] nginxインストール
EC2にsshでログインして、install_nginx.sh
を実行します。
sh install_nginx.sh
スクリプトの中身は次の通りです。nginxはAmazon Linux 2のextrasレポジトリで配布されているものをインストールします。
#!/bin/bash sudo amazon-linux-extras install nginx1 -y sudo systemctl start nginx sudo systemctl enable nginx
インストール後にバージョンを確認すると無事インストールされたことが分かります。
nginx -v # nginx version: nginx/1.18.0
[5] セキュリティグループの設定
EC2でset_security_group.sh
を実行します。
sh set_security_group.sh
実行後にマネージメントコンソールで確認すると、インバウンドルールが追加されています。
スクリプトの中身は次の通りです。セキュリティグループのIDとリージョン名は、スタック作成時に自動で設定されます。
#!/bin/bash IP_RANGE=`curl -s https://ip-ranges.amazonaws.com/ip-ranges.json` IP_LIST=`echo $IP_RANGE | jq -r '.prefixes[] | if .service == "CLOUDFRONT" then .ip_prefix else empty end'` count=0 for IP in $IP_LIST; do if [ $count -lt 60 ]; then SG={セキュリティグループ2のID} elif [ $count -lt 120 ]; then SG={セキュリティグループ3のID} else SG={セキュリティグループ4のID} fi aws ec2 authorize-security-group-ingress --region {リージョン名} --group-id $SG --protocol tcp --port 80 --cidr $IP echo $IP (( count++ )) done
[5-1] シェルスクリプトの解説
シェルスクリプトは下記記事を参考にさせていただきました。
EC2のセキュリティグループにCloudFrontからしかアクセスを許可しない設定を追加する(改良版) | 綺麗に死ぬITエンジニア
[5-1-1] CloudFrontのIPアドレス取得
CloudFrontエッジサーバのIPアドレスはip-ranges.jsonから取得します。ip-ranges.jsonから必要な情報をjq
コマンドで取り出します。
ip-ranges.jsonはCloudFront以外の情報も含んでいます。service == "CLOUDFRONT"
でCloudFrontの情報を抽出します。また、抽出した情報はIPアドレス以外の情報も含んでいるため.ip_prefix
でIPアドレスを抽出します。
IP_RANGE=`curl -s https://ip-ranges.amazonaws.com/ip-ranges.json` IP_LIST=`echo $IP_RANGE | jq -r '.prefixes[] | if .service == "CLOUDFRONT" then .ip_prefix else empty end'`
[5-1-2] セキュリティグループのルール追加
抽出したIPアドレスからのHTTPアクセス(TCPプロトコルのポート80)を許可するルールを、セキュリティグループに追加します。
count
でIPアドレスの個数をカウントします。1セキュリティグループあたり、60ルール以内に収まるようにしています。
セキュリティグループへのルール追加はaws ec2 authorize-security-group-ingress
で行います。
count=0 for IP in $IP_LIST; do if [ $count -lt 60 ]; then SG={セキュリティグループ2のID} elif [ $count -lt 120 ]; then SG={セキュリティグループ3のID} else SG={セキュリティグループ4のID} fi aws ec2 authorize-security-group-ingress --region {リージョン名} --group-id $SG --protocol tcp --port 80 --cidr $IP echo $IP (( count++ )) done
[6] CloudFrontの設定
外部からnginxサーバーにアクセスする際は、'test.example.com'のようにドメイン形式でアクセスできるようにします。そして、外部からのアクセスはCloudFrontがHTTPSのみ受け付けて、CloudFrontからEC2へHTTPへ流します。それぞれの関係を表すと次の通りです。
サブドメイン名(test.exmaple.com) <---> CloudFront <---> EC2のパブリック IPv4 DNS
[6-1] CloudFormationのテンプレート
[6-1-1] パラメータ
次の内容はユーザーが入力します。調べ方は後述します。
パラメータ | 備考 |
---|---|
EC2インスタンスのパブリック IPv4 DNS | CloudFrontのOrigin Domain Nameに指定。 |
Certificate ManagerのARN | CloudFrontのSSL Certificateに指定。 |
ホストゾーン ID | ドメイン登録時に作成されたホストゾーン。 |
サブドメイン名 | 任意。Route53で登録したドメインのサブドメイン。 |
AWSTemplateFormatVersion: 2010-09-09 Description: >- AWS CloudFormation template for the nginx server. Parameters: # Instance InstancePublicDns: Description: Instance Public DNS Type: String Default: 'ec2-xxx' # Certificate ManagerのARN AcmArn: Description: AWS Certificate Manager ARN Type: String Default: 'arn:aws:acm:xxx' # ホストゾーンのID HostedZoneId: Description: FQDN of the hosted zone Type: String Default: 'Z0xxx' # サブドメイン名 SubDomain: Description: Sub Domain Name Type: String Default: 'xxx.example.com'
[6-1-2] CloudFront
CloudFrontは次の設定にします。
- Origin側(EC2)はHTTP Only
- Target側(CloudFront)はHTTPからHTTPSへリダイレクト
- Certificate Managerで発行した証明書を関連付け
- サブドメインを使用する
Resources: # CloudFront Distribution CloudFrontDistribution: Type: 'AWS::CloudFront::Distribution' Properties: DistributionConfig: Aliases: - !Ref SubDomain # インスタンス側はHTTP Onlyに設定 Origins: - CustomOriginConfig: OriginProtocolPolicy: http-only DomainName: !Ref InstancePublicDns Id: !Sub '${AWS::StackName}-cloudfront-distribution' DefaultCacheBehavior: # TargetOriginIdは Origins Idと一致していないとNG TargetOriginId: !Sub '${AWS::StackName}-cloudfront-distribution' ViewerProtocolPolicy: redirect-to-https # ForwardedValuesは公式非推奨(CachePolicyが推奨だが面倒) ForwardedValues: QueryString: false # SSL Certificate ViewerCertificate: SslSupportMethod: sni-only AcmCertificateArn: !Ref AcmArn MinimumProtocolVersion: TLSv1.2_2019 HttpVersion: http2 IPV6Enabled: true # Enabledは必須 Enabled: true Tags: - Key: Name Value: !Sub '${AWS::StackName}-cloudfront-dns'
MinimumProtocolVersion
とHttpVersion
, IPV6Enabled
は任意のため指定しなくても問題はありません。
代替ドメイン名 (CNAME) を追加してカスタム URL を使用する - Amazon CloudFront
[6-1-3] Route53のエイリアスレコード
サブドメイン名とCloudFrontを関連付けします。CloudFront自体のドメイン名は'*.cloudfront.net'ですが、自分が決めたサブドメイン(www.example.com等)でCloudFrontにアクセスできるようにします。
# DNS Record DnsRecord: Type: 'AWS::Route53::RecordSet' Properties: HostedZoneId: !Ref HostedZoneId Comment: 'DNS name for CloudFront' Name: !Ref SubDomain Type: A AliasTarget: # AliasTargetのHostedZoneIdはホストゾーンのIDとは別 # CloudFrontの場合は固定 HostedZoneId: Z2FDTNDATAQYW2 DNSName: !GetAtt CloudFrontDistribution.DomainName
注意点はAliasTarget
のHostedZoneId
です。CloudFrontを使用する場合は「Z2FDTNDATAQYW2」固定です。
AWS::Route53::RecordSetGroup AliasTarget - AWS CloudFormation
[6-2] スタックの作成
先程と同様にマネージメントコンソールから作成します。
[6-2-1] パラメータの調べ方
サブドメイン名は任意に決めます。それ以外のパラメータはマネージメントコンソールから確認します。
EC2インスタンスのパブリック IPv4 DNS
Certificate ManagerのARN
ホストゾーン ID
[6-2-2] スタックの作成
調べたパラメータを入力します。
以降の項目はデフォルトのまま進めます。作成を開始して5分程度で作成が完了しました。
[7] 動作確認
設定したサブドメイン名(www.example.com等)でアクセスできるのかを確認します。
ブラウザのURL欄にサブドメイン名を入力します。正常に動作していれば、nginxのテストページが表示されます。
サーバ証明書を確認すると、有効になっていることが分かります。
EC2のIPアドレスを直に入力するとアクセスできないことが確認できます。セキュリティグループでCloudFrontからのアクセスのみ許可したため、EC2に直接アクセスできなくなっています。
[8] スタックの削除
作成したスタックを削除します。削除は新しいスタックから順に行います。
'CloudFrontDistribution'の削除は作成時と同様に5分程度かかります。
終わりに
複雑な構成では無いので手動で行えば、それほど時間はかかりません。しかし、CloudFormationでやろうとすると思いのほか時間がかかりました。
CloudFrontを使うことによって、CloudFormationのテンプレートが大分複雑になった感はあります。CloudFrontのIPをセキュリティグループに設定するのは、もう少し簡単になるといいなと思いました。
ただ、テンプレートを1回作成してしまえば何度でも使い回せるのはメリットです。だいぶ時間はかかりましたが、やってみて良かったなと思います。
ちなみに、HTTPS対応はCloudFrontだけが選択肢ではありません。ALBやNLBで代替可能な場合は検討してみるのもよいでしょう。また、EC2にSSHでログインして操作を行いましたが、Session Managerを使用する選択肢もアリです。
参考資料
AWSでWebサイトをHTTPS化 全パターンを整理してみました – ナレコムAWSレシピ
CloudFormationを使ってS3とCloudFrontの構成で静的Webサイトを構築する(Origin Access Identityは使わない) - Qiita
CloudFormation で OAI を使った CloudFront + S3 の静的コンテンツ配信インフラを作る | DevelopersIO