You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
jumpserver/run_server.py

523 lines
18 KiB

#!/usr/bin/env python
9 years ago
# coding: utf-8
import time
import datetime
import json
import os
import sys
import os.path
import threading
import re
import functools
from django.core.signals import request_started, request_finished
9 years ago
import tornado.ioloop
import tornado.options
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
from pyinotify import WatchManager, ProcessEvent, IN_DELETE, IN_CREATE, IN_MODIFY, AsyncNotifier
9 years ago
import select
from connect import Tty, User, Asset, PermRole, logger, get_object, gen_resource
9 years ago
from connect import TtyLog, Log, Session, user_have_perm, get_group_user_perm, MyRunner, ExecLog
try:
import simplejson as json
except ImportError:
import json
os.environ['DJANGO_SETTINGS_MODULE'] = 'jumpserver.settings'
from jumpserver.settings import IP, PORT
9 years ago
define("port", default=PORT, help="run on the given port", type=int)
define("host", default=IP, help="run port on given host", type=str)
9 years ago
from jlog.views import TermLogRecorder
9 years ago
def django_request_support(func):
@functools.wraps(func)
def _deco(*args, **kwargs):
9 years ago
request_started.send_robust(func)
response = func(*args, **kwargs)
9 years ago
request_finished.send_robust(func)
return response
9 years ago
return _deco
9 years ago
def require_auth(role='user'):
def _deco(func):
def _deco2(request, *args, **kwargs):
if request.get_cookie('sessionid'):
session_key = request.get_cookie('sessionid')
else:
session_key = request.get_argument('sessionid', '')
logger.debug('Websocket: session_key: %s' % session_key)
9 years ago
if session_key:
session = get_object(Session, session_key=session_key)
logger.debug('Websocket: session: %s' % session)
9 years ago
if session and datetime.datetime.now() < session.expire_date:
user_id = session.get_decoded().get('_auth_user_id')
9 years ago
request.user_id = user_id
9 years ago
user = get_object(User, id=user_id)
if user:
logger.debug('Websocket: user [ %s ] request websocket' % user.username)
9 years ago
request.user = user
if role == 'admin':
if user.role in ['SU', 'GA']:
return func(request, *args, **kwargs)
logger.debug('Websocket: user [ %s ] is not admin.' % user.username)
9 years ago
else:
return func(request, *args, **kwargs)
else:
logger.debug('Websocket: session expired: %s' % session_key)
9 years ago
try:
request.close()
except AttributeError:
pass
logger.warning('Websocket: Request auth failed.')
9 years ago
return _deco2
9 years ago
9 years ago
return _deco
class MyThread(threading.Thread):
def __init__(self, *args, **kwargs):
super(MyThread, self).__init__(*args, **kwargs)
def run(self):
try:
super(MyThread, self).run()
except WebSocketClosedError:
pass
class EventHandler(ProcessEvent):
def __init__(self, client=None):
self.client = client
def process_IN_MODIFY(self, event):
9 years ago
self.client.write_message(f.read().decode('utf-8', 'replace'))
9 years ago
def file_monitor(path='.', client=None):
wm = WatchManager()
mask = IN_DELETE | IN_CREATE | IN_MODIFY
notifier = AsyncNotifier(wm, EventHandler(client))
wm.add_watch(path, mask, auto_add=True, rec=True)
if not os.path.isfile(path):
logger.debug("File %s does not exist." % path)
9 years ago
sys.exit(3)
else:
logger.debug("Now starting monitor file %s." % path)
9 years ago
global f
f = open(path, 'r')
st_size = os.stat(path)[6]
f.seek(st_size)
while True:
try:
notifier.process_events()
if notifier.check_events():
notifier.read_events()
except KeyboardInterrupt:
print "keyboard Interrupt."
notifier.stop()
break
class MonitorHandler(tornado.websocket.WebSocketHandler):
clients = []
threads = []
def __init__(self, *args, **kwargs):
self.file_path = None
super(self.__class__, self).__init__(*args, **kwargs)
def check_origin(self, origin):
return True
@django_request_support
9 years ago
@require_auth('admin')
def open(self):
# 获取监控的path
self.file_path = self.get_argument('file_path', '')
MonitorHandler.clients.append(self)
thread = MyThread(target=file_monitor, args=('%s.log' % self.file_path, self))
MonitorHandler.threads.append(thread)
self.stream.set_nodelay(True)
try:
for t in MonitorHandler.threads:
if t.is_alive():
continue
t.setDaemon(True)
t.start()
except WebSocketClosedError:
client_index = MonitorHandler.clients.index(self)
MonitorHandler.threads[client_index].stop()
MonitorHandler.clients.remove(self)
MonitorHandler.threads.remove(MonitorHandler.threads[client_index])
logger.debug("Websocket: Monitor client num: %s, thread num: %s" % (len(MonitorHandler.clients),
len(MonitorHandler.threads)))
9 years ago
def on_message(self, message):
# 监控日志,发生变动发向客户端
pass
def on_close(self):
# 客户端主动关闭
# self.close()
logger.debug("Websocket: Monitor client close request")
9 years ago
try:
client_index = MonitorHandler.clients.index(self)
MonitorHandler.clients.remove(self)
MonitorHandler.threads.remove(MonitorHandler.threads[client_index])
except ValueError:
pass
class WebTty(Tty):
def __init__(self, *args, **kwargs):
super(WebTty, self).__init__(*args, **kwargs)
self.ws = None
self.data = ''
self.input_mode = False
class WebTerminalKillHandler(tornado.web.RequestHandler):
@django_request_support
9 years ago
@require_auth('admin')
def get(self):
ws_id = self.get_argument('id')
Log.objects.filter(id=ws_id).update(is_finished=True)
for ws in WebTerminalHandler.clients:
if ws.id == int(ws_id):
logger.debug("Kill log id %s" % ws_id)
9 years ago
ws.log.save()
ws.close()
logger.debug('Websocket: web terminal client num: %s' % len(WebTerminalHandler.clients))
9 years ago
class ExecHandler(tornado.websocket.WebSocketHandler):
clients = []
tasks = []
def __init__(self, *args, **kwargs):
self.id = 0
self.user = None
self.role = None
self.runner = None
self.assets = []
self.perm = {}
self.remote_ip = ''
super(ExecHandler, self).__init__(*args, **kwargs)
def check_origin(self, origin):
return True
@django_request_support
9 years ago
@require_auth('user')
def open(self):
logger.debug('Websocket: Open exec request')
9 years ago
role_name = self.get_argument('role', 'sb')
self.remote_ip = self.request.headers.get("X-Real-IP")
if not self.remote_ip:
self.remote_ip = self.request.remote_ip
9 years ago
logger.debug('Web执行命令: 请求系统用户 %s' % role_name)
self.role = get_object(PermRole, name=role_name)
self.perm = get_group_user_perm(self.user)
roles = self.perm.get('role').keys()
if self.role not in roles:
self.write_message('No perm that role %s' % role_name)
self.close()
self.assets = self.perm.get('role').get(self.role).get('asset')
res = gen_resource({'user': self.user, 'asset': self.assets, 'role': self.role})
self.runner = MyRunner(res)
message = '有权限的主机: ' + ', '.join([asset.hostname for asset in self.assets])
self.__class__.clients.append(self)
self.write_message(message)
def on_message(self, message):
data = json.loads(message)
pattern = data.get('pattern', '')
self.command = data.get('command', '')
self.asset_name_str = ''
if pattern and self.command:
9 years ago
for inv in self.runner.inventory.get_hosts(pattern=pattern):
self.asset_name_str += '%s ' % inv.name
self.write_message('匹配主机: ' + self.asset_name_str)
self.write_message('<span style="color: yellow">Ansible> %s</span>\n\n' % self.command)
self.__class__.tasks.append(MyThread(target=self.run_cmd, args=(self.command, pattern)))
9 years ago
for t in self.__class__.tasks:
if t.is_alive():
continue
try:
t.setDaemon(True)
t.start()
except RuntimeError:
pass
def run_cmd(self, command, pattern):
self.runner.run('shell', command, pattern=pattern)
ExecLog(host=self.asset_name_str, cmd=self.command, user=self.user.username,
remote_ip=self.remote_ip, result=self.runner.results).save()
9 years ago
newline_pattern = re.compile(r'\n')
for k, v in self.runner.results.items():
for host, output in v.items():
output = newline_pattern.sub('<br />', output)
if k == 'ok':
header = "<span style='color: green'>[ %s => %s]</span>\n" % (host, 'Ok')
else:
header = "<span style='color: red'>[ %s => %s]</span>\n" % (host, 'failed')
self.write_message(header)
self.write_message(output)
self.write_message('\n~o~ Task finished ~o~\n')
def on_close(self):
logger.debug('关闭web_exec请求')
9 years ago
class WebTerminalHandler(tornado.websocket.WebSocketHandler):
clients = []
tasks = []
def __init__(self, *args, **kwargs):
self.term = None
self.log_file_f = None
self.log_time_f = None
self.log = None
self.id = 0
self.user = None
self.ssh = None
self.channel = None
super(WebTerminalHandler, self).__init__(*args, **kwargs)
def check_origin(self, origin):
return True
@django_request_support
9 years ago
@require_auth('user')
def open(self):
logger.debug('Websocket: Open request')
9 years ago
role_name = self.get_argument('role', 'sb')
asset_id = self.get_argument('id', 9999)
asset = get_object(Asset, id=asset_id)
9 years ago
self.termlog = TermLogRecorder(User.objects.get(id=self.user_id))
9 years ago
if asset:
roles = user_have_perm(self.user, asset)
logger.debug(roles)
logger.debug('系统用户: %s' % role_name)
9 years ago
login_role = ''
for role in roles:
if role.name == role_name:
login_role = role
break
if not login_role:
logger.warning('Websocket: Not that Role %s for Host: %s User: %s ' % (role_name, asset.hostname,
self.user.username))
9 years ago
self.close()
return
else:
logger.warning('Websocket: No that Host: %s User: %s ' % (asset_id, self.user.username))
9 years ago
self.close()
return
logger.debug('Websocket: request web terminal Host: %s User: %s Role: %s' % (asset.hostname, self.user.username,
login_role.name))
9 years ago
self.term = WebTty(self.user, asset, login_role, login_type='web')
# self.term.remote_ip = self.request.remote_ip
9 years ago
self.term.remote_ip = self.request.headers.get("X-Real-IP")
if not self.term.remote_ip:
self.term.remote_ip = self.request.remote_ip
9 years ago
self.ssh = self.term.get_connection()
9 years ago
self.channel = self.ssh.invoke_shell(term='xterm')
9 years ago
WebTerminalHandler.tasks.append(MyThread(target=self.forward_outbound))
WebTerminalHandler.clients.append(self)
for t in WebTerminalHandler.tasks:
if t.is_alive():
continue
try:
t.setDaemon(True)
t.start()
except RuntimeError:
pass
def on_message(self, message):
jsondata = json.loads(message)
if not jsondata:
9 years ago
return
if 'resize' in jsondata.get('data'):
9 years ago
self.termlog.write(message)
self.channel.resize_pty(
9 years ago
width=jsondata.get('data').get('resize').get('cols', 80),
height=jsondata.get('data').get('resize').get('rows', 24)
)
elif jsondata.get('data'):
9 years ago
self.termlog.recoder = True
9 years ago
self.term.input_mode = True
if str(jsondata['data']) in ['\r', '\n', '\r\n']:
9 years ago
if self.term.vim_flag:
match = re.compile(r'\x1b\[\?1049', re.X).findall(self.term.vim_data)
9 years ago
if match:
if self.term.vim_end_flag or len(match) == 2:
self.term.vim_flag = False
self.term.vim_end_flag = False
else:
self.term.vim_end_flag = True
else:
result = self.term.deal_command(self.term.data)[0:200]
if len(result) > 0:
TtyLog(log=self.log, datetime=datetime.datetime.now(), cmd=result).save()
9 years ago
self.term.vim_data = ''
self.term.data = ''
self.term.input_mode = False
self.channel.send(jsondata['data'])
else:
pass
9 years ago
def on_close(self):
logger.debug('Websocket: Close request')
9 years ago
print self.termlog.CMD
self.termlog.save()
9 years ago
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()
9 years ago
self.log.filename = self.termlog.filename
9 years ago
self.log.save()
self.log_time_f.close()
self.ssh.close()
self.close()
except AttributeError:
pass
def forward_outbound(self):
self.log_file_f, self.log_time_f, self.log = self.term.get_log()
self.id = self.log.id
self.termlog.setid(self.id)
9 years ago
try:
data = ''
pre_timestamp = time.time()
while True:
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
if self.term.vim_flag:
self.term.vim_data += recv
try:
self.write_message(data.decode('utf-8', 'replace'))
9 years ago
self.termlog.write(data)
self.termlog.recoder = False
9 years ago
now_timestamp = time.time()
9 years ago
self.log_time_f.write('%s %s\n' % (round(now_timestamp - pre_timestamp, 4), len(data)))
9 years ago
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.data += data
data = ''
except UnicodeDecodeError:
pass
except IndexError:
pass
9 years ago
# class MonitorHandler(WebTerminalHandler):
# @django_request_support
# @require_auth('user')
# def open(self):
# try:
# self.returnlog = TermLogRecorder.loglist[self.get_argument('id')]
# self.returnlog.write_message = self.write_message
# except:
# self.write_message('Log is None')
# self.close()
#
# def on_message(self, message):
# pass
#
# def on_close(self):
# self.close()
9 years ago
class Application(tornado.web.Application):
def __init__(self):
handlers = [
(r'/monitor', MonitorHandler),
(r'/terminal', WebTerminalHandler),
(r'/kill', WebTerminalKillHandler),
(r'/exec', ExecHandler),
]
setting = {
'cookie_secret': 'DFksdfsasdfkasdfFKwlwfsdfsa1204mx',
'template_path': os.path.join(os.path.dirname(__file__), 'templates'),
'static_path': os.path.join(os.path.dirname(__file__), 'static'),
'debug': False,
}
tornado.web.Application.__init__(self, handlers, **setting)
def main():
from django.core.wsgi import get_wsgi_application
import tornado.wsgi
wsgi_app = get_wsgi_application()
container = tornado.wsgi.WSGIContainer(wsgi_app)
setting = {
'cookie_secret': 'DFksdfsasdfkasdfFKwlwfsdfsa1204mx',
'template_path': os.path.join(os.path.dirname(__file__), 'templates'),
'static_path': os.path.join(os.path.dirname(__file__), 'static'),
'debug': False,
}
tornado_app = tornado.web.Application(
[
(r'/ws/monitor', MonitorHandler),
(r'/ws/terminal', WebTerminalHandler),
(r'/kill', WebTerminalKillHandler),
9 years ago
(r'/ws/exec', ExecHandler),
(r"/static/(.*)", tornado.web.StaticFileHandler,
dict(path=os.path.join(os.path.dirname(__file__), "static"))),
('.*', tornado.web.FallbackHandler, dict(fallback=container)),
], **setting)
server = tornado.httpserver.HTTPServer(tornado_app)
server.listen(options.port)
9 years ago
tornado.ioloop.IOLoop.instance().start()
9 years ago
if __name__ == '__main__':
# tornado.options.parse_command_line()
# app = Application()
# server = tornado.httpserver.HTTPServer(app)
# server.bind(options.port, options.host)
# #server.listen(options.port)
# server.start(num_processes=5)
# tornado.ioloop.IOLoop.instance().start()
print "Run server on %s:%s" % (options.host, options.port)
main()