对webssh实现命令回放功能
想要实现webssh的命令记录功能需要一个前端的组件 asciinema
django服务端代码
1 class WebSSH(WebsocketConsumer): 2 message = {'status': 0, 'message': None} 3 """ 4 status: 5 0: ssh 连接正常, websocket 正常 6 1: 发生未知错误, 关闭 ssh 和 websocket 连接 7 8 message: 9 status 为 1 时, message 为具体的错误信息 10 status 为 0 时, message 为 ssh 返回的数据, 前端页面将获取 ssh 返回的数据并写入终端页面 11 """ 12 13 def connect(self): 14 try: 15 self.accept() 16 logger.info('open connet service ssh') 17 18 query_string = self.scope['query_string'] 19 logger.info(query_string) 20 connet_argv = QueryDict(query_string=query_string, encoding='utf-8') 21 logger.info(connet_argv) 22 serverid = connet_argv.get('unique') 23 width = connet_argv.get('width') 24 height = connet_argv.get('height') 25 appid = connet_argv.get('appid','') 26 execuserid = connet_argv.get('execuserid','') 27 28 width = int(width) 29 height = int(height) 30 31 service = models.ServiceModel.objects.get(id=serverid) 32 33 host = service.ipaddress 34 port = str(service.port) 35 user = system_config.log_user 36 logger.info('{} {} {} {} {}'.format(serverid,height,serverid,host,user)) 37 self.ssh = SSH(websocker=self, message=self.message) 38 self.execuserid = execuserid 39 self.serverid = serverid 40 41 42 self.ssh.connect( 43 host=host, 44 user=user, 45 port=port, 46 pty_width=width, 47 pty_height=height 48 ) 49 if appid: 50 app = models.ApplicationModel.objects.get(id=appid) 51 self.ssh.shell('/home/{}/showlog.sh {}\n'.format(user,app.tomcatname)) 52 53 except Exception as e: 54 logger.info(e) 55 self.message['status'] = 1 56 self.message['message'] = str(e) 57 message = json.dumps(self.message) 58 self.send(message) 59 self.close() 60 61 def disconnect(self, close_code): 62 try: 63 self.ssh.close() 64 except: 65 pass 66 67 def receive(self, text_data=None, bytes_data=None): 68 data = json.loads(text_data) 69 if type(data) == dict: 70 status = data['status'] 71 if status == 0: 72 data = data['data'] 73 self.ssh.shell(data,self) #这里执行命令的时候把对象本身传进去(用了channels和xterm.js通过websocket实现的webssh是一个字符一个字符发送的,在parimako这个模块中做了一系列的命令组装,组成一个完整的shell命令) 74 75 else: 76 cols = data['cols'] 77 rows = data['rows'] 78 self.ssh.resize_pty(cols=cols, rows=rows) 79 80 def savecommend(self,commend): #这里是保存命令的方法 81 servicecommit = ServiceCommitModel() 82 servicecommit.service_id = self.serverid 83 servicecommit.user_id = self.execuserid 84 json_commend = json.dumps(commend) 85 servicecommit.commitname = json_commend[1:-1] 86 servicecommit.save() 87 logger.info(json_commend)
1 import paramiko 2 from threading import Thread 3 from libs.ansible_libs.tools import get_key_obj 4 import socket 5 import json 6 import logging 7 8 9 class SSH: 10 def __init__(self, websocker, message): 11 self.websocker = websocker 12 self.message = message 13 14 def connect(self, host, user, password=None, pkey=None, port=22, timeout=120, 15 term='xterm', pty_width=80, pty_height=24): 16 try: 17 ssh_client = paramiko.SSHClient() 18 ssh_client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) 19 20 if password: 21 ssh_client.connect(username=user, password=password, hostname=host, port=port, timeout=timeout) 22 else: 23 ssh_client.connect(username=user, hostname=host, port=port, timeout=timeout) 24 25 transport = ssh_client.get_transport() 26 self.channel = transport.open_session() 27 self.channel.get_pty(term=term, width=pty_width, height=pty_height) 28 self.channel.invoke_shell() 29 30 for i in range(2): 31 recv = self.channel.recv(1024).decode('utf-8') 32 self.message['status'] = 0 33 self.message['message'] = recv 34 message = json.dumps(self.message) 35 self.websocker.send(message) 36 37 except socket.timeout as e: 38 self.message['status'] = 1 39 self.message['message'] = 'ssh 连接超时' 40 message = json.dumps(self.message) 41 logging.info('connet server timeout') 42 self.websocker.send(message) 43 self.websocker.close() 44 except Exception as e: 45 self.message['status'] = 1 46 self.message['message'] = str(e) 47 message = json.dumps(self.message) 48 logging.info('connet server custom') 49 self.websocker.send(message) 50 self.websocker.close() 51 52 def resize_pty(self, cols, rows): 53 self.channel.resize_pty(width=cols, height=rows) 54 55 56 def django_to_ssh(self, data): 57 try: 58 self.channel.send(data) 59 return 60 except: 61 self.close() 62 63 def websocket_to_django(self,httpobj): 64 try: 65 while True: 66 data = self.channel.recv(1024).decode('utf-8') 67 if not len(data): 68 return 69 self.message['status'] = 0 70 self.message['message'] = data 71 message = json.dumps(self.message) 72 self.websocker.send(message) 73 print(data) 74 try: 75 if httpobj: 76 httpobj.savecommend(str(data)) 在这里做命令记录,把命令保存到数据库 77 except Exception as e: 78 print(e) 79 # httpobj.savecommend(data) 80 except Exception as e: 81 print(e) 82 self.close() 83 84 def close(self): 85 self.message['status'] = 1 86 self.message['message'] = '关闭连接' 87 message = json.dumps(self.message) 88 self.websocker.send(message) 89 self.channel.close() 90 self.websocker.close() 91 92 def shell(self, data, httpobj=None): 93 Thread(target=self.django_to_ssh, args=(data,)).start() 94 Thread(target=self.websocket_to_django, args=(httpobj,)).start() #这里是返回到前端的线程,在这个线程中做命令的记录
当需要查看命令回放的时候
1 class CommandData(LoginRequiredMixin,View): 2 def get(self,request): 3 ''' 4 获取用户选择的参数以返回视频文件 5 :param request: 6 :return: 7 ''' 8 serverid = request.GET.get('sid') 9 end_time = request.GET.get('end_time') 10 start_time = request.GET.get('start_time') 11 userid = request.GET.get('uid') 12 servers = ServiceModel.objects.all() 13 users = User.objects.all() 14 datas = ServiceCommitModel.objects.filter(date__gte=start_time,date__lte=end_time,service_id=serverid,user_id=userid).values('commitname') 15 # fileurl = '/home/PyObject/static/image/demo/demo.json' 16 fileurl = '/opt/object/static/image/demo/demo.json' 17 datalist = list() 18 playindex = 0 19 with open(fileurl,'wb') as f: 20 f.write(bytes('{"version": 2, "width": 1500, "height": 1000, "timestamp": 1559530296, "env": {"SHELL": "/bin/bash", "TERM": "xterm-256color"}}\n',encoding='utf-8')) #这里是asciinema需要的文件头 21 for data in datas: 22 playindex += 0.4 #设置播放速度 23 24 commitname = data['commitname'] 25 26 linestr = '[{}, "o", "{}"]\n'.format(playindex,commitname) #把命令写入到asciiname文件里面(必须按照这种格式) 27 # print(type(commitname),print(commitname)) 28 f.write(bytes(linestr,encoding='utf-8')) 以二进制格式写入 29 # datalist.append(data['commitname']) 30 # print(json.dumps(datalist)) 31 # return JsonResponse({ 32 # 'datas': json.dumps(datalist) 33 # }) 34 player = 'ok' 35 return render(request,'command/playbackcommend.html',locals()) 返回页面
前端网页:
在html中导入asciinema的js和css文件
<div class="row"> {% if player %} <asciinema-player src="{% static 'image/demo/demo.json' %}" cols="260" rows="40"></asciinema-player> {% endif %} </div>
这样就实现了webssh的命令回放功能