Python - SSHモジュールのポート転送でMySQLにお気軽リモート接続

以前『sshトンネル(ポートフォワード)を使ってリモートのMySQLにつなぐ』という記事を書きましたが、sshコマンドでポートフォワードをしようとすると、パスワードログインの場合、毎回、パスワードを入力しないといけないのが面倒なので、

Python - SSHモジュールを使ってPythonでリモートのホストに自動ログイン。

これだと、リモートのMySQLのメンテナンス(更新)などの作業を自宅のパソコンから自動化したい場合、ポート転送用のPythonスクリプト自動起動するようにしておけば、マシン立ち上げと同時にポート転送が張れるので、便利ですね。


以下は、 Python - SSHモジュールさんのDemoにあったforward.pyを若干いじって、ローカルの3307ポートからリモートのMySQLの3306デフォルトポートへポート転送するようにさせて頂いたスクリプト

#!/usr/bin/env python

import getpass
import os
import socket
import select
import SocketServer
import sys

import ssh

server_host = "サーバーアドレス"
server_port = サーバSSHのログインポート
remote_host = "127.0.0.1"
remote_port = 3306
local_port = 3307
user = "SSHのログインユーザー名"
password = "パスワード"

g_verbose = True


class ForwardServer (SocketServer.ThreadingTCPServer):
    daemon_threads = True
    allow_reuse_address = True
    

class Handler (SocketServer.BaseRequestHandler):

    def handle(self):
        try:
            chan = self.ssh_transport.open_channel('direct-tcpip',
                                                   (self.chain_host, self.chain_port),
                                                   self.request.getpeername())
        except Exception, e:
            verbose('Incoming request to %s:%d failed: %s' % (self.chain_host,
                                                              self.chain_port,
                                                              repr(e)))
            return
        if chan is None:
            verbose('Incoming request to %s:%d was rejected by the SSH server.' %
                    (self.chain_host, self.chain_port))
            return

        verbose('Connected!  Tunnel open %r -> %r -> %r' % (self.request.getpeername(),
                                                            chan.getpeername(), (self.chain_host, self.chain_port)))
        while True:
            r, w, x = select.select([self.request, chan], [], [])
            if self.request in r:
                data = self.request.recv(1024)
                if len(data) == 0:
                    break
                chan.send(data)
            if chan in r:
                data = chan.recv(1024)
                if len(data) == 0:
                    break
                self.request.send(data)
        chan.close()
        self.request.close()
        verbose('Tunnel closed from %r' % (self.request.getpeername(),))


def forward_tunnel(local_port, remote_host, remote_port, transport):
    class SubHander (Handler):
        chain_host = remote_host
        chain_port = remote_port
        ssh_transport = transport
    ForwardServer(('', local_port), SubHander).serve_forever()


def verbose(s):
    if g_verbose:
        print s


def main():
    
    client = ssh.SSHClient()
    client.load_system_host_keys()

    verbose('Connecting to ssh host %s:%d ...' % (server_host, server_port))
    try:
        client.connect(server_host, port=server_port, username=user, password=password)
    except Exception, e:
        print '*** Failed to connect to %s:%d: %r' % (server_host, server_port, e)
        sys.exit(1)

    verbose('Now forwarding port %d to %s:%d ...' % (local_port, remote_host, remote_port))

    try:
        forward_tunnel(local_port, remote_host, remote_port, client.get_transport())
    except KeyboardInterrupt:
        print 'C-c: Port forwarding stopped.'
        sys.exit(0)


if __name__ == '__main__':
    main()


上記を、ローカルのパソコンで動かしておくと、↓こんな感じで、いつでも自宅のパソコンから、リーモートのMySQLにお気軽接続。

>>> import MySQLdb
>>> conn = MySQLdb.connect(db='music', host='127.0.0.1', port=3307 ,user='リモートのmySQLのユーザー名', passwd='パスワード')
>>> cursor = conn.cursor()
>>> cursor.execute( "select count(1) from mp3" )
1L
>>> cursor.fetchall()
((10439L,),)