From 43c0770770cc9fe40f5dd205b35e8ffb6bafdd20 Mon Sep 17 00:00:00 2001 From: ibuler Date: Fri, 6 Nov 2015 12:09:23 +0800 Subject: [PATCH 1/9] =?UTF-8?q?=E4=BD=BF=E7=94=A8=E5=8E=9F=E7=94=9Fselect?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- run_websocket.py | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/run_websocket.py b/run_websocket.py index 7283ed9b0..32083aa5f 100644 --- a/run_websocket.py +++ b/run_websocket.py @@ -21,7 +21,8 @@ from pyinotify import WatchManager, Notifier, ProcessEvent, IN_DELETE, IN_CREATE # from gevent import monkey # monkey.patch_all() # import gevent -from gevent.socket import wait_read, wait_write +# from gevent.socket import wait_read, wait_write +import struct, fcntl, signal, socket, select, fnmatch import paramiko @@ -200,16 +201,18 @@ class WebTerminalHandler(tornado.websocket.WebSocketHandler): try: data = '' while True: - wait_read(self.chan.fileno()) - recv = self.chan.recv(1024) - if not len(recv): - return - data += recv - try: - self.write_message(json.dumps({'data': data})) - data = '' - except UnicodeDecodeError: - pass + r, w, e = select.select([self.chan, sys.stdin], [], []) + if self.chan in r: + recv = self.chan.recv(1024) + print recv + if not len(recv): + return + data += recv + try: + self.write_message(json.dumps({'data': data})) + data = '' + except UnicodeDecodeError: + pass finally: self.close() From 24730ebd0f90b2441851b6ccb2259e0dd71d3a88 Mon Sep 17 00:00:00 2001 From: ibuler Date: Sat, 7 Nov 2015 13:38:50 +0800 Subject: [PATCH 2/9] =?UTF-8?q?web=20terminal=E8=AE=B0=E5=BD=95=E6=97=A5?= =?UTF-8?q?=E5=BF=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- connect.py | 265 +++++++++++++++++++-------------- jumpserver/settings.py | 2 +- run_websocket.py | 86 +++++++---- templates/jlog/log_online.html | 28 ++-- 4 files changed, 218 insertions(+), 163 deletions(-) diff --git a/connect.py b/connect.py index 063708ad3..f8dad935b 100644 --- a/connect.py +++ b/connect.py @@ -201,26 +201,6 @@ def deal_command(str_r, ssh): else: return '' -def remove_control_char(str_r): - """ - 处理日志特殊字符 - """ - control_char = re.compile(r""" - \x1b[ #%()*+\-.\/]. | - \r | #匹配 回车符(CR) - (?:\x1b\[|\x9b) [ -?]* [@-~] | #匹配 控制顺序描述符(CSI)... Cmd - (?:\x1b\]|\x9d) .*? (?:\x1b\\|[\a\x9c]) | \x07 | #匹配 操作系统指令(OSC)...终止符或振铃符(ST|BEL) - (?:\x1b[P^_]|[\x90\x9e\x9f]) .*? (?:\x1b\\|\x9c) | #匹配 设备控制串或私讯或应用程序命令(DCS|PM|APC)...终止符(ST) - \x1b. #匹配 转义过后的字符 - [\x80-\x9f] #匹配 所有控制字符 - """, re.X) - backspace = re.compile(r"[^\b][\b]") - line_filtered = control_char.sub('', str_r.rstrip()) - while backspace.search(line_filtered): - line_filtered = backspace.sub('', line_filtered) - - return line_filtered - def newline_code_in(strings): for i in ['\r', '\r\n', '\n']: @@ -230,17 +210,139 @@ def newline_code_in(strings): return False -class Jtty(object): +class Tty(object): + """ + A virtual tty class + 一个虚拟终端类,实现连接ssh和记录日志,基类 + """ + def __init__(self, username, asset_name): + self.username = username + self.asset_name = asset_name + self.ip = None + self.port = 22 + self.channel = None + self.user = None + self.asset = None + self.role = None + self.ssh = None + self.connect_info = None + self.login_type = 'ssh' + + @staticmethod + def is_output(strings): + newline_char = ['\n', '\r', '\r\n'] + for char in newline_char: + if char in strings: + return True + return False + + @staticmethod + def remove_control_char(str_r): + """ + 处理日志特殊字符 + """ + control_char = re.compile(r""" + \x1b[ #%()*+\-.\/]. | + \r | #匹配 回车符(CR) + (?:\x1b\[|\x9b) [ -?]* [@-~] | #匹配 控制顺序描述符(CSI)... Cmd + (?:\x1b\]|\x9d) .*? (?:\x1b\\|[\a\x9c]) | \x07 | #匹配 操作系统指令(OSC)...终止符或振铃符(ST|BEL) + (?:\x1b[P^_]|[\x90\x9e\x9f]) .*? (?:\x1b\\|\x9c) | #匹配 设备控制串或私讯或应用程序命令(DCS|PM|APC)...终止符(ST) + \x1b. #匹配 转义过后的字符 + [\x80-\x9f] #匹配 所有控制字符 + """, re.X) + backspace = re.compile(r"[^\b][\b]") + line_filtered = control_char.sub('', str_r.rstrip()) + while backspace.search(line_filtered): + line_filtered = backspace.sub('', line_filtered) + + return line_filtered + + def get_log_file(self): + """ + Logging user command and output. + 记录用户的日志 + """ + tty_log_dir = os.path.join(log_dir, 'tty') + timestamp_start = int(time.time()) + date_start = time.strftime('%Y%m%d', time.localtime(timestamp_start)) + time_start = time.strftime('%H%M%S', time.localtime(timestamp_start)) + today_connect_log_dir = os.path.join(tty_log_dir, date_start) + log_file_path = os.path.join(today_connect_log_dir, '%s_%s_%s' % (self.username, self.asset_name, time_start)) + + if self.login_type == 'ssh': + pid = os.getpid() + remote_ip = os.popen("who -m | awk '{ print $5 }'").read().strip('()\n') + else: + pid = 0 + remote_ip = 'Web' + + try: + is_dir(today_connect_log_dir, mode=0777) + except OSError: + raise ServerError('Create %s failed, Please modify %s permission.' % (today_connect_log_dir, tty_log_dir)) + + try: + log_file_f = open(log_file_path + '.log', 'a') + log_time_f = open(log_file_path + '.time', 'a') + except IOError: + raise ServerError('Create logfile failed, Please modify %s permission.' % today_connect_log_dir) + + log = Log(user=self.username, host=self.asset_name, remote_ip=remote_ip, + log_path=log_file_path, start_time=datetime.datetime.now(), pid=pid) + log_file_f.write('Start at %s\n' % datetime.datetime.now()) + log.save() + return log_file_f, log_time_f, log + + def get_connect_info(self): + """ + 获取需要登陆的主机的信息和映射用户的账号密码 + """ + + # 1. get ip, port + # 2. get 映射用户 + # 3. get 映射用户的账号,密码或者key + # self.connect_info = {'user': '', 'asset': '', 'ip': '', 'port': 0, 'role_name': '', 'role_pass': '', 'role_key': ''} + self.connect_info = {'user': 'a', 'asset': 'b', 'ip': '127.0.0.1', 'port': 22, 'role_name': 'root', 'role_pass': 'redhat', 'role_key': ''} + return self.connect_info + + def get_connection(self): + """ + 获取连接成功后的ssh + """ + connect_info = self.get_connect_info() + + # 发起ssh连接请求 Make a ssh connection + ssh = paramiko.SSHClient() + ssh.load_system_host_keys() + ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) + try: + if connect_info.get('role_pass'): + ssh.connect(connect_info.get('ip'), + port=connect_info.get('port'), + username=connect_info.get('role_name'), + password=connect_info.get('role_pass'), + look_for_keys=False) + else: + ssh.connect(connect_info.get('ip'), + port=connect_info.get('port'), + username=connect_info.get('role_name'), + key_filename=connect_info.get('role_key'), + look_for_keys=False) + + except paramiko.ssh_exception.AuthenticationException, paramiko.ssh_exception.SSHException: + raise ServerError('认证失败 Authentication Error.') + except socket.error: + raise ServerError('端口可能不对 Connect SSH Socket Port Error, Please Correct it.') + else: + self.ssh = ssh + return ssh + + +class SshTty(Tty): """ A virtual tty class 一个虚拟终端类,实现连接ssh和记录日志 """ - def __init__(self, username, ip): - self.chan = None - self.username = username - self.ip = ip - # self.user = user - # self.asset = asset @staticmethod def get_win_size(): @@ -263,49 +365,16 @@ class Jtty(object): """ try: win_size = self.get_win_size() - self.chan.resize_pty(height=win_size[0], width=win_size[1]) + self.channel.resize_pty(height=win_size[0], width=win_size[1]) except Exception: pass - def log_record(self): - """ - Logging user command and output. - 记录用户的日志 - """ - tty_log_dir = os.path.join(log_dir, 'tty') - timestamp_start = int(time.time()) - date_start = time.strftime('%Y%m%d', time.localtime(timestamp_start)) - time_start = time.strftime('%H%M%S', time.localtime(timestamp_start)) - today_connect_log_dir = os.path.join(tty_log_dir, date_start) - log_file_path = os.path.join(today_connect_log_dir, '%s_%s_%s' % (self.username, self.ip, time_start)) - pid = os.getpid() - pts = os.popen("ps axu | grep %s | grep -v grep | awk '{ print $7 }'" % pid).read().strip() - ip_list = os.popen("who | grep %s | awk '{ print $5 }'" % pts).read().strip('()\n') - - try: - is_dir(today_connect_log_dir) - except OSError: - raise ServerError('Create %s failed, Please modify %s permission.' % (today_connect_log_dir, tty_log_dir)) - - try: - # log_file_f = open('/opt/jumpserver/logs/tty/20151102/a_b_191034.log', 'a') - log_file_f = open(log_file_path + '.log', 'a') - log_time_f = open(log_file_path + '.time', 'a') - except IOError: - raise ServerError('Create logfile failed, Please modify %s permission.' % today_connect_log_dir) - - log = Log(user=self.username, host=self.ip, remote_ip=ip_list, - log_path=log_file_path, start_time=datetime.datetime.now(), pid=pid) - log_file_f.write('Start time is %s\n' % datetime.datetime.now()) - log.save() - return log_file_f, log_time_f, ip_list, log - - def posix_shell(self,ssh): + def posix_shell(self): """ Use paramiko channel connect server interactive. 使用paramiko模块的channel,连接后端,进入交互式 """ - log_file_f, log_time_f, ip_list, log = self.log_record() + log_file_f, log_time_f, log = self.get_log_file() old_tty = termios.tcgetattr(sys.stdin) pre_timestamp = time.time() input_r = '' @@ -314,29 +383,29 @@ class Jtty(object): try: tty.setraw(sys.stdin.fileno()) tty.setcbreak(sys.stdin.fileno()) - self.chan.settimeout(0.0) + self.channel.settimeout(0.0) while True: try: - r, w, e = select.select([self.chan, sys.stdin], [], []) + r, w, e = select.select([self.channel, sys.stdin], [], []) except Exception: pass - if self.chan in r: + if self.channel in r: try: - x = self.chan.recv(1024) + x = self.channel.recv(1024) if len(x) == 0: break sys.stdout.write(x) sys.stdout.flush() - log_file_f.write(x) now_timestamp = time.time() log_time_f.write('%s %s\n' % (round(now_timestamp-pre_timestamp, 4), len(x))) + log_file_f.write(x) pre_timestamp = now_timestamp log_file_f.flush() log_time_f.flush() - if input_mode and not newline_code_in(x): + if input_mode and not self.is_output(x): input_r += x except socket.timeout: @@ -348,14 +417,16 @@ class Jtty(object): input_mode = True if str(x) in ['\r', '\n', '\r\n']: - input_r = deal_command(input_r,ssh) + # input_r = deal_command(input_r,ssh) + input_r = self.remove_control_char(input_r) + TtyLog(log=log, datetime=datetime.datetime.now(), cmd=input_r).save() input_r = '' input_mode = False if len(x) == 0: break - self.chan.send(x) + self.channel.send(x) finally: termios.tcsetattr(sys.stdin, termios.TCSADRAIN, old_tty) @@ -365,42 +436,6 @@ class Jtty(object): log.end_time = datetime.datetime.now() log.save() - def get_connect_item(self): - """ - get args for connect: ip, port, username, passwd - 获取连接需要的参数,也就是服务ip, 端口, 用户账号和密码 - """ - # if not self.asset.is_active: - # raise ServerError('该主机被禁用 Host %s is not active.' % self.ip) - # - # if not self.user.is_active: - # raise ServerError('该用户被禁用 User %s is not active.' % self.username) - - # password = CRYPTOR.decrypt(self.]) - # return self.username, password, self.ip, int(self.asset.port) - return 'root', 'redhat', '127.0.0.1', 22 - - def get_connection(self): - """ - Get the ssh connection for reuse - 获取连接套接字 - """ - username, password, ip, port = self.get_connect_item() - logger.debug("username: %s, password: %s, ip: %s, port: %s" % (username, password, ip, port)) - - # 发起ssh连接请求 Make a ssh connection - ssh = paramiko.SSHClient() - ssh.load_system_host_keys() - ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) - try: - ssh.connect(ip, port=port, username=username, password=password) - except paramiko.ssh_exception.AuthenticationException, paramiko.ssh_exception.SSHException: - raise ServerError('认证错误 Authentication Error.') - except socket.error: - raise ServerError('端口可能不对 Connect SSH Socket Port Error, Please Correct it.') - else: - return ssh - def connect(self): """ Connect server. @@ -415,7 +450,7 @@ class Jtty(object): # 获取连接的隧道并设置窗口大小 Make a channel and set windows size global channel win_size = self.get_win_size() - self.chan = channel = ssh.invoke_shell(height=win_size[0], width=win_size[1]) + self.channel = channel = ssh.invoke_shell(height=win_size[0], width=win_size[1], term='xterm') try: signal.signal(signal.SIGWINCH, self.set_win_size) except: @@ -424,17 +459,17 @@ class Jtty(object): # 设置PS1并提示 Set PS1 and msg it #channel.send(ps1) #channel.send(login_msg) - channel.send('echo ${SSH_TTY}\n') - global SSH_TTY - while not channel.recv_ready(): - time.sleep(1) - tmp = channel.recv(1024) + # channel.send('echo ${SSH_TTY}\n') + # global SSH_TTY + # while not channel.recv_ready(): + # time.sleep(1) + # tmp = channel.recv(1024) #print 'ok'+tmp+'ok' # SSH_TTY = re.search(r'(?<=/dev/).*', tmp).group().strip() - SSH_TTY = '' + # SSH_TTY = '' channel.send('clear\n') # Make ssh interactive tunnel - self.posix_shell(ssh) + self.posix_shell() # Shutdown channel socket channel.close() diff --git a/jumpserver/settings.py b/jumpserver/settings.py index 23908986d..fa64dde1e 100644 --- a/jumpserver/settings.py +++ b/jumpserver/settings.py @@ -15,7 +15,7 @@ import getpass config = ConfigParser.ConfigParser() -BASE_DIR = os.path.dirname(os.path.dirname(__file__)) +BASE_DIR = os.path.abspath(os.path.dirname(os.path.dirname(__file__))) config.read(os.path.join(BASE_DIR, 'jumpserver.conf')) DB_HOST = config.get('db', 'host') diff --git a/run_websocket.py b/run_websocket.py index 32083aa5f..62d62998b 100644 --- a/run_websocket.py +++ b/run_websocket.py @@ -1,6 +1,7 @@ # coding: utf-8 import time +import datetime import json import os import sys @@ -25,6 +26,8 @@ from pyinotify import WatchManager, Notifier, ProcessEvent, IN_DELETE, IN_CREATE import struct, fcntl, signal, socket, select, fnmatch import paramiko +from connect import Tty +from connect import TtyLog try: import simplejson as json @@ -124,12 +127,6 @@ class MonitorHandler(tornado.websocket.WebSocketHandler): MonitorHandler.threads.append(thread) self.stream.set_nodelay(True) - print len(MonitorHandler.threads), len(MonitorHandler.clients) - - def on_message(self, message): - self.write_message('Connect WebSocket Success.
') - # 监控日志,发生变动发向客户端 - try: for t in MonitorHandler.threads: if t.is_alive(): @@ -143,6 +140,12 @@ class MonitorHandler(tornado.websocket.WebSocketHandler): MonitorHandler.clients.remove(self) MonitorHandler.threads.remove(MonitorHandler.threads[client_index]) + print len(MonitorHandler.threads), len(MonitorHandler.clients) + + def on_message(self, message): + # 监控日志,发生变动发向客户端 + pass + def on_close(self): # 客户端主动关闭 # self.close() @@ -153,28 +156,34 @@ class MonitorHandler(tornado.websocket.WebSocketHandler): MonitorHandler.threads.remove(MonitorHandler.threads[client_index]) +class WebTty(Tty): + def __init__(self, *args, **kwargs): + super(WebTty, self).__init__(*args, **kwargs) + self.login_type = 'web' + self.ws = None + self.input_r = '' + self.input_mode = False + + class WebTerminalHandler(tornado.websocket.WebSocketHandler): tasks = [] def __init__(self, *args, **kwargs): - self.chan = None - self.ssh = None + self.term = None + self.channel = None + self.log_file_f = None + self.log_time_f = None + self.log = None super(WebTerminalHandler, self).__init__(*args, **kwargs) def check_origin(self, origin): return True def open(self): - self.ssh = paramiko.SSHClient() - self.ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) - try: - self.ssh.connect('127.0.0.1', 22, 'root', 'redhat') - except: - self.write_message(json.loads({'data': 'Connect server Error'})) - self.close() - - self.chan = self.ssh.invoke_shell(term='xterm') - WebTerminalHandler.tasks.append(threading.Thread(target=self._forward_outbound)) + self.term = WebTty('a', 'b') + self.term.get_connection() + self.channel = self.term.ssh.invoke_shell(term='xterm') + WebTerminalHandler.tasks.append(MyThread(target=self.forward_outbound)) for t in WebTerminalHandler.tasks: if t.is_alive(): @@ -186,37 +195,50 @@ class WebTerminalHandler(tornado.websocket.WebSocketHandler): data = json.loads(message) if not data: return - if 'resize' in data: - self.chan.resize_pty( - data['resize'].get('width', 80), - data['resize'].get('height', 24)) - if 'data' in data: - self.chan.send(data['data']) + if data.get('data'): + self.term.input_mode = True + if str(data['data']) in ['\r', '\n', '\r\n']: + TtyLog(log=self.log, datetime=datetime.datetime.now(), cmd=self.term.remove_control_char(self.term.input_r)).save() + self.term.input_r = '' + self.term.input_mode = False + self.channel.send(data['data']) def on_close(self): - self.write_message(json.dumps({'data': 'close websocket'})) + print 'On_close' + self.log_file_f.write('End time is %s' % datetime.datetime.now()) + self.log.is_finished = True + self.log.end_time = datetime.datetime.now() + self.log.save() + self.close() - def _forward_outbound(self): - """ Forward outbound traffic (ssh -> websockets) """ + def forward_outbound(self): + self.log_file_f, self.log_time_f, self.log = self.term.get_log_file() try: data = '' + pre_timestamp = time.time() while True: - r, w, e = select.select([self.chan, sys.stdin], [], []) - if self.chan in r: - recv = self.chan.recv(1024) - print recv + r, w, e = select.select([self.channel, sys.stdin], [], []) + if self.channel in r: + recv = self.channel.recv(1024) if not len(recv): return data += recv try: self.write_message(json.dumps({'data': data})) + now_timestamp = time.time() + self.log_time_f.write('%s %s\n' % (round(now_timestamp-pre_timestamp, 4), len(data))) + self.log_file_f.write(data) + pre_timestamp = now_timestamp + self.log_file_f.flush() + self.log_time_f.flush() + if self.term.input_mode and not self.term.is_output(data): + self.term.input_r += data data = '' except UnicodeDecodeError: pass finally: self.close() - if __name__ == '__main__': tornado.options.parse_command_line() app = Application() diff --git a/templates/jlog/log_online.html b/templates/jlog/log_online.html index 23ee55647..2f855b084 100644 --- a/templates/jlog/log_online.html +++ b/templates/jlog/log_online.html @@ -127,8 +127,20 @@ var file_path = obj.attr('file_path'); var wsUri = '{{ web_monitor_uri }}'; var socket = new WebSocket(wsUri + '?file_path=' + file_path); + + var term = new Terminal({ + cols: 80, + rows: 24, + screenKeys: false + }); + + var tag = $('
'); + term.open(); + term.resize(80, 24); + socket.onopen = function(evt){ - socket.send(file_path) + socket.send('hello'); + term.write('~.~ Connect WebSocket Success.~.~ \r\n'); }; window.onbeforeunload = function(){ @@ -138,29 +150,15 @@ var username = obj.closest('tr').find('#username').text(); var ip = obj.closest('tr').find('#ip').text(); - - BootstrapDialog.show({message: function(){ //服务器端认证 {# socket.send('login', {userid:message.id, filename:message.filename,username:username,seed:seed});#} - var term = new Terminal({ - cols: 80, - rows: 24, - screenKeys: false - }); - var tag = $('
'); - term.open(); - term.resize(80, 24); - window.setTimeout(function(){ $('.terminal').detach().appendTo('#term'); socket.onmessage = function(evt){ term.write(evt.data); }}, 1000); - - - tag[0].style.color = "#00FF00"; return tag[0]; } , title:'Jumpserver实时监控 '+' 登录用户名: '+''+username+''+' 登录主机: '+''+ip, From 98c4d9bdba2ec2e57ad0b145086842a6f224bf4d Mon Sep 17 00:00:00 2001 From: ibuler Date: Sat, 7 Nov 2015 15:27:49 +0800 Subject: [PATCH 3/9] =?UTF-8?q?web=20terminal=20kill=E6=96=B9=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- connect.py | 22 ++++++++++++++-------- jlog/views.py | 5 +++-- jumpserver.conf | 2 +- run_websocket.py | 19 +++++++++++++++++++ templates/jlog/log_online.html | 12 +++++++++--- 5 files changed, 46 insertions(+), 14 deletions(-) diff --git a/connect.py b/connect.py index f8dad935b..d0d1a5f3a 100644 --- a/connect.py +++ b/connect.py @@ -269,12 +269,7 @@ class Tty(object): today_connect_log_dir = os.path.join(tty_log_dir, date_start) log_file_path = os.path.join(today_connect_log_dir, '%s_%s_%s' % (self.username, self.asset_name, time_start)) - if self.login_type == 'ssh': - pid = os.getpid() - remote_ip = os.popen("who -m | awk '{ print $5 }'").read().strip('()\n') - else: - pid = 0 - remote_ip = 'Web' + try: is_dir(today_connect_log_dir, mode=0777) @@ -287,8 +282,19 @@ class Tty(object): except IOError: raise ServerError('Create logfile failed, Please modify %s permission.' % today_connect_log_dir) - log = Log(user=self.username, host=self.asset_name, remote_ip=remote_ip, - log_path=log_file_path, start_time=datetime.datetime.now(), pid=pid) + if self.login_type == 'ssh': + pid = os.getpid() + remote_ip = os.popen("who -m | awk '{ print $5 }'").read().strip('()\n') + log = Log(user=self.username, host=self.asset_name, remote_ip=remote_ip, + log_path=log_file_path, start_time=datetime.datetime.now(), pid=pid) + else: + remote_ip = 'Web' + log = Log(user=self.username, host=self.asset_name, remote_ip=remote_ip, + log_path=log_file_path, start_time=datetime.datetime.now(), pid=0) + log.save() + log.pid = log.id + log.save() + log_file_f.write('Start at %s\n' % datetime.datetime.now()) log.save() return log_file_f, log_time_f, log diff --git a/jlog/views.py b/jlog/views.py index 02c3678ff..fbd953820 100644 --- a/jlog/views.py +++ b/jlog/views.py @@ -48,7 +48,8 @@ def log_list(request, offset): contact_list, p, contacts, page_range, current_page, show_first, show_end = pages(posts, request) - web_monitor_uri = '%s/monitor' % web_socket_host + web_monitor_uri = 'ws://%s/monitor' % web_socket_host + web_kill_uri = 'http://%s/kill' % web_socket_host return render_to_response('jlog/log_%s.html' % offset, locals(), context_instance=RequestContext(request)) @@ -103,6 +104,6 @@ def log_record(request): def web_terminal(request): - web_terminal_uri = '%s/terminal' % web_socket_host + web_terminal_uri = 'ws://%s/terminal' % web_socket_host return render_to_response('jlog/web_terminal.html', locals()) diff --git a/jumpserver.conf b/jumpserver.conf index cb0eba98d..7ab7e7f31 100644 --- a/jumpserver.conf +++ b/jumpserver.conf @@ -23,7 +23,7 @@ root_pw = secret234 [websocket] -web_socket_host = ws://192.168.244.129:3000 +web_socket_host = 192.168.244.129:3000 [mail] diff --git a/run_websocket.py b/run_websocket.py index 62d62998b..2daf002d4 100644 --- a/run_websocket.py +++ b/run_websocket.py @@ -7,6 +7,7 @@ import os import sys import os.path import threading +import uuid import tornado.ioloop import tornado.options @@ -96,6 +97,7 @@ class Application(tornado.web.Application): handlers = [ (r'/monitor', MonitorHandler), (r'/terminal', WebTerminalHandler), + (r'/kill', WebTerminalKillHandler), ] setting = { @@ -165,8 +167,20 @@ class WebTty(Tty): self.input_mode = False +class WebTerminalKillHandler(tornado.web.RequestHandler): + def get(self): + ws_id = self.get_argument('id') + for ws in WebTerminalHandler.clients: + print ws.id + if ws.id == int(ws_id): + print "killed" + ws.close() + print len(WebTerminalHandler.clients) + + class WebTerminalHandler(tornado.websocket.WebSocketHandler): tasks = [] + clients = [] def __init__(self, *args, **kwargs): self.term = None @@ -174,6 +188,7 @@ class WebTerminalHandler(tornado.websocket.WebSocketHandler): self.log_file_f = None self.log_time_f = None self.log = None + self.id = 0 super(WebTerminalHandler, self).__init__(*args, **kwargs) def check_origin(self, origin): @@ -184,6 +199,7 @@ class WebTerminalHandler(tornado.websocket.WebSocketHandler): self.term.get_connection() self.channel = self.term.ssh.invoke_shell(term='xterm') WebTerminalHandler.tasks.append(MyThread(target=self.forward_outbound)) + WebTerminalHandler.clients.append(self) for t in WebTerminalHandler.tasks: if t.is_alive(): @@ -205,6 +221,8 @@ class WebTerminalHandler(tornado.websocket.WebSocketHandler): def on_close(self): print 'On_close' + if self in WebTerminalHandler.clients: + WebTerminalHandler.clients.remove(self) self.log_file_f.write('End time is %s' % datetime.datetime.now()) self.log.is_finished = True self.log.end_time = datetime.datetime.now() @@ -213,6 +231,7 @@ class WebTerminalHandler(tornado.websocket.WebSocketHandler): def forward_outbound(self): self.log_file_f, self.log_time_f, self.log = self.term.get_log_file() + self.id = self.log.id try: data = '' pre_timestamp = time.time() diff --git a/templates/jlog/log_online.html b/templates/jlog/log_online.html index 2f855b084..3bc55bc5c 100644 --- a/templates/jlog/log_online.html +++ b/templates/jlog/log_online.html @@ -97,7 +97,7 @@ {% ifnotequal session_role_id 0 %} 命令统计 监控 - + {% endifnotequal %} {{ post.start_time|date:"Y-m-d H:i:s" }} @@ -202,8 +202,14 @@ {# }#} - function cut(num){ - var g_url = "/jlog/log_kill/?id="+num; + function cut(num, host){ + console.log(host); + if (host=='Web'){ + var g_url = '{{ web_kill_uri }}' + '?id=' + num; + } else { + g_url = "/jlog/log_kill/?id=" + num; + } + $.ajax({ type: "GET", url: g_url, From d899360c34835d7f9c4ecc2e674d9dee82705edf Mon Sep 17 00:00:00 2001 From: ibuler Date: Sat, 7 Nov 2015 17:32:32 +0800 Subject: [PATCH 4/9] =?UTF-8?q?=E5=A2=9E=E5=8A=A0websocket=E8=AE=A4?= =?UTF-8?q?=E8=AF=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- connect.py | 4 +--- jlog/views.py | 6 +++++- run_websocket.py | 38 ++++++++++++++++++++++++++++++++------ 3 files changed, 38 insertions(+), 10 deletions(-) diff --git a/connect.py b/connect.py index d0d1a5f3a..d17dd04cb 100644 --- a/connect.py +++ b/connect.py @@ -269,8 +269,6 @@ class Tty(object): today_connect_log_dir = os.path.join(tty_log_dir, date_start) log_file_path = os.path.join(today_connect_log_dir, '%s_%s_%s' % (self.username, self.asset_name, time_start)) - - try: is_dir(today_connect_log_dir, mode=0777) except OSError: @@ -308,7 +306,7 @@ class Tty(object): # 2. get 映射用户 # 3. get 映射用户的账号,密码或者key # self.connect_info = {'user': '', 'asset': '', 'ip': '', 'port': 0, 'role_name': '', 'role_pass': '', 'role_key': ''} - self.connect_info = {'user': 'a', 'asset': 'b', 'ip': '127.0.0.1', 'port': 22, 'role_name': 'root', 'role_pass': 'redhat', 'role_key': ''} + self.connect_info = {'user': 'a', 'asset': 'b', 'ip': '127.0.0.1', 'port': 22, 'role_name': 'root', 'role_pass': '', 'role_key': '/root/.ssh/id_rsa.bak'} return self.connect_info def get_connection(self): diff --git a/jlog/views.py b/jlog/views.py index fbd953820..dc55af700 100644 --- a/jlog/views.py +++ b/jlog/views.py @@ -104,6 +104,10 @@ def log_record(request): def web_terminal(request): - web_terminal_uri = 'ws://%s/terminal' % web_socket_host + #username = get_session.get('username', '') + token = request.COOKIES.get('sessionid') + username = request.user.username + asset_name = '127.0.0.1' + web_terminal_uri = 'ws://%s/terminal?username=%s&asset_name=%s&token=%s' % (web_socket_host, username, asset_name, token) return render_to_response('jlog/web_terminal.html', locals()) diff --git a/run_websocket.py b/run_websocket.py index 2daf002d4..120dd1097 100644 --- a/run_websocket.py +++ b/run_websocket.py @@ -7,7 +7,7 @@ import os import sys import os.path import threading -import uuid +import urllib import tornado.ioloop import tornado.options @@ -15,6 +15,7 @@ import tornado.web import tornado.websocket import tornado.httpserver import tornado.gen +import tornado.httpclient from tornado.websocket import WebSocketClosedError from tornado.options import define, options @@ -40,6 +41,20 @@ define("port", default=3000, help="run on the given port", type=int) define("host", default='0.0.0.0', help="run port on", type=str) +def require_auth(func): + def _deco(request, *args, **kwargs): + username = request.get_argument('username', '') + asset_name = request.get_argument('asset_name', '') + token = request.get_argument('token', '') + print username, asset_name, token + client = tornado.httpclient.HTTPClient() + # response = client.fetch('http://some/url') + urllib.urlencode({'username': username, + # 'asset_name': asset_name, 'token': token}) + return request.close() + # return func(request, *args, **kwargs) + return _deco + + class MyThread(threading.Thread): def __init__(self, *args, **kwargs): super(MyThread, self).__init__(*args, **kwargs) @@ -121,6 +136,7 @@ class MonitorHandler(tornado.websocket.WebSocketHandler): def check_origin(self, origin): return True + @require_auth def open(self): # 获取监控的path self.file_path = self.get_argument('file_path', '') @@ -174,6 +190,8 @@ class WebTerminalKillHandler(tornado.web.RequestHandler): print ws.id if ws.id == int(ws_id): print "killed" + ws.log.is_finished = True + ws.log.save() ws.close() print len(WebTerminalHandler.clients) @@ -194,7 +212,12 @@ class WebTerminalHandler(tornado.websocket.WebSocketHandler): def check_origin(self, origin): return True + @require_auth def open(self): + asset_name = self.get_argument('asset_name', '') + username = self.get_argument('username', '') + token = self.get_argument('token', '') + print asset_name, username, token self.term = WebTty('a', 'b') self.term.get_connection() self.channel = self.term.ssh.invoke_shell(term='xterm') @@ -223,11 +246,14 @@ class WebTerminalHandler(tornado.websocket.WebSocketHandler): print 'On_close' if self in WebTerminalHandler.clients: WebTerminalHandler.clients.remove(self) - self.log_file_f.write('End time is %s' % datetime.datetime.now()) - self.log.is_finished = True - self.log.end_time = datetime.datetime.now() - self.log.save() - self.close() + try: + self.log_file_f.write('End time is %s' % datetime.datetime.now()) + self.log.is_finished = True + self.log.end_time = datetime.datetime.now() + self.log.save() + self.close() + except AttributeError: + pass def forward_outbound(self): self.log_file_f, self.log_time_f, self.log = self.term.get_log_file() From e60a3a697ec46064afb383ec7efe8b7fcc340575 Mon Sep 17 00:00:00 2001 From: ibuler Date: Sat, 7 Nov 2015 17:41:16 +0800 Subject: [PATCH 5/9] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E6=B5=8B=E8=AF=95web=20t?= =?UTF-8?q?erminal=E6=8C=89=E9=92=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- run_websocket.py | 4 ++-- templates/jlog/log_online.html | 6 +++++- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/run_websocket.py b/run_websocket.py index 120dd1097..18c297ad5 100644 --- a/run_websocket.py +++ b/run_websocket.py @@ -50,8 +50,8 @@ def require_auth(func): client = tornado.httpclient.HTTPClient() # response = client.fetch('http://some/url') + urllib.urlencode({'username': username, # 'asset_name': asset_name, 'token': token}) - return request.close() - # return func(request, *args, **kwargs) + # return request.close() + return func(request, *args, **kwargs) return _deco diff --git a/templates/jlog/log_online.html b/templates/jlog/log_online.html index 3bc55bc5c..96d8a9d07 100644 --- a/templates/jlog/log_online.html +++ b/templates/jlog/log_online.html @@ -50,7 +50,7 @@
-
用户日志详细信息列表
+
用户日志详细信息列表
@@ -188,6 +188,10 @@ }}); return false; }); + + $('#test_connect').click(function(){ + window.open('/jlog/web_terminal/?asset_name="hello', '播放', 'height=400, width=600, top=89px, left=99px,toolbar=no,menubar=no,scrollbars=auto,resizeable=no,location=no,status=no'); + }); }); {# function log_search(){#} From 7ea23e0fccab0bf122e1a7eb10f3f5f82109fc17 Mon Sep 17 00:00:00 2001 From: ibuler Date: Sun, 8 Nov 2015 12:01:08 +0800 Subject: [PATCH 6/9] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E6=97=A0=E6=B3=95?= =?UTF-8?q?=E7=BB=88=E7=BB=93web=E8=BF=9B=E7=A8=8B=E7=9A=84bug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- run_websocket.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/run_websocket.py b/run_websocket.py index 18c297ad5..4b8f2f981 100644 --- a/run_websocket.py +++ b/run_websocket.py @@ -29,7 +29,7 @@ import struct, fcntl, signal, socket, select, fnmatch import paramiko from connect import Tty -from connect import TtyLog +from connect import TtyLog, Log try: import simplejson as json @@ -186,11 +186,11 @@ class WebTty(Tty): class WebTerminalKillHandler(tornado.web.RequestHandler): def get(self): ws_id = self.get_argument('id') + Log.objects.filter(id=ws_id).update(is_finished=True) for ws in WebTerminalHandler.clients: print ws.id if ws.id == int(ws_id): print "killed" - ws.log.is_finished = True ws.log.save() ws.close() print len(WebTerminalHandler.clients) From 7849ff2b92953a866a8d06c5d672f1a32c70d6bc Mon Sep 17 00:00:00 2001 From: ibuler Date: Mon, 9 Nov 2015 10:53:15 +0800 Subject: [PATCH 7/9] =?UTF-8?q?=E8=A7=A3=E5=86=B3=E6=90=9C=E7=B4=A2?= =?UTF-8?q?=E5=88=86=E9=A1=B5=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- templates/paginator.html | 70 ++++++++++++++++------------------------ 1 file changed, 27 insertions(+), 43 deletions(-) diff --git a/templates/paginator.html b/templates/paginator.html index 653e89045..63344fc4a 100644 --- a/templates/paginator.html +++ b/templates/paginator.html @@ -1,72 +1,56 @@
+ From 5692e9dc8699357e774e3f3d8d4beb2399395c6a Mon Sep 17 00:00:00 2001 From: ibuler Date: Mon, 9 Nov 2015 11:56:31 +0800 Subject: [PATCH 8/9] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E8=B5=84=E4=BA=A7?= =?UTF-8?q?=E7=9A=84=E9=BB=98=E8=AE=A4=E8=AE=BE=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- jumpserver/models.py | 1 + jumpserver/views.py | 46 ++++++++++++++++++++++++++---------------- templates/setting.html | 14 ++++++++++--- 3 files changed, 41 insertions(+), 20 deletions(-) diff --git a/jumpserver/models.py b/jumpserver/models.py index 5958c5e1a..068ce110e 100644 --- a/jumpserver/models.py +++ b/jumpserver/models.py @@ -7,6 +7,7 @@ class Setting(models.Model): name = models.CharField(max_length=100) default_user = models.CharField(max_length=100, null=True, blank=True) default_port = models.IntegerField(max_length=10, null=True, blank=True) + default_password = models.CharField(max_length=100, null=True, blank=True) default_pri_key_path = models.CharField(max_length=100, null=True, blank=True) class Meta: diff --git a/jumpserver/views.py b/jumpserver/views.py index 7df2409b8..a233332e7 100644 --- a/jumpserver/views.py +++ b/jumpserver/views.py @@ -245,27 +245,39 @@ def Logout(request): def setting(request): header_title, path1 = '项目设置', '设置' - setting_r = get_object(Setting, name='default') + setting_default = get_object(Setting, name='default') if request.method == "POST": - username = request.POST.get('username', '') - port = request.POST.get('port', '') - private_key = request.POST.get('key', '') + setting_raw = request.POST.get('setting', '') + if setting_raw == 'default': + username = request.POST.get('username', '') + port = request.POST.get('port', '') + password = request.POST.get('password', '') + private_key = request.POST.get('key', '') - if '' in [username, port, private_key]: - return HttpResponse('所填内容不能为空') - else: - settings = get_object(Setting, id=1) - private_key_path = os.path.join(BASE_DIR, 'keys', 'default', 'default_private_key.pem') - with open(private_key_path, 'w') as f: - f.write(private_key) - os.chmod(private_key_path, 0600) - if settings: - Setting.objects.filter(name='default').update(default_user=username, default_port=port, - default_pri_key_path=private_key_path) + if '' in [username, port] and ('' in password or '' in private_key): + return HttpResponse('所填内容不能为空, 且密码和私钥填一个') else: - setting_r = Setting(name='default', default_user=username, default_port=port, - default_pri_key_path=private_key_path).save() + private_key_path = os.path.join(BASE_DIR, 'keys', 'default', 'default_private_key.pem') + if private_key: + with open(private_key_path, 'w') as f: + f.write(private_key) + os.chmod(private_key_path, 0600) + + if setting_default: + if password != setting_default.default_password: + password_encode = CRYPTOR.encrypt(password) + else: + password_encode = password + Setting.objects.filter(name='default').update(default_user=username, default_port=port, + default_password=password_encode, + default_pri_key_path=private_key_path) + + else: + password_encode = CRYPTOR.encrypt(password) + setting_r = Setting(name='default', default_user=username, default_port=port, + default_password=password_encode, + default_pri_key_path=private_key_path).save() msg = "设置成功" return my_render('setting.html', locals(), request) diff --git a/templates/setting.html b/templates/setting.html index 9d4696887..55a49ba7c 100644 --- a/templates/setting.html +++ b/templates/setting.html @@ -46,20 +46,28 @@ {% endif %}
+
- +
- +
- + +
+ +
+
+
+
+
From 91b5d039cc7213c8002b5c3a7ef85067ebd9ab5c Mon Sep 17 00:00:00 2001 From: ibuler Date: Mon, 9 Nov 2015 16:28:10 +0800 Subject: [PATCH 9/9] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E9=82=AE=E4=BB=B6setting?= =?UTF-8?q?s?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- jumpserver.conf | 2 +- jumpserver/settings.py | 4 ++-- juser/user_api.py | 2 +- juser/views.py | 3 ++- 4 files changed, 6 insertions(+), 5 deletions(-) diff --git a/jumpserver.conf b/jumpserver.conf index 7ab7e7f31..1b9ed3b25 100644 --- a/jumpserver.conf +++ b/jumpserver.conf @@ -32,4 +32,4 @@ email_host = smtp.exmail.qq.com email_port = 25 email_host_user = noreply@jumpserver.org email_host_password = jumpserver1234 -email_use_tls = False +email_use_tls = True diff --git a/jumpserver/settings.py b/jumpserver/settings.py index fa64dde1e..4304c9f19 100644 --- a/jumpserver/settings.py +++ b/jumpserver/settings.py @@ -25,11 +25,13 @@ DB_PASSWORD = config.get('db', 'password') DB_DATABASE = config.get('db', 'database') AUTH_USER_MODEL = 'juser.User' # mail config +MAIL_ENABLE = config.get('mail', 'mail_enable') EMAIL_HOST = config.get('mail', 'email_host') EMAIL_PORT = config.get('mail', 'email_port') EMAIL_HOST_USER = config.get('mail', 'email_host_user') EMAIL_HOST_PASSWORD = config.get('mail', 'email_host_password') EMAIL_USE_TLS = config.getboolean('mail', 'email_use_tls') +EMAIL_TIMEOUT = 5 # ======== Log ========== LOG = False @@ -41,8 +43,6 @@ KEY = config.get('base', 'key') LOGIN_NAME = getpass.getuser() # LDAP_ENABLE = CONF.getint('ldap', 'ldap_enable') URL = config.get('base', 'url') -MAIL_ENABLE = config.get('mail', 'mail_enable') -MAIL_FROM = config.get('mail', 'email_host_user') log_dir = os.path.join(BASE_DIR, 'logs') log_level = config.get('base', 'log') web_socket_host = config.get('websocket', 'web_socket_host') diff --git a/juser/user_api.py b/juser/user_api.py index 627768bf7..832c6c70e 100644 --- a/juser/user_api.py +++ b/juser/user_api.py @@ -5,7 +5,7 @@ from subprocess import call from juser.models import AdminGroup from jumpserver.api import * -from jumpserver.settings import BASE_DIR +from jumpserver.settings import BASE_DIR, EMAIL_HOST_USER as MAIL_FROM def group_add_user(group, user_id=None, username=None): diff --git a/juser/views.py b/juser/views.py index e2a07d323..cef677a96 100644 --- a/juser/views.py +++ b/juser/views.py @@ -9,10 +9,11 @@ import uuid as uuid_r from django.db.models import Q from django.template import RequestContext from django.db.models import ObjectDoesNotExist -from jumpserver.settings import MAIL_FROM, MAIL_ENABLE +from jumpserver.settings import EMAIL_HOST_USER from juser.user_api import * from jperm.perm_api import _public_perm_api, perm_user_api, user_permed +MAIL_FROM = EMAIL_HOST_USER def chg_role(request): role = {'SU': 2, 'GA': 1, 'CU': 0}