如何使用Python和Paramiko创建SSH隧道?

bbmckpt7  于 2023-02-07  发布在  Python
关注(0)|答案(6)|浏览(258)

我需要创建隧道来从数据库读取信息。我使用Paramiko,但我还没有使用过隧道。请提供一个创建和关闭隧道的简单代码示例。

svgewumm

svgewumm1#

在工作中,我们通常创建ssh隧道转发端口。我们这样做的方法是,使用标准命令ssh -L port:addr:port addr,子进程在一个单独的线程中运行。https://github.com/paramiko/paramiko/blob/master/demos/forward.py,其中包含使用paramiko执行端口转发的示例。

wkyowqbh

wkyowqbh2#

我在项目中使用sshtunnel。将远程本地MySQL端口转发到主机本地端口的示例:

pip install sshtunnel
python -m sshtunnel -U root -P password -L :3306 -R 127.0.0.1:3306 -p 2222 localhost
lxkprmvk

lxkprmvk3#

尽管这没有使用paramiko,我相信这是一个非常干净的解决方案(类似于@dario的答案,但没有在python中管理线程)。
openssh客户端中有一个很少提及的特性,它允许我们通过unix套接字控制ssh进程,引用man ssh

-M      Places the ssh client into “master” mode for connection sharing.  Multiple -M options places ssh
         into “master” mode with confirmation required before slave connections are accepted.  Refer to the
         description of ControlMaster in ssh_config(5) for details.
-S ctl_path
         Specifies the location of a control socket for connection sharing, or the string “none” to disable
         connection sharing.  Refer to the description of ControlPath and ControlMaster in ssh_config(5)
         for details.

因此,您可以启动ssh的后台进程(使用-Nf),然后使用另一个ssh调用检查(或终止)它。
我在需要建立反向隧道的项目中使用此选项

from subprocess import call, STDOUT
import os
DEVNULL = open(os.devnull, 'wb')

CONFIG = dict(
    SSH_SERVER='ssh.server.com',
    SSH_PORT=2222,
    SSH_USER='myuser',
    SSH_KEY='/path/to/user.key',
    REMOTE_PORT=62222,
    UNIX_SOCKET='/tmp/ssh_tunnel.sock',
    KNOWN_HOSTS='/path/to/specific_known_host_to_conflicts',
)

def start():
    return call(
        [
            'ssh', CONFIG['SSH_SERVER'],
            '-Nfi', CONFIG['SSH_KEY'],
            '-MS', CONFIG['UNIX_SOCKET'],
            '-o', 'UserKnownHostsFile=%s' % CONFIG['KNOWN_HOSTS'],
            '-o', 'ExitOnForwardFailure=yes',
            '-p', str(CONFIG['SSH_PORT']),
            '-l', CONFIG['SSH_USER'],
            '-R', '%d:localhost:22' % CONFIG['REMOTE_PORT']
        ],
        stdout=DEVNULL,
        stderr=STDOUT
    ) == 0

def stop():
    return __control_ssh('exit') == 0

def status():
    return __control_ssh('check') == 0

def __control_ssh(command):
    return call(
        ['ssh', '-S', CONFIG['UNIX_SOCKET'], '-O', command, 'x'],
        stdout=DEVNULL,
        stderr=STDOUT
    )

-o ExitOnForwardFailure=yes确保如果无法建立隧道,ssh命令将失败,否则将不退出。

n3h0vuf2

n3h0vuf24#

我可以建议您尝试使用pyngrok之类的工具来编程管理ngrok隧道吗?完全公开,我是它的开发者。这里有SSH示例,但它和安装pyngrok一样简单:

pip install pyngrok

并使用它:

from pyngrok import ngrok

# <NgrokTunnel: "tcp://0.tcp.ngrok.io:12345" -> "localhost:22">
ssh_tunnel = ngrok.connect(22, "tcp")
6xfqseft

6xfqseft5#

我添加此解决方案用于多个端口上的多跳:
我有这样的设置:

目标是通过在端口33306上调用我的计算机来访问数据库。这是不可能的,因为只允许gateway2与数据库对话。我们无法访问gateway2,因为只允许gateway1与它对话。
下面是相应的ssh. config文件:

Host gateway1
    HostName gtw1_IP_address
    User gtw1_user
    IdentityFile "path_to_gtw1_ssh_key"
    IdentitiesOnly True

Host gateway2
    User gtw2_user
    Hostname gtw2_IP_address
    IdentityFile "path_to_gtw2_ssh_key"
    IdentitiesOnly True
    # mysql
    LocalForward 127.0.0.1:33306 127.0.0.1:3306
    ProxyCommand ssh -W %h:%p switch-cede

下面是我在python中的重现:

from sqlalchemy import create_engine
import config
import pandas as pd
import sshtunnel
from paramiko import SSHClient

with sshtunnel.open_tunnel(
    ssh_username='gtw1_user',
    ssh_address_or_host=('gtw1_IP_address', 22),
    remote_bind_addresses=[('gtw2_IP_address', 22), ('gtw2_IP_address', 33306)],
    local_bind_addresses=[('0.0.0.0', 22), ('0.0.0.0', 33306)], #this line is optional
    ssh_pkey=path_to_gtw1_ssh_key,
) as tunnel1: # tunnel1 is the tunnel between myMachine and gateway1 I believe
    print('Connection to tunnel1 (86.119.30.24:22) OK...')
    print(tunnel1.local_bind_ports)
    with sshtunnel.open_tunnel(
        ssh_address_or_host=('localhost', tunnel1.local_bind_ports[0]),
        remote_bind_addresses=[('127.0.0.1', 22),('127.0.0.1', 3306)],
        local_bind_addresses=[('0.0.0.0', 22), ('127.0.0.1', 33306)],
        ssh_username='gtw2_user',
        ssh_pkey=path_to_gtw2_ssh_key,
    ) as tunnel2: # tunnel2 is the tunnel between gtw1 and gtw2 I believe
        print('Connection to tunnel2 (192.168.142.140:22) OK...')
        print(tunnel2.local_bind_ports)
        db = create_engine(
            f"mysql+pymysql://{config.USER}:{config.PASSWORD}@{config.HOST}:{config.PORT}/{config.DATABASE}")
        print(db)
        query = "SELECT * FROM randomTable LIMIT 10;"
        df = pd.read_sql(query, db)
        print(df)
# note that config is a file holding the credentials to connect to the database
swvgeqrz

swvgeqrz6#

我在一年前的某个项目中使用了paramiko,下面是我连接到另一台计算机/服务器并执行一个简单python文件的部分代码:

import paramiko

ssh = paramiko.SSHClient()
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
ssh.connect(hostname='...', username='...', password='...')
stdin, stdout, stderr = ssh.exec_command('python hello.py')
ssh.close()

stdinstdoutsdterr包含所执行命令的输入/输出。
从这里,我想你可以连接到数据库。
Here is some good information about paramiko.

相关问题