AWS で IPv６アドレスの検証環境を整える (脱パブリックIPv4)

## はじめに

AWS のパブリック IPv4 アドレスが有償化になりましたが、
検証でなるべくお金をかけたくなかったので、IPv6 を使用する検証環境用の CFn を作成しました。
本記事は、その IPv6 の検証環境の使い方(IPv6アドレスを設定したEC2への接続方法)について解説した記事になります。

## 全体図

![Untitled Diagram-Page-2.drawio.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/317229/7d48b0f2-3938-abce-35aa-a8e1c7f277c2.png)


上記が本 CFn で作成する検証環境のイメージになります。
インスタンスの図を記載してますが、本 CFn を使用してもインスタンスは構築されません。
※インスタンスへの接続方法を記載する都合上、図があった方がわかりやすいため、インスタンスの図を記載しています。

イメージ図の中で重要なサービスについて、簡単に記載します。

- EC2 Instance Connect Endpoint
  - パブリック IPv4 アドレスがないインスタンスに接続できる機能。
  - プライベートサブネットの EC2 にも接続可能
  - 使用料金がなんと**無料!**
- Internet gateway
  - IPv6 のインスタンスでインターネットからの Ingress(受信)、Egress(送信)用に使用
- Egress Only インターネットゲートウェイ
  - プライベートサブネットに配置しているインスタンスの Egress(送信)用に使用。簡単に説明すると NAT ゲートウェイの IPv6 バージョン。
  - **追加費用なし！**

プライベートサブネットでは Egress Only インターネットゲートウェイが、
パブリックサブネットではインターネットゲートウェイのルートが設定してありますので
どちらのサブネットでインスタンスを立てた場合でも、インターネットへは接続できる状態です。

## CFn

本検証環境の構築で使用する CFn はこちらになります。

<details><summary>CFnコード</summary>


