Newer
Older
"""
This module persistently stores informion on tunnels in a yaml file
it probably shouldn't be used on a server handling multiple requests
due to the overhead of opening reading writing locking etc but its
probably OK for a single user computer
"""
import datetime
import yaml
class SSHSession:
"""Interfaces for working with processes forked from flask
in particular, we fork processes for ssh-agent and ssh tunnels and execution
def __init__(self,**kwargs):
self.last = datetime.datetime.now()
self.socket = None
self.token = None
self.port = None
self.key = ''
self.cert = ''
self.pids = []
self.authtok = None
self.__dict__.update(kwargs)
Chris Hines
committed
self.sshagent = 'ssh-agent'
Chris Hines
committed
self.sshkeygen = 'ssh-keygen'
def start_agent(self):
import subprocess
Chris Hines
committed
p = subprocess.Popen([self.sshagent],stdout=subprocess.PIPE,stderr=subprocess.PIPE)
(stdout,stderr) = p.communicate()
for l in stdout.decode().split(';'):
if 'SSH_AUTH_SOCK=' in l:
socket = l.split('=')[1]
self.socket = socket
if 'SSH_AGENT_PID=' in l:
pid = l.split('=')[1]
self.pids.append(pid)
def add_keycert(self,key,cert):
import tempfile
import os
import subprocess
import logging
logger = logging.getLogger()
if self.socket is None:
self.start_agent()
keyf = tempfile.NamedTemporaryFile(mode='w',delete=False)
keyname = keyf.name
keyf.write(key)
keyf.close()
certf = open(keyname+'-cert.pub',mode='w')
certf.write(cert)
certf.close()
env = os.environ.copy()
env['SSH_AUTH_SOCK'] = self.socket
Chris Hines
committed
cmd = [self.sshadd]
cmd.append(keyname)
p = subprocess.Popen(cmd,stdout=subprocess.PIPE,stderr=subprocess.PIPE,env=env)
(stdout,stderr) = p.communicate()
if p.returncode != 0:
logger.error("Couldn't add key and cert")
logger.debug(stdout)
logger.debug(stderr)
print(stderr)
print(stdout)
os.unlink(keyname+'-cert.pub')
os.unlink(keyname)
Chris Hines
committed
def get_cert_contents(self):
import os
import subprocess
import logging
logger=logging.getLogger()
Chris Hines
committed
res=[]
Chris Hines
committed
env = os.environ.copy()
env['SSH_AUTH_SOCK'] = self.socket
cmd = [self.sshadd,'-L']
p = subprocess.Popen(cmd,stdout=subprocess.PIPE,stderr=subprocess.PIPE,env=env)
(stdout,stderr) = p.communicate()
if stderr is not None:
logger.debug('called sshadd, got stderr {}'.format(stderr))
if stdout is not None:
logger.debug('called sshadd, got stdout {}'.format(stdout))
for l in stdout.splitlines():
logger.debug('is {} a cert?'.format(l))
if b'cert' in l:
logger.debug('decoding {}'.format(l))
p = subprocess.Popen([self.sshkeygen,'-L','-f','-'],stdin=subprocess.PIPE,stdout=subprocess.PIPE,stderr=subprocess.PIPE)
keygenout,keygenerr = p.communicate(l)
certcontents = SSHSession.parse_cert_contents(keygenout.decode().splitlines())
res.append(certcontents)
Chris Hines
committed
return res
@staticmethod
def parse_cert_contents(lines):
key = None
values = []
res = {}
for l in lines:
l = l.rstrip().lstrip()
Chris Hines
committed
if ':' in l:
if key is not None:
res[key] = values
values = []
v = v.lstrip().rstrip()
Chris Hines
committed
values = [v]
else:
Chris Hines
committed
return res
def refresh(self):
import datetime
self.last = datetime.datetime.now()
import logging
logger = logging.getLogger()
logger.debug("updated datetime to {} for {}".format(self.last,self.authtok))
print("updated datetime to {} for {}".format(self.last,self.authtok))
def set_authtok(self,authtok):
self.authtok = authtok
def addkey(self,key,cert):
pass
def kill(self):
import os
import signal
import logging
logger=logging.getLogger()
print("killing all processes associated with this sshsession")
logger.debug("shuting down ssh session for {} last seen at {}".format(self.authtok,self.last))
print("shuting down ssh session for {} last seen at {}".format(self.authtok,self.last))
print(self.pids)
for pid in self.pids:
try:
os.kill(int(pid), signal.SIGTERM)
except ProcessLookupError as e:
print("process {} not found".format(pid))
def get_port(self, authtok):
if self.authtok is not None and self.authtok == authtok:
return self.port
return None
@staticmethod
def get_sshsession():
import random
import string
from .. import sshsessions
from flask import session
sshsessid = session.get('sshsessid', None)
N = 8
while sshsessid is None:
key = ''.join(random.SystemRandom().choice(string.ascii_uppercase + string.digits) for _ in range(N))
if key not in session:
sshsessid = key
session['sshsessid'] = sshsessid
if sshsessid not in sshsessions:
sshsessions[sshsessid] = SSHSession()
return sshsessions[sshsessid]
@staticmethod
def remove_sshsession():
import random
import string
from .. import sshsessions
from flask import session
sshsessid = session.get('sshsessid', None)
N = 8
del sshsessions[sshsessid]
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
# class Tunnelstat:
# """
# class docstring same as module docstring
# """
# @staticmethod
# def loaddata():
# """load data from a file"""
# with open('tunnels.yml') as fptr:
# data = yaml.load(fptr.read())
# if data is None:
# data = {}
# return data
#
# @staticmethod
# def savedata(data):
# """save data to a file"""
# with open('tunnels.yml', 'w') as fptr:
# fptr.write(yaml.dump(data))
#
# @staticmethod
# def update(authtok):
# """update the last used time for a tunnel"""
# data = Tunnelstat.loaddata()
# for key, value in data.items():
# if key == authtok:
# value['last'] = datetime.datetime.now()
# Tunnelstat.savedata(data)
#
#
#
# @staticmethod
# def getport(authtok):
# """given authtok, retrieve the port of the corresponding ssh tunnel"""
# data = Tunnelstat.loaddata()
# if authtok in data:
# return data[authtok]['port']
# return None
#
# @staticmethod
# def newtunnel(authtok, port, pids):
# """a new tunnel has just been created, save the data info so we can
# a) reap it latter
# b) allow the transparent web socket proxy to connect
# """
# newtunnel = {'port': port, 'last':datetime.datetime.now(), 'pids':pids}
# data = Tunnelstat.loaddata()
# data[authtok] = newtunnel
# Tunnelstat.savedata(data)