我需要通过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
答案 0 :(得分:0)
根据this Github issue的说法,这似乎是不可能的,除非自从创建该问题以来已经做出了一些新的开发。
您可以使用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