AWS LambdaでVPC内のPrivateインスタンスにSSH接続する

http://aws.typepad.com/aws_japan/2016/02/access-resources-in-a-vpc-from-your-lambda-functions.html

昨年のAWS re:Inventで対応が発表されていましたが、とうとうAWS LambdaからVPC内リソースにアクセスが出来るようになったので何が出来るのか眺めていました。
そうすると、どうやら「VPC内にNetworkInterfaceを作ることでVPC内リソースにアクセスする」とのことだったので、もしかするとこれはLambdaの起動インスタンスからフリーダムな事ができるのではないかと思い、ちょっと検証してみることにしました。

やりたかったこと

LambdaからVPC内の(インターネットに公開していないインスタンスに)SSH接続して、その内部の情報を拾う/書き換えることが出来ないかを検証する。

今まで、AWS上のAPIで出来ることは基本的に "インスタンスの外側" を制御するものがほとんどであり、"インスタンスの内側" の制御を行う仕組みは基本的に存在していませんでした。
EC2のUserDataを使うなどすれば一時的には利用できますが、Launch時のみに限定されます。

これまでの機能だけでpush型で外部から定期的に制御を行うモデルを構築しようとすると、その制御システムを冗長化する必要があって料金が高くついたり、ある時間のジョブをどれか1つのマシンだけが実行する制御が必要になる場合があるなど、技術的・コスト的にペイさせるのが難しいという問題がありました。 一応、中身を操作するサービスとして CodeDeployがあるにはありましたが、アレはあれでエージェントのインストールが必要になるなど、そこそこ準備側のコストが高いように感じてました。

しかし、AWS Lambda + スケジューラ (or 外部トリガ)によるSSH通信によるプッシュ処理が仮に可能になると、"インスタンスの内側" の制御を堅牢*1に、安価に*2実行できる可能性が高まります。

ぱぱっと検証

私がPythonに慣れているので、PythonでのAWS Lambda Functionを組みました。

その中で、最初は subprocess.call('/bin/ping -c 1 [target]') のように pingコマンドを使おうとしたのですが、Lambdaの上では /bin/ 以下にすらアクセス権限がありませんでした (当たり前といえば当たり前ですが)

そこで、Pythonコードをアップロードするときに周辺のライブラリがアップロードできることを利用して、ssh接続を行うfabric (と、内部で利用しているライブラリ群) をアップロードして使うことにしました。

ハマったところ

使うモジュール群は "pip install fabric -t ." でローカルに落としてこれるのですが、何故か "pycrypto" だけがこのコマンドでやっても落ちてこない。 明示しても /tmp 以下にファイルが存在しないと言われて失敗する。

これは "sudo pip install pycrypto" でやると成功するので、そこでインストールした先である "/usr/local/lib64/python2.7/site-packages/" 以下から持ってくることで解決しました。

あと、Lambdaモジュールが利用するNetworkInterfaceの存在するsubnet内に例えIGW向けのルートが張ってあってもインターネットに出ることができません*3。 言い換えると、全てのAWS APIが使えなくなります。

これを避けるためにManaged NATを立てるか、S3だけであればS3 Endpointへのルーティングが必要となります。

やってみた

2回目以降エラーログが出力されたりする問題はありますが、とりあえずAWS LambdaのTestでssh接続を行って、リモートのコマンドを実行することには成功しました。
これは中々出来ることが広がるアップデートだと思います。 この使用方法が想定されていたかどうかは知りませんが。

ソースコード

簡単なサンプルを以下のpublicプロジェクトにアップロードしました。
ご自由にご参照/ご利用下さい。

https://github.com/a-hisame/ssh-connection-with-aws-lambda

*1:Lambdaその物が冗長化されている

*2:実際の実行時間のみ課金、待機時間の課金は不要

*3:Public IPを付与しないため当然といえば当然。