feat: SSH tunnel server extended to execute commands on remote host.

Signed-off-by: Sven Sager <akira@narux.de>
This commit is contained in:
2023-08-31 09:03:35 +02:00
parent a0b309ade0
commit 6ee53595e2

View File

@@ -10,14 +10,18 @@ __copyright__ = "Copyright (C) 2023 Sven Sager"
__license__ = "GPLv2" __license__ = "GPLv2"
import select import select
from logging import getLogger
from socketserver import BaseRequestHandler, ThreadingTCPServer from socketserver import BaseRequestHandler, ThreadingTCPServer
from threading import Thread from threading import Thread
from typing import Tuple, Union
from paramiko.client import MissingHostKeyPolicy, SSHClient from paramiko.client import MissingHostKeyPolicy, SSHClient
from paramiko.rsakey import RSAKey from paramiko.rsakey import RSAKey
from paramiko.ssh_exception import PasswordRequiredException from paramiko.ssh_exception import PasswordRequiredException
from paramiko.transport import Transport from paramiko.transport import Transport
log = getLogger("ssh_tunneling")
class ForwardServer(ThreadingTCPServer): class ForwardServer(ThreadingTCPServer):
daemon_threads = True daemon_threads = True
@@ -33,10 +37,14 @@ class Handler(BaseRequestHandler):
self.request.getpeername(), self.request.getpeername(),
) )
except Exception as e: except Exception as e:
log.error(e)
return return
if chan is None: if chan is None:
log.error("Could not create a ssh channel")
return return
log.info("Starting tunnel exchange loop")
while True: while True:
r, w, x = select.select([self.request, chan], [], [], 5.0) r, w, x = select.select([self.request, chan], [], [], 5.0)
if self.request in r: if self.request in r:
@@ -50,6 +58,8 @@ class Handler(BaseRequestHandler):
break break
self.request.send(data) self.request.send(data)
log.info("Stopped tunnel exchange loop")
chan.close() chan.close()
self.request.close() self.request.close()
@@ -162,6 +172,30 @@ class SSHLocalTunnel:
return True return True
return False return False
def send_cmd(self, cmd: str, timeout: float = None) -> Union[Tuple[str, str], Tuple[None, None]]:
"""
Send simple command to ssh host.
The output of stdout and stderr is returned as a tuple of two elements.
This elements could be None, in case of an internal error.
:param cmd: Shell command to execute on remote host
:param timeout: Timeout for execution
:return: Tuple with stdout and stderr
"""
if not self._th_server.is_alive():
raise RuntimeError("Not connected")
try:
_, stdout, stderr = self._ssh_client.exec_command(cmd, 1024, timeout)
buffer_out = stdout.read()
buffer_err = stderr.read()
return buffer_out.decode(), buffer_err.decode()
except Exception as e:
log.error(e)
return None, None
@property @property
def connected(self): def connected(self):
"""Check connection state of ssh tunnel.""" """Check connection state of ssh tunnel."""