踏み台EC2を廃止してSession Manager接続に置き換えました
こんにちは、エウレカ SRE チームの原田です。
今年 (2021年) エウレカでは、公開鍵認証で接続するEC2の踏み台サーバを廃止し、代わりに各サーバへの接続をIAMで認証できるSSM Session Managerへのリプレースを行いました。本記事ではそのモチベーションや、実装のポイントを紹介していきたいと思います。
旧来の踏み台サーバ
エウレカで長く運用されていた踏み台サーバ (Gateway) は以下のようなものでした。
- 各開発者は、自分の秘密鍵を使って踏み台サーバへSSHを行う ( 踏み台サーバ上には各開発者の個別ユーザーおよび公開鍵が登録されている )
- 踏み台上では、接続が許可されているSSH対象のサーバの秘密鍵がユーザー毎に配置されており、その鍵で各サーバにSSHする
- MySQL / Elasticsearch / Redis など、Private Subnetに配置された各種データストアへの接続の踏み台としても利用している
- サーバーの変更時にはPacker+AnsibleでGolden AMIを都度作成してTerraformでASGのLaunch Templateに反映 ( Immutable Infrastructure )
課題
機能上は上記で全く問題なかったのですが、運用の観点で以下のような課題を感じていました。
日常的な変更のリードタイムと労力が大きい
入退社時にSSH鍵とユーザーの登録が必要で、変更発生頻度が高い上に、その都度 設定変更→ packer build → terraform反映 → サーバのローテーション(手作業) を行う必要があったため、変更反映のリードタイムと労力が大きい
鍵の管理が大変
サーバーの秘密鍵はAnsible Vaultで暗号化した状態でGitリポジトリに保管していたため、平文コミットの可能性が拭えなかったり、ローテーションの対応が必要であるなど、管理上考えることが多く悩ましい
サーバー設定の複雑・肥大化
各ユーザーのPermission設定やサーバ上での監査ログ収集などの要件が多く、AnsibleのPlaybookが複雑化、肥大化していた。また、変更のリードタイムの長さから、こういった設定を変更する際のデバッグがしづらい
SSM Session Manager とは
Session Manager は AWS Systems Managerの機能で、AWSコンソールまたはAWS CLIのコマンド経由で、SSM Agentを経由してサーバに接続することができます。
aws ssm start-session --target <instance_id>
接続制御をIAMに集約できるので鍵の管理や踏み台ホストの管理が不要になることや、接続ログもCloudTrailで取れるなど前述の課題をうまく解決してくれそうだったので導入することにしました。
新アーキテクチャ
Session Managerを活用して、最終的に以下のような構成で運用しています。
- 各サーバへはSession Managerで接続し、サーバ接続用途の踏み台サーバは撤廃されている
- ユーザーはAWS SSOでコンソールへサインインまたは一時キーを取得し、ssm:StartSessionで各サーバーに接続する
- 誰がどのサーバーに接続できるかは、AWS SSOのIAM Roleアサインで一元的に管理されている
- Private Subnetにデータストア接続用の踏み台は引き続き必要だったので、EC2より管理の楽なECS on Fargateで踏み台を再構築し、ここへもSession Managerで接続させている
- ローカルでのGUIクライアント利用をサポートするため、socatを使ってtcpリレーを行っている
EC2への接続
チームごとのアクセス可否制御をIAM Policyで表現する
チーム毎にサーバへのアクセス可否を設定する必要があったため、各チームごとに接続できるサーバをresource句で定義したRoleを定義し、AWS SSOで各チームにアサインしました。
具体的にはssm:StartSessionのconditionで制御を行います。 以下のようなIAM Policyを各チーム向けのIAM Roleにアタッチしておきます。
statement {
effect = "Allow"
actions = ["ssm:StartSession"]
resources = [
"arn:aws:ec2:*:*:instance/*",
]
condition {
test = "StringLike"
variable = "ssm:resourceTag/Name"
values = ["hoge-server", "fuga-server"] // allowed server names
}
}
オートスケール構成だとinstance arnが事前に確定しないため、resource句での制御はできません。代わりにStringLike conditionを使ったTagでの制御を行っています。なお、Tagを変更するとこの制限を突破できてしまうので、別途SCP (Service Control Policy) などでTagの変更が確実にDenyされていることを保証する必要があります。
MySQL / Elasticsearch / Redis への接続
Private Subnetに配置しているこれらのデータソースに接続するには、引き続き踏み台となるホストが必要となります。EC2での管理は辞めたかったので、ECS on FargateでSSM Agentがインストールされたコンテナを動かしてそれを踏み台とし、各種データソースに接続 / クエリしてもらうようにしています。
具体的な構築方法は以下の記事を参考にさせていただきました。
実は、この踏み台サーバーのリプレース中に特に何もインストールしてないコンテナにもStartSessionができる「ECS Exec」がリリースされています。今から構築するならこちらを使うのがよさそうです。ただしデフォルトだと 各コンテナの ECS Exec セッションの最大数 が 2 に設定されているので、複数人でつなぎに行く踏み台として使う場合ここの引き上げ申請は必要そうです。
ローカル用 Database GUI Client のサポート
DatabaseにSequel, DataGripなどのGUIクライアントから接続したい要望がありました。これまでは踏み台のSSH情報 + MySQLの認証情報で接続していたのですが、新しい踏み台はそのままだとSSHの接続情報はないためうまく接続できません。
SSH over SSM を使うことをまず検討したのですが、鍵の管理や全員の ~/.ssh/config をもれなく変更してもらうのはやや大変と感じました。
別のアプローチを探したところ、以下のRedditのスレッドで議論されているような socatでのtcpリレーを踏み台上で実行し、ローカルから SSM Sessions ManagerのPort Forwarding機能 を使う方針ですすめることにしました。
動作イメージを図にすると以下のようになり、事前にローカルでSSM Port Forwardのコマンドを実行しておくことで、localhostの:3306に接続すると各Databaseの:3306につながるようになります。
(※2022.05.28追記) SSMがリモートホスト上でのポートフォワードをサポートしたため、上記のような面倒くさいこと (踏み台上でsocatで待ち受けておく) をやらなくても済むようになりました。
接続用のスクリプト / ドキュメント整備
開発者全員に使ってもらうものなので、必要以上に裏側を意識しなくても使えるような工夫として、ローカルから接続してもらう際gatewayのinstance idやポート番号をいちいち入れずに使えるスクリプトを用意したり、接続方法のドキュメントを充実させたりしています。
導入してどうなったか
入退社時にAWS SSOへの自動登録/解除が行われるため、そこと連動することで踏み台サーバ関連の反映リードタイムや労力がゼロとなり、かなり楽になりました。また各サーバへの接続用の鍵管理がなくなり、全てAWS SSOのアサインとIAM Policyでかけるようになったことで随分スッキリとしました。
今後改善できそうなポイントとしては、GUIクライアント利用時のPort Forwardスクリプト実行の手間が増えてしまったので、この部分の省力化することを考えています。AWS CLI v2とAWS SSOの統合 を使うと改善できそうだったり、鍵やssh config変更の手間はあるものの SSH over SSM をやる方針も再度検討してみたいと思っています。
(※2023.06.26追記) いま同じことをやるなら…
re:Inforce 2023で発表されたEC2 Instance Connect Endpointを使ったほうがよりラクそうです。