使用跳转主机和远程数据库进行 SSH 隧道转发

ble*_*lep 6 python sql ssh sqlalchemy paramiko

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

[A: local host] -> [B: jump host] -> [C: target host] => [D: RDS MySQL host]
Run Code Online (Sandbox Code Playgroud)

我想通过 Python 访问 D,使用 paramiko 和/或 sshtunnel。我能找到的所有信息都涉及:

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

我目前的代码:

[A: local host] -> [B: jump host] -> [C: target host] => [D: RDS MySQL host]
Run Code Online (Sandbox Code Playgroud)

我正在寻找这样的东西(从sshtunnel 文档中的示例 2 修改):

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
Run Code Online (Sandbox Code Playgroud)

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

ble*_*lep 7

我想到了。它结合了 ssh 配置设置和sshtunnel库中的 SSHTunnelForwarder 上下文管理器。

使用以下模型和命名约定

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

我将 ~/.ssh/config 设置为从 A 到 C 通过 B:

Host C_ssh_shortcut
    HostName C_host
    User C_user
    Port 22
    ForwardAgent yes
    ProxyCommand ssh B_user@B_host -W %h:%p
Run Code Online (Sandbox Code Playgroud)

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

ssh-add
Run Code Online (Sandbox Code Playgroud)

最后我设置了 SSHTunnelForwarder:

import sqlalchemy
from sshtunnel import SSHTunnelForwarder

with SSHTunnelForwarder(
    "C_ssh_shortcut",                     # The SSHTunnelForwarder "ssh_address_or_host" argument, which takes care of bypassing B through the ProxyCommand set up in ~/.ssh/config
    remote_bind_address=(D_host, 3306),   # Points to your desired destination, ie. database host on 3306, which is the MySQL port
    local_bind_address=('', 1111)         # Gives a local way to access this host and port on your machine. '' is localhost / 127.0.0.1, 1111 is an unused port
) as server:
    connection_string = "mysql+pymysql://D_user:D_password@localhost:1111/D_dbname"  # note that D_host and D_port were replaced by the host and port defined in "local_bind_address"
    engine = sqlalchemy.create_engine(connection_string)
    # do your thing
Run Code Online (Sandbox Code Playgroud)

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