Python3 用于访问远程服务器上的数据库的 Fabric ssh 隧道 [paramiko.ssh_exception.NoValidConnectionsError]

Python3 Fabric ssh tunnel to access database on remote server [paramiko.ssh_exception.NoValidConnectionsError]

我的网络跃点是 A(本地)-> B -> C -> D(在端口 3306 上为本地主机打开数据库)。

我需要从 A 访问 D 上的那个数据库。

我正在使用 fabric 进行 ssh 连接

我的代码如下所示:

import socket
from contextlib import closing
from fabric import Connection
import mysql.connector

def _connect_to_middleware_database(self, port):
    return mysql.connector.connect(
        user="DB-USER",
        password="MYPASS",
        host="127.0.0.1",
        port=port,
        database="tasks",
        connection_timeout=10)


def get_free_port():
    with closing(socket.socket(socket.AF_INET, socket.SOCK_STREAM)) as s:
        s.bind(("localhost", 0))
        s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        return s.getsockname()[1]

def _get_connection(self, ip, port, user, pw):
    return Connection(
        host=ip,
        port=port,
        user=user,
        connect_kwargs={
            "password": pw,
            "allow_agent": False,
            "look_for_keys": False,
        },
    )


res = {}
local_port = get_free_port()
local_port_1 = get_free_port()
local_port_2 = get_free_port()

c1 = self._get_connection(
    "IP-B",
    22,
    "USER-B",
    "PASS-B",
)

c2 = self._get_connection(
    "127.0.0.1",
    local_port,
    "USER-C",
    "PASS-C",
)

c3 = self._get_connection(
    "127.0.0.1",
    local_port_1,
    "USER-D",
    "PASS-D",
)



with c1.forward_local(
    local_port=local_port,
    remote_port=22,
    local_host="127.0.0.1",
    remote_host="IP-C",
):
    with c2.forward_local(
        local_port=local_port_1,
        remote_port=22,
        local_host="127.0.0.1",
        remote_host="IP-D",
    ):
        with c3.forward_local(
            local_port=local_port_2,
            remote_port=3306,
            local_host="127.0.0.1",
            remote_host="127.0.0.1",
        ):

            cnx = _connect_to_middleware_database(
                local_port_2
            )
            if cnx:
                cursor = cnx.cursor()
                cursor.execute("show tables")
                sql_result = cursor.fetchall()
                print(str(sql_result))

问题是它确实不一致。在一些尝试中它有效,但在大多数情况下我得到:

Traceback (most recent call last):
  File "tunnel_test.py", line 78, in <module>
    remote_host="IP-D",
  File "/usr/lib/python3.6/contextlib.py", line 159, in helper
    return _GeneratorContextManager(func, args, kwds)
  File "/usr/lib/python3.6/contextlib.py", line 60, in __init__
    self.gen = func(*args, **kwds)
  File "<decorator-gen-6>", line 2, in forward_local
  File "/usr/local/lib/python3.6/dist-packages/fabric/connection.py", line 29, in opens
    self.open()
  File "/usr/local/lib/python3.6/dist-packages/fabric/connection.py", line 634, in open
    self.client.connect(**kwargs)
  File "/usr/local/lib/python3.6/dist-packages/paramiko/client.py", line 368, in connect
    raise NoValidConnectionsError(errors)
paramiko.ssh_exception.NoValidConnectionsError: [Errno None] Unable to connect to port 59235 on 127.0.0.1

它在某些情况下有效,而且我使用的是绝对可访问的主机。

感谢大家的回答。

使用 sshtunnel 代替 fabric

import sshtunnel

with sshtunnel.open_tunnel(
    ssh_address_or_host=('IP_B', 22),
    remote_bind_address=('IP_C', 22),
    ssh_username='USER_B',
    ssh_password='PASS_B',
    block_on_close=False
) as tunnel1:
    print('Connection to B OK...')
    with sshtunnel.open_tunnel(
        ssh_address_or_host=('localhost', tunnel1.local_bind_port),
        remote_bind_address=('IP_D', 22),
        ssh_username='USER_C',
        ssh_password='PASS_C',
        block_on_close=False
    ) as tunnel2:
        print('Connection to C OK...')
        with sshtunnel.open_tunnel(
            ssh_address_or_host=('localhost', tunnel2.local_bind_port),
            remote_bind_address=('127.0.0.1', 3306),
            ssh_username='USER_D',
            ssh_password='PASS_D',
            block_on_close=False
        ) as tunnel3:
            print('Connection to D OK...')
            cnx = _connect_to_middleware_database(
                tunnel3.local_bind_port
            )
            if cnx:
                cursor = cnx.cursor()
                cursor.execute("show tables")
                sql_result = cursor.fetchall()
                print(str(sql_result))