```shell
AWSTemplateFormatVersion: "2010-09-09"
Description: "IPv6 VPC"
Metadata:
  AWS::CloudFormation::Interface:
    ParameterGroups:
      - Label:
          default: "Network Configuration"
        Parameters:
          - CIDR
      - Label:
          default: "Normal Configuration"
        Parameters:
          - Prefix
          - Regiton

Parameters:
  Prefix:
    Type: String
    Default: test-Ipv6
  CIDR:
    Description: Please type the CidrBlock.
    Type: String
    Default: 192.168.0.0/16

Resources:
  # ------------------------------------------------------------#
  #  VPC
  # ------------------------------------------------------------#
  Vpc:
    Type: AWS::EC2::VPC
    Properties:
      CidrBlock: !Ref CIDR
      EnableDnsHostnames: true
      EnableDnsSupport: true
      Tags:
        - Key: Name
          Value: !Sub ${Prefix}-vpc
  # ------------------------------------------------------------#
  # IPv6 Cidr
  # ------------------------------------------------------------#
  VpcIpv6:
    Type: AWS::EC2::VPCCidrBlock
    Properties:
      AmazonProvidedIpv6CidrBlock: true
      VpcId: !Ref Vpc

  # ------------------------------------------------------------#
  #  Subnets
  # ------------------------------------------------------------#
  PublicSubnet1a:
    DependsOn: VpcIpv6
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref Vpc
      AvailabilityZone: !Sub "${AWS::Region}a"
      CidrBlock: !Select [0, !Cidr [!GetAtt Vpc.CidrBlock, 2, 8]] # 192.168.0.0/24
      AssignIpv6AddressOnCreation: true
      Ipv6CidrBlock:
        !Select [0, !Cidr [!Select [0, !GetAtt Vpc.Ipv6CidrBlocks], 2, 64]]
      Tags:
        - Key: Name
          Value: !Sub ${Prefix}-subnet-public-a

  PrivateSubnet1a:
    DependsOn: VpcIpv6
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref Vpc
      AvailabilityZone: !Sub "${AWS::Region}a"
      CidrBlock: !Select [1, !Cidr [!GetAtt Vpc.CidrBlock, 2, 8]]
      AssignIpv6AddressOnCreation: true
      Ipv6CidrBlock:
        !Select [1, !Cidr [!Select [0, !GetAtt Vpc.Ipv6CidrBlocks], 2, 64]]
      Tags:
        - Key: Name
          Value: !Sub ${Prefix}-subnet-private-a

  # ------------------------------------------------------------#
  #  Security Groups
  # ------------------------------------------------------------#
  EICSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      VpcId: !Ref Vpc
      GroupDescription: EIC SG
      GroupName: !Sub ${Prefix}-eic-sg
      Tags:
        - Key: Name
          Value: !Sub ${Prefix}-eic-sg

  EICSecurityGroupEgress1:
    Type: AWS::EC2::SecurityGroupEgress
    DependsOn: Ec2SecurityGroup
    Properties:
      Description: EC2 ssh egress
      IpProtocol: tcp
      GroupId: !Ref EICSecurityGroup
      FromPort: 22
      ToPort: 22
      DestinationSecurityGroupId: !Ref Ec2SecurityGroup

  EICSecurityGroupEgress2:
    Type: AWS::EC2::SecurityGroupEgress
    DependsOn: Ec2SecurityGroup
    Properties:
      Description: EC2 rdp egress
      IpProtocol: tcp
      GroupId: !Ref EICSecurityGroup
      FromPort: 3389
      ToPort: 3389
      DestinationSecurityGroupId: !Ref Ec2SecurityGroup

  Ec2SecurityGroup:
    DependsOn: EICSecurityGroup
    Type: AWS::EC2::SecurityGroup
    Properties:
      VpcId: !Ref Vpc
      GroupDescription: EC2 SG
      GroupName: !Sub ${Prefix}-ec2-sg
      SecurityGroupIngress:
        - IpProtocol: tcp
          FromPort: 22
          ToPort: 22
          SourceSecurityGroupId: !Ref EICSecurityGroup
        - IpProtocol: tcp
          FromPort: 3389
          ToPort: 3389
          SourceSecurityGroupId: !Ref EICSecurityGroup
      SecurityGroupEgress:
        - IpProtocol: -1
          DestinationSecurityGroupId: !Ref EICSecurityGroup
        - IpProtocol: -1
          CidrIp: 0.0.0.0/0
        - IpProtocol: -1
          CidrIpv6: ::/0
      Tags:
        - Key: Name
          Value: !Sub ${Prefix}-ec2-sg

  # ------------------------------------------------------------#
  #  EC2 Instance Connect Endpoint
  # ------------------------------------------------------------#
  EicEndpointPrivate:
    Type: AWS::EC2::InstanceConnectEndpoint
    DependsOn: EICSecurityGroup
    Properties:
      SubnetId: !Ref PrivateSubnet1a
      SecurityGroupIds:
        - !Ref EICSecurityGroup

  # ------------------------------------------------------------#
  #  Internet Gateway
  # ------------------------------------------------------------#
  InternetGateway:
    Type: AWS::EC2::InternetGateway

  InternetGatewayAttachment:
    Type: AWS::EC2::VPCGatewayAttachment
    Properties:
      VpcId: !Ref Vpc
      InternetGatewayId: !Ref InternetGateway

  # ------------------------------------------------------------#
  #  Egress Only Internet Gateway
  # ------------------------------------------------------------#
  EgressOnlyInternetGateway:
    Type: AWS::EC2::EgressOnlyInternetGateway
    Properties:
      VpcId: !Ref Vpc
  # ------------------------------------------------------------#
  #  Route Tables
  # ------------------------------------------------------------#
  PublicRouteTable:
    Type: "AWS::EC2::RouteTable"
    Properties:
      VpcId: !Ref Vpc
      Tags:
        - Key: Name
          Value: !Sub ${Prefix}-public-rtb

  PrivateRouteTable:
    Type: "AWS::EC2::RouteTable"
    Properties:
      VpcId: !Ref Vpc
      Tags:
        - Key: Name
          Value: !Sub ${Prefix}-private-rtb

  # ------------------------------------------------------------#
  #  Subnet Route Table Associations
  # ------------------------------------------------------------#
  PublicSubnet1aRouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      SubnetId: !Ref PublicSubnet1a
      RouteTableId: !Ref PublicRouteTable

  PrivateSubnet1aRouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      SubnetId: !Ref PrivateSubnet1a
      RouteTableId: !Ref PrivateRouteTable

  # ------------------------------------------------------------#
  #  Routing
  # ------------------------------------------------------------#
  PublicRoute:
    Type: AWS::EC2::Route
    Properties:
      RouteTableId: !Ref PublicRouteTable
      DestinationIpv6CidrBlock: ::/0
      GatewayId: !Ref InternetGateway

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

  PrivateRoute1:
    Type: AWS::EC2::Route
    Properties:
      RouteTableId: !Ref PrivateRouteTable
      DestinationIpv6CidrBlock: ::/0
      EgressOnlyInternetGatewayId: !Ref EgressOnlyInternetGateway

Outputs:
  VpcId:
    Description: The VPC ID
    Value: !Ref Vpc
    Export:
      Name: VpcId

  PublicSubnetId:
    Description: The public subnet ID
    Value: !Ref PublicSubnet1a
    Export:
      Name: PublicSubnetId

  PrivateSubnetId:
    Description: The private subnet ID
    Value: !Ref PrivateSubnet1a
    Export:
      Name: PrivateSubnetId

  EICSecurityGroupId:
    Description: The EIC SG ID
    Value: !Ref EICSecurityGroup
    Export:
      Name: EICSecurityGroupId

  Ec2SecurityGroupId:
    Description: The EC2 SG ID
    Value: !Ref Ec2SecurityGroup
    Export:
      Name: Ec2SecurityGroupId

```
</details>


