CloudFormationによるAWSネットワーク構築③：VPC環境の構築とパブリックサブネット内への踏み台サーバーから本番サーバーSSH接続

## はじめに
この記事は、前回のVPC構築に関する内容の続編です。

今回は、プライベートサブネットに本番サーバーを構築し、踏み台サーバーを経由してSSHで本番サーバーにログインできるようにすることをゴールとします。

このステップにより、本番サーバーへの外部からの直接アクセスを避けつつ、安全に管理できる環境を構築します。

## 前回のおさらい
前回の記事では、「ホンダ-vpc」という名前のVPCを作成し、各リージョン（ap-northeast-1a、ap-northeast-1c）に複数のパブリックサブネットとプライベートサブネットを配置し、セキュリティと可用性を強化しました。

https://qiita.com/free-honda/items/06d092c82d1dcb1e77fe#%E3%81%BE%E3%81%A8%E3%82%81

VPCにはインターネットゲートウェイをアタッチし、パブリックとプライベート用のルートテーブルを作成して通信を分離しました。

:::note
**また、NATゲートウェイを使用して、プライベートサブネットからのインターネットアクセスも可能にしています。**
:::

さらに、SSHアクセス用のセキュリティグループを適用した踏み台サーバーにより、VPC内リソースへの安全なアクセスを確保しました。

## 本番サーバー用EC2インスタンスの追加構成
ここでは、CloudFormationを使用して本番サーバー用のEC2インスタンスを構築し、専用のセキュリティグループを設定する方法を説明します。

以下は、CloudFormationテンプレートの設定例です。このテンプレートでは、インバウンドでポート22（SSH）を許可する「ProductionSecurityGroup」を作成し、本番サーバー用のEC2インスタンスに適用します。

```yaml
  # 本番サーバー用セキュリティグループの作成
  ProductionSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: "Allow SSH access to production server"
      VpcId: !Ref HondaVpc
      SecurityGroupIngress:
        - IpProtocol: tcp
          FromPort: 22
          ToPort: 22
          CidrIp: 0.0.0.0/0
      Tags:
        - Key: Name
          Value: ホンダ-sg-production

  # 本番サーバー用のEC2インスタンスの作成（PrivateSubnet1に配置）
  ProductionInstance:
    Type: AWS::EC2::Instance
    Properties:
      InstanceType: t2.micro
      SubnetId: !Ref PrivateSubnet1
      ImageId: ami-0b6fe957a0eb4c1b9
      KeyName: "honda"
      SecurityGroupIds:
        - !Ref BastionSecurityGroup
      Tags:
        - Key: Name
          Value: "本番サーバー"
```

追加のポイントは以下になります。

:::note
- **ProductionSecurityGroup:** 
本番サーバー用のセキュリティグループを作成し、SSHアクセス（ポート22）を許可しています。プライベートサブネットに配置するため、セキュリティグループで制御を行い、外部からの直接アクセスは避けます。

- **ProductionInstance:** 
本番サーバー用のEC2インスタンスを作成し、「ProductionSecurityGroup」を適用しています。このインスタンスは、プライベートサブネットに配置されており、踏み台サーバーを経由したアクセスのみが可能です。
:::

## 完成したコード

前回のコードを組み合わせ、最終的に以下のコードが完成しました。

```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: "踏み台サーバー"

  # 本番サーバー用セキュリティグループの作成
  ProductionSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: "Allow SSH access to production server"
      VpcId: !Ref HondaVpc
      SecurityGroupIngress:
        - IpProtocol: tcp
          FromPort: 22
          ToPort: 22
          CidrIp: 0.0.0.0/0
      Tags:
        - Key: Name
          Value: ホンダ-sg-production

  # 本番サーバー用のEC2インスタンスの作成（PrivateSubnet1に配置）
  ProductionInstance:
    Type: AWS::EC2::Instance
    Properties:
      InstanceType: t2.micro
      SubnetId: !Ref PrivateSubnet1
      ImageId: ami-0b6fe957a0eb4c1b9
      KeyName: "honda"
      SecurityGroupIds:
        - !Ref BastionSecurityGroup
      Tags:
        - Key: Name
          Value: "本番サーバー"
```

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

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)

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

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

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

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

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3662571/e21b817f-91e6-bdc9-cb1d-da287de9d376.png)

## 踏み台サーバーから本番サーバーにSSH接続してみた

CloudFormationでスタックを実行後、EC2サービス画面で踏み台サーバーと本番サーバーが作成されたことを確認しました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3662571/0f57073d-2255-7f53-2baf-785ea345677b.png)

パブリックサブネットの踏み台サーバーにはパブリックIPが付与されていないため、Elastic IPアドレスを付与する必要があります（詳細な手順は省略します）。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3662571/6040e830-8e73-1e09-8592-e7e0ad573f83.png)

ここでは、EC2インスタンスコネクトを使用して接続します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3662571/31e686ef-be3e-95f8-5726-6e8535010b27.png)

プライベートサブネットの本番サーバーにSSH接続するには、pemファイルが必要です。

そこで、CloudShellを使用し、ローカルにダウンロード済みのpemファイルをサーバーに配置します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3662571/d434155f-7ffe-843d-7ffc-b46129614135.png)

まず、マネジメントコンソール画面左下からCloudShellを開き、ファイルのアップロードをクリックしてCloudShell上に配置します。

その後、以下のコマンドで踏み台サーバーにpemファイルをコピーします。

```bash
 sudo scp -i honda.pem honda.pem ec2-user@ec2-54-95-224-16.ap-northeast-1.compute.amazonaws.com:~
```

次に、以下のコマンドでpemファイルのアクセス権を設定します。

```bash
chmod 400 "honda.pem"
```

最後に、以下のSSHコマンドでプライベートサブネットの本番サーバーに接続します。

```bash
ec2-54-95-224-16.ap-northeast-1.compute.amazonaws.com
```

実際に、踏み台サーバー経由で本番サーバーへのログインが成功しました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3662571/3bf8693c-a1e3-211a-dc99-91677e6dd26a.png)

## まとめ

本記事では、CloudFormationを用いてプライベートサブネットに本番サーバーを追加し、踏み台サーバー経由でのアクセスを実現しました。

この構成により、外部からの直接アクセスを制限し、安全な環境で本番サーバーを管理できるようになりました。

次回は、この構成に基づく運用上の注意点やセキュリティ対策についてさらに深掘りしていきます。

## 関連記事

今回と似た記事として、インフラのコード化に関する記事も参考にしてみてください。

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

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

https://qiita.com/free-honda/items/4cadfd1bafa6c71c85fb