🔒

PrivateEC2 + EC2 Instance Connect EndpointをCDKで構築

2024/01/14に公開1

はじめに

諸事情でネットワークから切り離したEC2を用意する必要があり、マネジメントコンソールから用意するのは面倒だったためCDKで作成したので備忘録として残します。

EC2 Instance Connect Endpointとは

https://docs.aws.amazon.com/ja_jp/AWSEC2/latest/UserGuide/connect-with-ec2-instance-connect-endpoint.html
PrivateSubnetに配置したEC2に鍵設定なしでマネジメントコンソールからアクセスできる仕組み。PublicIpv4(お金かかるようになりますし…)も不要、踏み台も不要なので簡単な操作なら非常に役立ちます。

ソースコード

スタック全体
import { Stack, StackProps } from "aws-cdk-lib";
import {
  CfnInstanceConnectEndpoint,
  Instance,
  InstanceClass,
  InstanceSize,
  InstanceType,
  MachineImage,
  Port,
  SecurityGroup,
  SubnetType,
  Vpc,
} from "aws-cdk-lib/aws-ec2";
import { Construct } from "constructs";

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

    // VPC
    const vpc = new Vpc(this, "VPC", {
      maxAzs: 1,
      subnetConfiguration: [
        {
          name: "private",
          subnetType: SubnetType.PRIVATE_ISOLATED,
        },
      ],
    });

    // SecurityGroup
    const instanceSG = new SecurityGroup(this, "InstanceSG", {
      vpc,
    });

    const eicSG = new SecurityGroup(this, "EICSG", {
      vpc,
      allowAllOutbound: false,
    });

    instanceSG.addIngressRule(eicSG, Port.tcp(22));
    eicSG.addEgressRule(instanceSG, Port.tcp(22));

    // EC2Instance
    new Instance(this, "Instance", {
      vpc,
      instanceType: InstanceType.of(InstanceClass.T2, InstanceSize.MICRO),
      machineImage: MachineImage.latestAmazonLinux2023(),
      securityGroup: instanceSG,
    });

    // EC2EIP
    new CfnInstanceConnectEndpoint(this, "EIP", {
      subnetId: vpc.selectSubnets({ subnetType: SubnetType.PRIVATE_ISOLATED }).subnetIds[0],
      securityGroupIds: [eicSG.securityGroupId],
    });
  }
}

VPC

VPCには今回プライベートのみ用意します。

const vpc = new Vpc(this, "VPC", {
  maxAzs: 1,
  subnetConfiguration: [
    {
      name: "private",
      subnetType: SubnetType.PRIVATE_ISOLATED,
    },
  ],
});

Security Group

EC2とEndpointが疎通できるようにセキュリティグループを設定します。

// SecurityGroup
const instanceSG = new SecurityGroup(this, "InstanceSG", {
  vpc,
});

const eicSG = new SecurityGroup(this, "EICSG", {
  vpc,
  allowAllOutbound: false,
});

instanceSG.addIngressRule(eicSG, Port.tcp(22));
eicSG.addEgressRule(instanceSG, Port.tcp(22));

EC2 Connect Instance Endpoint

EC2 Connect Instance EndpointはL1で提供してくれているのでそちらを使います。
https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_ec2.CfnInstanceConnectEndpoint.html

new CfnInstanceConnectEndpoint(this, "EIP", {
  subnetId: vpc.selectSubnets({ subnetType: SubnetType.PRIVATE_ISOLATED }).subnetIds[0],
  securityGroupIds: [eicSG.securityGroupId],
});

マネジメントコンソールで確認

デプロイが完了したら、AWSマネジメントコンソールを開いて確認していきます。

VPC

PrivateSubnetがあり、ルートテーブルが存在しないVPCを確認。(外部への接続がない状態)

EC2

プライベートサブネットに配置されたEC2が起動されていることを確認。

VPCエンドポイント

EC2 Instance Connect Endpointが作成されていることを確認。

接続確認

作成されたEC2から接続を試みます。
EC2 Instance Connect エンドポイントを使用して接続するを選択してエンドポイントを指定します。作成したエンドポイントが表示されることを確認し選択します。

接続を試みてシェルが立ち上がれば成功です。

まとめ

踏み台の用意や、鍵を用意してグローバルIPを調べて絞るなど昔はやること多かったのですが、非常に楽になりました。ただ、マネジメントコンソールからのみとなるのでマクロを実行したい場合などは今まで通りの手順は必要そうです。
とはいえ簡単な確認作業とかなら便利なので今後も機会があれば使っていこうと思います。
https://github.com/tHyt-lab/ec2-eic-cdk

Discussion

WinterYukkyWinterYukky

ただ、マネジメントコンソールからのみとなるのでマクロを実行したい場合などは今まで通りの手順は必要そうです。

実は OpenSSH コマンドなどで EC2 Instance Connect Endpoint を利用する方法があります。
公式ドキュメントにも記載されていて、要は以下のような ssh config を設定すると接続できます。

~/.ssh/config
Host my-server
  User ec2-user
  HostName i-0123456789example
  IdentityFile my-key-pair.pem
  ProxyCommand aws ec2-instance-connect open-tunnel --instance-id %h

この情報がお役に立てば何よりです!