毎週セキュリティパッチ適用済みの踏み台サーバを自動で立ち上げる（CloudFormation）

下記のようなわがままな要件に対応する踏み台サーバ用CloudFormationを作成したのでメモ
- 毎週決まった時間に起動、停止してほしい
- ゴールデンイメージは作りたくない
- 起動時にセキュリティパッチ、その他必要なパッケージインストールをしたい
- 決まった時間以外でも起動、停止を行いたい

## CloudFormation
テンプレートの解説
- Amazon Linux 2023
- 毎週月曜日08:00にインスタンス作成（新規）
- 毎週金曜日20:00にインスタンス削除
- インスタンス作成時にセキュリティパッチの更新、mysqlクライアントのインストールを実施
    - amazon-ssm-agentの停止は、Amazon Inspectorの誤検知対策（不完全ではあるが）
    - rebootはkernelアップデートに必要

```yaml
AWSTemplateFormatVersion: 2010-09-09

###########################
# Parameters
###########################
Metadata:
  AWS::CloudFormation::Interface:
    ParameterGroups:
      - Label:
          default: EC2 Instance Parameters
        Parameters:
          - AMIId
          - InstanceType
    ParameterLabels:
      AMIId:
        default: Launch Template EC2 AMI Id.
      InstanceType:
        default: Launch Template EC2 Instance Type.

Parameters:
  AMIId:
    Type: AWS::EC2::Image::Id
    Default: ami-0e0820ad173f20fbb # Amazon Linux 2023 64bit x86 (2023/05)
  InstanceType:
    Type: String
    Default: t2.micro

###########################
# Resources
###########################
Resources:
  AutoScalingGroup:
    Type: AWS::AutoScaling::AutoScalingGroup
    Properties:
      AutoScalingGroupName: ec2-autoscaling-group
      DesiredCapacity: 0
      LaunchTemplate:
        LaunchTemplateId: !Ref LaunchTemplate
        Version: !GetAtt LaunchTemplate.LatestVersionNumber
      MaxSize: 1
      MinSize: 0
      Tags:
        - Key: Name
          Value: ec2-autoscaling-group
          PropagateAtLaunch: false
      VPCZoneIdentifier:
        - Fn::ImportValue: PrivateSubnet01
        - Fn::ImportValue: PrivateSubnet02

  # Scheduled Action
  ScheduledActionEC2Create:
    Type: AWS::AutoScaling::ScheduledAction
    Properties:
      AutoScalingGroupName: !Ref AutoScalingGroup
      DesiredCapacity: 1
      MaxSize: 1
      MinSize: 0
      TimeZone: Asia/Tokyo
      Recurrence: 0 8 * * Mon

  ScheduledActionEC2Delete:
    Type: AWS::AutoScaling::ScheduledAction
    Properties:
      AutoScalingGroupName: !Ref AutoScalingGroup
      DesiredCapacity: 0
      MaxSize: 1
      MinSize: 0
      TimeZone: Asia/Tokyo
      Recurrence: 0 20 * * Fri

  # Launch Template
  LaunchTemplate:
    Type: AWS::EC2::LaunchTemplate
    Properties:
      LaunchTemplateName: ec2-launch-template
      TagSpecifications:
        - ResourceType: launch-template
          Tags:
            - Key: Name
              Value: ec2-launch-template
      LaunchTemplateData:
        IamInstanceProfile:
          Arn: !GetAtt IAMInstanceProfile.Arn
        ImageId: !Ref AMIId
        InstanceType: !Ref InstanceType
        SecurityGroupIds:
          - !Ref SecurityGroupForSSM
          - !Ref SecurityGroupForPackageInstall
        MetadataOptions:
          HttpTokens: required
        TagSpecifications:
          - ResourceType: instance
            Tags:
              - Key: Name
                Value: ec2-bastion
        UserData:
          Fn::Base64: |
            #!/bin/bash
            systemctl stop amazon-ssm-agent
            dnf -y upgrade --releasever=latest
            dnf -y localinstall  https://dev.mysql.com/get/mysql80-community-release-el9-1.noarch.rpm
            dnf -y install mysql mysql-community-client
            yum -y install mysql mysql-devel
            wget -P /home/ssm-user https://truststore.pki.rds.amazonaws.com/global/global-bundle.pem
            reboot

  SecurityGroupForSSM:
    Type: AWS::EC2::SecurityGroup
    Properties:
      VpcId: {
          Fn::ImportValue: "VpcID", # double-quote required
        }
      SecurityGroupEgress:
        - IpProtocol: tcp
          FromPort: 443
          ToPort: 443
          DestinationSecurityGroupId: {
              Fn::ImportValue: "VpceSG", # double-quote required
            }
          Description: HTTPS For VPC Endpoints.
      GroupDescription: Security group for Bastion to access Session Manager.
      Tags:
        - Key: Name
          Value: vpc-sg-ssmaccess-bastion

  SecurityGroupForPackageInstall:
    Type: AWS::EC2::SecurityGroup
    Properties:
      VpcId: {
          Fn::ImportValue: "VpcID", # double-quote required
        }
      SecurityGroupEgress:
        - IpProtocol: tcp
          FromPort: 80
          ToPort: 80
          CidrIp: 0.0.0.0/0
          Description: HTTP For Package Install
        - IpProtocol: tcp
          FromPort: 443
          ToPort: 443
          CidrIp: 0.0.0.0/0
          Description: HTTPS For Package Install
      GroupDescription: Security group for Bastion to access Packege Install.
      Tags:
        - Key: Name
          Value: vpc-sg-packageinstall-bastion

  IAMRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Statement:
          - Effect: Allow
            Principal:
              Service:
                - ec2.amazonaws.com
            Action:
              - sts:AssumeRole
      Path: /
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore
        - arn:aws:iam::aws:policy/CloudWatchAgentServerPolicy

  IAMInstanceProfile:
    Type: AWS::IAM::InstanceProfile
    Properties:
      Path: /
      Roles:
        - !Ref IAMRole

  # SSM Session Manager Log
  LogGroup:
    Type: AWS::Logs::LogGroup
    Properties:
      LogGroupName: /aws/ssm/ec2-bastion
      RetentionInDays: 365
      Tags:
        - Key: Name
          Value: ec2-bastion
```
※ ImportValueは他テンプレートから参照を想定