2022-11-23 19:18:40 +00:00
|
|
|
from sshim import *
|
2022-12-01 18:03:17 +00:00
|
|
|
import pylxd
|
2022-11-23 19:18:40 +00:00
|
|
|
import paramiko
|
|
|
|
import os
|
2022-11-25 15:41:02 +00:00
|
|
|
import uuid
|
|
|
|
import lxd_interface
|
|
|
|
import threading
|
|
|
|
import logging
|
2022-11-28 19:25:39 +00:00
|
|
|
import select
|
2022-11-26 23:06:01 +00:00
|
|
|
import time
|
2022-12-05 18:45:58 +00:00
|
|
|
import ipaddress
|
|
|
|
import secrets
|
2022-11-26 23:48:22 +00:00
|
|
|
import inspect
|
2022-11-25 15:41:02 +00:00
|
|
|
|
2022-11-26 22:24:31 +00:00
|
|
|
logger = logging.getLogger(__name__)
|
2022-11-24 19:59:21 +00:00
|
|
|
|
|
|
|
|
2022-11-29 18:23:11 +00:00
|
|
|
def check_channel_request(self, kind, channel_id):
|
|
|
|
logger.debug(f"Client requested {kind}")
|
|
|
|
if kind in ('session', 'sftp'):
|
|
|
|
return paramiko.OPEN_SUCCEEDED
|
|
|
|
return paramiko.OPEN_FAILED_ADMINISTRATIVELY_PROHIBITED
|
|
|
|
|
|
|
|
|
2022-11-25 15:41:02 +00:00
|
|
|
def check_channel_shell_request(self, channel):
|
2022-11-28 19:25:39 +00:00
|
|
|
logger.debug("Check channel shell request: %s" % channel.get_id())
|
2022-12-05 18:45:58 +00:00
|
|
|
Runner(self, self.username, 'shell', channel).start()
|
2022-11-25 15:41:02 +00:00
|
|
|
|
|
|
|
return True
|
|
|
|
|
|
|
|
|
2022-12-01 18:08:42 +00:00
|
|
|
def check_channel_exec_request(self, channel):
|
|
|
|
logger.debug("Check channel exec request: %s" % channel.get_id())
|
2022-12-05 18:45:58 +00:00
|
|
|
Runner(self, self.username, 'exec', channel).start()
|
2022-12-01 18:08:42 +00:00
|
|
|
|
|
|
|
return True
|
|
|
|
|
|
|
|
|
2022-11-29 18:54:09 +00:00
|
|
|
def check_channel_subsystem_request(self, channel, name):
|
|
|
|
if name == 'sftp':
|
2022-12-05 18:45:58 +00:00
|
|
|
Runner(self, self.username, 'sftp', channel).start()
|
2022-11-29 18:54:09 +00:00
|
|
|
return True
|
|
|
|
else:
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
2022-11-23 19:18:40 +00:00
|
|
|
def check_auth_none(self, username):
|
2022-11-24 19:59:21 +00:00
|
|
|
return paramiko.AUTH_FAILED
|
2022-11-23 19:18:40 +00:00
|
|
|
|
|
|
|
|
|
|
|
def check_auth_password(self, username, password):
|
2022-11-28 19:55:27 +00:00
|
|
|
logger.debug(f"{username} just tried to connect")
|
2022-12-05 18:45:58 +00:00
|
|
|
# ensure that the connection is made from a local ip
|
|
|
|
if ipaddress.ip_address(self.address).is_private is not True:
|
|
|
|
return paramiko.AUTH_FAILED
|
|
|
|
if secrets.compare_digest(password, os.environ["SSH_PASSWORD"]):
|
|
|
|
self.username = username
|
|
|
|
Runner(self, self.username).start()
|
2022-11-23 19:18:40 +00:00
|
|
|
return paramiko.AUTH_SUCCESSFUL
|
|
|
|
return paramiko.AUTH_FAILED
|
|
|
|
|
|
|
|
|
|
|
|
def check_auth_publickey(self, username, key):
|
|
|
|
return paramiko.AUTH_FAILED
|
|
|
|
|
|
|
|
|
2022-11-25 15:41:02 +00:00
|
|
|
class Runner(threading.Thread):
|
2022-12-05 18:45:58 +00:00
|
|
|
def __init__(self, client, username: str, channel_type: str = None, channel: paramiko.Channel = None):
|
2022-12-01 18:03:17 +00:00
|
|
|
self.instance_name = "instance-" + username
|
|
|
|
self.runner_identifier = "runner-" + str(uuid.uuid4())
|
|
|
|
threading.Thread.__init__(self, name=f'sshim.Runner {self.instance_name} {self.runner_identifier}')
|
|
|
|
self.instance_password = self.instance_name # TODO: fix - VERY INSECURE!
|
2022-11-25 15:41:02 +00:00
|
|
|
self.daemon = True
|
|
|
|
self.client = client
|
2022-12-05 18:45:58 +00:00
|
|
|
self.channel_type = channel_type
|
|
|
|
self.channel = channel
|
2022-11-25 15:41:02 +00:00
|
|
|
|
|
|
|
def run(self) -> None:
|
2022-12-01 18:03:17 +00:00
|
|
|
try:
|
|
|
|
vm_ip = lxd_interface.create_instance(self.instance_name, self.instance_password)['address']
|
|
|
|
except pylxd.exceptions.LXDAPIException as e:
|
|
|
|
logger.debug(e)
|
|
|
|
vm_ip = lxd_interface.get_networking(self.instance_name)['address']
|
2022-11-25 15:41:02 +00:00
|
|
|
|
2022-12-05 23:33:33 +00:00
|
|
|
if self.channel is not None:
|
|
|
|
self.channel.get_transport().set_keepalive(5) # TODO: make config option
|
2022-12-05 18:45:58 +00:00
|
|
|
|
2022-11-25 16:08:10 +00:00
|
|
|
with paramiko.SSHClient() as ssh_client:
|
2022-12-05 23:33:33 +00:00
|
|
|
# wait for instance to start if it hasn't started yet
|
|
|
|
is_not_int = True
|
|
|
|
while is_not_int and self.channel is not None:
|
2022-12-05 18:45:58 +00:00
|
|
|
try:
|
2022-12-05 23:33:33 +00:00
|
|
|
int(lxd_interface.get_description(self.instance_name))
|
|
|
|
is_not_int = False
|
|
|
|
except ValueError as e:
|
2022-12-05 18:45:58 +00:00
|
|
|
logger.debug(e)
|
|
|
|
time.sleep(0.2)
|
2022-12-05 23:33:33 +00:00
|
|
|
|
|
|
|
ssh_client.set_missing_host_key_policy(paramiko.WarningPolicy)
|
|
|
|
ssh_client.connect(vm_ip, username='root', password=self.instance_password)
|
2022-12-05 18:45:58 +00:00
|
|
|
if self.channel_type == "shell" or self.channel_type == "exec":
|
|
|
|
client_channel = ssh_client.invoke_shell()
|
|
|
|
elif self.channel_type == "sftp":
|
|
|
|
client_channel = ssh_client.open_sftp().get_channel()
|
2022-11-26 23:06:01 +00:00
|
|
|
|
2022-12-01 18:03:17 +00:00
|
|
|
last_save_time = round(time.time())
|
2022-12-01 18:08:42 +00:00
|
|
|
lxd_interface.set_description(self.instance_name, str(last_save_time))
|
2022-12-05 18:45:58 +00:00
|
|
|
forward_channel_return = True
|
|
|
|
while forward_channel_return is True:
|
2022-12-01 18:03:17 +00:00
|
|
|
current_time_rounded = round(time.time())
|
|
|
|
if current_time_rounded != last_save_time:
|
|
|
|
lxd_interface.set_description(self.instance_name, str(current_time_rounded))
|
|
|
|
last_save_time = current_time_rounded
|
|
|
|
|
2022-12-05 23:33:33 +00:00
|
|
|
if "client_channel" in locals():
|
|
|
|
forward_channel_return = self.forward_channel(client_channel)
|
2022-11-29 18:23:11 +00:00
|
|
|
|
2022-12-01 18:03:17 +00:00
|
|
|
exit_time = round(time.time())
|
|
|
|
time.sleep(10)
|
|
|
|
while True:
|
|
|
|
last_run_time = int(lxd_interface.get_description(self.instance_name))
|
|
|
|
|
|
|
|
if exit_time < last_run_time:
|
|
|
|
break
|
|
|
|
elif round(time.time()) > (last_run_time + 15):
|
|
|
|
lxd_interface.destroy_instance(self.instance_name)
|
|
|
|
break
|
2022-11-28 19:38:07 +00:00
|
|
|
|
2022-12-05 18:45:58 +00:00
|
|
|
def forward_channel(self, client_channel) -> bool:
|
|
|
|
if self.channel is None:
|
|
|
|
return False
|
|
|
|
else:
|
|
|
|
r, w, e = select.select([client_channel, self.channel], [], [])
|
|
|
|
if self.channel in r:
|
|
|
|
x = self.channel.recv(1024)
|
|
|
|
if len(x) == 0:
|
|
|
|
self.channel.close()
|
|
|
|
return False
|
|
|
|
client_channel.send(x)
|
|
|
|
if client_channel in r:
|
|
|
|
x = client_channel.recv(1024)
|
|
|
|
if len(x) == 0:
|
|
|
|
self.channel.close()
|
|
|
|
return False
|
|
|
|
self.channel.send(x)
|
|
|
|
return True
|
2022-11-29 18:23:11 +00:00
|
|
|
|
2022-11-23 19:18:40 +00:00
|
|
|
|
2022-11-29 18:54:09 +00:00
|
|
|
Handler.check_channel_request = check_channel_request
|
2022-11-25 15:41:02 +00:00
|
|
|
Handler.check_channel_shell_request = check_channel_shell_request
|
2022-11-29 18:54:09 +00:00
|
|
|
Handler.check_channel_subsystem_request = check_channel_subsystem_request
|
2022-11-23 19:18:40 +00:00
|
|
|
Handler.check_auth_none = check_auth_none
|
|
|
|
Handler.check_auth_password = check_auth_password
|
|
|
|
Handler.check_auth_publickey = check_auth_publickey
|
2022-11-24 19:59:21 +00:00
|
|
|
Handler.enable_auth_gssapi = paramiko.server.ServerInterface.enable_auth_gssapi
|
2022-11-25 15:41:02 +00:00
|
|
|
Handler.get_allowed_auths = paramiko.server.ServerInterface.get_allowed_auths
|