AWS CDKでIPv6オンリーなVPCとEC2を作成してみたけど、なかなか厳しいものがある

AWSのパブリックIPv4アドレスの有料化されたので、無駄なお金は使いたくないなーと思って、パブリックIPv4は持たずにIPv6を持つ検証用のEC2をCDKで作りました。 作ってみたものの、IPv4アドレスが無いのはやっぱり苦しい。
2024.03.08

AWSのパブリックIPv4アドレスの有料化されたので、無駄なお金は使いたくないなーと思って、パブリックIPv4は持たずにIPv6を持つ検証用のEC2を作りたいなーと思っていました。

僕はAWS CDKが好きなので、AWS CDKでIPv6なVPCとEC2を作成します。

以前調べたときは、AWS CDKではまだIPv6に対応していなかったのですが、バージョン v2.122.0 で対応したようです。やったぜ。

Features

ec2: add dual stack vpc support (#28480) (caf83f1), closes #894

どうせならSSMセッションマネージャーでEC2に接続できたら便利じゃん?とも思いましたが、現時点(2024/03/08)ではIPv6に対応していないようです。残念。

詳しくはこちらのブログを御覧ください。

今回は、SSHでEC2に直接接続する方針で、IPv6オンリーなEC2を作成します。

前提条件

AWS CDKはv2を使用します。 v2.122.0 以降であれば動くと思います。

$ cdk --version
2.131.0 (build 92b912d)

CDKでIPv6なVPCとEC2を作成する

TypeScriptのcdkでIPv6なVPCとEC2を作成するコードは次の通りです。

cdk.ts

import { Construct } from "constructs";
import * as cdk from "aws-cdk-lib";
import * as ec2 from "aws-cdk-lib/aws-ec2";
import * as iam from "aws-cdk-lib/aws-iam";

const myKeyPairName: string = "<<YOU_KEY_PAIR_NAME>>";
const myIpV6Cidr: string = "<<YOUR_IPv6_CIDR_BLOCK>>";

export class SampleStack extends cdk.Stack {
  constructor(scope: Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    const vpc = new ec2.Vpc(this, "MyVpc", {
      natGateways: 0,
      maxAzs: 1,
      ipProtocol: ec2.IpProtocol.DUAL_STACK,
      subnetConfiguration: [
        {
          name: "PublicSubnet",
          subnetType: ec2.SubnetType.PUBLIC,
        },
      ],
    });

    const instance = new ec2.Instance(this, "MyInstance", {
      vpc,
      instanceType: ec2.InstanceType.of(
        ec2.InstanceClass.T3A,
        ec2.InstanceSize.NANO,
      ),
      machineImage: ec2.MachineImage.latestAmazonLinux2023(),
      vpcSubnets: {
        subnetType: ec2.SubnetType.PUBLIC,
      },
      keyPair: ec2.KeyPair.fromKeyPairName(this, "MyKeyPair", myKeyPairName),
      allowAllIpv6Outbound: true,
      associatePublicIpAddress: false,
    });

    instance.connections.allowFrom(
      ec2.Peer.ipv6(myIpV6Cidr),
      ec2.Port.tcp(22),
      "Allow SSH access from my IPv6 CIDR",
    );

    instance.role.addManagedPolicy(
      iam.ManagedPolicy.fromAwsManagedPolicyName("ReadOnlyAccess"),
    );

    new cdk.CfnOutput(this, "GetIpv6AddressCommand", {
      value: `INSTANCE_ID=${instance.instanceId} aws ec2 describe-instances --instance-ids \${INSTANCE_ID} --query "Reservations[].Instances[].Ipv6Address" --output text`,
    });
  }
}

const app = new cdk.App();
new SampleStack(app, "SampleStack");

次のコマンドを実行してデプロイすると、IPv6なVPCとEC2が作成できます。簡単ですね。

$ cdk deploy SampleStack

ちょっとコードの解説します。

コピペして使う場合は 6,7行目 の、 myKeyPairName に自分で作成したキーペア名を指定してください。 myIpV6Cidr に自分のIPv6アドレスのCIDRを指定してください。

自分のIPv6アドレスがわからなかったら、次のような自分のIPv6アドレスを表示するサイトとか使ってください。

16行目 で、VPCの ipProtocolec2.IpProtocol.DUAL_STACK を指定しています。 こうすることでIPv4,IPv6どちらも持つVPCを作成できます。

EC2といっしょに作られるセキュリティグループは、デフォルトIPv6のアウトバウンドトラフィックを許可しません。

36行目 で、Instanceの allowAllIPv6Outboundtrue に設定することで、IPv6のアウトバウンドトラフィックを全て許可できます。

37行目 で、EC2はIPv4のパブリックIPアドレスは持ちたくないので、Instanceの associatePublicIpAddressfalse に設定しています。

40〜44行目 で、Instanceの connections.allowFrom を利用して、自分のIPv6アドレスからのSSH接続を許可しています。

50〜52行目 で、現状、CDK(CloudFormation)でEC2のIPv6アドレスを出力する方法がないので、苦肉の策としてAWS CLIでEC2のIPv6アドレスを取得するコマンドをエクスポートしています。

このcdkをデプロイすると、アウトプットとして、IPv6アドレスを取得するためのこんな感じのAWS CLIコマンドを出力します。

Outputs:
SampleStack.GetIpv6AddressCommand = INSTANCE_ID=<<インスタンスID>> aws ec2 describe-instances --instance-ids ${INSTANCE_ID} --query "Reservations[].Instances[].Ipv6Address" --output text

SSHでEC2に接続してみる

EC2が構築できたら、IPv6アドレスを調べてSSHで接続してみましょう。

EC2のIPv6アドレスは、エクスポートしたAWS CLIを実行すれば取得できます。

$ INSTANCE_ID=<<インスタンスID>> aws ec2 describe-instances --instance-ids ${INSTANCE_ID} --query "Reservations[].Instances[].Ipv6Address" --output text
2001:0db8:0000:0000:0000:0000:0000:0001

IPv6でも普通にSSHできました。 curl ifconfig.io で外部ネットワークに接続できることも確認できました。良いですね。

AWS CLIを使ってみる…?

ネットワークに繋がることが確認できたので、AWS CLIを使ってみます。 次のコマンドを実行してみると…、あれ?応答が返ってこないぞ。

$ aws ec2 describe-instances

Connect timeout on endpoint URL: "https://ec2.ap-northeast-1.amazonaws.com/"

なぜAWS CLIが使えないのか

普段意識していないので忘れがちなのですが、AWS CLIはAWSのWebAPIのラッパーとして動いています。

そのため、IPv6オンリーなEC2で使うためには、AWS APIがIPv6に対応している必要があります。

このAWS APIのURLは エンドポイント と呼ばれています。

AWS CLIを使う際、エンドポイントを指定しなければ、基本的に各サービスの リージョンエンドポイント が使われます。

そしていくつかのサービスは、 デュアルスタックエンドポイント という IPv4/IPv6両方に対応したエンドポイント を持っています。

そして、AWSサービスによって リージョンエンドポイントデュアルスタックエンドポイント異なる ものと、 同じ ものがあります。

例えば、EC2の場合、リージョンエンドポイントとデュアルスタックエンドポイントは 異なります

Secrets Managerの場合、リージョンエンドポイントとデュアルスタックエンドポイントは 同じ です。

IPv6に対応しているAWSサービスの詳細は、次のドキュメントで確認できます。

具体的にEC2のドキュメントを見ると、通常のリージョンエンドポイントは IPv4しかサポートしていない ことがわかります。だから、IPv6環境から繋がらなかったんですね…。

また、IPv6に対応したデュアルスタックエンドポイントを持っていることもわかります。 東京(ap-northeast-1)リージョンには無さそうですが…。

IPv6環境でAWS CLIを使ってみる

東京リージョンにデュアルスタックエンドポイントが無かったので、オレゴン(us-west-2)リージョンでEC2を再作成して試してみます。

普通にやると、やっぱり応答が返ってきません。

$ aws ec2 describe-instances

Connect timeout on endpoint URL: "https://ec2.us-west-2.amazonaws.com/"

オレゴンリージョンのデュアルスタックエンドポイントは ec2.us-west-2.api.aws です。

なので、AWS CLI実行時に --endpoint-url オプションでエンドポイントを指定すると、IPv6環境でもAWS CLIを使うことができます。

$ aws ec2 describe-instances --endpoint-url https://ec2.us-west-2.api.aws

ちなみに、AWS CLIの場合、環境変数や設定ファイルに次のような設定を追加することで、デフォルトでデュアルスタックエエンドポイントを使えます。

なので、こういうコマンドでも動きます。こっちの方が楽ですね。

$ export AWS_USE_DUALSTACK_ENDPOINT=true
$ aws ec2 describe-instances

AWS CLIの実行だけでも、IPv6オンリーだとちゃんとAWSサービスのエンドポイントがIPv6に対応しているのかとか、気にしなきゃいけないことが多いですね。厳しい。

まとめ

AWS CDKでIPv6なVPCとEC2を作成してみました。

作っては見たものの、IPv4だけ対応しているWebサービスも多いので、検証環境と言えどもIPv6オンリーな環境で動かすのは厳しいものがありますね。

まだまだ、IPv4のお世話になりそうです。