簡體   English   中英

使用帶有 SOCKS 代理的 Paramiko 連接到 SSH 服務器

[英]Connect to an SSH server using Paramiko with a SOCKS proxy

我需要通過 SSH 連接到服務器並運行一些命令,並想使用 Python 編寫命令腳本。 我曾嘗試使用 Paramiko,但這很困難,因為(我認為)我需要使用的 ProxyCommand 不使用ssh而是nc

鑒於myserver的以下 SSH 配置,如何使用 Paramiko 創建此服務器的連接?

Host myserver
  HostName     myserver.domain.tld
  Port         2222
  ForwardAgent yes
  User         myusername
  IdentityFile ~/.ssh/myprivatekey
  ProxyCommand nc -x socks-proxy.intranet.domain.tld:1085 -X 5 %h %p 2> /dev/null

最終,此腳本將在 RunDeck 上執行,因此理想情況下不需要依賴 OS 功能。

我看過以前的問題,但似乎沒有一個涵蓋我的具體用例。 據我所知:

# using PySocks
import paramiko 
import socks
sock = socks.socksocket()
sock.set_proxy(
 proxy_type=socks.SOCKS5,
 addr='socks-proxy.intranet.domain.tld',
 port=1085
)
sock.connect(('myserver.domain.tld', 2222))
private_key = paramiko.RSAKey.from_private_key_file('/home/myusername/.ssh/myprivatekey')
ssh = paramiko.SSHClient()
ssh.connect('myserver.domain.tld', port=2222, sock=sock, pkey=private_key)

我得到的錯誤信息是

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/home/myusername/.virtualenvs/image-deleter/lib/python3.6/site-packages/paramiko/client.py", line 406, in connect
    t.start_client(timeout=timeout)
  File "/home/myusername/.virtualenvs/image-deleter/lib/python3.6/site-packages/paramiko/transport.py", line 660, in start_client
    raise e
  File "/home/myusername/.virtualenvs/image-deleter/lib/python3.6/site-packages/paramiko/transport.py", line 2055, in run
    ptype, m = self.packetizer.read_message()
  File "/home/myusername/.virtualenvs/image-deleter/lib/python3.6/site-packages/paramiko/packet.py", line 459, in read_message
    header = self.read_all(self.__block_size_in, check_rekey=True)
  File "/home/myusername/.virtualenvs/image-deleter/lib/python3.6/site-packages/paramiko/packet.py", line 303, in read_all
    raise EOFError()
EOFError

根據這個 Github 問題,這聽起來不太可能,除非自該問題創建以來已經取得了一些新的發展。

您可以使用 shell 命令進行 SSH,但這需要在 Windows 機器上安裝 Putty。 這是我過去使用此方法使用的一些代碼。 它不會創建 SOCKS 代理,但您應該能夠輕松修改 shell 命令以在適當的 SOCKS 代理下運行。

import subprocess
import os
import re
from pathlib import Path

serverlist = ['some_server_name' = [
    'ssh-username' = '',
    'host' = '',
    'ssh-password' = '',
]]

