GoogleAppEngine/Pythonでparamikoを使ってSSH接続

GoogleAppEngineでsocketが使えるようになったのは1年ぐらい前だった気がしますが、使ったことなかったので試してました。
Sockets Python API Overview  |  Python  |  Google Cloud Platform
まだプレビューリリースですね。
socketを使えるのならparamikoSSH接続できたら面白いなーと思ってやってみたところ、そのままでは動かなかったのですが、少しコードを変更すれば動きました。
試したバージョンはPython 2.7, GoogleAppEngine SDK 1.9.5, paramiko 1.14.0です。

AppEngineで動くようにparamikoのコードを変更する

subprocessモジュールがAppEngineでは使えないので、importのところでこけました。ProxyCommandは使わないので、PopenとPIPEをNoneでとりあえず埋めときます。
paramiko/proxy.py:L24-25を変更

#from subprocess import Popen, PIPE
Popen, PIPE = None, None

また、Transportクラスでsocketのタイムアウトが0.1秒に設定されてしまい、AppEngineだと「[Errno 11] Resource temporarily unavailable」のエラーとなってしまうので、タイムアウト時間を変更します。とりあえず10秒
paramiko/transport.py:L201

            self.sock.settimeout(10)

これで動きました。

サンプルコード

main.py
import os
import paramiko


def app(environ, start_response):
    base_dir = os.path.dirname(os.path.abspath(__file__))
    client = paramiko.SSHClient()
    client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
    client.connect(
        'example.com',
        username='spam',
        key_filename=os.path.join(base_dir, 'secret.key'))
    stdin, stdout, stderr = client.exec_command('uname -a')

    start_response('200 OK', [('Content-type', 'text/plain')])
    return stdout.read()

ホスト名、ユーザー名、鍵ファイルは自分のものを使用。paramikoと依存するecdsaモジュールはsys.pathの通っている場所に置いてます。
また、app.yamlでpycryptoを有効にしています。

実行結果

指定したホストにSSHで接続してuname -aコマンドを実行したときの標準出力が返ってきます。

雑感

Sockets APIはpaid appsでしか使えないのだけど、それは仕方ないかー。接続のところが遅いのはAppEngineの仕様なのかな。長時間つなぎっぱなしにできないし、bindして待ち受けることもできないので用途は限られる感じ。
とはいえできることの幅が広がったのはいい感じだなー。