EC2 Instance Connect EndpointをCDKで使い倒す

はじめに

EC2 Instance Connect Endpoint(EIC Endpoint)使っていますか? 無料で踏み台ホストを撲滅できる画期的なサービスです。

docs.aws.amazon.com

これをCDKで簡単に作成できるようになったので、そのご紹介です。

ネタバレ

EC2への管理通信はもちろんのこと、RDS(Aurora)への管理通信も確立できます。 めちゃめちゃ便利だと思うので、ぜひお試しください。

下準備

EIC Endpointは公式L2コンストラクトが存在しませんが、コミュニティ主導でのConstruct libraryであるopen-constructsにL2が存在しています。 そこで、CDKプロジェクトをセットアップし、open-constructsをインストールして使っていきます。

npx cdk init --language=typescript
npm install @open-constructs/aws-cdk

ユースケース

EC2 instanceへの管理通信

プライベートサブネットにEC2インスタンスを設置し、SSHでの管理通信をEIC Endpoint経由で実行してみます。

EIC Endpointを経由したSSH接続

CDKでの実装例

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

    // NATGWやIGWも不要です。適宜削除してください。
    const vpc = new ec2.Vpc(this, 'VPC')

    // EC2 instance
    const insntance = new ec2.Instance(this, 'Instance', {
      vpc,
      instanceType: ec2.InstanceType.of(ec2.InstanceClass.T2, ec2.InstanceSize.MICRO),
      machineImage: new ec2.AmazonLinuxImage({
        generation: ec2.AmazonLinuxGeneration.AMAZON_LINUX_2023,
      })
    });

    // EIC EndpointのL2コンストラクト
    const eicEndpoint = new ocf.aws_ec2.InstanceConnectEndpoint(this, 'InstanceConnectEndpoint', {
      vpc,
    });

    // EIC Endpoint -> EC2 InstanceへのSecurity Groupの穴あけ
    eicEndpoint.connections.allowTo(insntance, ec2.Port.tcp(22));

    // Instance IDを出力
    new cdk.CfnOutput(this, 'InstanceId', {
      value: insntance.instanceId,
    });
  }
}

接続方法

上記CDKコードをデプロイ後、以下のコマンドを実行します。 instance idはcdk deploy時に出力されたものに修正してください。

$ aws ec2-instance-connect ssh --instance-id i-12345example --connection-type eice

The authenticity of host '10.0.0.1 (<no hostip for proxy command>)' can't be established.
ED25519 key fingerprint is SHA256:abcdefg.
This key is not known by any other names.
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Warning: Permanently added '10.0.0.1' (ED25519) to the list of known hosts.
   ,     #_
   ~\_  ####_        Amazon Linux 2023
  ~~  \_#####\
  ~~     \###|
  ~~       \#/ ___   https://aws.amazon.com/linux/amazon-linux-2023
   ~~       V~' '->
    ~~~         /
      ~~._.   _/
         _/ _/
       _/m/'
[ec2-user@ip-10-0-0-1 ~]$

さっくり繋がりました!最高ですね。

RDS(Aurora)への管理通信

続いて、プライベートサブネットにAurora(mysql)を設置し、EIC Endpoint経由で管理通信を行ってみます。 mysqlのデフォルトポートは3306なのでEIC Endpointは非対応ですが、DBがlistenするポートを3389に変える荒業で切り抜けてみたいと思います。

EIC Endpointを経由したDB接続

