CloudFormationによるAWSネットワーク構築⑤：SSMセッションマネージャーをコード化してプライベートサブネットの本番サーバーにアクセスしてみた

## はじめに
この記事では、AWSのCloudFormationテンプレートを用いて、SSMセッションマネージャーのセットアップコードを追加する方法について説明します。

セッションマネージャーについての詳しい情報は、過去の記事で解説しているため、より深く知りたい方はぜひご参照ください。

https://qiita.com/free-honda/items/9740ef602f072ab2b88c

コードはインフラをコード化する観点から可読性を重視しているため、内容が少し長くなっていますがご了承ください。🙇

## 前回の記事の続きになります。

この記事は過去の記事の続きです。詳しく知りたい方は以下の記事も参考にしてください。

https://qiita.com/free-honda/items/5a8c69df40c631549663

https://qiita.com/free-honda/items/06d092c82d1dcb1e77fe

https://qiita.com/free-honda/items/da70f65d9d93a89abd23

https://qiita.com/free-honda/items/d0a10b8db9579b525d58

今回は、CloudFormationを用いてSSMセッションマネージャーの追加設定を行うコードを記述します。

:::note warn
具体的には、以下のリソースを定義し、各構成要素について詳しく解説します。
- **SSM用IAMロールの作成とアタッチ**
- **SSM用VPCエンドポイントのセキュリティグループ**
- **SSM関連のVPCエンドポイント**
:::

## SSMセッションマネージャーをセットアップ
ここでは、AWSのCloudFormationテンプレートを用いて、SSMセッションマネージャーをセットアップするコードを追加していきます。

```yaml
  # SSM用のIAMロール
  SSMRole:
    Type: AWS::IAM::Role
    Properties: 
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: Allow
            Principal:
              Service: ec2.amazonaws.com
            Action: sts:AssumeRole
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore
      Tags:
        - Key: Name
          Value: ホンダ-ssm-role

  # SSMインスタンスプロファイルの作成
  SSMInstanceProfile:
    Type: AWS::IAM::InstanceProfile
    Properties:
      Roles:
        - !Ref SSMRole

  # 本番サーバー用のEC2インスタンスにIAMロールをアタッチ
  ProductionInstance:
    Type: AWS::EC2::Instance
    Properties:
      InstanceType: t2.micro
      SubnetId: !Ref PrivateSubnet1
      ImageId: ami-0b6fe957a0eb4c1b9
      KeyName: "honda"
      SecurityGroupIds:
        - !Ref ProductionSecurityGroup
      IamInstanceProfile: !Ref SSMInstanceProfile  # インスタンスプロファイルをアタッチ
      Tags:
        - Key: Name
          Value: "本番サーバー"
      UserData: 
        Fn::Base64: !Sub |
          #!/bin/bash
          sudo yum install -y httpd
          sudo systemctl start httpd
          sudo systemctl enable httpd

  # SSM 用 VPC エンドポイントセキュリティグループ
  SSMVpcEndpointSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: SSM VPC Endpoint Security Group
      VpcId: !Ref HondaVpc
      SecurityGroupIngress:
        - IpProtocol: tcp
          FromPort: 443
          ToPort: 443
          CidrIp: 0.0.0.0/0  # 必要に応じてIPレンジを制限
      Tags:
        - Key: Name
          Value: SSM-VPCEndpoint-SG

  # SSM VPC エンドポイント
  SSMVpcEndpoint:
    Type: AWS::EC2::VPCEndpoint
    Properties:
      VpcId: !Ref HondaVpc
      ServiceName: com.amazonaws.ap-northeast-1.ssm
      VpcEndpointType: Interface
      SubnetIds:
        - !Ref PrivateSubnet1
      SecurityGroupIds:
        - !Ref SSMVpcEndpointSecurityGroup

  # EC2 メッセージ VPC エンドポイント
  EC2MessagesVpcEndpoint:
    Type: AWS::EC2::VPCEndpoint
    Properties:
      VpcId: !Ref HondaVpc
      ServiceName: com.amazonaws.ap-northeast-1.ec2messages
      VpcEndpointType: Interface
      SubnetIds:
        - !Ref PrivateSubnet1
      SecurityGroupIds:
        - !Ref SSMVpcEndpointSecurityGroup

  # SSM メッセージ VPC エンドポイント
  SSMMessagesVpcEndpoint:
    Type: AWS::EC2::VPCEndpoint
    Properties:
      VpcId: !Ref HondaVpc
      ServiceName: com.amazonaws.ap-northeast-1.ssmmessages
      VpcEndpointType: Interface
      SubnetIds:
        - !Ref PrivateSubnet1
      SecurityGroupIds:
        - !Ref SSMVpcEndpointSecurityGroup
```

ここでは、SSMセッションマネージャーを使用するために、必要なVPC設定を行っています。

## 完成したコード

前回のコードと新たに追加した部分を組み合わせた最終的なコードは、以下の通りです。

:::note warn
**複雑なアーキテクチャとなっているため、コードが長くなっていますが、流れをつかむ感じで見ていただければと思います（笑）。**
:::

```yaml
Resources:
  # VPCの作成
  HondaVpc:
    Type: AWS::EC2::VPC
    Properties:
      CidrBlock: 10.0.0.0/16
      EnableDnsSupport: true
      EnableDnsHostnames: true
      InstanceTenancy: default
      Tags:
        - Key: Name
          Value: ホンダ-vpc

  # パブリックサブネットの作成
  PublicSubnet1:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref HondaVpc
      CidrBlock: 10.0.0.0/20
      AvailabilityZone: ap-northeast-1a
      Tags:
        - Key: Name
          Value: ホンダ-subnet-public1-ap-northeast-1a

  PublicSubnet2:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref HondaVpc
      CidrBlock: 10.0.16.0/20
      AvailabilityZone: ap-northeast-1c
      Tags:
        - Key: Name
          Value: ホンダ-subnet-public2-ap-northeast-1c

  # プライベートサブネットの作成
  PrivateSubnet1:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref HondaVpc
      CidrBlock: 10.0.128.0/20
      AvailabilityZone: ap-northeast-1a
      Tags:
        - Key: Name
          Value: ホンダ-subnet-private1-ap-northeast-1a

  PrivateSubnet2:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref HondaVpc
      CidrBlock: 10.0.144.0/20
      AvailabilityZone: ap-northeast-1c
      Tags:
        - Key: Name
          Value: ホンダ-subnet-private2-ap-northeast-1c

  PrivateSubnet3:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref HondaVpc
      CidrBlock: 10.0.160.0/20
      AvailabilityZone: ap-northeast-1a
      Tags:
        - Key: Name
          Value: ホンダ-subnet-private3-ap-northeast-1a

  PrivateSubnet4:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref HondaVpc
      CidrBlock: 10.0.176.0/20
      AvailabilityZone: ap-northeast-1c
      Tags:
        - Key: Name
          Value: ホンダ-subnet-private4-ap-northeast-1c

  # インターネットゲートウェイの作成
  InternetGateway:
    Type: AWS::EC2::InternetGateway
    Properties:
      Tags:
        - Key: Name
          Value: ホンダ-igw

  # インターネットゲートウェイをVPCにアタッチ
  AttachGateway:
    Type: AWS::EC2::VPCGatewayAttachment
    Properties:
      VpcId: !Ref HondaVpc
      InternetGatewayId: !Ref InternetGateway

  # パブリックルートテーブルの作成とパブリックサブネットとの関連付け
  PublicRouteTable:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref HondaVpc
      Tags:
        - Key: Name
          Value: ホンダ-rtb-public

  PublicRoute:
    Type: AWS::EC2::Route
    Properties:
      RouteTableId: !Ref PublicRouteTable
      DestinationCidrBlock: 0.0.0.0/0
      GatewayId: !Ref InternetGateway

  PublicSubnet1RouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      SubnetId: !Ref PublicSubnet1
      RouteTableId: !Ref PublicRouteTable

  PublicSubnet2RouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      SubnetId: !Ref PublicSubnet2
      RouteTableId: !Ref PublicRouteTable

  # Elastic IP の作成
  ElasticIP1:
    Type: AWS::EC2::EIP
    Properties:
      Domain: vpc
      Tags:
        - Key: Name
          Value: ホンダ-eip-ap-northeast-1a

  # NATゲートウェイの作成
  NatGateway:
    Type: AWS::EC2::NatGateway
    Properties:
      SubnetId: !Ref PublicSubnet1
      AllocationId: !GetAtt ElasticIP1.AllocationId
      Tags:
        - Key: Name
          Value: ホンダ-nat-public1-ap-northeast-1a

  # プライベートルートテーブルの作成と各サブネットとの関連付け
  PrivateRouteTable1:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref HondaVpc
      Tags:
        - Key: Name
          Value: ホンダ-rtb-private1-ap-northeast-1a

  PrivateRoute1:
    Type: AWS::EC2::Route
    Properties:
      RouteTableId: !Ref PrivateRouteTable1
      DestinationCidrBlock: 0.0.0.0/0
      NatGatewayId: !Ref NatGateway

  PrivateSubnet1RouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      SubnetId: !Ref PrivateSubnet1
      RouteTableId: !Ref PrivateRouteTable1

  # 踏み台サーバー（Bastion）用のセキュリティグループの作成
  BastionSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: "Allow SSH access"
      VpcId: !Ref HondaVpc
      SecurityGroupIngress:
        - IpProtocol: tcp
          FromPort: 22
          ToPort: 22
          CidrIp: 0.0.0.0/0
      Tags:
        - Key: Name
          Value: ホンダ-sg-bastion

  # Elastic IP の作成（別のElastic IP）
  ElasticIP2:
    Type: AWS::EC2::EIP
    Properties:
      Domain: vpc
      Tags:
        - Key: Name
          Value: ホンダ-eip-ap-northeast-1c

  # 踏み台サーバーのEC2インスタンスの作成
  BastionInstance:
    Type: AWS::EC2::Instance
    Properties:
      InstanceType: t2.micro
      SubnetId: !Ref PublicSubnet1
      ImageId: ami-0b6fe957a0eb4c1b9
      KeyName: "honda"
      SecurityGroupIds:
        - !Ref BastionSecurityGroup
      Tags:
        - Key: Name
          Value: "踏み台サーバー"

  # ALBのセキュリティグループの作成
  ALBSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: "Allow HTTP access to ALB"
      VpcId: !Ref HondaVpc
      SecurityGroupIngress:
        - IpProtocol: tcp
          FromPort: 80
          ToPort: 80
          CidrIp: 0.0.0.0/0
      Tags:
        - Key: Name
          Value: ホンダ-sg-alb

  # 本番サーバー用セキュリティグループの作成
  ProductionSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: "Allow HTTP access from ALB and SSH access from anywhere"
      VpcId: !Ref HondaVpc
      SecurityGroupIngress:
        # ALBからのHTTPアクセスを許可
        - IpProtocol: tcp
          FromPort: 80
          ToPort: 80
          SourceSecurityGroupId: !Ref ALBSecurityGroup
        # SSHアクセスを任意の場所から許可
        - IpProtocol: tcp
          FromPort: 22
          ToPort: 22
          CidrIp: 0.0.0.0/0
      Tags:
        - Key: Name
          Value: ホンダ-sg-production

  # SSM用のIAMロール
  SSMRole:
    Type: AWS::IAM::Role
    Properties: 
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: Allow
            Principal:
              Service: ec2.amazonaws.com
            Action: sts:AssumeRole
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore
      Tags:
        - Key: Name
          Value: ホンダ-ssm-role

  # SSMインスタンスプロファイルの作成
  SSMInstanceProfile:
    Type: AWS::IAM::InstanceProfile
    Properties:
      Roles:
        - !Ref SSMRole

  # 本番サーバー用のEC2インスタンスにIAMロールをアタッチ
  ProductionInstance:
    Type: AWS::EC2::Instance
    Properties:
      InstanceType: t2.micro
      SubnetId: !Ref PrivateSubnet1
      ImageId: ami-0b6fe957a0eb4c1b9
      KeyName: "honda"
      SecurityGroupIds:
        - !Ref ProductionSecurityGroup
      IamInstanceProfile: !Ref SSMInstanceProfile  # インスタンスプロファイルをアタッチ
      Tags:
        - Key: Name
          Value: "本番サーバー"
      UserData: 
        Fn::Base64: !Sub |
          #!/bin/bash
          sudo yum install -y httpd
          sudo systemctl start httpd
          sudo systemctl enable httpd

  # ALBの作成
  ApplicationLoadBalancer:
    Type: AWS::ElasticLoadBalancingV2::LoadBalancer
    Properties:
      Name: Honda-ALB
      Scheme: internet-facing
      Subnets:
        - !Ref PublicSubnet1
        - !Ref PublicSubnet2
      SecurityGroups:
        - !Ref ALBSecurityGroup
      Type: application
      Tags:
        - Key: Name
          Value: ホンダ-alb

  # ALBのリスナーの作成
  ALBListener:
    Type: AWS::ElasticLoadBalancingV2::Listener
    Properties:
      DefaultActions:
        - Type: forward
          TargetGroupArn: !Ref ALBTargetGroup
      LoadBalancerArn: !Ref ApplicationLoadBalancer
      Port: 80
      Protocol: HTTP

  # ターゲットグループの作成（80番ポート用）
  ALBTargetGroup:
    Type: AWS::ElasticLoadBalancingV2::TargetGroup
    Properties:
      Name: Honda-TargetGroup
      Port: 80
      Protocol: HTTP
      VpcId: !Ref HondaVpc
      TargetType: instance
      HealthCheckProtocol: HTTP
      HealthCheckPort: "80"
      HealthCheckPath: /
      HealthCheckIntervalSeconds: 30
      HealthCheckTimeoutSeconds: 5
      HealthyThresholdCount: 5
      UnhealthyThresholdCount: 2
      Tags:
        - Key: Name
          Value: ホンダ-target-group
      Targets:
        - Id: !Ref ProductionInstance

  # SSM 用 VPC エンドポイントセキュリティグループ
  SSMVpcEndpointSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: SSM VPC Endpoint Security Group
      VpcId: !Ref HondaVpc
      SecurityGroupIngress:
        - IpProtocol: tcp
          FromPort: 443
          ToPort: 443
          CidrIp: 0.0.0.0/0  # 必要に応じてIPレンジを制限
      Tags:
        - Key: Name
          Value: SSM-VPCEndpoint-SG

  # SSM VPC エンドポイント
  SSMVpcEndpoint:
    Type: AWS::EC2::VPCEndpoint
    Properties:
      VpcId: !Ref HondaVpc
      ServiceName: com.amazonaws.ap-northeast-1.ssm
      VpcEndpointType: Interface
      SubnetIds:
        - !Ref PrivateSubnet1
      SecurityGroupIds:
        - !Ref SSMVpcEndpointSecurityGroup

  # EC2 メッセージ VPC エンドポイント
  EC2MessagesVpcEndpoint:
    Type: AWS::EC2::VPCEndpoint
    Properties:
      VpcId: !Ref HondaVpc
      ServiceName: com.amazonaws.ap-northeast-1.ec2messages
      VpcEndpointType: Interface
      SubnetIds:
        - !Ref PrivateSubnet1
      SecurityGroupIds:
        - !Ref SSMVpcEndpointSecurityGroup

  # SSM メッセージ VPC エンドポイント
  SSMMessagesVpcEndpoint:
    Type: AWS::EC2::VPCEndpoint
    Properties:
      VpcId: !Ref HondaVpc
      ServiceName: com.amazonaws.ap-northeast-1.ssmmessages
      VpcEndpointType: Interface
      SubnetIds:
        - !Ref PrivateSubnet1
      SecurityGroupIds:
        - !Ref SSMVpcEndpointSecurityGroup
```

