Skip to content

Commit 5fa7bf9

Browse files
author
Dan
committed
WIP - Added pure python SSH proxy support (aka tunnelling)
1 parent 8a9f634 commit 5fa7bf9

File tree

1 file changed

+42
-21
lines changed

1 file changed

+42
-21
lines changed

pssh.py

Lines changed: 42 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,8 @@ class SSHClient(object):
8080
def __init__(self, host,
8181
user=None, password=None, port=None,
8282
pkey=None, forward_ssh_agent=True,
83-
num_retries=DEFAULT_RETRIES, _agent=None, timeout=None):
83+
num_retries=DEFAULT_RETRIES, _agent=None, timeout=None,
84+
proxy_host=None, proxy_port=22):
8485
"""Connect to host honouring any user set configuration in ~/.ssh/config \
8586
or /etc/ssh/ssh_config
8687
@@ -135,36 +136,53 @@ def __init__(self, host,
135136
client.set_missing_host_key_policy(paramiko.MissingHostKeyPolicy())
136137
self.forward_ssh_agent = forward_ssh_agent
137138
self.client = client
138-
self.channel = None
139139
self.user = user
140140
self.password = password
141141
self.pkey = pkey
142142
self.port = port if port else 22
143143
self.host = resolved_address
144-
self.proxy_command = paramiko.ProxyCommand(host_config['proxycommand']) if 'proxycommand' in \
145-
host_config else None
146-
if self.proxy_command:
147-
logger.debug("Proxy configured for destination host %s - ProxyCommand: '%s'",
148-
self.host, " ".join(self.proxy_command.cmd),)
149144
if _agent:
150145
self.client._agent = _agent
151146
self.num_retries = num_retries
152147
self.timeout = timeout
153-
self._connect()
154-
155-
def _connect(self, retries=1):
148+
self.proxy_host, self.proxy_port = proxy_host, proxy_port
149+
if self.proxy_host and self.proxy_port:
150+
logger.debug("Proxy configured for destination host %s - Proxy host: %s:%s",
151+
self.host, self.proxy_host, self.proxy_port,)
152+
self._connect_proxy()
153+
else:
154+
self._connect(self.client, self.host, self.port)
155+
156+
def _connect_proxy(self):
157+
"""Connects to SSH server and returns transport channel to be used
158+
to forward proxy to another SSH server.
159+
client (me) -> proxy (ssh server to proxy through) -> \
160+
destination (ssh server to run command)
161+
:rtype: `:mod:paramiko.Channel` Channel to proxy through"""
162+
self.proxy_client = paramiko.SSHClient()
163+
self.proxy_client.set_missing_host_key_policy(paramiko.MissingHostKeyPolicy())
164+
self._connect(self.proxy_client, self.proxy_host, self.proxy_port)
165+
logger.info("Connecting via SSH proxy %s:%s -> %s:%s", self.proxy_host,
166+
self.proxy_port, self.host, self.port,)
167+
proxy_channel = self.proxy_client.get_transport().\
168+
open_channel('direct-tcpip', (self.host, self.port,),
169+
('127.0.0.1', 0))
170+
return self._connect(self.client, self.host, self.port, sock=proxy_channel)
171+
172+
def _connect(self, client, host, port, sock=None, retries=1):
156173
"""Connect to host, throw UnknownHost exception on DNS errors"""
157174
try:
158-
self.client.connect(self.host, username=self.user,
159-
password=self.password, port=self.port,
160-
pkey=self.pkey,
161-
sock=self.proxy_command, timeout=self.timeout)
175+
client.connect(host, username=self.user,
176+
password=self.password, port=port,
177+
pkey=self.pkey,
178+
sock=sock, timeout=self.timeout)
162179
except sock_gaierror, e:
163180
logger.error("Could not resolve host '%s' - retry %s/%s",
164181
self.host, retries, self.num_retries)
165182
while retries < self.num_retries:
166183
gevent.sleep(5)
167-
return self._connect(retries=retries+1)
184+
return self._connect(client, host, port, sock=sock,
185+
retries=retries+1)
168186
raise UnknownHostException("%s - %s - retry %s/%s",
169187
str(e.args[1]),
170188
self.host, retries, self.num_retries)
@@ -173,8 +191,8 @@ def _connect(self, retries=1):
173191
self.host, self.port, retries, self.num_retries)
174192
while retries < self.num_retries:
175193
gevent.sleep(5)
176-
return self._connect(retries=retries+1)
177-
194+
return self._connect(client, host, port, sock=sock,
195+
retries=retries+1)
178196
error_type = e.args[1] if len(e.args) > 1 else e.args[0]
179197
raise ConnectionErrorException("%s for host '%s:%s' - retry %s/%s",
180198
str(error_type), self.host, self.port,
@@ -184,7 +202,7 @@ def _connect(self, retries=1):
184202
except paramiko.ProxyCommandFailure, e:
185203
logger.error("Error executing ProxyCommand - %s", e.message,)
186204
raise ProxyCommandException(e.message)
187-
# SSHException is more general so should below other types
205+
# SSHException is more general so should be below other types
188206
# of SSH failure
189207
except paramiko.SSHException, e:
190208
raise SSHException(e)
@@ -211,7 +229,7 @@ def exec_command(self, command, sudo=False, user=None, **kwargs):
211229
channel = self.client.get_transport().open_session()
212230
if self.forward_ssh_agent:
213231
agent_handler = paramiko.agent.AgentRequestHandler(channel)
214-
channel.get_pty()
232+
# channel.get_pty()
215233
if self.timeout:
216234
channel.settimeout(self.timeout)
217235
_stdout, _stderr = channel.makefile('rb'), \
@@ -301,7 +319,7 @@ class ParallelSSHClient(object):
301319
def __init__(self, hosts,
302320
user=None, password=None, port=None, pkey=None,
303321
forward_ssh_agent=True, num_retries=DEFAULT_RETRIES, timeout=None,
304-
pool_size=10):
322+
pool_size=10, proxy_host=None, proxy_port=22):
305323
"""
306324
:param hosts: Hosts to connect to
307325
:type hosts: list(str)
@@ -401,6 +419,7 @@ def __init__(self, hosts,
401419
self.pkey = pkey
402420
self.num_retries = num_retries
403421
self.timeout = timeout
422+
self.proxy_host, self.proxy_port = proxy_host, proxy_port
404423
# To hold host clients
405424
self.host_clients = dict((host, None) for host in hosts)
406425

@@ -500,7 +519,9 @@ def _exec_command(self, host, *args, **kwargs):
500519
port=self.port, pkey=self.pkey,
501520
forward_ssh_agent=self.forward_ssh_agent,
502521
num_retries=self.num_retries,
503-
timeout=self.timeout)
522+
timeout=self.timeout,
523+
proxy_host=self.proxy_host,
524+
proxy_port=self.proxy_port)
504525
return self.host_clients[host].exec_command(*args, **kwargs)
505526

506527
def get_output(self, commands=None):

0 commit comments

Comments
 (0)