CDKでの実装例

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

    const vpc = new ec2.Vpc(this, 'VPC')

    // Aurora cluster
    const auroraCluster = new rds.DatabaseCluster(this, 'Aurora', {
      engine: rds.DatabaseClusterEngine.auroraMysql({
        version: rds.AuroraMysqlEngineVersion.VER_3_06_0,
      }),
      // 【重要】EIC Endpointでアクセスするため、ポートを3389に変更
      port: 3389,
      // パスワードハードコーディングの駄目な例です。本番ではrds.Credentials.fromGeneratedSecret('hogeuser')などを使ってください
      credentials: rds.Credentials.fromPassword('admin', cdk.SecretValue.unsafePlainText('testPass')),
      vpc,
      writer: rds.ClusterInstance.serverlessV2('writer'),
    });

    const eicEndpoint = new ocf.aws_ec2.InstanceConnectEndpoint(this, 'InstanceConnectEndpoint', {
      vpc,
    });

    // EIC Endpoint -> Aurora Cluster へのSecurity Groupの穴あけ
    eicEndpoint.connections.allowTo(auroraCluster, ec2.Port.tcp(3389));

    // DB Endpoint (プライベートIPの取得に用います)
    new cdk.CfnOutput(this, 'AuroraEndpoint', {
      value: auroraCluster.clusterEndpoint.hostname,
    });

    new cdk.CfnOutput(this, 'EicEndpointId', {
      value: eicEndpoint.instanceConnectEndpointId,
    });
  }
}

接続方法

まず、DBのプライベートIPを取得します。パブリックDNSサーバ上での名前解決で問題ありません。

$ nslookup hogehoge.cluster-cvekcubvryhp.ap-northeast-1.rds.amazonaws.com
Server:         8.8.8.8
Address:        8.8.8.8#53

Non-authoritative answer:
 hogehoge.cluster-cvekcubvryhp.ap-northeast-1.rds.amazonaws.com   canonical name =  hogehoge.cluster-cvekcubvryhp.ap-northeast-1.rds.amazonaws.com.
Name:    hogehoge.cluster-cvekcubvryhp.ap-northeast-1.rds.amazonaws.com
Address: 10.0.198.63 // こちら

続いて、SSHトンネルを張ります。以下の通り、パラメータを指定してください。

パラメータ名 value
--private-ip-address DBのプライベートIP
--instance-connect-endpoint-id EIC EndpointのID
--local-port ローカルでlistenするポート (何でもOK)
--remote-port DBサーバがlistenするポート(3389)
$ aws ec2-instance-connect open-tunnel --instance-connect-endpoint-id eice-hogehoge --private-ip-address 10.0.198.63 --local-port 3306 --remote-port 3389
Listening for connections on port 3306.

無事にトンネルが張れました!上記のコマンドは動かしたまま、適当なDBクライアントツールでlocalhost:3306に接続してみましょう。

今回はSequel Aceで接続してみます。クレデンシャルはCDKで指定したusername:admin, pass: testPassです。

接続設定
接続結果

いけました〜!最高ですね。

これはSession Managerではできないので、大きな差別化ポイントだと思います。

使用上の注意

EIC Endpoint経由でのアクセス時の権限

ユーザに特定の権限が必要です。正確な情報は公式ドキュメントを参照してください。

docs.aws.amazon.com

これを活用することで、特定のユーザにのみアクセス権限を与えることができます。IAMのお陰で認可管理もラクラクですね!

DBアクセスはEIC Endpointのユースケースに含まれていますか?

わかりません。多分グレーゾーンです。

当初EIC Endpointのサービスリリース時には全portが開放されていましたが、すぐに22, 3389以外closeされてしまいました。したがって、AWS的には大人しくSSH, RDPでだけ使ってほしいのだと思っています。

現状、EIC Endpointはあくまでポート番号だけ(L4レイヤだけ)で通信の許可/拒否を判定しています。 更にL7レイヤのペイロードまで深堀って許可/拒否の判定は流石にしないと思うのですが、、そもそも技術的にはできるのでしょうか...??詳しい人教えて下さい...

ということで、動きはしますが、自己責任でお願いいたします。

その他のVPCリソースへのアクセスもできますか?

VPC内のプライベートIPとポート番号さえ決まればイケルはずです。

最後に

実はこのL2コンストラクトは私がPRを作成しました!

github.com

open-constructsにはまだ2つしかL2が無いので、コントリビュートチャンスが無限に眠っています。 皆様もぜひチャレンジください〜!