SSH跳转主机和远程数据库的隧道转发

时间:2016-11-16 23:51:46

标签: python sql ssh sqlalchemy paramiko

我在Amazon RDS(“D”)上托管了一个远程MySQL数据库。出于安全考虑,只能通过远程服务器(“C”)访问它。可以通过跳转主机“B”通过ssh访问C.我需要一个双ssh隧道来访问远程SQL主机。

[A: local host] -> [B: jump host] -> [C: target host] => [D: RDS MySQL host]

我想使用paramiko和/或sshtunnel通过Python访问D.我能找到的所有信息都包括:

到目前为止,我正在使用paramiko和一个代理命令从A到C.我可以通过在C上执行命令来访问D,但不能通过连接mysqldb或sqlalchemy(我的最终目标)来访问D.

我目前的代码:

import paramiko

ssh = paramiko.SSHClient()
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
proxy = paramiko.ProxyCommand("ssh -A B_username@B_host -W C_host:12345")
ssh.connect("C_host", username="C_username", sock=proxy)

stdin, stdout, stderr = ssh.exec_command("mysql -u D_username -p D_password -h D_host_rds")
print("STDOUT:\n{}\n\nSTDERR:\n{}\n".format(stdout.read(), stderr.read()))
# successfully prints out MySQL welcome screen

我正在寻找类似这样的内容(从sshtunnel docs中的示例2修改):

import paramiko
from sshtunnel import SSHTunnelForwarder

with SSHTunnelForwarder(
    intermediate = {
        ("B_host", 22),
        ssh_username = "B_username",
        ssh_password = "B_password")},
    remote = {
        ("C_host", 12345),
        ssh_username = "C_username",
        ssh_password = "C_password")},
    remote_bind_address=("D_host_rds", 3306),
    local_bind_address=("0.0.0.0", 3307)) as server:

    conn = MySQLdb.connect(
        user = "D_username",
        passwd = "D_password",
        db = "my_database",
        host = "127.0.0.1",
        port = 3307)

tl; dr:如何通过Python中的两个ssh跳转转发端口?

3 个答案:

答案 0 :(得分:2)

我明白了。它与ssh配置设置和sshtunnel库中的SSHTunnelForwarder上下文管理器结合使用。

Using the following model and naming conventions

A = Vec<u32>

我将〜/ .ssh / config设置为从A到C到B:

[A: local host] -> [B: jump host] -> [C: target host] => [D: RDS MySQL host]

我添加了用于登录B和C的密钥/密钥到我的ssh-agent:

Host C_ssh_shortcut
    HostName C_host
    User C_user
    Port 22
    ForwardAgent yes
    ProxyCommand ssh B_user@B_host -W %h:%p

最后我设置了SSHTunnelForwarder:

ssh-add

从这里开始,我可以像往常一样使用我的引擎与我的数据库进行交互。

答案 1 :(得分:1)

此代码对我有用

import pymysql
import paramiko
from paramiko import SSHClient
from sshtunnel import SSHTunnelForwarder
from sqlalchemy import create_engine

#ssh config
mypkey = paramiko.RSAKey.from_private_key_file('your/user/location/.ssh/id_rsa')             
ssh_host = 'your_ssh_host'
ssh_user = 'ssh_host_username'
ssh_port = 22  

#mysql config         
sql_hostname = 'your_mysql_host name'
sql_username = 'mysql_user'
sql_password = 'mysql_password'
sql_main_database = 'your_database_name'
sql_port = 3306
host = '127.0.0.1'



with SSHTunnelForwarder(
        (ssh_host, ssh_port),
        ssh_username=ssh_user,
        ssh_pkey=mypkey,
        remote_bind_address=(sql_hostname, sql_port)) as tunnel:              

    engine = create_engine('mysql+pymysql://'+sql_username+':'+sql_password+'@'+host+':'+str(tunnel.local_bind_port)+'/'+sql_main_database)
    connection = engine.connect()
    print('engine creating...')
    sql = text(""" select * from nurse_profiles np limit 50""")
    nurseData = connection.execute(sql)
    connection.close()


    nurseList = []
    for row in nurseData:
        nurseList.append(dict(row))
    print('nurseList len: ', len(nurseList))
    print('nurseList: ', nurseList)

答案 2 :(得分:0)

我将此代码用于PostgreSQL数据库,并且可以正常工作。我确信如果使用MySQL数据库,它也能正常工作。我将PostgreSQL数据库部分更改为MySQL,这是代码:

import pymysql
import paramiko
import sqlalchemy
from sshtunnel import SSHTunnelForwarder
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
import pandas as pd

#SSH config
mypkey = paramiko.RSAKey.from_private_key_file('id_rsa_file', password = 'id_rsa_password')
ssh_host = 'your_ssh_host'
ssh_user = 'your_ssh_host_username'
ssh_port = 22

#SQL config         
sql_hostname = 'your_sql_host_name'
sql_username = 'sql_user'
sql_password = 'sql_password'
sql_main_database = 'your_database_name'
sql_port = 3306
host = '127.0.0.1'

with SSHTunnelForwarder((ssh_host, ssh_port),
                        ssh_username=ssh_user,
                        ssh_pkey=mypkey,
                        remote_bind_address=(sql_hostname, sql_port)) as tunnel:
    #Connect to SQL
    local_port = str(tunnel.local_bind_port)
    engine = create_engine(f'mysql+pymysql://{sql_username}:{sql_password}@127.0.0.1:' + local_port +f'/{sql_main_database}')
    Session = sessionmaker(bind = engine)
    session = Session()
    print('Database session created!')

    #To inspect the schemas and tables in your database
    inspector = inspect(engine)
    schemas = inspector.get_schema_names()
    for schema in schemas:
        print(f'schema:{schema}')
        for table_name in inspector.get_table_names(schema = schema):
            print(f'table: {table_name}')

    query_code = "your query code from SQL here"

    #Execute query code
    exec_database = session.execute(query_code)
    df = pd.DataFrame(exec_database.fetchall())
    df.columns = exec_database.keys()
    
    print('Dataframe created from database!')
    session.close()
    engine.dispose()

您还可以更改以下部分:

#Execute query code    
exec_database = session.execute(query_code)
df = pd.DataFrame(exec_database.fetchall())
df.columns = exec_database.keys()

使用下面的代码直接使用pandas读取SQL查询:

df = pd.read_sql_query(query_code, engine)

此外,下面的代码部分:

#To inspect the schemas and tables in your database
inspector = inspect(engine)
schemas = inspector.get_schema_names()
for schema in schemas:
    print(f'schema:{schema}')
    for table_name in inspector.get_table_names(schema = schema):
        print(f'table: {table_name}')

仅在您不知道数据库中有哪些模式和表时才需要。您可以使用上面的代码进行检查和显示。