def run(server, command, shell=True, outside_ssh='', verbose=False):
    """Determines the best method for running an SSH command, and then runs it, returning the ouput as a string.
    If anything is outputted to stderr, an error is raised. It outside_ssh is provided, it'll be added onto the right-side of the generated command."""

    command = command.replace('"', '\\"')

    if "localhost" in servers_get(server, "host") or "127.0.0.1" in servers_get(server, "host"):
        if verbose:
            print("executing:", command)
        stdout_fh = io.StringIO()
        stderr_fh = io.StringIO()
        with redirect_stderr(stderr_fh):
            with redirect_stdout(stdout_fh):
                subprocess.run(command, shell=shell)
        error_msg = stderr_fh.getvalue()
        error_msg = error_msg.replace("stdin: is not a tty", "")
        error_msg = error_msg.replace("Warning: Using a password on the command line interface can be insecure.", "")
        error_msg = error_msg.strip()
        if error_msg:
            raise SSHError(error_msg)
        return stdout_fh.getvalue()

    s = servers_get(server)
    s_user = s["ssh-username"]
    s_host = s["host"]
    s_passwd = s["ssh-password"]

    if os.name == "nt":
        if command:
            cmd = 'plink -ssh {s_user}@{s_host} -pw {s_passwd} "{command}" {outside_ssh}'.format(**locals())
        else:
            cmd = 'putty -ssh {s_user}@{s_host} -pw {s_passwd} {outside_ssh}'.format(**locals())

        ssh_config = Path("~") / ".ssh" / "config"
        ssh_config = ssh_config.expanduser()
        if ssh_config.is_file() and server in ssh_config.read_text():

            #check if we have to use putty or if we already have ssh keys configured
            #Putty opens up in a new window which is annoying, so if ssh keys are already installed we use those,
            #otherwise, we use putty so that we can pass in the password on the command line
            cmd2 = "ssh -q -o ConnectTimeout=1 {server} exit".format(**locals())
            try:
                subprocess.check_output(cmd2) #this will fail if ssh keys are not setup
                cmd = 'ssh {server} "{command}"'.format(**locals())
            except subprocess.CalledProcessError:
                pass
    else:
        cmd = 'sshpass -p "{s_passwd}" ssh -o StrictHostKeyChecking=no {s_user}@{s_host} "{command}" {outside_ssh}'.format(**locals())

    try:
        try:
            if verbose:
                print("executing:", cmd)
            proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True)
        except (FileNotFoundError, subprocess.CalledProcessError):
            #if putty is not installed (windows) or if sshpass does not work (Linux), run the normal ssh command and the user will have to type in the password on the command line
            cmd = 'ssh {s_user}@{s_host} "{command}" {outside_ssh}'.format(**locals())
            if verbose:
                print("nevermind, that command failed. Executing", cmd)
            print("use the password {s_passwd} when prompted".format(**locals()))

            proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True)
    except subprocess.CalledProcessError:
        if proc.stderr:
            ssh_error_msg = b"\n".join(proc.stderr.readlines()).decode("utf-8")
            ssh_error_msg = ssh_error_msg.replace("stdin: is not a tty", "")
            ssh_error_msg = ssh_error_msg.replace("Warning: Using a password on the command line interface can be insecure.", "")
            ssh_error_msg = ssh_error_msg.strip()
            if ssh_error_msg:
                print()
                print("-"*79)
                print("recieved an error when running the command")
                print(cmd)
                print("-"*79)
                print()
                raise SSHError(ssh_error_msg)
        raise

    if proc.stderr:
        ssh_error_msg = b"\n".join(proc.stderr.readlines()).decode("utf-8")
        ssh_error_msg = ssh_error_msg.replace("stdin: is not a tty", "")
        ssh_error_msg = ssh_error_msg.replace("Warning: Using a password on the command line interface can be insecure.", "")
        ssh_error_msg = ssh_error_msg.replace("mysqldump: [Warning] Using a password on the command line interface can be insecure.", "")
        ssh_error_msg = re.sub(r"Warning: Permanently added '[^']+' \(ECDSA\) to the list of known hosts.", "", ssh_error_msg)
        # Maybe we should just make any error message starting with "Warning:..." be ignored.
        ssh_error_msg = ssh_error_msg.strip()
        if ssh_error_msg:
            print()
            print("-"*79)
            print("recieved an error when running the command")
            print(cmd)
            print("-"*79)
            print()
            raise SSHError(ssh_error_msg)

    return b"".join(proc.stdout.readlines()).decode("utf-8")

def servers_get(server, lookup=None, deprecated=True):
    """ returns a dictionary of info about a server entry,
    or looks up a specific item in this dictionary if lookup is specified.
    returns None if the server entry does not exist."""
    global serverlist

    if server not in serverlist:
        return
    if lookup:
        return serverlist[server].get(lookup)
    return serverlist[server]

class MyBaseException(Exception):
    def __init__(self, title, message=None):
        """pass in either a title and an error message, or just an error message"""
        if message and title:
            self.title = title
            self.message = self.msg = message
            super(OWException, self).__init__("\n"+"-"*80+"\n"+title+": "+self.message)
        else:
            self.message = self.msg = title
            super(OWException, self).__init__(title)

class SSHError(MyBaseException):
    """ Raised when an SSH command returns a non-zero exit status.
    One of the subprocess error could still be raised if the command fails for some other reason """
    def __init__(self, title, message=None, *args, **kwargs):
        super(SSHError, self).__init__(title, message, *args, **kwargs)
        self.title = title
        if not message:
            message = title
        self.message = self.msg = message

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM