AWSでVPCに配置した踏み台サーバを通してRDSにポートフォワーディングしたメモ

## 概要
[前回](https://qiita.com/hibohiboo/items/9a6f6cf4b01927f670d0)はRDSと踏み台サーバを作成し、
SSMを使って踏み台サーバ上からのRDSへの接続を確認した。
今回は、ローカルのクライアントからポートフォワーディングしてRDSを操作することを試す。

[ソースコード](https://github.com/hibohiboo/aws-cdk-v2/tree/id/2)

## 環境
* Windows 10
* gitbash
* aws-cli/2.4.7 Python/3.8.8 Windows/10 exe/AMD64 prompt/off

## 検討

ぐぐったところ、ポートフォワーディングのアプローチは2つある模様。
鍵の管理が面倒そうなので、2の方法を行う。

1. セッションマネージャーを使用して SSH トンネルを開始
  * 「サーバに公開鍵の設定」 or 「EC2 InstanceConnectで一時的に公開鍵を設定」
1. ポートフォワーディング+TCPリレー
  * サーバにsocatのインストールが必要
  * 2時間でポートフォワーディングが切れる

[Session Managerを使ってEC2の先にあるRDSに接続してみた](https://dev.classmethod.jp/articles/try-port-forwarding-with-ssm/)を参考に設定していく。

## アーキテクチャ
まず、SSMの[Port Forwarding](https://aws.amazon.com/jp/blogs/news/new-port-forwarding-using-aws-system-manager-sessions-manager/1)だけでは、SSHトンネルのポートフォワーディングを行うことはできない。ポートフォワーディング先のEC2がリッスンしているポートのポートフォワーディングができるだけである。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/99876/622c72ac-4753-411b-d45e-d667e7bdd41d.png)

そのため、EC2でリッスンしたポートをRDSへTCPリレーするように設定する。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/99876/29008b1a-172c-540f-b579-cbcc828b877a.png)

## ec2にsocatのインストール

cdkを修正

```diff:cdk/lib/bastion-stack.ts
    host.instance.addUserData(
      'yum -y update',
      'yum install -y postgresql jq',
+      'yum install -y socat',
    );
```


## ec2でsocatを起動するSSMドキュメントの作成
踏み台サーバでsocatを有効にし、TCPリレーを行うためのコマンド。
[AWS Systems Manager ](https://docs.aws.amazon.com/ja_jp/systems-manager/latest/userguide/what-is-systems-manager.html)の[SSM ドキュメントスキーマ](https://docs.aws.amazon.com/ja_jp/systems-manager/latest/userguide/document-schemas-features.html)を定義して、起動の設定を行う。
### ドキュメントの作成

```yml:bin/command-document.yml
---
schemaVersion: "2.2"
description: Port Forwarding
parameters:
  localPort:
    type: String
    default: "5432"
mainSteps:
  - action: aws:runShellScript
    name: run
    inputs:
      runCommand:
        - secret=$(aws secretsmanager get-secret-value --region ap-northeast-1 --secret-id db-secrets | jq .SecretString | jq fromjson)
        - endpoint=$(echo $secret | jq -r .host)
        - targetPort=$(echo $secret | jq -r .port)
        - socat tcp4-listen:{{ localPort }},reuseaddr,fork TCP:$endpoint:$targetPort


```

```sh:bin/create-documnet.sh
#!/bin/bash
bin_dir=$(cd $(dirname $0) && pwd)
DOCUMENT_NAME=port-relay

# 再作成するときはいったん削除してから設定
# aws ssm delete-document \
#     --name ${DOCUMENT_NAME}

cd $bin_dir && aws ssm create-document \
    --content file://command-document.yml \
    --name ${DOCUMENT_NAME} \
    --document-type "Command" \
    --document-format "YAML"
```

### 踏み台サーバのTCPリレー設定

作成したSSMコマンドを実行する

```sh:bin/send-command.sh
#!/bin/bash
DOCUMENT_NAME='port-relay'
INSTANCE_ID=$( aws ec2 describe-instances --filter "Name=instance-state-name,Values=running" "Name=tag:Name,Values=BastionHost" --query "Reservations[].Instances[].InstanceId"  | jq -r '.[0]')

aws ssm send-command \
  --instance-ids ${INSTANCE_ID} \
  --document-name ${DOCUMENT_NAME}

```

### ポートフォワーディングの開始
[ポートフォワーディング](https://docs.aws.amazon.com/ja_jp/systems-manager/latest/userguide/session-manager-working-with-sessions-start.html#sessions-start-port-forwarding)を開始する。

```sh:bin/start-port-fowarding.sh
#!/bin/bash
LOCAL_PORT=15432
SECRET_NAME=db-secrets
INSTANCE_ID=$( aws ec2 describe-instances --filter "Name=instance-state-name,Values=running" "Name=tag:Name,Values=BastionHost" --query "Reservations[].Instances[].InstanceId"| jq -r '.[0]')
SECRET=$(aws secretsmanager get-secret-value --region ap-northeast-1 --secret-id $SECRET_NAME  | jq .SecretString | jq fromjson)
PASSWORD=$(echo $SECRET | jq -r .password)
USERNAME=$(echo $SECRET | jq -r .username)
PORT=$(echo $SECRET | jq -r .port)

# dbクライアントに設定する用の情報を表示
echo 'DB接続用情報(ポートフォワーディング)'
echo host= localhost
echo databasename= postgres
echo port= $LOCAL_PORT
echo username= $USERNAME
echo password= $PASSWORD

# ポートフォワーディング開始
aws ssm start-session \
    --target ${INSTANCE_ID} \
    --document-name AWS-StartPortForwardingSession \
    --parameters portNumber=$PORT,localPortNumber=$LOCAL_PORT
```

### 確認
使用するDBクライアントを通じて接続を確認する。
今回はa5SQLを利用した。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/99876/924c22ec-a69b-2791-e980-52dde741337c.png)


## 参考

### 公式
[AWS Systems Manager 経由で SSH トンネルを使用してプライベート VPC リソースにアクセスするにはどうすればよいですか?][*1]
[手順8 :(オプション）SessionManagerを介したSSH接続のアクセス許可の有効化と制御][*2]
[EC2 InstanceConnectを使用してLinuxインスタンスに接続](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/Connect-using-EC2-Instance-Connect.html)
[AWS System Manager Sessions Manager を使用した新しい機能 – Port Forwarding](https://aws.amazon.com/jp/blogs/news/new-port-forwarding-using-aws-system-manager-sessions-manager/1)
[SSM ドキュメントスキーマの機能と例](https://docs.aws.amazon.com/ja_jp/systems-manager/latest/userguide/document-schemas-features.html)
[AWS Systems Manager とは](https://docs.aws.amazon.com/ja_jp/systems-manager/latest/userguide/what-is-systems-manager.html)

### 野生
[CDKで作成した踏み台サーバにAWS SSM Session Manager と AWS Instance Connect を利用してSSH接続する](https://qiita.com/dairappa/items/96d55e929cce22ba90a5)
[AWSにおける踏み台(Bastion)サーバーの作り方](https://selmertsx.hatenablog.com/entry/2021/08/11/AWS%E3%81%AB%E3%81%8A%E3%81%91%E3%82%8B%E8%B8%8F%E3%81%BF%E5%8F%B0%28Bastion%29%E3%82%B5%E3%83%BC%E3%83%90%E3%83%BC%E3%81%AE%E4%BD%9C%E3%82%8A%E6%96%B9)
[セッションマネージャー over SSH 経由でプライベートサブネット内のRDSへ接続する方法](https://qiita.com/syoimin/items/eb6d4d9e01f460623531)
[Session Manager と踏み台サーバの共存構成](https://future-architect.github.io/articles/20200423/)


[Session Managerを使ってEC2の先にあるRDSに接続してみた](https://dev.classmethod.jp/articles/try-port-forwarding-with-ssm/)
[socatコマンドを利用した お手軽TCPリレー](https://dev.classmethod.jp/articles/relay-tcp-with-socat/)
[AWS System Managerセッションマネージャーでポートフォワードする][*3]
[さらば踏み台サーバ。Session Managerを使ってEC2に直接SSHする](https://www.karakaram.com/aws-session-manager-tunneling-support-for-ssh/)


[*1]:https://aws.amazon.com/jp/premiumsupport/knowledge-center/systems-manager-ssh-vpc-resources/
[*2]:https://docs.aws.amazon.com/systems-manager/latest/userguide/session-manager-getting-started-enable-ssh-connections.html
[*3]:https://dev.classmethod.jp/articles/port-forwarding-using-aws-system-manager-sessions-manager/