简体   繁体   中英

Accessing MySQL database using Python over SSH Tunnel port forwarding

I have MySQL database on a remote server and before accessing it I would need to do port forwarding using ssh tunnel. Without python I usually execute the below command and type in my password in the shell. Once authenticated I access the database in mysql workbench by connecting to localhost:3306. I do everything from my Mac OS X El Capitan

ssh -L 3306:remote.database.host:3306 xxxx@xxxx.com

I would now like to access the database from Python and here is the script I used:

import pexpect
import time
import sqlalchemy as sql
from sqlalchemy.engine.url import URL

myDB = URL(drivername='mysql+pymysql', host='localhost',
database='test_db',
username='roott',
password='xxxxxx',
port=3306)

child = pexpect.spawnu('ssh -L 3306:remote.database.host:3306 xxxx@xxxx.com')
child.expect (u'password:')
child.sendline ('xxxxx')

while child.isalive():
    try:
    engine = sql.create_engine(name_or_url=myDB)
    connection = engine.connect()
    connection.close()
    break
finally:
    child.close()

Above script is giving me the following error:

sqlalchemy.exc.OperationalError: (pymysql.err.OperationalError) (2003, "Can't connect to MySQL server on 'localhost' ([Errno 61] Connection refused)")

I also tried using sshtunnelforwarder by referring to jsjc's solution at Python - SSH Tunnel Setup and MySQL DB Access and it doesn't work either.

from sshtunnel import SSHTunnelForwarder
from sqlalchemy import create_engine
from sqlalchemy.engine.url import URL

with SSHTunnelForwarder(('ssh_host', 22),
                    ssh_username='xxxxx',
                    ssh_pkey='/path/to/key/file',
                    local_bind_address=('127.0.0.1', 3306),
                    remote_bind_address=('127.0.0.1', 3306)) as server:


myDB = URL(drivername='mysql+pymysql', host='127.0.0.1',
           database='test_db',
           username='roott',
           password='xxxxx',
           port=3306
           )

engine = create_engine(name_or_url=myDB)
connection = engine.connect()    
connection.close()

I get the below information logged into the console and nothing happens, it just hangs in there, I have to manually kill the program.

DEBUG:paramiko.transport:starting thread (client mode): 0xbe25940
DEBUG:paramiko.transport:Local version/idstring: SSH-2.0-paramiko_1.16.0
DEBUG:paramiko.transport:Remote version/idstring: SSH-2.0-OpenSSH_6.2
INFO:paramiko.transport:Connected (version 2.0, client OpenSSH_6.2)
DEBUG:paramiko.transport:........
DEBUG:paramiko.transport:Kex agreed: diffie-hellman-group1-sha1
DEBUG:paramiko.transport:Cipher agreed: aes128-ctr
DEBUG:paramiko.transport:MAC agreed: hmac-sha2-256
DEBUG:paramiko.transport:Compression agreed: none
DEBUG:paramiko.transport:kex engine KexGroup1 specified hash_algo <built-in function openssl_sha1>
DEBUG:paramiko.transport:Switch to new keys ...
DEBUG:paramiko.transport:Attempting public-key auth...
DEBUG:paramiko.transport:userauth is OK
INFO:paramiko.transport:Auth banner: b'Amazon Linux AMI release 2014.09\nKernel \\r on an \\m\n'
INFO:paramiko.transport:Authentication continues...
DEBUG:paramiko.transport:Methods: ['password']
DEBUG:paramiko.transport:[chan 0] Max packet in: 32768 bytes
WARNING:paramiko.transport:Oops, unhandled type 3

This is an issue that I have encountered before. The issue is that when using localhost as the database server, the driver insists on connecting to the local unix socket rather than connecting via the loopback interface using the specified port.

The workaround is to change the database server to be something besides localhost. This will cause the driver to use the server/port you specified rather than attempting to connect to the local socket.

myDB = URL(drivername='mysql+pymysql', host='127.0.0.1', ...)
myDB = URL(drivername='mysql+pymysql', host='your.ip.add.ress', ...)
myDB = URL(drivername='mysql+pymysql', host='your.hostname', ...)

Idk if OP has figured out the problem (has been 3 years so I hope so), but my solution was extremely simple. I just had to indent myDB, engine, and connection definitions inside the "with" clause. Otherwise, the ssh tunnel would close before you have any chance to connect to the database:

with SSHTunnelForwarder(('ipforsshing', sshport), ssh_username='sshuser', ssh_pkey='privatekeypath', local_bind_address=('127.0.0.1', 3305), remote_bind_address=('127.0.0.1', 3306)) as server:
    adcrawl_db = URL(drivername='mysql+pymysql', host='127.0.0.1', database='dbname', username='dbusername',password='dbpassword', port=3305)

    engine = create_engine(name_or_url=adcrawl_db)
    connection = engine.connect()
    print("CONNECTED!")
    connection.close()

Hope this helps anyone in the future.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM