リモートワーク中はすぐに眠くなるので困っているSREチームの井原 ( @tossy_yukky ) です。
11月に入り、さすがに寒くなってきたかなと思いきやまた20度を超えてきたりと、過ごしにくい日々が続いてますが皆様いかがお過ごしでしょうか。
さて、今回のテーマは「AWSのSessionManagerを使ってセキュアなサーバーログイン」ということで、登場人物は以下になります。
経緯
これまで、フォースタートアップス(以下フォースタ)のインフラとしては、EC2(サーバー)に対してのSSH接続は基本的に社内から行う、障害対応などで外部から接続する場合には都度セキュリティグループに22番ポートの穴を開けて対応、という形になっていました。
ただ、今回の新型コロナがあり弊社としてもリモートワークが進んできたため、都度22番を開けるというよりは常時開放に近い状態になってしまったので「これはどうにかしないとな」というのがきっかけです。
そんな中、(おそらく)このあたりの記事をなにかの拍子に見つけたことで、コレダ!となり、その勢いで導入した、というお話になります。
要件を決めよう
CTOの戸村( @KenjiTomura ) とも話をして、「よしやろう」ということになったため、実際に要件を詰めていきます。
SessionManagerによってAWSコンソールから接続できるのはPoC時点で確認できていたんですが、このままでは下記のような「今までできていたこと」ができなくなってしまうので、それらは担保する方向にします。
出たのは大きく以下の4つ。
- ローカルPCからターミナルで接続
- scp、rsyncなどのover sshでのファイル転送
- 各自使っているDBクライアントから、SSHトンネリングをしつつDB接続
- Capistranoでのデプロイ
ローカルPCからターミナルで接続
こちらに関しては言わずもがな。
これができないようではこれ以降の要件も満たせそうにありません。
フォースタではRailsを使ったアプリケーションが多く、まだサーバーにログインしてrailsコマンドを叩くこともあるので、何があっても必須の要件となります。
scp、rsyncなどのover sshでのファイル転送
頻繁にあるものではないですし、TerraformやAnsibleなどを用いてInfrastructure as codeが進んでいる昨今ではそこまで必要ではないと思いますが、何かあった際にはできるに越したことはありません。
sshコマンドが使える状態であれば必然的に利用可能であるというところから、今回この要件そのものに対して何かを行ってはいません。
各自使っているDBクライアントから、SSHトンネリングをしつつDB接続
DBに接続して直接DDLを投げることはさすがにないものの、DML/DCLに関しては投げたいこともあります。
そしてDBはAmazon RDSを利用しており、基本的にPrivateなサブネットに配置しているため、踏み台となるサーバーがないと接続できないようにはなっています。
なので、踏み台サーバーも22番ポートを閉じたとしてもこのSSHトンネリングが可能な状態になっていないといけません。
ざっと周囲に聞いてみたところ、クライアントとしてはSequel ProかJetBrains製IDEが多かったため、それらクライアントでSSHトンネリングしつつDB接続(してクエリを投げられる)を担保する必要がありそうです。
Capistranoでのデプロイ
フォースタのアプリケーションとしては大半がRailsを利用しており、デプロイにはCapistranoを使っています。
デプロイの実行はCircleCIで行っているため、CircleCIで使っているコンテナからのアクセスを許容しなければいけません。
実際の作業
参考にしたのは大体この辺です。
初めてのAWS Session Manager(SSM)
Session Manager を使用した Linux インスタンスへの接続
最初は公式ドキュメントの日本語版から見ていたんですが、あまりの翻訳のひどさに解読を諦めました。。。
2020/10/9時点ではまだ英語版の方が読みやすい気がします。
以前はここまでひどくなかったと思うんですが一体どうしちゃったんでしょうか。
サーバーにSessionManager経由でログインできるようにする
- EC2にSSM用のポリシーをつけたロールを付与
- 具体的な手順に関しては上記の参考サイトを見ていただくとして、Qiitaの方の記事では AmazonEC2RoleforSSM となっているポリシーに関しては、設定しようとすると「古いよー、今はこっちだよー」と言われるので新しい方の AmazonSSMManagedInstanceCore というポリシーを選択します。
- SessionManagerのインスタンス一覧に出てくることを確認
- 上記ロールを付与してしばらくすると出てくるので気長に待ちます。
ローカルからsshコマンドでログインできるようにする
# SSH over Session Manager
host i-* mi-*
ProxyCommand sh -c "aws ssm start-session --target %h --document-name AWS-StartSSHSession --parameters 'portNumber=%p'"
- こうしておくことで、
$ ssh i-xxxxxxxxxx
- というコマンドだけでログインできるようになりました。
- 実際には i-xxxxxxxxxx ではどのホストなのかわからないため、ホスト名をつけた上で --target %h の値をそれぞれ直接書いています。
- DBクライアントからの接続を確認
- さて、普通にsshコマンドでログインできるようになったので、このホストを踏み台としてDBクライアントにSSHトンネリングの接続設定を行ってみます。
- そのままで行けるかと思いきや、ここで問題発生です。
sh: aws: command not found
- `` awsコマンドがないと言われてしまいました。
- これはDBクライアントからProxyCommandを実行する際、PATHが読み込まれていないからということなので、ProxyCommandの中を
ProxyCommand sh -c "export PATH=/usr/bin:/usr/local/bin && /usr/local/bin/aws ssm start-session --target %h --document-name AWS-StartSSHSession --parameters 'portNumber=%p'"
- のようにPATHを無理やり指定してやることで接続できるようになりました。
CircleCIでのCapistrano対応
そして最後はデプロイ周りをやっていきます。
フォースタでは大半のサービスがRailsで構築されており、デプロイには定番のCapistranoを利用しています。
CapistranoはSSHで対象ホストにログインしてデプロイを行うので、SSHで使う22番ポートを閉じてしまうとデプロイすることができません。
同時に、デプロイはGithubのmasterリポジトリへのmergeをトリガーにCircleCI上で行っているため、ローカルPCと同様の方法を取ろうとすると対象ホストが増える度に設定ファイルの書き換えが必要になってしまいます。
上記から、デプロイ時にはSessionManagerを利用するのではなく、対象ホストの22番ポートに対してCircleCIのIPを一時的に開放することで対応という形にします。
作業としては この辺り を参考に設定しました。
具体的な内容は上記記事を見ていただくとして、大きく
- ビルド/デプロイで使用するコンテナにAWS CLIをインストール
- AWS CLIを使用して対象ホストに紐付いたセキュリティグループを、実行中のCircleCIコンテナのアウトバウンドIPに対して22番ポートを開放するよう変更
- デプロイ後、上記セキュリティグループを元に戻す
といった形の流れになります。
これで基本的に22番ポートを閉じた状態でもデプロイを行うことができるようになりました。
最後に感想を
SSHというかサーバーへのログインに対するセキュリティというと、今までであれば踏み台サーバーを構築する、VPNを構築する、といったものになると思いますが、(AWS限定ではあるものの)これだけ簡単な作業で22番ポート自体を閉じてしまえるというのは素晴らしい体験だなと思いました。
よく「ロックインされるのが嫌」という話も聞きますが、敢えてロックインされることによって生産性・開発体験が向上するのであれば、そのリスクよりもメリットが上回る典型例ではないかと思います。