### CFnのパラメータ
CFnのパラメータについて簡単に説明します。
- CIDR
    プライベートIPv4のCIDR範囲。デフォルトで192.168.0.0/16を設定されている。
- Prefix
    各リソース名の接頭辞。Nameタグなどで使用する。
- Regiton
    構築するリージョン先。本CFnのデフォルト値は、東京リージョンで構築するよう設定しています。


## 本検証環境に起動したEC2への接続方法

### ① EC2 Instance Connect Endpoint を利用して、パブリック IPv4 がない Linux インスタンスに接続する。

1. AWS マネジメントコンソール等から、CFn で作成した環境内に Linux インスタンスを起動する。

   - VPC: CFn で作成した VPC を選択
   - サブネット： プライベート、パブリックどちらのサブネットでも可
   - キーペア：適宜設定。
   - パブリック IP の自動割り当て： 無効
   - IPv6IP を自動で割り当てる： 有効化
   - セキュリティグループ: [『Prefix}-ec2-sg]を選択

2. インスタンス起動後、[インスタンス]から起動したインスタンスを選択し、[接続]を選択する。
3. [EC2 Instance Connect]タブを選択し、[EC2 Instance Connect エンドポイントを使用して接続する]を選択し、[接続]を選択する。
   EIC エンドポイントが選択されていない場合は、CFn で作成したエンドポイントを選択する。
![スクリーンショット 2024-03-03 8.36.53.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/317229/c4cc7391-b799-cfe3-141b-8513fa129102.png)


パブリック IPv4 がなくても Linux に接続することができました！また、インターネットへの接続も問題なさそうです。
![スクリーンショット 2024-03-03 8.41.08.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/317229/fd2bc1db-15ec-92e4-f995-3f98559529db.png)

ちなみにですが、EIC エンドポイントと AWS CLI を使用することによって、ローカル環境から ssh ができるようになります。
以下が接続する際のコマンドになります。IP アドレスを指定せずともインスタンス ID の指定だけで接続できるようです。

```shell
ssh -i my-key-pair.pem ec2-user@{instance_id} \
    -o ProxyCommand='aws ec2-instance-connect open-tunnel --instance-id {instance_id}'
```

こちらについても無事接続できました！
![スクリーンショット 2024-03-03 8.54.24.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/317229/809f169c-4dca-678e-0249-709a25e79564.png)


### ② EC2 Instance Connect Endpoint を利用して、パブリック IPv4 がない Windows インスタンスに RDP 接続する。

:::note warn
警告
本手順は AWS CLI を設定する必要がありますので、ご注意ください。
:::


1. AWS マネジメントコンソール等から、CFn で作成した環境内に Windows インスタンスを起動する。

   - VPC: CFn で作成した VPC を選択
   - サブネット： プライベート、パブリックどちらのサブネットでも可
   - キーペア：適宜設定。
   - パブリック IP の自動割り当て： 無効
   - IPv6IP を自動で割り当てる： 有効化
   - セキュリティグループ: [{Prefix}-ec2-sg]を選択

2. 以下のコマンドを実行し、WebSocket のトンネルを張る。

   ```shell
   aws ec2-instance-connect open-tunnel --instance-id {instance_id} --remote-port 3389 --local-port {任意のポート}
   ```

3. 手順 2 実行後、[Listening for connections on port <任意のポート>]が表示されることを確認する。

4. RDP ソフトを起動し、[localhost:<任意のポート>]に接続する。

EIC エンドポイントを利用して、RDP 接続できました！
![スクリーンショット 2024-03-03 9.23.50.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/317229/aededd19-a0c4-5b24-51a6-1eb1c4386df0.png)


## まとめ

パブリック IPv4 アドレスを使用せずとも、Linux、Windows に接続できるようになりました。
また、IPv6アドレスを設定したインスタンスの外部へ通信も、設定することができました。
これで、パブリック IPv4 とはお別れすることができそうです。

## 参照

- [Egress-Only インターネットゲートウェイの基本](https://docs.aws.amazon.com/ja_jp/vpc/latest/userguide/egress-only-internet-gateway.html#egress-only-internet-gateway-basics)
- [EC2 Instance Connect Endpoint を使用して Linux インスタンスに接続する](https://docs.aws.amazon.com/ja_jp/AWSEC2/latest/UserGuide/connect-using-eice.html)