使 Python 通过 SSH 隧道连接到 MySQL

Posted

技术标签:

【中文标题】使 Python 通过 SSH 隧道连接到 MySQL【英文标题】:Enable Python to Connect to MySQL via SSH Tunnelling 【发布时间】:2014-03-21 03:16:44 【问题描述】:

我在 Python 2.7 中使用 mysqldb 以允许 Python 连接到另一个 MySQL 服务器

import MySQLdb
db = MySQLdb.connect(host="sql.domain.com",
     user="dev", 
      passwd="*******", 
      db="appdb")

除了像这样正常连接,如何使用 SSH 密钥对通过 SSH 隧道建立连接?

理想情况下,SSH 隧道应该由 Python 打开。 SSH隧道主机和MySQL服务器是同一台机器。

【问题讨论】:

你谷歌了吗?使用 python 打开一个 ssh 隧道:***.com/questions/4364355/…,通过该隧道连接到 MySql:***.com/questions/3577555/… 您可能有充分的理由使用 SSH,但如果这是与 MySQL 服务器的直接连接,请改用 SSL。减少可能出错的事情。 @geertjanvdk 这很有趣,为什么 SSL 会是更好的选择?我希望在客户端和服务器之间建立安全连接,首先想到的是 SSH 您不使用 SSH 连接到安全网站,是吗?这只会使事情复杂化。如果您的 MySQL 服务器可直接访问,则 SSL 是可行的方法。此外,SSL 适用于任何连接器或操作系统,如 Windows。使用 SSH 隧道时,您需要对其进行维护、监控等。 【参考方案1】:

只有这个对我有用

import pymysql
import paramiko
import pandas as pd
from paramiko import SSHClient
from sshtunnel import SSHTunnelForwarder
from os.path import expanduser

home = expanduser('~')
mypkey = paramiko.RSAKey.from_private_key_file(home + pkeyfilepath)
# if you want to use ssh password use - ssh_password='your ssh password', bellow

sql_hostname = 'sql_hostname'
sql_username = 'sql_username'
sql_password = 'sql_password'
sql_main_database = 'db_name'
sql_port = 3306
ssh_host = 'ssh_hostname'
ssh_user = 'ssh_username'
ssh_port = 22
sql_ip = '1.1.1.1.1'

with SSHTunnelForwarder(
        (ssh_host, ssh_port),
        ssh_username=ssh_user,
        ssh_pkey=mypkey,
        remote_bind_address=(sql_hostname, sql_port)) as tunnel:
    conn = pymysql.connect(host='127.0.0.1', user=sql_username,
            passwd=sql_password, db=sql_main_database,
            port=tunnel.local_bind_port)
    query = '''SELECT VERSION();'''
    data = pd.read_sql_query(query, conn)
    conn.close()

【讨论】:

sql_ip是干什么用的? 工作就像一个魅力。谢谢! @kathanshah 如果中间有跳转主机,如何使用它? & 它是否允许使用默认的 .ssh/config 文件设置? @OneAdamTwelve, sql_ip 似乎是不必要且未使用的。但是这个例子有效! (我用的是 MySQLdb) 对于使用此解决方案遇到“xxx.com Host is not allowed to connect to this MySQL server”的任何人来说,只是一个小小的附加评论。然后需要在 SSHTunnelForwarder() 的第一部分添加 local_bind_address = ('127.0.0.1', sql_port),例如:with SSHTunnelForwarder((ssh_host, ssh_port), ssh_username=ssh_user, ssh_pkey=mypkey, remote_bind_address=('127.0 .0.1', sql_port), local_bind_address = ('127.0.0.1', sql_port) ) 作为隧道:为什么?默认本地地址解析为mysql无法解析的外部主机名。【参考方案2】:

我猜你需要端口转发。我推荐sshtunnel.SSHTunnelForwarder

import mysql.connector
import sshtunnel

with sshtunnel.SSHTunnelForwarder(
        (_host, _ssh_port),
        ssh_username=_username,
        ssh_password=_password,
        remote_bind_address=(_remote_bind_address, _remote_mysql_port),
        local_bind_address=(_local_bind_address, _local_mysql_port)
) as tunnel:
    connection = mysql.connector.connect(
        user=_db_user,
        password=_db_password,
        host=_local_bind_address,
        database=_db_name,
        port=_local_mysql_port)
    ...

【讨论】:

我应该在 remote_bind_address 中放什么?我在哪里可以找到这些信息? 如果你在本地,你可以试试这样:_remote_bind_address = '127.0.0.1' _local_bind_address = '0.0.0.0' 这是什么类型的转发?远程转发?需要任何服务器设置吗? 它不需要任何服务器设置。实际上,它是一种端口转发设置,可以通过 SSH 隧道访问安全外壳 (SSH) 连接的服务器端的应用程序。【参考方案3】:
from sshtunnel import SSHTunnelForwarder
import pymysql
import pandas as pd

tunnel = SSHTunnelForwarder(('SSH_HOST', 22), ssh_password=SSH_PASS, ssh_username=SSH_UNAME,
     remote_bind_address=(DB_HOST, 3306)) 
tunnel.start()
conn = pymysql.connect(host='127.0.0.1', user=DB_UNAME, passwd=DB_PASS, port=tunnel.local_bind_port)
data = pd.read_sql_query("SHOW DATABASES;", conn)

感谢https://www.reddit.com/r/learnpython/comments/53wph1/connecting_to_a_mysql_database_in_a_python_script/

【讨论】:

使用启动隧道的方法对我来说比使用'with'语句的方法更好。它以某种方式解决了我的问题:***.com/questions/63774543/… 我不知道为什么,但这里也只能使用这种方式。使用with 语句似乎对我不起作用。 使用 with 启动 tunnel.close(),由于某种原因挂起。使用 tunnel.stop() 然后使用 tunnel.close() 似乎可以绕过这个问题。【参考方案4】:

如果您的私钥文件已加密,这对我有用:

    mypkey = paramiko.RSAKey.from_private_key_file(<<file location>>, password='password')
    sql_hostname = 'sql_hostname'
    sql_username = 'sql_username'
    sql_password = 'sql_password'
    sql_main_database = 'sql_main_database'
    sql_port = 3306
    ssh_host = 'ssh_host'
    ssh_user = 'ssh_user'
    ssh_port = 22


    with SSHTunnelForwarder(
            (ssh_host, ssh_port),
            ssh_username=ssh_user,
            ssh_pkey=mypkey,
            ssh_password='ssh_password',
            remote_bind_address=(sql_hostname, sql_port)) as tunnel:
        conn = pymysql.connect(host='localhost', user=sql_username,
                               passwd=sql_password, db=sql_main_database,
                               port=tunnel.local_bind_port)
        query = '''SELECT VERSION();'''
        data = pd.read_sql_query(query, conn)
        print(data)
        conn.close()

【讨论】:

【参考方案5】:

您只能写入私钥文件的路径:ssh_pkey='/home/userName/.ssh/id_ed25519'(文档在此处:https://sshtunnel.readthedocs.io/en/latest/)。

如果您使用 Oracle 的 mysql.connector,您必须使用构造 cnx = mysql.connector.MySQLConnection(... 重要:建筑 cnx = mysql.connector.connect(... 不能通过 SSH 工作!这是一个错误。 (文档在这里:https://dev.mysql.com/doc/connector-python/en/connector-python-connectargs.html)。

此外,您的 SQL 语句必须是理想的。如果 SQL 服务器端出现错误,您不会收到来自 SQL 服务器的错误消息。

import sshtunnel
import numpy as np

with sshtunnel.SSHTunnelForwarder(ssh_address_or_host='ssh_host',
                                  ssh_username="ssh_username",
                                  ssh_pkey='/home/userName/.ssh/id_ed25519',
                                  remote_bind_address=('localhost', 3306),
                                  ) as tunnel:
    cnx = mysql.connector.MySQLConnection(user='sql_username',
                                          password='sql_password',
                                          host='127.0.0.1',
                                          database='db_name',
                                          port=tunnel.local_bind_port)
    cursor = cnx.cursor()
    cursor.execute('SELECT * FROM db_name.tableName;')
    arr = np.array(cursor.fetchall())
    cursor.close()
    cnx.close()

【讨论】:

您能否提供有关该错误的支持参考?我有 .connect() 在早期版本中工作,但不再工作,所以这是一个有用的线索。【参考方案6】:

Paramiko 是执行 ssh 隧道的最佳 python 模块。在此处查看代码: https://github.com/paramiko/paramiko/blob/master/demos/forward.py

正如在 cmets 中所说,这一款非常完美。 SSH Tunnel for Python MySQLdb connection

【讨论】:

【参考方案7】:

最佳实践是参数化连接变量。 这是我的实施方式。像魅力一样工作!

import mysql.connector
import sshtunnel
import pandas as pd
import configparser

config = configparser.ConfigParser()
config.read('c:/work/tmf/data_model/tools/config.ini')

ssh_host = config['db_qa01']['SSH_HOST']
ssh_port = int(config['db_qa01']['SSH_PORT'])
ssh_username = config['db_qa01']['SSH_USER']
ssh_pkey = config['db_qa01']['SSH_PKEY']
sql_host = config['db_qa01']['HOST']
sql_port = int(config['db_qa01']['PORT'])
sql_username = config['db_qa01']['USER']
sql_password = config['db_qa01']['PASSWORD']

with sshtunnel.SSHTunnelForwarder(
        (ssh_host,ssh_port),
        ssh_username=ssh_username,
        ssh_pkey=ssh_pkey,
        remote_bind_address=(sql_host, sql_port)) as tunnel:
    connection = mysql.connector.connect(
        host='127.0.0.1',
        port=tunnel.local_bind_port,
        user=sql_username,
        password=sql_password)
    query = 'select version();'
    data = pd.read_sql_query(query, connection)
    print(data)
    connection.close()

【讨论】:

【参考方案8】:

这对我有用:

import mysql.connector
import sshtunnel
with sshtunnel.SSHTunnelForwarder(
    ('ip-of-ssh-server', 'port-in-number-format'),
    ssh_username = 'ssh-username',
    ssh_password = 'ssh-password',
    remote_bind_address = ('127.0.0.1', 3306)
) as tunnel:
    connection = mysql.connector.connect(
        user = 'database-username',
        password = 'database-password',
        host = '127.0.0.1',
        port = tunnel.local_bind_port,
        database = 'databasename',
    )
    mycursor = connection.cursor()
    query = "SELECT * FROM datos"
    mycursor.execute(query)

【讨论】:

以上是关于使 Python 通过 SSH 隧道连接到 MySQL的主要内容,如果未能解决你的问题,请参考以下文章