使 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的主要内容,如果未能解决你的问题,请参考以下文章

使用 Python 通过 SSH 隧道连接到远程 PostgreSQL 数据库

使用 Python Paramiko 通过双 SSH 隧道连接到数据库

RMySQL 通过 ssh 隧道

通过 ssh 隧道访问远程数据库(Python 3)

通过 SSH 隧道连接到 Elasticsearch

通过ssh隧道连接到MySQL到localhost