コードが非常に長くなっていますが、基本的にはシンプルで簡単な内容で構成しており、それほど難しくはありません！

:::note
**今さらですが、こうしたコードは自分のGitHubにアップロードしたほうが良かったかなと、少し後悔しています（笑）。**
:::

## 実際にスタックを実行してみた

CloudFormationのサービス画面からスタックの作成に進み、作成した``yamlファイル``をアップロードします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3662571/1f238e06-6895-82e7-2628-fa85366f6ac7.png)

スタック名は任意で設定できます。ここでは私の名前「``honda``」としています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3662571/1c75259d-d21d-c09a-f4ca-e962527617fb.png)

画面遷移後のスタックオプション設定については、個々の設定に依存するため、私は設定せずに進めていきます。

:::note warn
**ただ、スタックの中でIAMを使う場合は、以下のように確認が求められますでの、✅を入れて進んでください。**
:::

![スクリーンショット 2024-11-13 20.19.18.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3662571/160ece74-1b4f-808c-d3c5-9407125141fc.png)


最後に確認を行い、問題がなければ最下部までスクロールし、「``送信``」をクリックして完了です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3662571/0f0a08f4-d6e4-cf96-b900-11e27e0176db.png)

スタックを実行後、イベントタブで成功したことが確認できました（しばらく時間がかかります）。

![スクリーンショット 2024-11-13 20.23.27.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3662571/d66d9bd3-193e-61ac-859b-c6eec0446b27.png)

## プライベートサブネットの本番サーバーにSSMで接続してみた

EC2のサービス画面から本番サーバーを選択し、SSMセッションマネージャーで実際に接続を試みました。

![スクリーンショット 2024-11-13 20.25.38.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3662571/4a86b665-f988-9b90-9f1c-18bef14c4051.png)

画面が遷移し、「インスタンスに接続」画面に切り替わったことを確認して、接続ボタンをクリックします。

![スクリーンショット 2024-11-13 20.27.01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3662571/29553cb4-47d2-3b85-b8d0-be6481563f5e.png)

接続が正常に行われていることが確認でき、今回の検証は成功です！

![スクリーンショット 2024-11-13 20.27.52.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3662571/574d15e2-09b1-3039-d9ae-ac71650ac671.png)

また、スタックでコード化した3つのVPCエンドポイントも問題なく作成されていることが、サービス画面から確認できました。

![スクリーンショット 2024-11-13 20.29.29.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3662571/badb6422-41a4-d787-1794-1733ef97db34.png)

## まとめ

今回の検証で、SSMセッションマネージャーを活用してプライベートサブネット内の本番サーバーに安全に接続できることを確認できました。

コード化したVPCエンドポイントも正しく機能しており、インフラの信頼性が向上しました。

:::note
**SSMはシンプルでありながら強力な管理ツールで、今後も多くの場面で活用していきたいと思います。**
:::

CloudFormationによるインフラのコード化もだいぶ慣れてきたので、一旦、記事としてのアウトプットはここまでとします！