Commit 6db76423 authored by liuzheng712's avatar liuzheng712

Merge branch 'dev' of https://git.coding.net/jumpserver/jumpserver into NormalUserPageLZ

parents cab34668 91b5d039
...@@ -201,26 +201,6 @@ def deal_command(str_r, ssh): ...@@ -201,26 +201,6 @@ def deal_command(str_r, ssh):
else: else:
return '' 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): def newline_code_in(strings):
for i in ['\r', '\r\n', '\n']: for i in ['\r', '\r\n', '\n']:
...@@ -230,44 +210,54 @@ def newline_code_in(strings): ...@@ -230,44 +210,54 @@ def newline_code_in(strings):
return False return False
class Jtty(object): class Tty(object):
""" """
A virtual tty class A virtual tty class
一个虚拟终端类,实现连接ssh和记录日志 一个虚拟终端类,实现连接ssh和记录日志,基类
""" """
def __init__(self, username, ip): def __init__(self, username, asset_name):
self.chan = None
self.username = username self.username = username
self.ip = ip self.asset_name = asset_name
# self.user = user self.ip = None
# self.asset = asset 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 @staticmethod
def get_win_size(): def is_output(strings):
""" newline_char = ['\n', '\r', '\r\n']
This function use to get the size of the windows! for char in newline_char:
获得terminal窗口大小 if char in strings:
""" return True
if 'TIOCGWINSZ' in dir(termios): return False
TIOCGWINSZ = termios.TIOCGWINSZ
else:
TIOCGWINSZ = 1074295912L
s = struct.pack('HHHH', 0, 0, 0, 0)
x = fcntl.ioctl(sys.stdout.fileno(), TIOCGWINSZ, s)
return struct.unpack('HHHH', x)[0:2]
def set_win_size(self, sig, data): @staticmethod
def remove_control_char(str_r):
""" """
This function use to set the window size of the terminal! 处理日志特殊字符
设置terminal窗口大小
""" """
try: control_char = re.compile(r"""
win_size = self.get_win_size() \x1b[ #%()*+\-.\/]. |
self.chan.resize_pty(height=win_size[0], width=win_size[1]) \r | #匹配 回车符(CR)
except Exception: (?:\x1b\[|\x9b) [ -?]* [@-~] | #匹配 控制顺序描述符(CSI)... Cmd
pass (?:\x1b\]|\x9d) .*? (?:\x1b\\|[\a\x9c]) | \x07 | #匹配 操作系统指令(OSC)...终止符或振铃符(ST|BEL)
(?:\x1b[P^_]|[\x90\x9e\x9f]) .*? (?:\x1b\\|\x9c) | #匹配 设备控制串或私讯或应用程序命令(DCS|PM|APC)...终止符(ST)
def log_record(self): \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. Logging user command and output.
记录用户的日志 记录用户的日志
...@@ -277,35 +267,118 @@ class Jtty(object): ...@@ -277,35 +267,118 @@ class Jtty(object):
date_start = time.strftime('%Y%m%d', time.localtime(timestamp_start)) date_start = time.strftime('%Y%m%d', time.localtime(timestamp_start))
time_start = time.strftime('%H%M%S', 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) 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)) log_file_path = os.path.join(today_connect_log_dir, '%s_%s_%s' % (self.username, self.asset_name, 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: try:
is_dir(today_connect_log_dir) is_dir(today_connect_log_dir, mode=0777)
except OSError: except OSError:
raise ServerError('Create %s failed, Please modify %s permission.' % (today_connect_log_dir, tty_log_dir)) raise ServerError('Create %s failed, Please modify %s permission.' % (today_connect_log_dir, tty_log_dir))
try: 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_file_f = open(log_file_path + '.log', 'a')
log_time_f = open(log_file_path + '.time', 'a') log_time_f = open(log_file_path + '.time', 'a')
except IOError: except IOError:
raise ServerError('Create logfile failed, Please modify %s permission.' % today_connect_log_dir) 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, if self.login_type == 'ssh':
log_path=log_file_path, start_time=datetime.datetime.now(), pid=pid) pid = os.getpid()
log_file_f.write('Start time is %s\n' % datetime.datetime.now()) 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() log.save()
return log_file_f, log_time_f, ip_list, log return log_file_f, log_time_f, log
def get_connect_info(self):
"""
获取需要登陆的主机的信息和映射用户的账号密码
"""
def posix_shell(self,ssh): # 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': '', 'role_key': '/root/.ssh/id_rsa.bak'}
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和记录日志
"""
@staticmethod
def get_win_size():
"""
This function use to get the size of the windows!
获得terminal窗口大小
"""
if 'TIOCGWINSZ' in dir(termios):
TIOCGWINSZ = termios.TIOCGWINSZ
else:
TIOCGWINSZ = 1074295912L
s = struct.pack('HHHH', 0, 0, 0, 0)
x = fcntl.ioctl(sys.stdout.fileno(), TIOCGWINSZ, s)
return struct.unpack('HHHH', x)[0:2]
def set_win_size(self, sig, data):
"""
This function use to set the window size of the terminal!
设置terminal窗口大小
"""
try:
win_size = self.get_win_size()
self.channel.resize_pty(height=win_size[0], width=win_size[1])
except Exception:
pass
def posix_shell(self):
""" """
Use paramiko channel connect server interactive. Use paramiko channel connect server interactive.
使用paramiko模块的channel,连接后端,进入交互式 使用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) old_tty = termios.tcgetattr(sys.stdin)
pre_timestamp = time.time() pre_timestamp = time.time()
input_r = '' input_r = ''
...@@ -314,29 +387,29 @@ class Jtty(object): ...@@ -314,29 +387,29 @@ class Jtty(object):
try: try:
tty.setraw(sys.stdin.fileno()) tty.setraw(sys.stdin.fileno())
tty.setcbreak(sys.stdin.fileno()) tty.setcbreak(sys.stdin.fileno())
self.chan.settimeout(0.0) self.channel.settimeout(0.0)
while True: while True:
try: try:
r, w, e = select.select([self.chan, sys.stdin], [], []) r, w, e = select.select([self.channel, sys.stdin], [], [])
except Exception: except Exception:
pass pass
if self.chan in r: if self.channel in r:
try: try:
x = self.chan.recv(1024) x = self.channel.recv(1024)
if len(x) == 0: if len(x) == 0:
break break
sys.stdout.write(x) sys.stdout.write(x)
sys.stdout.flush() sys.stdout.flush()
log_file_f.write(x)
now_timestamp = time.time() now_timestamp = time.time()
log_time_f.write('%s %s\n' % (round(now_timestamp-pre_timestamp, 4), len(x))) log_time_f.write('%s %s\n' % (round(now_timestamp-pre_timestamp, 4), len(x)))
log_file_f.write(x)
pre_timestamp = now_timestamp pre_timestamp = now_timestamp
log_file_f.flush() log_file_f.flush()
log_time_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 input_r += x
except socket.timeout: except socket.timeout:
...@@ -348,14 +421,16 @@ class Jtty(object): ...@@ -348,14 +421,16 @@ class Jtty(object):
input_mode = True input_mode = True
if str(x) in ['\r', '\n', '\r\n']: 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() TtyLog(log=log, datetime=datetime.datetime.now(), cmd=input_r).save()
input_r = '' input_r = ''
input_mode = False input_mode = False
if len(x) == 0: if len(x) == 0:
break break
self.chan.send(x) self.channel.send(x)
finally: finally:
termios.tcsetattr(sys.stdin, termios.TCSADRAIN, old_tty) termios.tcsetattr(sys.stdin, termios.TCSADRAIN, old_tty)
...@@ -365,42 +440,6 @@ class Jtty(object): ...@@ -365,42 +440,6 @@ class Jtty(object):
log.end_time = datetime.datetime.now() log.end_time = datetime.datetime.now()
log.save() 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): def connect(self):
""" """
Connect server. Connect server.
...@@ -415,7 +454,7 @@ class Jtty(object): ...@@ -415,7 +454,7 @@ class Jtty(object):
# 获取连接的隧道并设置窗口大小 Make a channel and set windows size # 获取连接的隧道并设置窗口大小 Make a channel and set windows size
global channel global channel
win_size = self.get_win_size() 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: try:
signal.signal(signal.SIGWINCH, self.set_win_size) signal.signal(signal.SIGWINCH, self.set_win_size)
except: except:
...@@ -424,17 +463,17 @@ class Jtty(object): ...@@ -424,17 +463,17 @@ class Jtty(object):
# 设置PS1并提示 Set PS1 and msg it # 设置PS1并提示 Set PS1 and msg it
#channel.send(ps1) #channel.send(ps1)
#channel.send(login_msg) #channel.send(login_msg)
channel.send('echo ${SSH_TTY}\n') # channel.send('echo ${SSH_TTY}\n')
global SSH_TTY # global SSH_TTY
while not channel.recv_ready(): # while not channel.recv_ready():
time.sleep(1) # time.sleep(1)
tmp = channel.recv(1024) # tmp = channel.recv(1024)
#print 'ok'+tmp+'ok' #print 'ok'+tmp+'ok'
# SSH_TTY = re.search(r'(?<=/dev/).*', tmp).group().strip() # SSH_TTY = re.search(r'(?<=/dev/).*', tmp).group().strip()
SSH_TTY = '' # SSH_TTY = ''
channel.send('clear\n') channel.send('clear\n')
# Make ssh interactive tunnel # Make ssh interactive tunnel
self.posix_shell(ssh) self.posix_shell()
# Shutdown channel socket # Shutdown channel socket
channel.close() channel.close()
......
...@@ -48,7 +48,8 @@ def log_list(request, offset): ...@@ -48,7 +48,8 @@ def log_list(request, offset):
contact_list, p, contacts, page_range, current_page, show_first, show_end = pages(posts, request) 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)) return render_to_response('jlog/log_%s.html' % offset, locals(), context_instance=RequestContext(request))
...@@ -103,6 +104,10 @@ def log_record(request): ...@@ -103,6 +104,10 @@ def log_record(request):
def web_terminal(request): def web_terminal(request):
web_terminal_uri = '%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()) return render_to_response('jlog/web_terminal.html', locals())
...@@ -23,7 +23,7 @@ root_pw = secret234 ...@@ -23,7 +23,7 @@ root_pw = secret234
[websocket] [websocket]
web_socket_host = ws://192.168.244.129:3000 web_socket_host = 192.168.244.129:3000
[mail] [mail]
...@@ -32,4 +32,4 @@ email_host = smtp.exmail.qq.com ...@@ -32,4 +32,4 @@ email_host = smtp.exmail.qq.com
email_port = 25 email_port = 25
email_host_user = noreply@jumpserver.org email_host_user = noreply@jumpserver.org
email_host_password = jumpserver1234 email_host_password = jumpserver1234
email_use_tls = False email_use_tls = True
...@@ -7,6 +7,7 @@ class Setting(models.Model): ...@@ -7,6 +7,7 @@ class Setting(models.Model):
name = models.CharField(max_length=100) name = models.CharField(max_length=100)
default_user = models.CharField(max_length=100, null=True, blank=True) default_user = models.CharField(max_length=100, null=True, blank=True)
default_port = models.IntegerField(max_length=10, 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) default_pri_key_path = models.CharField(max_length=100, null=True, blank=True)
class Meta: class Meta:
......
...@@ -15,7 +15,7 @@ import getpass ...@@ -15,7 +15,7 @@ import getpass
config = ConfigParser.ConfigParser() 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')) config.read(os.path.join(BASE_DIR, 'jumpserver.conf'))
DB_HOST = config.get('db', 'host') DB_HOST = config.get('db', 'host')
...@@ -25,11 +25,13 @@ DB_PASSWORD = config.get('db', 'password') ...@@ -25,11 +25,13 @@ DB_PASSWORD = config.get('db', 'password')
DB_DATABASE = config.get('db', 'database') DB_DATABASE = config.get('db', 'database')
AUTH_USER_MODEL = 'juser.User' AUTH_USER_MODEL = 'juser.User'
# mail config # mail config
MAIL_ENABLE = config.get('mail', 'mail_enable')
EMAIL_HOST = config.get('mail', 'email_host') EMAIL_HOST = config.get('mail', 'email_host')
EMAIL_PORT = config.get('mail', 'email_port') EMAIL_PORT = config.get('mail', 'email_port')
EMAIL_HOST_USER = config.get('mail', 'email_host_user') EMAIL_HOST_USER = config.get('mail', 'email_host_user')
EMAIL_HOST_PASSWORD = config.get('mail', 'email_host_password') EMAIL_HOST_PASSWORD = config.get('mail', 'email_host_password')
EMAIL_USE_TLS = config.getboolean('mail', 'email_use_tls') EMAIL_USE_TLS = config.getboolean('mail', 'email_use_tls')
EMAIL_TIMEOUT = 5
# ======== Log ========== # ======== Log ==========
LOG = False LOG = False
...@@ -41,8 +43,6 @@ KEY = config.get('base', 'key') ...@@ -41,8 +43,6 @@ KEY = config.get('base', 'key')
LOGIN_NAME = getpass.getuser() LOGIN_NAME = getpass.getuser()
# LDAP_ENABLE = CONF.getint('ldap', 'ldap_enable') # LDAP_ENABLE = CONF.getint('ldap', 'ldap_enable')
URL = config.get('base', 'url') 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_dir = os.path.join(BASE_DIR, 'logs')
log_level = config.get('base', 'log') log_level = config.get('base', 'log')
web_socket_host = config.get('websocket', 'web_socket_host') web_socket_host = config.get('websocket', 'web_socket_host')
......
...@@ -246,27 +246,39 @@ def Logout(request): ...@@ -246,27 +246,39 @@ def Logout(request):
def setting(request): def setting(request):
header_title, path1 = '项目设置', '设置' header_title, path1 = '项目设置', '设置'
setting_r = get_object(Setting, name='default') setting_default = get_object(Setting, name='default')
if request.method == "POST": if request.method == "POST":
username = request.POST.get('username', '') setting_raw = request.POST.get('setting', '')
port = request.POST.get('port', '') if setting_raw == 'default':
private_key = request.POST.get('key', '') username = request.POST.get('username', '')
port = request.POST.get('port', '')
if '' in [username, port, private_key]: password = request.POST.get('password', '')
return HttpResponse('所填内容不能为空') private_key = request.POST.get('key', '')
else:
settings = get_object(Setting, id=1) if '' in [username, port] and ('' in password or '' in private_key):
private_key_path = os.path.join(BASE_DIR, 'keys', 'default', 'default_private_key.pem') return HttpResponse('所填内容不能为空, 且密码和私钥填一个')
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)
else: else:
setting_r = Setting(name='default', default_user=username, default_port=port, private_key_path = os.path.join(BASE_DIR, 'keys', 'default', 'default_private_key.pem')
default_pri_key_path=private_key_path).save() 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 = "设置成功" msg = "设置成功"
return my_render('setting.html', locals(), request) return my_render('setting.html', locals(), request)
......
...@@ -5,7 +5,7 @@ from subprocess import call ...@@ -5,7 +5,7 @@ from subprocess import call
from juser.models import AdminGroup from juser.models import AdminGroup
from jumpserver.api import * 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): def group_add_user(group, user_id=None, username=None):
......
...@@ -10,10 +10,11 @@ from django.contrib.auth.decorators import login_required ...@@ -10,10 +10,11 @@ from django.contrib.auth.decorators import login_required
from django.db.models import Q from django.db.models import Q
from django.template import RequestContext from django.template import RequestContext
from django.db.models import ObjectDoesNotExist 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 juser.user_api import *
from jperm.perm_api import _public_perm_api, perm_user_api, user_permed from jperm.perm_api import _public_perm_api, perm_user_api, user_permed
MAIL_FROM = EMAIL_HOST_USER
def chg_role(request): def chg_role(request):
role = {'SU': 2, 'GA': 1, 'CU': 0} role = {'SU': 2, 'GA': 1, 'CU': 0}
......
# coding: utf-8 # coding: utf-8
import time import time
import datetime
import json import json
import os import os
import sys import sys
import os.path import os.path
import threading import threading
import urllib
import tornado.ioloop import tornado.ioloop
import tornado.options import tornado.options
...@@ -13,6 +15,7 @@ import tornado.web ...@@ -13,6 +15,7 @@ import tornado.web
import tornado.websocket import tornado.websocket
import tornado.httpserver import tornado.httpserver
import tornado.gen import tornado.gen
import tornado.httpclient
from tornado.websocket import WebSocketClosedError from tornado.websocket import WebSocketClosedError
from tornado.options import define, options from tornado.options import define, options
...@@ -21,9 +24,12 @@ from pyinotify import WatchManager, Notifier, ProcessEvent, IN_DELETE, IN_CREATE ...@@ -21,9 +24,12 @@ from pyinotify import WatchManager, Notifier, ProcessEvent, IN_DELETE, IN_CREATE
# from gevent import monkey # from gevent import monkey
# monkey.patch_all() # monkey.patch_all()
# import gevent # 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 import paramiko
from connect import Tty
from connect import TtyLog, Log
try: try:
import simplejson as json import simplejson as json
...@@ -35,6 +41,20 @@ define("port", default=3000, help="run on the given port", type=int) ...@@ -35,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) 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): class MyThread(threading.Thread):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super(MyThread, self).__init__(*args, **kwargs) super(MyThread, self).__init__(*args, **kwargs)
...@@ -92,6 +112,7 @@ class Application(tornado.web.Application): ...@@ -92,6 +112,7 @@ class Application(tornado.web.Application):
handlers = [ handlers = [
(r'/monitor', MonitorHandler), (r'/monitor', MonitorHandler),
(r'/terminal', WebTerminalHandler), (r'/terminal', WebTerminalHandler),
(r'/kill', WebTerminalKillHandler),
] ]
setting = { setting = {
...@@ -115,6 +136,7 @@ class MonitorHandler(tornado.websocket.WebSocketHandler): ...@@ -115,6 +136,7 @@ class MonitorHandler(tornado.websocket.WebSocketHandler):
def check_origin(self, origin): def check_origin(self, origin):
return True return True
@require_auth
def open(self): def open(self):
# 获取监控的path # 获取监控的path
self.file_path = self.get_argument('file_path', '') self.file_path = self.get_argument('file_path', '')
...@@ -123,12 +145,6 @@ class MonitorHandler(tornado.websocket.WebSocketHandler): ...@@ -123,12 +145,6 @@ class MonitorHandler(tornado.websocket.WebSocketHandler):
MonitorHandler.threads.append(thread) MonitorHandler.threads.append(thread)
self.stream.set_nodelay(True) self.stream.set_nodelay(True)
print len(MonitorHandler.threads), len(MonitorHandler.clients)
def on_message(self, message):
self.write_message('Connect WebSocket Success. <br/>')
# 监控日志,发生变动发向客户端
try: try:
for t in MonitorHandler.threads: for t in MonitorHandler.threads:
if t.is_alive(): if t.is_alive():
...@@ -142,6 +158,12 @@ class MonitorHandler(tornado.websocket.WebSocketHandler): ...@@ -142,6 +158,12 @@ class MonitorHandler(tornado.websocket.WebSocketHandler):
MonitorHandler.clients.remove(self) MonitorHandler.clients.remove(self)
MonitorHandler.threads.remove(MonitorHandler.threads[client_index]) MonitorHandler.threads.remove(MonitorHandler.threads[client_index])
print len(MonitorHandler.threads), len(MonitorHandler.clients)
def on_message(self, message):
# 监控日志,发生变动发向客户端
pass
def on_close(self): def on_close(self):
# 客户端主动关闭 # 客户端主动关闭
# self.close() # self.close()
...@@ -152,28 +174,55 @@ class MonitorHandler(tornado.websocket.WebSocketHandler): ...@@ -152,28 +174,55 @@ class MonitorHandler(tornado.websocket.WebSocketHandler):
MonitorHandler.threads.remove(MonitorHandler.threads[client_index]) 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 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.save()
ws.close()
print len(WebTerminalHandler.clients)
class WebTerminalHandler(tornado.websocket.WebSocketHandler): class WebTerminalHandler(tornado.websocket.WebSocketHandler):
tasks = [] tasks = []
clients = []
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
self.chan = None self.term = None
self.ssh = None self.channel = None
self.log_file_f = None
self.log_time_f = None
self.log = None
self.id = 0
super(WebTerminalHandler, self).__init__(*args, **kwargs) super(WebTerminalHandler, self).__init__(*args, **kwargs)
def check_origin(self, origin): def check_origin(self, origin):
return True return True
@require_auth
def open(self): def open(self):
self.ssh = paramiko.SSHClient() asset_name = self.get_argument('asset_name', '')
self.ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) username = self.get_argument('username', '')
try: token = self.get_argument('token', '')
self.ssh.connect('127.0.0.1', 22, 'root', 'redhat') print asset_name, username, token
except: self.term = WebTty('a', 'b')
self.write_message(json.loads({'data': 'Connect server Error'})) self.term.get_connection()
self.close() self.channel = self.term.ssh.invoke_shell(term='xterm')
WebTerminalHandler.tasks.append(MyThread(target=self.forward_outbound))
self.chan = self.ssh.invoke_shell(term='xterm') WebTerminalHandler.clients.append(self)
WebTerminalHandler.tasks.append(threading.Thread(target=self._forward_outbound))
for t in WebTerminalHandler.tasks: for t in WebTerminalHandler.tasks:
if t.is_alive(): if t.is_alive():
...@@ -185,35 +234,56 @@ class WebTerminalHandler(tornado.websocket.WebSocketHandler): ...@@ -185,35 +234,56 @@ class WebTerminalHandler(tornado.websocket.WebSocketHandler):
data = json.loads(message) data = json.loads(message)
if not data: if not data:
return return
if 'resize' in data: if data.get('data'):
self.chan.resize_pty( self.term.input_mode = True
data['resize'].get('width', 80), if str(data['data']) in ['\r', '\n', '\r\n']:
data['resize'].get('height', 24)) TtyLog(log=self.log, datetime=datetime.datetime.now(), cmd=self.term.remove_control_char(self.term.input_r)).save()
if 'data' in data: self.term.input_r = ''
self.chan.send(data['data']) self.term.input_mode = False
self.channel.send(data['data'])
def on_close(self): def on_close(self):
self.write_message(json.dumps({'data': 'close websocket'})) print 'On_close'
if self in WebTerminalHandler.clients:
WebTerminalHandler.clients.remove(self)
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): def forward_outbound(self):
""" Forward outbound traffic (ssh -> websockets) """ self.log_file_f, self.log_time_f, self.log = self.term.get_log_file()
self.id = self.log.id
try: try:
data = '' data = ''
pre_timestamp = time.time()
while True: while True:
wait_read(self.chan.fileno()) r, w, e = select.select([self.channel, sys.stdin], [], [])
recv = self.chan.recv(1024) if self.channel in r:
if not len(recv): recv = self.channel.recv(1024)
return if not len(recv):
data += recv return
try: data += recv
self.write_message(json.dumps({'data': data})) try:
data = '' self.write_message(json.dumps({'data': data}))
except UnicodeDecodeError: now_timestamp = time.time()
pass 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: finally:
self.close() self.close()
if __name__ == '__main__': if __name__ == '__main__':
tornado.options.parse_command_line() tornado.options.parse_command_line()
app = Application() app = Application()
......
...@@ -50,7 +50,7 @@ ...@@ -50,7 +50,7 @@
<div class="col-lg-12"> <div class="col-lg-12">
<div class="ibox float-e-margins"> <div class="ibox float-e-margins">
<div id="ibox-content" class="ibox-title"> <div id="ibox-content" class="ibox-title">
<h5> 用户日志详细信息列表 </h5> <h5> 用户日志详细信息列表 <input type="button" id="test_connect" class="btn btn-primary" value="测试连接 web terminal" /> </h5>
<div class="ibox-tools"> <div class="ibox-tools">
<a class="collapse-link"> <a class="collapse-link">
<i class="fa fa-chevron-up"></i> <i class="fa fa-chevron-up"></i>
...@@ -97,7 +97,7 @@ ...@@ -97,7 +97,7 @@
{% ifnotequal session_role_id 0 %} {% ifnotequal session_role_id 0 %}
<td class="text-center"><a href="/jlog/history/?id={{ post.id }}" class="log_command"> 命令统计 </a></td> <td class="text-center"><a href="/jlog/history/?id={{ post.id }}" class="log_command"> 命令统计 </a></td>
<td class="text-center"><a class="monitor" file_path="{{ post.log_path }}"> 监控 </a></td> <td class="text-center"><a class="monitor" file_path="{{ post.log_path }}"> 监控 </a></td>
<td class="text-center"><input type="button" id="cut" class="btn btn-danger btn-xs" name="cut" value="阻断" onclick='cut("{{ post.pid }}")' /></td> <td class="text-center"><input type="button" id="cut" class="btn btn-danger btn-xs" name="cut" value="阻断" onclick='cut("{{ post.pid }}", "{{ post.remote_ip }}")' /></td>
{% endifnotequal %} {% endifnotequal %}
<td class="text-center" id="start_time"> {{ post.start_time|date:"Y-m-d H:i:s" }} </td> <td class="text-center" id="start_time"> {{ post.start_time|date:"Y-m-d H:i:s" }} </td>
</tr> </tr>
...@@ -127,8 +127,20 @@ ...@@ -127,8 +127,20 @@
var file_path = obj.attr('file_path'); var file_path = obj.attr('file_path');
var wsUri = '{{ web_monitor_uri }}'; var wsUri = '{{ web_monitor_uri }}';
var socket = new WebSocket(wsUri + '?file_path=' + file_path); var socket = new WebSocket(wsUri + '?file_path=' + file_path);
var term = new Terminal({
cols: 80,
rows: 24,
screenKeys: false
});
var tag = $('<div id="term" style="height:500px; overflow: auto;background-color: rgba(0, 0, 0, 0);border: none"></div>');
term.open();
term.resize(80, 24);
socket.onopen = function(evt){ socket.onopen = function(evt){
socket.send(file_path) socket.send('hello');
term.write('~.~ Connect WebSocket Success.~.~ \r\n');
}; };
window.onbeforeunload = function(){ window.onbeforeunload = function(){
...@@ -138,29 +150,15 @@ ...@@ -138,29 +150,15 @@
var username = obj.closest('tr').find('#username').text(); var username = obj.closest('tr').find('#username').text();
var ip = obj.closest('tr').find('#ip').text(); var ip = obj.closest('tr').find('#ip').text();
BootstrapDialog.show({message: function(){ BootstrapDialog.show({message: function(){
//服务器端认证 //服务器端认证
{# socket.send('login', {userid:message.id, filename:message.filename,username:username,seed:seed});#} {# 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 = $('<div id="term" style="height:500px; overflow: auto;background-color: rgba(0, 0, 0, 0);border: none"></div>');
term.open();
term.resize(80, 24);
window.setTimeout(function(){ window.setTimeout(function(){
$('.terminal').detach().appendTo('#term'); $('.terminal').detach().appendTo('#term');
socket.onmessage = function(evt){ socket.onmessage = function(evt){
term.write(evt.data); term.write(evt.data);
}}, 1000); }}, 1000);
tag[0].style.color = "#00FF00";
return tag[0]; return tag[0];
} , } ,
title:'Jumpserver实时监控 '+' 登录用户名: '+'<span class="text-info">'+username+'</span>'+' 登录主机: '+'<span class="text-info">'+ip, title:'Jumpserver实时监控 '+' 登录用户名: '+'<span class="text-info">'+username+'</span>'+' 登录主机: '+'<span class="text-info">'+ip,
...@@ -190,6 +188,10 @@ ...@@ -190,6 +188,10 @@
}}); }});
return false; 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(){#} {# function log_search(){#}
...@@ -204,8 +206,14 @@ ...@@ -204,8 +206,14 @@
{# }#} {# }#}
function cut(num){ function cut(num, host){
var g_url = "/jlog/log_kill/?id="+num; console.log(host);
if (host=='Web'){
var g_url = '{{ web_kill_uri }}' + '?id=' + num;
} else {
g_url = "/jlog/log_kill/?id=" + num;
}
$.ajax({ $.ajax({
type: "GET", type: "GET",
url: g_url, url: g_url,
......
<div class="col-sm-6"> <div class="col-sm-6">
<div class="dataTables_paginate paging_simple_numbers" id="editable_paginate"> <div class="dataTables_paginate paging_simple_numbers" id="editable_paginate">
<ul class="pagination" style="margin-top: 0; float: right"> <ul class="pagination" style="margin-top: 0; float: right">
{% if keyword %}
{% if contacts.has_previous %} {% if contacts.has_previous %}
<li class="paginate_button previous" aria-controls="editable" tabindex="0" id="editable_previous"> <li class="paginate_button previous" aria-controls="editable" tabindex="0" id="editable_previous">
<a href="?keyword={{ keyword }}&page={{ contacts.previous_page_number }}">Previous</a> <a class="page" href="?page={{ contacts.previous_page_number }}">Previous</a>
</li> </li>
{% else %} {% else %}
<li class="paginate_button previous disabled" aria-controls="editable" tabindex="0" id="editable_previous"> <li class="paginate_button previous disabled" aria-controls="editable" tabindex="0" id="editable_previous">
<a href="#">Previous</a> <a class="page" href="#">Previous</a>
</li> </li>
{% endif %} {% endif %}
{% ifequal show_first 1 %} {% ifequal show_first 1 %}
<li class="paginate_button" aria-controls="editable" tabindex="0"><a href="?keyword={{ keyword }}&page=1" title="第1页">1...</a></li> <li class="paginate_button" aria-controls="editable" tabindex="0"><a class="page" href="?page=1" title="第1页">1...</a></li>
{% endifequal %} {% endifequal %}
{% for page in page_range %} {% for page in page_range %}
{% ifequal current_page page %} {% ifequal current_page page %}
<li class="paginate_button active" aria-controls="editable" tabindex="0"><a href="?keyword={{ keyword }}&page={{ page }}" title="第{{ page }}页">{{ page }}</a></li> <li class="paginate_button active" aria-controls="editable" tabindex="0"><a class="page" href="?page={{ page }}" title="第{{ page }}页">{{ page }}</a></li>
{% else %} {% else %}
<li class="paginate_button" aria-controls="editable" tabindex="0"><a href="?keyword={{ keyword }}&page={{ page }}" title="第{{ page }}页">{{ page }}</a></li> <li class="paginate_button" aria-controls="editable" tabindex="0"><a class="page" href="?page={{ page }}" title="第{{ page }}页">{{ page }}</a></li>
{% endifequal %} {% endifequal %}
{% endfor %} {% endfor %}
{% ifequal show_end 1 %} {% ifequal show_end 1 %}
<li class="paginate_button" aria-controls="editable" tabindex="0"><a href="?keyword={{ keyword }}&page={{ p.num_pages }}" title="第{{ page }}页">...{{ p.num_pages }}</a></li> <li class="paginate_button" aria-controls="editable" tabindex="0"><a class="page" href="?page={{ p.num_pages }}" title="第{{ page }}页">...{{ p.num_pages }}</a></li>
{% endifequal %} {% endifequal %}
{% if contacts.has_next %} {% if contacts.has_next %}
<li class="paginate_button next" aria-controls="editable" tabindex="0" id="editable_next"> <li class="paginate_button next" aria-controls="editable" tabindex="0" id="editable_next">
<a href="?keyword={{ keyword }}&page={{ contacts.next_page_number }}">Next</a> <a class="page" href="?page={{ contacts.next_page_number }}">Next</a>
</li> </li>
{% else %} {% else %}
<li class="paginate_button next disabled" aria-controls="editable" tabindex="0" id="editable_next"> <li class="paginate_button next disabled" aria-controls="editable" tabindex="0" id="editable_next">
<a href="#">Next</a> <a class="page" href="#">Next</a>
</li> </li>
{% endif %} {% endif %}
{% else %}
{% if contacts.has_previous %}
<li class="paginate_button previous" aria-controls="editable" tabindex="0" id="editable_previous">
<a href="?page={{ contacts.previous_page_number }}">Previous</a>
</li>
{% else %}
<li class="paginate_button previous disabled" aria-controls="editable" tabindex="0" id="editable_previous">
<a href="#">Previous</a>
</li>
{% endif %}
{% ifequal show_first 1 %}
<li class="paginate_button" aria-controls="editable" tabindex="0"><a href="?page=1" title="第1页">1...</a></li>
{% endifequal %}
{% for page in page_range %}
{% ifequal current_page page %}
<li class="paginate_button active" aria-controls="editable" tabindex="0"><a href="?page={{ page }}" title="第{{ page }}页">{{ page }}</a></li>
{% else %}
<li class="paginate_button" aria-controls="editable" tabindex="0"><a href="?page={{ page }}" title="第{{ page }}页">{{ page }}</a></li>
{% endifequal %}
{% endfor %}
{% ifequal show_end 1 %}
<li class="paginate_button" aria-controls="editable" tabindex="0"><a href="?page={{ p.num_pages }}" title="第{{ page }}页">...{{ p.num_pages }}</a></li>
{% endifequal %}
{% if contacts.has_next %}
<li class="paginate_button next" aria-controls="editable" tabindex="0" id="editable_next">
<a href="?page={{ contacts.next_page_number }}">Next</a>
</li>
{% else %}
<li class="paginate_button next disabled" aria-controls="editable" tabindex="0" id="editable_next">
<a href="#">Next</a>
</li>
{% endif %}
{% endif %}
</ul> </ul>
</div> </div>
</div> </div>
<script>
$(document).ready(function(){
$('.page').click(function(){
var searchStr = location.search;
var old_href = $(this).attr('href').replace('?', '');
var searchArray = searchStr.split('&');
if (searchStr.indexOf('page')){
searchArray.pop();
}
searchArray.push(old_href);
if (searchArray.length > 2){
$(this).attr('href', searchArray.join('&'));
}
})
});
</script>
...@@ -46,20 +46,28 @@ ...@@ -46,20 +46,28 @@
{% endif %} {% endif %}
<div class="form-group"> <div class="form-group">
<label for="username" class="col-sm-2 control-label">默认用户名<span class="red-fonts">*</span></label> <label for="username" class="col-sm-2 control-label">默认用户名<span class="red-fonts">*</span></label>
<input name="setting" value="default" style="display: none">
<div class="col-sm-8"> <div class="col-sm-8">
<input id="username" name="username" placeholder="Username" type="text" value="{{ setting_r.default_user }}" class="form-control"> <input id="username" name="username" placeholder="Username" type="text" value="{{ setting_default.default_user }}" class="form-control">
</div> </div>
</div> </div>
<div class="hr-line-dashed"></div> <div class="hr-line-dashed"></div>
<div class="form-group"> <div class="form-group">
<label for="port" class="col-sm-2 control-label">默认ssh端口<span class="red-fonts">*</span></label> <label for="port" class="col-sm-2 control-label">默认ssh端口<span class="red-fonts">*</span></label>
<div class="col-sm-8"> <div class="col-sm-8">
<input id="port" name="port" placeholder="Port" type="text" value="{{ setting_r.default_port }}" class="form-control"> <input id="port" name="port" placeholder="Port" type="text" value="{{ setting_default.default_port }}" class="form-control">
</div> </div>
</div> </div>
<div class="hr-line-dashed"></div> <div class="hr-line-dashed"></div>
<div class="form-group"> <div class="form-group">
<label for="key" class="col-sm-2 control-label">默认密钥<span class="red-fonts">*</span></label> <label for="key" class="col-sm-2 control-label">默认密码</label>
<div class="col-sm-8">
<input id="password" name="password" placeholder="Password" type="password" value="{{ setting_default.default_password }}" class="form-control">
</div>
</div>
<div class="hr-line-dashed"></div>
<div class="form-group">
<label for="key" class="col-sm-2 control-label">默认密钥</label>
<div class="col-sm-8"> <div class="col-sm-8">
<textarea class="form-control" name="key" placeholder="请复制粘贴私钥(原来的因为安全原因不被显示)" rows="10" style="font-size: 9px;"></textarea> <textarea class="form-control" name="key" placeholder="请复制粘贴私钥(原来的因为安全原因不被显示)" rows="10" style="font-size: 9px;"></textarea>
</div> </div>
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment