You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何通过已SSH连接的主机B中转实现主机A到主机C的文件传输及命令执行(基于Paramiko)

如何通过已SSH连接的主机B中转实现主机A到主机C的文件传输及命令执行(基于Paramiko)

我完全理解你的处境——受网络拓扑限制,hostA只能访问hostB,而hostC只有hostB能碰到,现在已经搞定了A到B的文件传输和命令执行,卡在了B到C的中转环节。用Paramiko实现这个其实有两种很实用的方案,我来一步步帮你解决,顺便把命令执行和文件传输的问题都搞定:

核心思路说明

你现在的代码是在hostA上运行的,要访问hostC,本质是要通过hostB作为SSH跳板。这里有两种主流实现方式:

  • 方案1(推荐):直接通过hostB代理连接hostC:用Paramiko的TCP通道代理功能,在hostA上直接建立到hostC的SSH连接,所有操作(命令执行、文件传输)都像直接连C一样,可控性更强。
  • 方案2:在hostB上执行SSH/SCP命令中转:在hostB的SSH会话里,通过执行ssh或scp命令来操作hostC,适合快速临时场景,但密码处理相对麻烦。

方案1:通过HostB代理直接连接HostC(推荐)

我先修正你现有代码的小问题,然后添加代理连接hostC的逻辑,同时实现命令执行和文件传输:

完整修改后的代码

import os
import paramiko

def main():
    try:
        # 第一步:连接到hostB
        ssh_client_b = connect_to_host("100.100.100.100", "hostB", "hostB123")
        execute_command_over_ssh(ssh_client_b, "uname")
        execute_command_over_ssh(ssh_client_b, "ls /home/hostB/")
        
        # 从hostA传输文件到hostB
        src_path_on_A = "/home/hostA/repos/repo1/"
        dest_path_on_B = "/home/hostB/repos/"
        transfer_file_or_directory(ssh_client_b, src_path_on_A, dest_path_on_B)

        # 第二步:通过hostB作为代理,连接到hostC(核心逻辑)
        ssh_client_c = connect_to_host(
            host_name="200.200.200.200",
            user_name="hostC",
            password="hostC123",
            proxy_ssh_client=ssh_client_b  # 传入hostB的连接作为跳板
        )

        # 在hostC上执行命令并获取结果
        execute_command_over_ssh(ssh_client_c, "uname")
        execute_command_over_ssh(ssh_client_c, "ls /home/hostC/")

        # 方式1:在hostB上执行scp命令,将B上的文件传到C(符合你先存B再转C的需求)
        # 注意:若要免密执行,需在hostB上配置到hostC的SSH密钥对,替代sshpass
        scp_command = f"sshpass -p 'hostC123' scp -r {dest_path_on_B}/repo1/ hostC@200.200.200.200:/home/hostC/repos/"
        execute_command_over_ssh(ssh_client_b, scp_command)

        # 方式2(更高效):直接从A通过B代理传文件到C(跳过B的留存)
        # transfer_file_or_directory(ssh_client_c, src_path_on_A, "/home/hostC/repos/")

        # 关闭连接
        ssh_client_c.close()
        ssh_client_b.close()
    except Exception as e:
        print(f"Error occurred: {str(e)}")
    finally:
        print("All SSH connections closed")

def connect_to_host(host_name, user_name, password, proxy_ssh_client=None):
    """
    建立SSH连接,支持通过已有SSH连接作为代理(跳板)
    :param proxy_ssh_client: 已连接的SSHClient对象(如hostB的连接)
    """
    ssh_client = paramiko.SSHClient()
    ssh_client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
    try:
        if proxy_ssh_client:
            # 建立TCP代理通道:将hostA的流量通过hostB中转到hostC的22端口
            sock = proxy_ssh_client.get_transport().open_channel(
                "direct-tcpip",
                (host_name, 22),
                (proxy_ssh_client.get_transport().getpeername()[0], 0)
            )
            # 通过代理通道连接hostC
            ssh_client.connect(
                hostname=host_name,
                username=user_name,
                password=password,
                sock=sock
            )
        else:
            # 直接连接目标主机
            ssh_client.connect(
                hostname=host_name,
                username=user_name,
                password=password,
                port=22
            )
        print(f"Successfully connected to {host_name}")
        return ssh_client
    except Exception as e:
        print(f"Failed to connect to {host_name}: {str(e)}")
        ssh_client.close()
        raise

