CDKで作成した踏み台サーバにAWS SSM Session Manager と AWS Instance Connect を利用してSSH接続する

## TL;DR
- CDK を利用すると踏み台サーバが超簡単に作成できる
- CDK の踏み台サーバにはkeypair設定ができないが、EC2 Instance Connect を利用することで Session Manager 経由のSSH接続を実行できる
- 上記設定は `.ssh/config` に記載し実行できるのですごく便利

以下、一つずつ要素を説明していきます。

## CDKにはEC2だけではなく、踏み台サーバ専用のConstructがある
CDKをちょっと触る始めようか、というところでとりあえずEC2の踏み台サーバくらいの、簡単なお題から作ってみようかなと思いドキュメントをあさっていたところ、なんと以下のようなConstructがあるのに気づきました

- [@aws-cdk_aws-ec2.BastionHostLinux](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-ec2.BastionHostLinux.html)

BastionHostLinux!! はい、そのものずばりの踏み台サーバです。
そして説明をよく見てみると、

> This creates a linux bastion host you can use to connect to other instances or services in your VPC.
> The recommended way to connect to the bastion host is by using AWS Systems Manager Session Manager.
> The operating system is Amazon Linux 2 with the latest SSM agent installed
> You can also configure this bastion host to allow connections via SSH

つまるところ、Session Manager周りの設定がすでにビルドインされたEC2インスタンスということです！これは必要十分ですね。というわけで以下のようにサクッとCDKで踏み台を建てることが出来ます。

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

    // VPCは既存のものを引っ張ってくる想定
    const vpc = ec2.Vpc.fromLookup(this, "VPC", { vpcId: "your-vpc-id" })

    const bastionSg = new ec2.SecurityGroup(this, "from-bastion", {
      securityGroupName: "from-bastion",
      vpc: vpc
    });

    const allowFromBastionSg = new ec2.SecurityGroup(this, "allow-from-bastion", {
      securityGroupName: "allow-from-bastion",
      vpc: vpc
    });

    allowFromBastionSg.addIngressRule(
      bastionSg,
      ec2.Port.tcp(22)
    );

    const bastion = new ec2.BastionHostLinux(this, "bastion", {
      vpc: vpc,
      instanceName: "bastion",
      instanceType: new ec2.InstanceType("t3.micro"),
      securityGroup: bastionSg
    });

  }
}

```

この設定だけで、AWS SSM Session Manager 経由でシェル接続が可能になります。素晴らしい！！

## CDKの踏み台EC2にはKeyPair設定がない
さて、Session Managerへのシェル接続は実現できましたが、これはまだSSH接続ではありません。シェルでも大半の用途は可能ですが、例えばRDBへの接続への踏み台など、SSH接続が必要な場面は結構あります。また最近は[Visual Studio Remote Developmentへの接続](https://qiita.com/dairappa/items/8347fe7faf89b6ba364e)もありますしね。

というわけでKeypairも設定したいわけですが、ドキュメントを読んでもKeyPairを設定する方法の記載がありません。通常のEC2にはあるので、これは踏み台としての特性上あえて落としているんでしょう。

しかし、SSHするにはKeyPairは必須です。どうするべきか、と思っていれば公式のissueに対応案がありました。

- https://github.com/aws/aws-cdk/issues/5264#issuecomment-560128105

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/447106/052642d2-01cb-dfed-e40c-8d01e601e6cc.png)


あ、なるほど、Instance Connectね。

EC2 Instance Connect は、Session Managerとは別アプローチでSSH接続を行う仕組みです。コマンドを直接実行するには、EC2インスタンスに直接SSH接続できるような接続設定になっている必要があり、要はPrivate Subnetには踏み台を置けないなど、Session Manager と比べるとちょっと使いづらいのでこれまで使ってはいませんでした。

ただ、EC2 Instance Connect には面白い仕組みがあり、KeyPairを設定していないEC2にもSSH接続ができるんですよね。これはどういうことかというと、AWS CLIを経由して、ローカルの公開鍵をEC2に送りつけるAPIがあるのです。ちなみに送りつけたキーペアは短期間で無効になります。

そう、Instance Connect自体はちょっと使いづらいのですが、この「KeyPairの送信機能」をうまく使えば、現在の課題である、Session ManagerのKeyPair問題にうまく対処できるのです。

具体的には、まずは以下のようなSession Managerに対応したssh configを用意します。

```ssh
Host %YOUR_SERVER_NAME%
    User ec2-user
    HostName %YOUR_INSTANCE_ID%
    Port 22
    ProxyCommand sh -c "aws ssm start-session --target %h --document-name AWS-StartSSHSession --parameters 'portNumber=%p' --profile %YOUR_AWS_PROFILE%"
    IdentityFile "path/to/your/ssh/key"
```

単純にこれを実行するだけでは、当然KeyPairエラーになりますが、ここで自分の公開鍵を以下のようにEC2に送りつけてやります。

```sh
aws ec2-instance-connect send-ssh-public-key \
    --instance-id your-instance-id \
    --availability-zone your-az-id \
    --instance-os-user ec2-user \
    --ssh-public-key file:///path/to/your/ssh/pub/key
```

上記のコマンドを実行後にssh接続を行うことで、無事にKeyPairの登録なしにSSH接続が可能になります。

## どうせなら1コマンドでやりたいよね
さて、これらのコマンドを1箇所に集めることはできそうでしょうか？具体的には**ssh configに全部書きたい**ですね。やってみましょう。

と言っても難しいことは特になく、ProxyCommandの中でInstance ConnectのAPIを叩いてやればいいのです。
ちょっと長くなりますが、以下のようにしちゃえば大丈夫です。なお、キーペアはデフォルトで使われる `id_rsa` を利用する前提で、IdentityFileの記述も削除しています。


```ssh
Host %YOUR_SERVER_NAME%
    User ec2-user
    HostName %YOUR_INSTANCE_ID%
    Port 22
    ProxyCommand sh -c "aws ec2-instance-connect send-ssh-public-key  --instance-id %h --availability-zone your-az-id --instance-os-user ec2-user --ssh-public-key file://~/.ssh/id_rsa.pub && aws ssm start-session --target %h --document-name AWS-StartSSHSession --parameters 'portNumber=%p'"
```

というわけで、これでMac/Linuxであれば、任意の環境への接続が以下の状況でできるようになります。

- Private Subnet下、ポート開放なし
- KeyPair設定無し
- SSM SessionManagerは導入済み（ただしCDKのデフォルトでOK）

Windowsはまた後ほど、、、