エブリーで小売業界向き合いの開発を行っている @kosukeohmura です。
エブリーでは全社的に SSH を使ったサーバーへのログインから、AWS Systems Manager Session Manager ( 以下 Session Manager ) を使った運用に切り替えました。
これは私達のチームで管理している他社クラウド (AWS 以外という意味で他社です) 上に存在するサーバについても対象ですが、Session Manager を直接利用することはできません。そこで Session Manager を使った SSH 踏み台サーバーを構築し、それ経由で SSH 接続することで、他社クラウド上のマシンの 22 番ポートを開放する範囲を限定することができました。
構成
簡単に前提とする構成を説明します。
AWS VPC と他社クラウドのネットワーク同士が Site-to-Site VPN で接続されている構成です。開発者や CI/CD は、AWS SSM を通して踏み台サーバーに SSH 接続し、更に踏み台サーバーから他社クラウド上の Target Server へ SSH でアクセスします。
この構成を取ることによって、目的のサーバーの SSH ポートの開放範囲を AWS VPC の CIDR ブロックのみに限定することができます。
Session Manager を通した SSH 接続
セッション開始時には aws ssm start-session
というコマンドを使いますが、その際 AWS-StartSSHSession
というドキュメントを指定することによって、SSH セッションが開始できます*1 。
なおその際の接続対象 (今回の場合、踏み台サーバー) では SSH 接続をサポートするように設定する必要があります*2。つまり SSH サーバーである必要があります。
- *1: ステップ 8: (オプション) Session Manager を通して SSH 接続のアクセス許可を付与および制御する - AWS Systems Manager
- *2: セッションを開始する - AWS Systems Manager
踏み台サーバーの構築
sshd を動かすシンプルなサーバーを作ります。今回 ECS タスクとして動作させますが、環境に合わせて EC2 や EKS などでも良いかと思います。
Dockerfile は下記のようになりました。シンプルですね。
FROM debian:stable-20250520-slim RUN apt-get update && \ apt-get -y upgrade && \ apt-get -y install openssh-server net-tools && \ apt-get clean && \ rm -rf /var/lib/apt/lists/* && \ mkdir /var/run/sshd COPY --chmod=755 entrypoint.sh /root/entrypoint.sh EXPOSE 22 ENTRYPOINT ["/root/entrypoint.sh"]
次に Dockerfile 内で ENTRYPOINT
としている起動スクリプトです。
# entrypoint.sh #!/bin/bash if [ -n "$SSH_PUBLIC_HOST_KEY_BASE64ENCODED" ]; then echo "$SSH_PUBLIC_HOST_KEY_BASE64ENCODED" | base64 -d > /etc/ssh/ssh_host_ed25519_key.pub chmod 644 /etc/ssh/ssh_host_ed25519_key.pub fi if [ -n "$SSH_PRIVATE_HOST_KEY_BASE64ENCODED" ]; then echo "$SSH_PRIVATE_HOST_KEY_BASE64ENCODED" | base64 -d > /etc/ssh/ssh_host_ed25519_key chmod 600 /etc/ssh/ssh_host_ed25519_key fi echo "$ROOT_PUBLIC_SSH_KEY_BASE64ENCODED" | base64 -d > /root/.ssh/authorized_keys chmod 600 /root/.ssh/authorized_keys exec /usr/sbin/sshd -D
ホストキーが起動毎に毎回生成されるのを避けるため、タスク起動時にあらかじめ生成した鍵でファイルを作成しています。なお各キーは AWS Secrets Manager に Base64 エンコードし保管してあるものを、そのまま ECS タスクの環境変数として注入している都合でデコードしています。
運用上の工夫
目的のサーバーへ踏み台サーバーを経由し SSH 接続をするためには、SSM セッションを確立後に SSH での多段接続という手順を踏む必要があり、毎回行うのは手間です。そこで簡単なスクリプトを使って、接続時の手間を軽減しています。
まず ECS タスクへと aws ssm start-session
コマンドを実行する場合、--target
オプションを <cluster_name>_<task_id>_<container_runtime_id>
といった形式での指定が手間なので、下記 Make ターゲットで出力できるようにしています(中身はいささか力技ですが)。
CLUSTER_NAME := '<cluster_name>' FAMILY_NAME := '<family_name>' ACCOUNT_ID := `aws sts get-caller-identity --query "Account" --output text;` TASK_ID := `aws ecs describe-tasks --cluster $(CLUSTER_NAME) --tasks \`aws ecs list-tasks --cluster $(CLUSTER_NAME) --family $(FAMILY_NAME) | jq -r '.taskArns[0]'\` | jq -r '.tasks[0].taskArn' | awk -F'/' '{print $$3}'` CONTAINER_RUNTIME_ID := `aws ecs describe-tasks --cluster $(CLUSTER_NAME) --tasks \`aws ecs list-tasks --cluster $(CLUSTER_NAME) --family $(FAMILY_NAME) | jq -r '.taskArns[0]'\` | jq -r '.tasks[0].containers[0].runtimeId'` .PHONY: echo-as-ssm-target echo-as-ssm-target: @echo "ecs:$(CLUSTER_NAME)_$(TASK_ID)_$(CONTAINER_RUNTIME_ID)"
.ssh/config は下記のようにしています。少々トリッキーですが、make
コマンドを .ssh/config 内で使用しています。ProxyJump
オプションにより、bastion
経由で target_server
への多段接続が自動的に行われます。結果的に ssh target_server
を実行するだけで目的のサーバーへログインできます。
# .ssh/config host bastion User root IdentityFile ~/.ssh/<bastion_key_name> ProxyCommand sh -c "aws ssm start-session --target `make -C <path_to_makefile_dir> echo-as-ssm-target` --document-name AWS-StartSSHSession --parameters 'portNumber=%p'" Host target_server ProxyJump bastion IdentityFile ~/.ssh/<target_key_name> HostName ...
GitHub Actions などの CI/CD でも同様に、適切な IAM Role を渡せば SSM セッションを開始し、SSH 経由でデプロイやコマンド実行が可能です。
まとめ
今回踏み台サーバーを構築し、Session Manager を通した SSH 接続を行うことで、他社クラウドのマシンにおいても SSH ポートの開放範囲を絞ることができ、また運用上の手間も減らすことができました。似たような環境の方のお役に立てばと思います。