def execute_command_over_ssh(ssh_client, command):
    """在SSH连接上执行命令,返回并打印输出/错误信息"""
    try:
        stdin, stdout, stderr = ssh_client.exec_command(command)
        output = stdout.read().decode('utf-8').strip()
        error = stderr.read().decode('utf-8').strip()
        
        print(f"=== Command: `{command}` ===")
        if output:
            print(f"Output:\n{output}")
        if error:
            print(f"Error:\n{error}")
        return output, error
    except Exception as e:
        print(f"Failed to execute command `{command}`: {str(e)}")
        raise

def transfer_file_or_directory(ssh_client, local_path, remote_path):
    """递归传输文件或目录到远程主机"""
    try:
        sftp = ssh_client.open_sftp()
        # 确保远程目标目录存在
        try:
            sftp.stat(remote_path)
        except FileNotFoundError:
            sftp.mkdir(remote_path, mode=0o755)

        if os.path.isfile(local_path):
            remote_file = os.path.join(remote_path, os.path.basename(local_path))
            sftp.put(local_path, remote_file)
            print(f"Transferred file: {local_path} -> {remote_file}")
        elif os.path.isdir(local_path):
            _transfer_dir_recursive(sftp, local_path, os.path.join(remote_path, os.path.basename(local_path)))
            print(f"Transferred directory: {local_path} -> {remote_path}")
        else:
            raise ValueError(f"Invalid path: {local_path} is neither file nor directory")
        sftp.close()
    except Exception as e:
        print(f"Transfer failed: {str(e)}")
        raise

def _transfer_dir_recursive(sftp, local_dir, remote_dir):
    """递归传输目录的辅助函数"""
    try:
        sftp.stat(remote_dir)
    except FileNotFoundError:
        sftp.mkdir(remote_dir, mode=0o755)
    
    for item in os.listdir(local_dir):
        local_item = os.path.join(local_dir, item)
        remote_item = os.path.join(remote_dir, item)
        
        if os.path.isfile(local_item):
            sftp.put(local_item, remote_item)
        elif os.path.isdir(local_item):
            _transfer_dir_recursive(sftp, local_item, remote_item)

if __name__ == "__main__":
    main()

关键部分解释

  1. 带代理的SSH连接:
    我修改了connect_to_host函数,增加了proxy_ssh_client参数。当传入已连接的ssh_client_b时,会通过它建立一个直接TCP通道,把hostA的SSH流量通过hostB中转到hostC的22端口,实现“通过hostB连hostC”的效果,完全不需要在hostB上额外配置。

  2. HostC的命令执行:
    优化后的execute_command_over_ssh函数不仅能执行命令,还能捕获标准输出和错误输出,比你原来的函数更健壮,能帮你快速定位命令执行的问题。

  3. B到C的文件传输:
    提供了两种符合需求的方式:

    • 方式一:在hostB上执行scp命令,把已存到B的文件传到C,完全匹配你“先存B再转C”的需求;
    • 方式二:直接从A通过B代理传文件到C,省去中间在B的存储步骤,更高效。

方案2:在HostB上执行SSH/SCP命令中转(快速临时方案)

如果你不想修改太多Paramiko连接逻辑,也可以直接在hostB的SSH会话里执行原生SSH命令操作hostC:

示例代码片段

# 在hostB上执行命令,登录hostC并执行uname
ssh_cmd = "sshpass -p 'hostC123' ssh hostC@200.200.200.200 'uname'"
execute_command_over_ssh(ssh_client_b, ssh_cmd)

# 在hostB上执行scp命令,把文件传到hostC
scp_cmd = "sshpass -p 'hostC123' scp -r /home/hostB/repos/repo1/ hostC@200.200.200.200:/home/hostC/repos/"
execute_command_over_ssh(ssh_client_b, scp_cmd)

注意事项

  • 安全提示:sshpass会明文暴露密码,推荐在hostB上配置到hostC的SSH密钥对(执行ssh-keygen生成密钥,将公钥传到hostC的~/.ssh/authorized_keys),这样就可以去掉sshpass部分,直接用ssh hostC@xxx command。
  • 输出处理:这种方式需要自己解析命令输出,可控性不如方案1。

额外提示

  • 错误处理:代码中添加了完整的异常捕获,能及时发现连接、命令执行、文件传输中的问题;
  • SSH密钥替代密码:所有用明文密码的地方都推荐换成SSH密钥对,既安全又避免硬编码密码(Paramiko支持通过key_filename参数加载密钥文件);
  • 大文件传输:传输大文件时,可以给sftp.put添加callback参数实现进度条,方便监控传输状态。

内容来源于stack exchange

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.08 07:34:37