Session Managerを使ってEC2の先にあるRDSに接続してみた

2021.02.26

こんにちは!DA(データアナリティクス)事業本部 インテグレーション部の大高です。

最近、AWS System ManagerのSession Managerを利用するとポートフォワーディングができるということをはじめて知りました。

ただ、このままだと接続先のEC2上のポートをローカルにフォワーディングできるのですが、更にEC2を踏み台にして、その先のRDSに接続するというようなことは出来ません。

この場合はSession Manager経由でSSHをすれば良いのですが、この場合にはどうしてもキーが必要になります。

個人的にこのキーの管理が煩雑だったため、どうにかできないか試してみました。

前提

今回の環境は以下のような環境です。

  • 接続先のEC2はAmazon Linux 2
  • EC2はSession Managerで接続できるように設定済み
  • 接続元のローカルPCも接続先EC2にSession Manager経由で接続できるように設定済み
  • EC2からしかアクセスできないRDSがあって、ここにローカルPCからアクセスしたい

対応方針を検討する

EC2上のポートをローカルにフォワーディングするのは簡単にできるので、あとはEC2とRDS間をどうするかです。

ここは、今回はsocatを利用したいと思います。標準ではAmazon Linux 2にインストールされていないのでインストールする必要が出てきてしまうのですが、目をつむります。

socatコマンドを利用したTCPリレーについては、以前に書いた通りなのでこれをそのままSSMのRumCommandで実行し、その後にEC2上のポートをローカルにフォワーディングする方向でやってみます。

EC2 - RDS間のポートリレーをさせる

今回はすべてローカルPC上から操作を完結させたいので、SSMドキュメントを作成して実行させるようにします。

まずは以下のようなドキュメントを用意します。

command-document.yml

---
schemaVersion: "2.2"
description: Port Forwarding
parameters:
  localPort:
    type: String
    default: "5432"
    description: "フォワーディング元のポート番号を指定"
  targetHost:
    type: String
    default: ""
    description: "フォワーディング対象ホストを指定"
  targetPort:
    type: String
    default: "5432"
    description: "フォワーディング先のポート番号を指定"
mainSteps:
  - action: aws:runShellScript
    name: install
    inputs:
      runCommand:
        - yum install socat -y
  - action: aws:runShellScript
    name: run
    inputs:
      runCommand:
        - socat tcp4-listen:{{ localPort }},reuseaddr,fork TCP:{{ targetHost }}:{{ targetPort }}

ドキュメントが準備できたらCLIから登録します。以下のようなシェルを作成して登録してみました。念の為に、事前に同名ドキュメントを削除するコマンドも入れています。

create-document.sh

#!/bin/bash
DOCUMENT_NAME=port-relay

aws ssm delete-document \
    --name ${DOCUMENT_NAME}

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

ドキュメントが登録できたら、実際に実行します。こちらも以下のようなシェルを作成して実行してみました。

send-command.sh

#!/bin/bash
INSTANCE_ID=i-xxxxxxxxxxxxxxxxx
DOCUMENT_NAME=port-relay
TARGET_HOST="foobar.ap-northeast-1.rds.amazonaws.com"

aws ssm send-command \
  --instance-ids ${INSTANCE_ID} \
  --document-name ${DOCUMENT_NAME} \
  --parameters targetHost=${TARGET_HOST}

これで、EC2でsocatコマンドが実行されている状態になります。

ローカルPCにポートフォワーディング

あとはローカルPCにポートフォワーディングするだけです。以下のシェルを実行してポートフォワーディングします。

start-port-forwarding.sh

#!/bin/bash
INSTANCE_ID=i-xxxxxxxxxxxxxxxxx

aws ssm start-session \
    --target ${INSTANCE_ID} \
    --document-name AWS-StartPortForwardingSession \
    --parameters portNumber=5432,localPortNumber=5432

これで、localhost:5432にアクセスすれば、EC2上の先にあるRDSにアクセスできるようになりました!

課題

一見よさそうですが、ちょっと問題があります。

下記の記事にもある通り、RunCommandで実行されているコマンドには「実行タイムアウト」が7200秒で設定されています。このため、2時間経過するとsocatのプロセスが死んでしまうという問題があります

socatコマンドの実行をシェルスクリプト化したり、RunCommandで実行するコマンドをnohup xxx &のようにしたりもしてみたのですが、RunCommandで実行されるコマンドには効果がなく、現状では解決策が見つかりませんでした。

良い方法が見つかったら後で追記したいと思います。

まとめ

以上、Session Managerを使ってポートフォワーディングをしてみました。Session Managerを利用すると、これまでのようにEC2インスタンスにパブリックIP経由でSSH接続せずとも色々と作業ができるのを知ったので、これから活用していきたいと思います。

どなたかのお役に立てば幸いです。それでは!

参考