ECS Fargateを踏み台にポートフォワードしてVPC内のRDSに接続する
AWSにおいて、RDSなどVPC内のリソースにVPC外からアクセスしたい場合、VPNを利用したり、踏み台サーバーを経由することが一般的です。
本記事では、ECS Fargateを踏み台に、SSM Session Managerのポートフォワードを利用して、VPC内のリソースに接続する方法を紹介します。
SSM Session Managerのポートフォワード機能について
SSM Session ManagerはSSM AgentをインストールしたEC2インスタンスにシェルアクセスを提供する機能です。
docker exec
のECS版であるECS Execもこの延長上で実装されています。
SSM Session Managerはシェルアクセスだけでなく、インバウンドポートを閉じたままのSCP/SSH接続やポートフォワード機能も提供しています。
このポートフォワード機能を利用すると、SSMエージェントがインストールされているインスタンス内のポートフォワードだけでなく、同インスタンスを踏み台としたリモートホスト・ポートのフォワードにも対応しています。
EC2環境では良く知られた構成をECS Fargateにも適用したのが本記事です。
事前準備
ポートフォワードの踏み台に利用したいタスクに対して、ECS Execができるように設定を済ませておいてください。
特に、ECSタスクロールにSSM周りのポリシーを忘れずに付与してください。
ECS Fargateタスクに対してSSM Session Managerで接続するには?
EC2に対してSSM Session Managerで接続する場合、インスタンスIDを指定します。
$ aws ssm start-session \
--target i-123
しかし、ECS FargateのコンテナにはインスタンスIDが割り当てられていません。
代わりに、ecs:クラスター名_タスクID_ランタイムID
を --target
引数に指定します。
クラスター名とタスクIDはコンソールから確認可能です。
ランタイムIDは、ECS:DescribeTasks APIから確認できます。
$ aws ecs describe-tasks \
--cluster test \
--task ae8bd3553e9247308fd488b268de766b
{
"tasks": [
{
...
"containers": [
{
"containerArn": "arn:aws:ecs:REGION:123456789012:container/test/ae8bd3553e9247308fd488b268de766b/80afabe0-62bc-4c7a-8fb3-b34eda3d5cb6",
"taskArn": "arn:aws:ecs:REGION:123456789012:task/test/ae8bd3553e9247308fd488b268de766b",
"name": "fargate-app",
"image": "public.ecr.aws/docker/library/httpd:latest",
"runtimeId": "ae8bd3553e9247308fd488b268de766b-3681984407",
"lastStatus": "RUNNING",
...
}
],
...
}
],
"failures": []
}
containers
内に runtimeId
フィールドを確認できます。
あとは
- クラスター名
- タスクID
- ランタイムID
を_
でつなげたものを target
に渡すだけです。
$ aws ssm start-session \
--target ecs:test_ae8bd3553e9247308fd488b268de766b_ae8bd3553e9247308fd488b268de766b-3681984407
Starting session with SessionId: xxx-0d8b5abb61b899597
#
ECS Execは実行時に
- クラスター名
- タスクID
- コンテナ名
を指定しています。
ECS:DescribeTasks
API のレスポンスからも、これらの情報があればランタイムIDを一意に特定できることがわかりますね。
SSM Session Managerでポートフォワードするには?
ECS FargateにSSM Session Managerで接続できることを確認できたので、あとはポートフォワード用のパラメーターを追加するだけです。
$ aws ssm start-session \
--target ecs:クラスター名_タスクID_ランタイムID \
--document-name AWS-StartPortForwardingSessionToRemoteHost \
--parameters '{"host":["リモートホスト"],"portNumber":["リモートポート番号"], "localPortNumber":["ローカルポート番号"]}'
--parameters
の host
とportNumber
に、RDSエンドポイントなどポートフォワードしたいリモートシステムのホスト・ポートを指定します。
よりシンプルに、コンテナ内のポートをフォワードしたい場合、次の様に指定します。
$ aws ssm start-session \
--target ecs:クラスター名_タスクID_ランタイムID \
--document-name AWS-StartPortForwardingSession \
--parameters '{"portNumber":["リモートポート番号"], "localPortNumber":["ローカルポート番号"]}'
ECS FargateタスクのSSMリモートポートフォワード対応は2022年11月から
SSMのリモートホストポートフォワードは2022年5月から対応しています。
一方で、FargateのSSMエージェントが同機能に対応したバージョン(version >= 3.1.1374.0)にアップデートされたのは、約半年後の2022年11月からです。
$ /managed-agents/execute-command/amazon-ssm-agent --version
SSM Agent version: 3.1.1732.0
この機能のおかげで、ECS Fargateの踏み台化が非常にシンプルになりました。
ツールを使って入力の手間を減らす
@fujiwaraさん作成のECSツールを利用すると、シュッとポートフォワードできます。
人生は短いので、お手製のラッパーを書くくらいなら、このようなツールの利用を検討しましょう。
ecspresso を利用
$ ecspresso exec \
--port-forward \
--local-port 8080 \
--port 80 \
--host github.com
ecsta を利用
$ ecsta portforward \
--local-port=8080 \
--remote-port=80 \
--remote-host=github.com
補足:ecstaのコードからSession ManagerのECS用ターゲットID生成箇所を確認
ECSのSSMではSession Manager時のターゲットに、インスタンスIDの代わりに、ecs:クラスター名_タスクID_ランタイムID
を 指定します。
ecstaのコードでは、次の箇所が該当します。
func buildSsmRequestParameters(task types.Task, targetContainer string) (*ssmRequestParameters, error) {
values := strings.Split(*task.TaskArn, "/")
clusterName := values[1]
taskID := values[2]
var runtimeID string
for _, c := range task.Containers {
if *c.Name == targetContainer {
runtimeID = *c.RuntimeId
}
}
return &ssmRequestParameters{
Target: fmt.Sprintf("ecs:%s_%s_%s", clusterName, taskID, runtimeID),
}, nil
}
Note:
English translation of this article : Securely connect to an Amazon RDS using ECS Fargate as a bastion
Discussion