diff --git a/connect.py b/connect.py index 9cfc7e063..49cdb4fec 100644 --- a/connect.py +++ b/connect.py @@ -14,21 +14,25 @@ import getpass import readline import django import paramiko +import errno import struct, fcntl, signal, socket, select from io import open as copen +import uuid os.environ['DJANGO_SETTINGS_MODULE'] = 'jumpserver.settings' if django.get_version() != '1.6': django.setup() from django.contrib.sessions.models import Session -from jumpserver.api import ServerError, User, Asset, PermRole, AssetGroup, get_object, mkdir, get_asset_info, get_role -from jumpserver.api import logger, Log, TtyLog, get_role_key, CRYPTOR -from jperm.perm_api import gen_resource, get_group_asset_perm, get_group_user_perm, user_have_perm +from jumpserver.api import ServerError, User, Asset, PermRole, AssetGroup, get_object, mkdir, get_asset_info +from jumpserver.api import logger, Log, TtyLog, get_role_key, CRYPTOR, bash, get_tmp_dir +from jperm.perm_api import gen_resource, get_group_asset_perm, get_group_user_perm, user_have_perm, PermRole from jumpserver.settings import LOG_DIR -from jperm.ansible_api import Command -from jlog.log_api import escapeString +from jperm.ansible_api import MyRunner +# from jlog.log_api import escapeString +from jlog.models import ExecLog, FileLog login_user = get_object(User, username=getpass.getuser()) +remote_ip = os.popen("who -m | awk '{ print $5 }'").read().strip('()\n') try: import termios @@ -46,8 +50,11 @@ def color_print(msg, color='red', exits=False): """ color_msg = {'blue': '\033[1;36m%s\033[0m', 'green': '\033[1;32m%s\033[0m', - 'red': '\033[1;31m%s\033[0m'} - msg = color_msg.get(color, 'blue') % msg + 'yellow': '\033[1;33m%s\033[0m', + 'red': '\033[1;31m%s\033[0m', + 'title': '\033[30;42m%s\033[0m', + 'info': '\033[32m%s\033[0m'} + msg = color_msg.get(color, 'red') % msg print msg if exits: time.sleep(2) @@ -66,19 +73,18 @@ class Tty(object): A virtual tty class 一个虚拟终端类,实现连接ssh和记录日志,基类 """ - def __init__(self, user, asset, role): + def __init__(self, user, asset, role, login_type='ssh'): self.username = user.username self.asset_name = asset.hostname self.ip = None self.port = 22 + self.ssh = None self.channel = None self.asset = asset self.user = user self.role = role - self.ssh = None self.remote_ip = '' - self.connect_info = None - self.login_type = 'ssh' + self.login_type = login_type self.vim_flag = False self.ps1_pattern = re.compile('\[.*@.*\][\$#]') self.vim_data = '' @@ -101,6 +107,41 @@ class Tty(object): cmd_str = patch_char.sub('', cmd_str.rstrip()) return cmd_str + @staticmethod + def deal_backspace(match_str, result_command, pattern_str, backspace_num): + ''' + 处理删除确认键 + ''' + if backspace_num > 0: + if backspace_num > len(result_command): + result_command += pattern_str + result_command = result_command[0:-backspace_num] + else: + result_command = result_command[0:-backspace_num] + result_command += pattern_str + del_len = len(match_str)-3 + if del_len > 0: + result_command = result_command[0:-del_len] + return result_command, len(match_str) + + @staticmethod + def deal_replace_char(match_str,result_command,backspace_num): + ''' + 处理替换命令 + ''' + str_lists = re.findall(r'(?<=\x1b\[1@)\w',match_str) + tmp_str =''.join(str_lists) + result_command_list = list(result_command) + if len(tmp_str) > 1: + result_command_list[-backspace_num:-(backspace_num-len(tmp_str))] = tmp_str + elif len(tmp_str) > 0: + if result_command_list[-backspace_num] == ' ': + result_command_list.insert(-backspace_num, tmp_str) + else: + result_command_list[-backspace_num] = tmp_str + result_command = ''.join(result_command_list) + return result_command, len(match_str) + def remove_control_char(self, result_command): """ 处理日志特殊字符 @@ -127,10 +168,7 @@ class Tty(object): """ 处理命令中特殊字符 """ - str_r = re.sub('\x07', '', str_r) # 删除响铃 - patch_char = re.compile('\x08\x1b\[C') # 删除方向左右一起的按键 - while patch_char.search(str_r): - str_r = patch_char.sub('', str_r.rstrip()) + str_r = self.remove_obstruct_char(str_r) result_command = '' # 最后的结果 backspace_num = 0 # 光标移动的个数 @@ -139,31 +177,21 @@ class Tty(object): while str_r: tmp = re.match(r'\s*\w+\s*', str_r) if tmp: + str_r = str_r[len(str(tmp.group(0))):] if reach_backspace_flag: pattern_str += str(tmp.group(0)) - str_r = str_r[len(str(tmp.group(0))):] continue else: result_command += str(tmp.group(0)) - str_r = str_r[len(str(tmp.group(0))):] continue tmp = re.match(r'\x1b\[K[\x08]*', str_r) if tmp: - if backspace_num > 0: - if backspace_num > len(result_command): - result_command += pattern_str - result_command = result_command[0:-backspace_num] - else: - result_command = result_command[0:-backspace_num] - result_command += pattern_str - del_len = len(str(tmp.group(0)))-3 - if del_len > 0: - result_command = result_command[0:-del_len] + result_command, del_len = self.deal_backspace(str(tmp.group(0)), result_command, pattern_str, backspace_num) reach_backspace_flag = False backspace_num = 0 pattern_str = '' - str_r = str_r[len(str(tmp.group(0))):] + str_r = str_r[del_len:] continue tmp = re.match(r'\x08+', str_r) @@ -179,7 +207,14 @@ class Tty(object): continue else: break - + + tmp = re.match(r'(\x1b\[1@\w)+', str_r) #处理替换的命令 + if tmp: + result_command,del_len = self.deal_replace_char(str(tmp.group(0)), result_command, backspace_num) + str_r = str_r[del_len:] + backspace_num = 0 + continue + if reach_backspace_flag: pattern_str += str_r[0] else: @@ -189,22 +224,8 @@ class Tty(object): if backspace_num > 0: result_command = result_command[0:-backspace_num] + pattern_str - 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] | (?:\x1b\]0.*) | \[.*@.*\][\$#] | (.*mysql>.*) #匹配 所有控制字符 - """, re.X) - result_command = control_char.sub('', result_command.strip()) - if not self.vim_flag: - if result_command.startswith('vi') or result_command.startswith('fg'): - self.vim_flag = True - return result_command.decode('utf8', "ignore") - else: - return '' + result_command = self.remove_control_char(result_command) + return result_command def get_log(self): """ @@ -223,29 +244,26 @@ class Tty(object): mkdir(today_connect_log_dir, mode=0777) except OSError: logger.debug('创建目录 %s 失败,请修改%s目录权限' % (today_connect_log_dir, tty_log_dir)) - raise ServerError('Create %s failed, Please modify %s permission.' % (today_connect_log_dir, tty_log_dir)) + raise ServerError('创建目录 %s 失败,请修改%s目录权限' % (today_connect_log_dir, tty_log_dir)) try: - # log_file_f = copen(log_file_path + '.log', mode='at', encoding='utf-8', errors='replace') - # log_time_f = copen(log_file_path + '.time', mode='at', encoding='utf-8', errors='replace') log_file_f = open(log_file_path + '.log', 'a') log_time_f = open(log_file_path + '.time', 'a') except IOError: logger.debug('创建tty日志文件失败, 请修改目录%s权限' % today_connect_log_dir) - raise ServerError('Create logfile failed, Please modify %s permission.' % today_connect_log_dir) + raise ServerError('创建tty日志文件失败, 请修改目录%s权限' % today_connect_log_dir) if self.login_type == 'ssh': # 如果是ssh连接过来,记录connect.py的pid,web terminal记录为日志的id pid = os.getpid() - self.remote_ip = os.popen("who -m | awk '{ print $5 }'").read().strip('()\n') # 获取远端IP + self.remote_ip = remote_ip # 获取远端IP else: pid = 0 log = Log(user=self.username, host=self.asset_name, remote_ip=self.remote_ip, login_type=self.login_type, log_path=log_file_path, start_time=date_today, pid=pid) - log.save() if self.login_type == 'web': - log.pid = log.id + log.pid = log.id # 设置log id为websocket的id, 然后kill时干掉websocket log.save() log_file_f.write('Start at %s\r\n' % datetime.datetime.now()) @@ -256,17 +274,13 @@ class Tty(object): 获取需要登陆的主机的信息和映射用户的账号密码 """ asset_info = get_asset_info(self.asset) - role_key = get_role_key(self.user, self.role) + role_key = get_role_key(self.user, self.role) # 获取角色的key,因为ansible需要权限是600,所以统一生成用户_角色key role_pass = CRYPTOR.decrypt(self.role.password) - self.connect_info = {'user': self.user, 'asset': self.asset, 'ip': asset_info.get('ip'), - 'port': int(asset_info.get('port')), 'role_name': self.role.name, - 'role_pass': role_pass, 'role_key': role_key} - logger.debug("Connect: Host: %s Port: %s User: %s Pass: %s Key: %s" % (asset_info.get('ip'), - asset_info.get('port'), - self.role.name, - role_pass, - role_key)) - return self.connect_info + connect_info = {'user': self.user, 'asset': self.asset, 'ip': asset_info.get('ip'), + 'port': int(asset_info.get('port')), 'role_name': self.role.name, + 'role_pass': role_pass, 'role_key': role_key} + logger.debug(connect_info) + return connect_info def get_connection(self): """ @@ -285,18 +299,19 @@ class Tty(object): ssh.connect(connect_info.get('ip'), port=connect_info.get('port'), username=connect_info.get('role_name'), + password=connect_info.get('role_pass'), key_filename=role_key, look_for_keys=False) - self.ssh = ssh return ssh except (paramiko.ssh_exception.AuthenticationException, paramiko.ssh_exception.SSHException): - logger.warning('Use ssh key %s Failed.' % role_key) + logger.warning(u'使用ssh key %s 失败, 尝试只使用密码' % role_key) pass ssh.connect(connect_info.get('ip'), port=connect_info.get('port'), username=connect_info.get('role_name'), password=connect_info.get('role_pass'), + allow_agent=False, look_for_keys=False) except paramiko.ssh_exception.AuthenticationException, paramiko.ssh_exception.SSHException: @@ -357,18 +372,30 @@ class SshTty(Tty): while True: try: r, w, e = select.select([self.channel, sys.stdin], [], []) + flag = fcntl.fcntl(sys.stdin, fcntl.F_GETFL, 0) + fcntl.fcntl(sys.stdin.fileno(), fcntl.F_SETFL, flag|os.O_NONBLOCK) except Exception: pass if self.channel in r: try: - x = self.channel.recv(1024) + x = self.channel.recv(10240) if len(x) == 0: break if self.vim_flag: self.vim_data += x - sys.stdout.write(x) - sys.stdout.flush() + index = 0 + len_x = len(x) + while index < len_x: + try: + n = os.write(sys.stdout.fileno(), x[index:]) + sys.stdout.flush() + index += n + except OSError as msg: + if msg.errno == errno.EAGAIN: + continue + #sys.stdout.write(x) + #sys.stdout.flush() now_timestamp = time.time() log_time_f.write('%s %s\n' % (round(now_timestamp-pre_timestamp, 4), len(x))) log_time_f.flush() @@ -384,7 +411,7 @@ class SshTty(Tty): pass if sys.stdin in r: - x = os.read(sys.stdin.fileno(), 1) + x = os.read(sys.stdin.fileno(), 4096) input_mode = True if str(x) in ['\r', '\n', '\r\n']: if self.vim_flag: @@ -420,34 +447,25 @@ class SshTty(Tty): Connect server. 连接服务器 """ - ps1 = "PS1='[\u@%s \W]\$ '\n" % self.ip - login_msg = "clear;echo -e '\\033[32mLogin %s done. Enjoy it.\\033[0m'\n" % self.ip - # 发起ssh连接请求 Make a ssh connection ssh = self.get_connection() + + transport = ssh.get_transport() + transport.set_keepalive(30) + transport.use_compression(True) # 获取连接的隧道并设置窗口大小 Make a channel and set windows size global channel win_size = self.get_win_size() - self.channel = channel = ssh.invoke_shell(height=win_size[0], width=win_size[1], term='xterm') + #self.channel = channel = ssh.invoke_shell(height=win_size[0], width=win_size[1], term='xterm') + self.channel = channel = transport.open_session() + channel.get_pty(term='xterm', height=win_size[0], width=win_size[1]) + channel.invoke_shell() try: signal.signal(signal.SIGWINCH, self.set_win_size) except: pass - - # 设置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) - #print 'ok'+tmp+'ok' - # SSH_TTY = re.search(r'(?<=/dev/).*', tmp).group().strip() - # SSH_TTY = '' - # channel.send('clear\n') - # Make ssh interactive tunnel + self.posix_shell() # Shutdown channel socket @@ -456,6 +474,9 @@ class SshTty(Tty): class Nav(object): + """ + 导航提示类 + """ def __init__(self, user): self.user = user self.search_result = {} @@ -467,24 +488,17 @@ class Nav(object): Print prompt 打印提示导航 """ - msg = """\n\033[1;32m### Welcome To Use JumpServer, A Open Source System . ### \033[0m - 1) Type \033[32mID\033[0m To Login. - 2) Type \033[32m/\033[0m + \033[32mIP, Host Name, Host Alias or Comments \033[0mTo Search. - 3) Type \033[32mP/p\033[0m To Print The Servers You Available. - 4) Type \033[32mG/g\033[0m To Print The Server Groups You Available. - 5) Type \033[32mG/g\033[0m\033[0m + \033[32mGroup ID\033[0m To Print The Server Group You Available. - 6) Type \033[32mE/e\033[0m To Execute Command On Several Servers. - 7) Type \033[32mQ/q\033[0m To Quit. - """ + msg = """\n\033[1;32m### 欢迎使用Jumpserver开源跳板机系统 ### \033[0m - msg = """\n\033[1;32m### 欢迎使用Jumpserver开源跳板机 ### \033[0m 1) 输入 \033[32mID\033[0m 直接登录. - 2) 输入 \033[32m/\033[0m + \033[32mIP, 主机名, 主机别名 or 备注 \033[0m搜索. + 2) 输入 \033[32m/\033[0m + \033[32mIP, 主机名 or 备注 \033[0m搜索. 3) 输入 \033[32mP/p\033[0m 显示您有权限的主机. 4) 输入 \033[32mG/g\033[0m 显示您有权限的主机组. 5) 输入 \033[32mG/g\033[0m\033[0m + \033[32m组ID\033[0m 显示该组下主机. 6) 输入 \033[32mE/e\033[0m 批量执行命令. - 7) 输入 \033[32mQ/q\033[0m 退出. + 7) 输入 \033[32mU/u\033[0m 批量上传文件. + 8) 输入 \033[32mD/d\033[0m 批量下载文件. + 9) 输入 \033[32mQ/q\033[0m 退出. """ print textwrap.dedent(msg) @@ -512,17 +526,14 @@ class Nav(object): user_asset_search = user_asset_all self.search_result = dict(zip(range(len(user_asset_search)), user_asset_search)) - print '\033[32m[%-3s] %-15s %-15s %-5s %-10s %s \033[0m' % ('ID', 'AssetName', 'IP', 'Port', 'Role', 'Comment') + color_print('[%-3s] %-12s %-15s %-5s %-10s %s' % ('ID', u'主机名', 'IP', u'端口', u'角色', u'备注'), 'title') for index, asset in self.search_result.items(): # 获取该资产信息 asset_info = get_asset_info(asset) # 获取该资产包含的角色 role = [str(role.name) for role in self.user_perm.get('asset').get(asset).get('role')] - if asset.comment: - print '[%-3s] %-15s %-15s %-5s %-10s %s' % (index, asset.hostname, asset.ip, asset_info.get('port'), - role, asset.comment) - else: - print '[%-3s] %-15s %-15s %-5s %-10s' % (index, asset.hostname, asset.ip, asset_info.get('port'), role) + print '[%-3s] %-15s %-15s %-5s %-10s %s' % (index, asset.hostname, asset.ip, asset_info.get('port'), + role, asset.comment) print def print_asset_group(self): @@ -530,44 +541,11 @@ class Nav(object): 打印用户授权的资产组 """ user_asset_group_all = get_group_user_perm(self.user).get('asset_group', []) - - print '\033[32m[%-3s] %-15s %s \033[0m' % ('ID', 'GroupName', 'Comment') + color_print('[%-3s] %-20s %s' % ('ID', '组名', '备注'), 'title') for asset_group in user_asset_group_all: - if asset_group.comment: - print '[%-3s] %-15s %s' % (asset_group.id, asset_group.name, asset_group.comment) - else: - print '[%-3s] %-15s' % (asset_group.id, asset_group.name) + print '[%-3s] %-15s %s' % (asset_group.id, asset_group.name, asset_group.comment) print - def get_exec_log(self, assets_name_str): - exec_log_dir = os.path.join(LOG_DIR, 'exec') - date_today = datetime.datetime.now() - date_start = date_today.strftime('%Y%m%d') - time_start = date_today.strftime('%H%M%S') - today_connect_log_dir = os.path.join(exec_log_dir, date_start) - log_file_path = os.path.join(today_connect_log_dir, '%s_%s' % (self.user.username, time_start)) - - try: - mkdir(os.path.dirname(today_connect_log_dir), mode=0777) - mkdir(today_connect_log_dir, mode=0777) - except OSError: - logger.debug('创建目录 %s 失败,请修改%s目录权限' % (today_connect_log_dir, exec_log_dir)) - raise ServerError('Create %s failed, Please modify %s permission.' % (today_connect_log_dir, exec_log_dir)) - - try: - log_file_f = open(log_file_path + '.log', 'a') - log_file_f.write('Start at %s\r\n' % datetime.datetime.now()) - log_time_f = open(log_file_path + '.time', 'a') - except IOError: - logger.debug('创建tty日志文件失败, 请修改目录%s权限' % today_connect_log_dir) - raise ServerError('Create logfile failed, Please modify %s permission.' % today_connect_log_dir) - - remote_ip = os.popen("who -m | awk '{ print $5 }'").read().strip('()\n') - log = Log(user=self.user.username, host=assets_name_str, remote_ip=remote_ip, login_type='exec', - log_path=log_file_path, start_time=datetime.datetime.now(), pid=os.getpid()) - log.save() - return log_file_f, log_time_f, log - def exec_cmd(self): """ 批量执行命令 @@ -575,98 +553,173 @@ class Nav(object): while True: if not self.user_perm: self.user_perm = get_group_user_perm(self.user) - print '\033[32m[%-2s] %-15s \033[0m' % ('ID', '角色') + roles = self.user_perm.get('role').keys() - role_check = dict(zip(range(len(roles)), roles)) + if len(roles) > 1: # 授权角色数大于1 + color_print('[%-2s] %-15s' % ('ID', '角色'), 'info') + role_check = dict(zip(range(len(roles)), roles)) - for i, r in role_check.items(): - print '[%-2s] %-15s' % (i, r.name) - print - print "请输入运行命令角色的ID, q退出" + for i, r in role_check.items(): + print '[%-2s] %-15s' % (i, r.name) + print + print "请输入运行命令角色的ID, q退出" - try: - role_id = raw_input("\033[1;32mRole>:\033[0m ").strip() - if role_id == 'q': - break + try: + role_id = raw_input("\033[1;32mRole>:\033[0m ").strip() + if role_id == 'q': + break + except (IndexError, ValueError): + color_print('错误输入') else: role = role_check[int(role_id)] - assets = list(self.user_perm.get('role', {}).get(role).get('asset')) - print "该角色有权限的所有主机" - for asset in assets: - print asset.hostname - print - print "请输入主机名、IP或ansile支持的pattern, q退出" - pattern = raw_input("\033[1;32mPattern>:\033[0m ").strip() - if pattern == 'q': - break - else: - res = gen_resource({'user': self.user, 'asset': assets, 'role': role}, perm=self.user_perm) - cmd = Command(res) - logger.debug("批量执行res: %s" % res) - asset_name_str = '' - for inv in cmd.inventory.get_hosts(pattern=pattern): - print inv.name - asset_name_str += inv.name - print - - log_file_f, log_time_f, log = self.get_exec_log(asset_name_str) - pre_timestamp = time.time() - while True: - print "请输入执行的命令, 按q退出" - data = 'ansible> ' - write_log(log_file_f, data) - now_timestamp = time.time() - write_log(log_time_f, '%s %s\n' % (round(now_timestamp-pre_timestamp, 4), len(data))) - pre_timestamp = now_timestamp - command = raw_input("\033[1;32mCmds>:\033[0m ").strip() - data = '%s\r\n' % command - write_log(log_file_f, data) - now_timestamp = time.time() - write_log(log_time_f, '%s %s\n' % (round(now_timestamp-pre_timestamp, 4), len(data))) - pre_timestamp = now_timestamp - TtyLog(log=log, cmd=command, datetime=datetime.datetime.now()).save() - if command == 'q': - log.is_finished = True - log.end_time = datetime.datetime.now() - log.save() - break - result = cmd.run(module_name='shell', command=command, pattern=pattern) - for k, v in result.items(): - if k == 'ok': - for host, output in v.items(): - header = color_print("%s => %s" % (host, 'Ok'), 'green') - print output - output = re.sub(r'[\r\n]', '\r\n', output) - data = '%s\r\n%s\r\n' % (header, output) - now_timestamp = time.time() - write_log(log_file_f, data) - write_log(log_time_f, '%s %s\n' % (round(now_timestamp-pre_timestamp, 4), len(data))) - pre_timestamp = now_timestamp - print - else: - for host, output in v.items(): - header = color_print("%s => %s" % (host, k), 'red') - output = color_print(output, 'red') - output = re.sub(r'[\r\n]', '\r\n', output) - data = '%s\r\n%s\r\n' % (header, output) - now_timestamp = time.time() - write_log(log_file_f, data) - write_log(log_time_f, '%s %s\n' % (round(now_timestamp-pre_timestamp, 4), len(data))) - pre_timestamp = now_timestamp - print - print "=" * 20 - print - - except (IndexError, KeyError): - color_print('ID输入错误') - continue - - except EOFError: - print + elif len(roles) == 1: # 授权角色数为1 + role = roles[0] + assets = list(self.user_perm.get('role', {}).get(role).get('asset')) # 获取该用户,角色授权主机 + print "该角色有权限的所有主机" + for asset in assets: + print ' %s' % asset.hostname + print + print "请输入主机名、IP或ansile支持的pattern, 多个主机:分隔, q退出" + pattern = raw_input("\033[1;32mPattern>:\033[0m ").strip() + if pattern == 'q': break - finally: - log.is_finished = True - log.end_time = datetime.datetime.now() + else: + res = gen_resource({'user': self.user, 'asset': assets, 'role': role}, perm=self.user_perm) + runner = MyRunner(res) + asset_name_str = '' + print "匹配主机:" + for inv in runner.inventory.get_hosts(pattern=pattern): + print ' %s' % inv.name + asset_name_str += '%s ' % inv.name + print + + while True: + print "请输入执行的命令, 按q退出" + command = raw_input("\033[1;32mCmds>:\033[0m ").strip() + if command == 'q': + break + runner.run('shell', command, pattern=pattern) + ExecLog(host=asset_name_str, user=self.user.username, cmd=command, remote_ip=remote_ip, + result=runner.results).save() + for k, v in runner.results.items(): + if k == 'ok': + for host, output in v.items(): + color_print("%s => %s" % (host, 'Ok'), 'green') + print output + print + else: + for host, output in v.items(): + color_print("%s => %s" % (host, k), 'red') + color_print(output, 'red') + print + print "~o~ Task finished ~o~" + print + + def upload(self): + while True: + if not self.user_perm: + self.user_perm = get_group_user_perm(self.user) + try: + print "进入批量上传模式" + print "请输入主机名、IP或ansile支持的pattern, 多个主机:分隔 q退出" + pattern = raw_input("\033[1;32mPattern>:\033[0m ").strip() + if pattern == 'q': + break + else: + assets = self.user_perm.get('asset').keys() + res = gen_resource({'user': self.user, 'asset': assets}, perm=self.user_perm) + runner = MyRunner(res) + asset_name_str = '' + print "匹配主机:" + for inv in runner.inventory.get_hosts(pattern=pattern): + print inv.name + asset_name_str += '%s ' % inv.name + + if not asset_name_str: + color_print('没有匹配主机') + continue + tmp_dir = get_tmp_dir() + logger.debug('Upload tmp dir: %s' % tmp_dir) + os.chdir(tmp_dir) + bash('rz') + filename_str = ' '.join(os.listdir(tmp_dir)) + if not filename_str: + color_print("上传文件为空") + continue + logger.debug('上传文件: %s' % filename_str) + + runner = MyRunner(res) + runner.run('copy', module_args='src=%s dest=%s directory_mode' + % (tmp_dir, tmp_dir), pattern=pattern) + ret = runner.results + FileLog(user=self.user.name, host=asset_name_str, filename=filename_str, + remote_ip=remote_ip, type='upload', result=ret).save() + logger.debug('Upload file: %s' % ret) + if ret.get('failed'): + error = '上传目录: %s \n上传失败: [ %s ] \n上传成功 [ %s ]' % (tmp_dir, + ', '.join(ret.get('failed').keys()), + ', '.join(ret.get('ok').keys())) + color_print(error) + else: + msg = '上传目录: %s \n传送成功 [ %s ]' % (tmp_dir, ', '.join(ret.get('ok').keys())) + color_print(msg, 'green') + print + + except IndexError: + pass + + def download(self): + while True: + if not self.user_perm: + self.user_perm = get_group_user_perm(self.user) + try: + print "进入批量下载模式" + print "请输入主机名、IP或ansile支持的pattern, 多个主机:分隔,q退出" + pattern = raw_input("\033[1;32mPattern>:\033[0m ").strip() + if pattern == 'q': + break + else: + assets = self.user_perm.get('asset').keys() + res = gen_resource({'user': self.user, 'asset': assets}, perm=self.user_perm) + runner = MyRunner(res) + asset_name_str = '' + print "匹配用户:\n" + for inv in runner.inventory.get_hosts(pattern=pattern): + asset_name_str += '%s ' % inv.name + print ' %s' % inv.name + if not asset_name_str: + color_print('没有匹配主机') + continue + print + while True: + tmp_dir = get_tmp_dir() + logger.debug('Download tmp dir: %s' % tmp_dir) + print "请输入文件路径(不支持目录)" + file_path = raw_input("\033[1;32mPath>:\033[0m ").strip() + if file_path == 'q': + break + runner.run('fetch', module_args='src=%s dest=%s' % (file_path, tmp_dir), pattern=pattern) + ret = runner.results + FileLog(user=self.user.name, host=asset_name_str, filename=file_path, type='download', + remote_ip=remote_ip, result=ret).save() + logger.debug('Download file result: %s' % ret) + os.chdir('/tmp') + tmp_dir_name = os.path.basename(tmp_dir) + if not os.listdir(tmp_dir): + color_print('下载全部失败') + continue + bash('tar czf %s.tar.gz %s && sz %s.tar.gz' % (tmp_dir, tmp_dir_name, tmp_dir)) + + if ret.get('failed'): + error = '文件名称: %s \n下载失败: [ %s ] \n下载成功 [ %s ]' % \ + ('%s.tar.gz' % tmp_dir_name, ', '.join(ret.get('failed').keys()), ', '.join(ret.get('ok').keys())) + color_print(error) + else: + msg = '文件名称: %s \n下载成功 [ %s ]' % ('%s.tar.gz' % tmp_dir_name, ', '.join(ret.get('ok').keys())) + color_print(msg, 'green') + print + except IndexError: + pass def main(): @@ -701,12 +754,16 @@ def main(): elif option in ['E', 'e']: nav.exec_cmd() continue + elif option in ['U', 'u']: + nav.upload() + elif option in ['D', 'd']: + nav.download() elif option in ['Q', 'q', 'exit']: sys.exit() else: try: asset = nav.search_result[int(option)] - roles = get_role(login_user, asset) + roles = nav.user_perm.get('asset').get(asset).get('role') if len(roles) > 1: role_check = dict(zip(range(len(roles)), roles)) print "\033[32m[ID] 角色\033[0m" @@ -724,7 +781,7 @@ def main(): color_print('请输入正确ID', 'red') continue elif len(roles) == 1: - role = roles[0] + role = list(roles)[0] else: color_print('没有映射用户', 'red') continue diff --git a/docs/AddUserAsset.py b/docs/AddUserAsset.py deleted file mode 100644 index bfe44d27e..000000000 --- a/docs/AddUserAsset.py +++ /dev/null @@ -1,141 +0,0 @@ -#coding:utf-8 -import django -import os -import sys -import random -import datetime - -sys.path.append('../') -os.environ['DJANGO_SETTINGS_MODULE'] = 'jumpserver.settings' -#django.setup() - - -from juser.views import db_add_user, md5_crypt, CRYPTOR, db_add_group -from jasset.models import Asset, IDC, BisGroup -from juser.models import UserGroup, DEPT, User -from jperm.models import CmdGroup -from jlog.models import Log - - -def install(): - IDC.objects.create(name='ALL', comment='ALL') - IDC.objects.create(name='默认', comment='默认') - DEPT.objects.create(name="默认", comment="默认部门") - DEPT.objects.create(name="超管部", comment="超级管理员部门") - dept = DEPT.objects.get(name='超管部') - dept2 = DEPT.objects.get(name='默认') - UserGroup.objects.create(name='ALL', dept=dept, comment='ALL') - UserGroup.objects.create(name='默认', dept=dept, comment='默认') - - BisGroup.objects.create(name='ALL', dept=dept, comment='ALL') - BisGroup.objects.create(name='默认', dept=dept, comment='默认') - - User(id=5000, username="admin", password=md5_crypt('admin'), - name='admin', email='admin@jumpserver.org', role='SU', is_active=True, dept=dept).save() - User(id=5001, username="group_admin", password=md5_crypt('group_admin'), - name='group_admin', email='group_admin@jumpserver.org', role='DA', is_active=True, dept=dept2).save() - - -def test_add_idc(): - for i in range(1, 20): - name = 'IDC' + str(i) - IDC.objects.create(name=name, comment='') - print 'Add: %s' % name - - -def test_add_dept(): - for i in range(1, 100): - name = 'DEPT' + str(i) - print "Add: %s" % name - DEPT.objects.create(name=name, comment=name) - - -def test_add_group(): - dept_all = DEPT.objects.all() - for i in range(1, 100): - name = 'UserGroup' + str(i) - UserGroup.objects.create(name=name, dept=random.choice(dept_all), comment=name) - print 'Add: %s' % name - - -def test_add_cmd_group(): - for i in range(1, 20): - name = 'CMD' + str(i) - cmd = '/sbin/ping%s, /sbin/ifconfig/' % str(i) - CmdGroup.objects.create(name=name, cmd=cmd, comment=name) - print 'Add: %s' % name - - -def test_add_user(): - for i in range(1, 500): - username = "test" + str(i) - dept_all = DEPT.objects.all() - group_all = UserGroup.objects.all() - group_all_id = [group.id for group in group_all] - db_add_user(username=username, - password=md5_crypt(username), - dept=random.choice(dept_all), - name=username, email='%s@jumpserver.org' % username, - groups=[random.choice(group_all_id) for i in range(1, 4)], role='CU', - ssh_key_pwd=CRYPTOR.encrypt(username), - ldap_pwd=CRYPTOR.encrypt(username), - is_active=True, - date_joined=datetime.datetime.now()) - print "Add: %s" % username - - -def test_add_asset_group(): - dept = DEPT.objects.get(name='默认') - for i in range(1, 20): - name = 'AssetGroup' + str(i) - group = BisGroup(name=name, dept=dept, comment=name) - group.save() - print 'Add: %s' % name - - -def test_add_asset(): - idc_all = IDC.objects.all() - test_idc = random.choice(idc_all) - bis_group_all = BisGroup.objects.all() - dept_all = DEPT.objects.all() - for i in range(1, 500): - ip = '192.168.5.' + str(i) - asset = Asset(ip=ip, port=22, login_type='L', idc=test_idc, is_active=True, comment='test') - asset.save() - asset.bis_group = [random.choice(bis_group_all) for i in range(2)] - asset.dept = [random.choice(dept_all) for i in range(2)] - print "Add: %s" % ip - - -def test_add_log(): - li_date = [] - today = datetime.date.today() - oneday = datetime.timedelta(days=1) - for i in range(0, 7): - today = today-oneday - li_date.append(today) - user_list = ['马云', '马化腾', '丁磊', '周鸿祎', '雷军', '柳传志', '陈天桥', '李彦宏', '李开复', '罗永浩'] - for i in range(1, 1000): - user = random.choice(user_list) - ip = random.randint(1, 20) - start_time = random.choice(li_date) - end_time = datetime.datetime.now() - log_path = '/var/log/jumpserver/test.log' - host = '192.168.1.' + str(ip) - Log.objects.create(user=user, host=host, remote_ip='8.8.8.8', dept_name='运维部', log_path=log_path, pid=168, start_time=start_time, - is_finished=1, log_finished=1, end_time=end_time) - - -if __name__ == '__main__': - # install() - # test_add_dept() - # test_add_group() - # test_add_user() - # test_add_idc() - # test_add_asset_group() - test_add_asset() - # test_add_log() - - - - diff --git a/docs/initial_data.yaml b/docs/initial_data.yaml index 32d05f26f..1d82565cb 100644 --- a/docs/initial_data.yaml +++ b/docs/initial_data.yaml @@ -7,22 +7,3 @@ email: admin@jumpserver.org role: SU is_active: 1 -- model: juser.user - pk: 5001 - fields: - username: group_admin - name: group_admin - password: pbkdf2_sha256$20000$ttObUWd15q10$NJoyZf2OZz9oiw2g4j2TkTh9zGgyVDRFdUkhn8X0nB0= - email: group_admin@jumpserver.org - role: GA - is_active: 1 -- model: juser.usergroup - pk: 1 - fields: - name: ALL - comment: ALL -- model: juser.usergroup - pk: 2 - fields: - name: 默认 - comment: 默认 diff --git a/docs/install.py b/docs/install.py deleted file mode 100644 index a032bf327..000000000 --- a/docs/install.py +++ /dev/null @@ -1,135 +0,0 @@ -#coding:utf-8 -import django -import os -import sys -import random -import datetime - -sys.path.append('../') -os.environ['DJANGO_SETTINGS_MODULE'] = 'jumpserver.settings' -#django.setup() - - -from juser.views import db_add_user, md5_crypt, CRYPTOR, db_add_group -from jasset.models import Asset, IDC, BisGroup -from juser.models import UserGroup, DEPT, User -from jasset.views import jasset_group_add -from jperm.models import CmdGroup -from jlog.models import Log - - -def install(): - IDC.objects.create(name='ALL', comment='ALL') - IDC.objects.create(name='默认', comment='默认') - DEPT.objects.create(name="默认", comment="默认部门") - DEPT.objects.create(name="超管部", comment="超级管理员部门") - dept = DEPT.objects.get(name='超管部') - dept2 = DEPT.objects.get(name='默认') - UserGroup.objects.create(name='ALL', dept=dept, comment='ALL') - UserGroup.objects.create(name='默认', dept=dept, comment='默认') - - BisGroup.objects.create(name='ALL', dept=dept, comment='ALL') - BisGroup.objects.create(name='默认', dept=dept, comment='默认') - - User(id=5000, username="admin", password=md5_crypt('admin'), - name='admin', email='admin@jumpserver.org', role='SU', is_active=True, dept=dept).save() - User(id=5001, username="group_admin", password=md5_crypt('group_admin'), - name='group_admin', email='group_admin@jumpserver.org', role='DA', is_active=True, dept=dept2).save() - - -def test_add_idc(): - for i in range(1, 20): - name = 'IDC' + str(i) - IDC.objects.create(name=name, comment='') - print 'Add: %s' % name - - -def test_add_dept(): - for i in range(1, 100): - name = 'DEPT' + str(i) - print "Add: %s" % name - DEPT.objects.create(name=name, comment=name) - - -def test_add_group(): - dept_all = DEPT.objects.all() - for i in range(1, 100): - name = 'UserGroup' + str(i) - UserGroup.objects.create(name=name, dept=random.choice(dept_all), comment=name) - print 'Add: %s' % name - - -def test_add_cmd_group(): - for i in range(1, 20): - name = 'CMD' + str(i) - cmd = '/sbin/ping%s, /sbin/ifconfig/' % str(i) - CmdGroup.objects.create(name=name, cmd=cmd, comment=name) - print 'Add: %s' % name - - -def test_add_user(): - for i in range(1, 500): - username = "test" + str(i) - dept_all = DEPT.objects.all() - group_all = UserGroup.objects.all() - group_all_id = [group.id for group in group_all] - db_add_user(username=username, - password=md5_crypt(username), - dept=random.choice(dept_all), - name=username, email='%s@jumpserver.org' % username, - groups=[random.choice(group_all_id) for i in range(1, 4)], role='CU', - ssh_key_pwd=CRYPTOR.encrypt(username), - ldap_pwd=CRYPTOR.encrypt(username), - is_active=True, - date_joined=datetime.datetime.now()) - print "Add: %s" % username - - -def test_add_asset_group(): - dept = DEPT.objects.get(name='默认') - for i in range(1, 20): - name = 'AssetGroup' + str(i) - group = BisGroup(name=name, dept=dept, comment=name) - group.save() - print 'Add: %s' % name - - -def test_add_asset(): - idc_all = IDC.objects.all() - test_idc = random.choice(idc_all) - bis_group_all = BisGroup.objects.all() - dept_all = DEPT.objects.all() - for i in range(1, 500): - ip = '192.168.1.' + str(i) - asset = Asset(ip=ip, port=22, login_type='L', idc=test_idc, is_active=True, comment='test') - asset.save() - asset.bis_group = [random.choice(bis_group_all) for i in range(2)] - asset.dept = [random.choice(dept_all) for i in range(2)] - print "Add: %s" % ip - - -def test_add_log(): - li_date = [] - today = datetime.date.today() - oneday = datetime.timedelta(days=1) - for i in range(0, 7): - today = today-oneday - li_date.append(today) - user_list = ['马云', '马化腾', '丁磊', '周鸿祎', '雷军', '柳传志', '陈天桥', '李彦宏', '李开复', '罗永浩'] - for i in range(1, 1000): - user = random.choice(user_list) - ip = random.randint(1, 20) - start_time = random.choice(li_date) - end_time = datetime.datetime.now() - log_path = '/var/log/jumpserver/test.log' - host = '192.168.1.' + str(ip) - Log.objects.create(user=user, host=host, log_path=log_path, pid=168, start_time=start_time, - is_finished=1, log_finished=1, end_time=end_time) - - -if __name__ == '__main__': - install() - - - - diff --git a/docs/requirements.txt b/docs/requirements.txt index 28d7b9137..23ab9d3b0 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -1,16 +1,17 @@ -sphinx-me==0.3 +#sphinx-me==0.3 django==1.6 pycrypto==2.6.1 -paramiko==1.15.2 +paramiko==1.16.0 ecdsa==0.13 MySQL-python==1.2.5 -django-uuidfield==0.5.0 -psutil==2.2.1 +#django-uuidfield==0.5.0 +psutil==3.3.0 xlsxwriter==0.7.7 xlrd==0.9.4 -django-bootstrap-form -tornado -ansible -pyinotify -passlib -argparse \ No newline at end of file +django-bootstrap-form==3.2 +tornado==4.3 +ansible==1.9.4 +pyinotify==0.9.6 +passlib==1.6.5 +argparse==1.4.0 +django_crontab==0.6.0 \ No newline at end of file diff --git a/jasset/asset_api.py b/jasset/asset_api.py index 0034f6495..7edb391da 100644 --- a/jasset/asset_api.py +++ b/jasset/asset_api.py @@ -1,4 +1,5 @@ # coding: utf-8 +from __future__ import division import xlrd import xlsxwriter from django.db.models import AutoField @@ -6,6 +7,7 @@ from jumpserver.api import * from jasset.models import ASSET_STATUS, ASSET_TYPE, ASSET_ENV, IDC, AssetRecord from jperm.ansible_api import MyRunner from jperm.perm_api import gen_resource +from jumpserver.templatetags.mytags import get_disk_info def group_add_asset(group, asset_id=None, asset_ip=None): @@ -75,83 +77,6 @@ def db_asset_update(**kwargs): Asset.objects.filter(id=asset_id).update(**kwargs) -# -# -# def batch_host_edit(host_alter_dic, j_user='', j_password=''): -# """ 批量修改主机函数 """ -# j_id, j_ip, j_idc, j_port, j_type, j_group, j_dept, j_active, j_comment = host_alter_dic -# groups, depts = [], [] -# is_active = {u'是': '1', u'否': '2'} -# login_types = {'LDAP': 'L', 'MAP': 'M'} -# a = Asset.objects.get(id=j_id) -# if '...' in j_group[0].split(): -# groups = a.bis_group.all() -# else: -# for group in j_group[0].split(): -# c = BisGroup.objects.get(name=group.strip()) -# groups.append(c) -# -# if '...' in j_dept[0].split(): -# depts = a.dept.all() -# else: -# for d in j_dept[0].split(): -# p = DEPT.objects.get(name=d.strip()) -# depts.append(p) -# -# j_type = login_types[j_type] -# j_idc = IDC.objects.get(name=j_idc) -# if j_type == 'M': -# if a.password != j_password: -# j_password = cryptor.decrypt(j_password) -# a.ip = j_ip -# a.port = j_port -# a.login_type = j_type -# a.idc = j_idc -# a.is_active = j_active -# a.comment = j_comment -# a.username = j_user -# a.password = j_password -# else: -# a.ip = j_ip -# a.port = j_port -# a.idc = j_idc -# a.login_type = j_type -# a.is_active = is_active[j_active] -# a.comment = j_comment -# a.save() -# a.bis_group = groups -# a.dept = depts -# a.save() -# -# -# def db_host_delete(request, host_id): -# """ 删除主机操作 """ -# if is_group_admin(request) and not validate(request, asset=[host_id]): -# return httperror(request, '删除失败, 您无权删除!') -# -# asset = Asset.objects.filter(id=host_id) -# if asset: -# asset.delete() -# else: -# return httperror(request, '删除失败, 没有此主机!') -# -# -# def db_idc_delete(request, idc_id): -# """ 删除IDC操作 """ -# if idc_id == 1: -# return httperror(request, '删除失败, 默认IDC不能删除!') -# -# default_idc = IDC.objects.get(id=1) -# -# idc = IDC.objects.filter(id=idc_id) -# if idc: -# idc_class = idc[0] -# idc_class.asset_set.update(idc=default_idc) -# idc.delete() -# else: -# return httperror(request, '删除失败, 没有这个IDC!') - - def sort_ip_list(ip_list): """ ip地址排序 """ ip_list.sort(key=lambda s: map(int, s.split('.'))) @@ -232,7 +157,7 @@ def db_asset_alert(asset, username, alert_dic): for group_id in value[1]: group_name = AssetGroup.objects.get(id=int(group_id)).name new.append(group_name) - if old == new: + if sorted(old) == sorted(new): continue else: alert_info = [field_name, ','.join(old), ','.join(new)] @@ -274,28 +199,32 @@ def write_excel(asset_all): workbook = xlsxwriter.Workbook('static/files/excels/%s' % file_name) worksheet = workbook.add_worksheet(u'CMDB数据') worksheet.set_first_sheet() - worksheet.set_column('A:Z', 14) - title = [u'主机名', u'IP', u'IDC', u'MAC', u'远控IP', u'CPU', u'内存', u'硬盘', u'操作系统', u'机柜位置', - u'所属主机组', u'机器状态', u'备注'] + worksheet.set_column('A:E', 15) + worksheet.set_column('F:F', 40) + worksheet.set_column('G:Z', 15) + title = [u'主机名', u'IP', u'IDC', u'所属主机组', u'操作系统', u'CPU', u'内存(G)', u'硬盘(G)', + u'机柜位置', u'MAC', u'远控IP', u'机器状态', u'备注'] for asset in asset_all: group_list = [] for p in asset.group.all(): group_list.append(p.name) + disk = get_disk_info(asset.disk) group_all = '/'.join(group_list) status = asset.get_status_display() idc_name = asset.idc.name if asset.idc else u'' - system_type = asset.system_type if asset.idc else u'' - system_version = asset.system_version if asset.idc else u'' + system_type = asset.system_type if asset.system_type else u'' + system_version = asset.system_version if asset.system_version else u'' system_os = unicode(system_type) + unicode(system_version) - alter_dic = [asset.hostname, asset.ip, idc_name, asset.mac, asset.remote_ip, asset.cpu, asset.memory, - asset.disk, system_os, asset.cabinet, group_all, status, - asset.comment] + alter_dic = [asset.hostname, asset.ip, idc_name, group_all, system_os, asset.cpu, asset.memory, + disk, asset.cabinet, asset.mac, asset.remote_ip, status, asset.comment] data.append(alter_dic) format = workbook.add_format() format.set_border(1) format.set_align('center') + format.set_align('vcenter') + format.set_text_wrap() format_title = workbook.add_format() format_title.set_border(1) @@ -384,12 +313,21 @@ def excel_to_db(excel_file): def get_ansible_asset_info(asset_ip, setup_info): - disk_all = setup_info.get("ansible_devices") + print asset_ip disk_need = {} - for disk_name, disk_info in disk_all.iteritems(): - if disk_name.startswith('sd') or disk_name.startswith('hd') or disk_name.startswith('vd'): - disk_need[disk_name] = disk_info.get("size") - + disk_all = setup_info.get("ansible_devices") + if disk_all: + for disk_name, disk_info in disk_all.iteritems(): + print disk_name, disk_info + if disk_name.startswith('sd') or disk_name.startswith('hd') or disk_name.startswith('vd'): + disk_size = disk_info.get("size", '') + if 'M' in disk_size: + disk_format = round(float(disk_size[:-2]) / 1000, 0) + elif 'T' in disk_size: + disk_format = round(float(disk_size[:-2]) * 1000, 0) + else: + disk_format = float(disk_size[:-2]) + disk_need[disk_name] = disk_format all_ip = setup_info.get("ansible_all_ipv4_addresses") other_ip_list = all_ip.remove(asset_ip) if asset_ip in all_ip else [] other_ip = ','.join(other_ip_list) if other_ip_list else '' @@ -401,13 +339,17 @@ def get_ansible_asset_info(asset_ip, setup_info): cpu_cores = setup_info.get("ansible_processor_count") cpu = cpu_type + ' * ' + unicode(cpu_cores) memory = setup_info.get("ansible_memtotal_mb") + try: + memory_format = int(round((int(memory) / 1000), 0)) + except Exception: + memory_format = memory disk = disk_need system_type = setup_info.get("ansible_distribution") system_version = setup_info.get("ansible_distribution_version") + system_arch = setup_info.get("ansible_architecture") # asset_type = setup_info.get("ansible_system") sn = setup_info.get("ansible_product_serial") - asset_info = [other_ip, mac, cpu, memory, disk, sn, system_type, system_version, brand] - + asset_info = [other_ip, mac, cpu, memory_format, disk, sn, system_type, system_version, brand, system_arch] return asset_info @@ -415,6 +357,7 @@ def asset_ansible_update(obj_list, name=''): resource = gen_resource(obj_list) ansible_instance = MyRunner(resource) ansible_asset_info = ansible_instance.run(module_name='setup', pattern='*') + logger.debug('获取硬件信息: %s' % ansible_asset_info) for asset in obj_list: try: setup_info = ansible_asset_info['contacted'][asset.hostname]['ansible_facts'] @@ -422,7 +365,7 @@ def asset_ansible_update(obj_list, name=''): continue else: asset_info = get_ansible_asset_info(asset.ip, setup_info) - other_ip, mac, cpu, memory, disk, sn, system_type, system_version, brand = asset_info + other_ip, mac, cpu, memory, disk, sn, system_type, system_version, brand, system_arch = asset_info asset_dic = {"other_ip": other_ip, "mac": mac, "cpu": cpu, @@ -431,6 +374,7 @@ def asset_ansible_update(obj_list, name=''): "sn": sn, "system_type": system_type, "system_version": system_version, + "system_arch": system_arch, "brand": brand } diff --git a/jasset/forms.py b/jasset/forms.py index 03eca04ed..7f96b30c6 100644 --- a/jasset/forms.py +++ b/jasset/forms.py @@ -12,7 +12,8 @@ class AssetForm(forms.ModelForm): fields = [ "ip", "other_ip", "hostname", "port", "group", "username", "password", "use_default_auth", "idc", "mac", "remote_ip", "brand", "cpu", "memory", "disk", "system_type", "system_version", - "cabinet", "position", "number", "status", "asset_type", "env", "sn", "is_active", "comment" + "cabinet", "position", "number", "status", "asset_type", "env", "sn", "is_active", "comment", + "system_arch" ] diff --git a/jasset/models.py b/jasset/models.py index b3765fbe4..558479111 100644 --- a/jasset/models.py +++ b/jasset/models.py @@ -78,6 +78,7 @@ class Asset(models.Model): disk = models.CharField(max_length=128, blank=True, null=True, verbose_name=u'硬盘') system_type = models.CharField(max_length=32, blank=True, null=True, verbose_name=u"系统类型") system_version = models.CharField(max_length=8, blank=True, null=True, verbose_name=u"系统版本号") + system_arch = models.CharField(max_length=16, blank=True, null=True, verbose_name=u"系统平台") cabinet = models.CharField(max_length=32, blank=True, null=True, verbose_name=u'机柜号') position = models.IntegerField(blank=True, null=True, verbose_name=u'机器位置') number = models.CharField(max_length=32, blank=True, null=True, verbose_name=u'资产编号') diff --git a/jasset/views.py b/jasset/views.py index dff5621d8..0d9e07c82 100644 --- a/jasset/views.py +++ b/jasset/views.py @@ -6,8 +6,7 @@ from jumpserver.api import * from jumpserver.models import Setting from jasset.forms import AssetForm, IdcForm from jasset.models import Asset, IDC, AssetGroup, ASSET_TYPE, ASSET_STATUS -from jperm.ansible_api import Tasks, MyRunner -from jperm.perm_api import gen_resource +from jperm.perm_api import get_group_asset_perm, get_group_user_perm @require_role('admin') @@ -96,7 +95,9 @@ def group_list(request): header_title, path1, path2 = u'查看资产组', u'资产管理', u'查看资产组' keyword = request.GET.get('keyword', '') asset_group_list = AssetGroup.objects.all() - + group_id = request.GET.get('id') + if group_id: + asset_group_list = asset_group_list.filter(id=group_id) if keyword: asset_group_list = asset_group_list.filter(Q(name__contains=keyword) | Q(comment__contains=keyword)) @@ -256,11 +257,13 @@ def asset_list(request): asset list view """ header_title, path1, path2 = u'查看资产', u'资产管理', u'查看资产' + username = request.user.username + user_perm = request.session['role_id'] idc_all = IDC.objects.filter() asset_group_all = AssetGroup.objects.all() asset_types = ASSET_TYPE asset_status = ASSET_STATUS - + asset_id = request.GET.get('id') idc_name = request.GET.get('idc', '') group_name = request.GET.get('group', '') asset_type = request.GET.get('asset_type', '') @@ -279,12 +282,19 @@ def asset_list(request): if idc: asset_find = Asset.objects.filter(idc=idc) else: - asset_find = Asset.objects.all() + if user_perm != 0: + asset_find = Asset.objects.all() + else: + user = get_object(User, username=username) + asset_perm = get_group_user_perm(user) if user else {'asset': ''} + asset_find = asset_perm['asset'].keys() + asset_group_all = list(asset_perm['asset_group']) if idc_name: asset_find = asset_find.filter(idc__name__contains=idc_name) if group_name: + print asset_find, type(asset_find) asset_find = asset_find.filter(group__name__contains=group_name) if asset_type: @@ -293,6 +303,9 @@ def asset_list(request): if status: asset_find = asset_find.filter(status__contains=status) + if asset_id: + asset_find = asset_find.filter(id=asset_id) + if keyword: asset_find = asset_find.filter( Q(hostname__contains=keyword) | @@ -318,7 +331,10 @@ def asset_list(request): smg = u'excel文件已生成,请点击下载!' return my_render('jasset/asset_excel_download.html', locals(), request) assets_list, p, assets, page_range, current_page, show_first, show_end = pages(asset_find, request) - return my_render('jasset/asset_list.html', locals(), request) + if user_perm != 0: + return my_render('jasset/asset_list.html', locals(), request) + else: + return my_render('jasset/asset_cu_list.html', locals(), request) @require_role('admin') @@ -410,6 +426,18 @@ def asset_detail(request): header_title, path1, path2 = u'主机详细信息', u'资产管理', u'主机详情' asset_id = request.GET.get('id', '') asset = get_object(Asset, id=asset_id) + perm_info = get_group_asset_perm(asset) + log = Log.objects.filter(host=asset.hostname) + if perm_info: + user_perm = [] + for perm, value in perm_info.items(): + if perm == 'user': + for user, role_dic in value.items(): + user_perm.append([user, role_dic.get('role', '')]) + elif perm == 'user_group' or perm == 'rule': + user_group_perm = value + print perm_info + asset_record = AssetRecord.objects.filter(asset=asset).order_by('-alert_time') return my_render('jasset/asset_detail.html', locals(), request) diff --git a/jlog/models.py b/jlog/models.py index 2b43d3e52..c8ffd77a2 100644 --- a/jlog/models.py +++ b/jlog/models.py @@ -3,7 +3,7 @@ from django.db import models class Log(models.Model): user = models.CharField(max_length=20, null=True) - host = models.CharField(max_length=20, null=True) + host = models.CharField(max_length=200, null=True) remote_ip = models.CharField(max_length=100) login_type = models.CharField(max_length=100) log_path = models.CharField(max_length=100) @@ -24,5 +24,26 @@ class Alert(models.Model): class TtyLog(models.Model): log = models.ForeignKey(Log) - datetime = models.DateTimeField() + datetime = models.DateTimeField(auto_now=True) cmd = models.CharField(max_length=200) + + +class ExecLog(models.Model): + user = models.CharField(max_length=100) + host = models.TextField() + cmd = models.TextField() + remote_ip = models.CharField(max_length=100) + result = models.TextField(default='') + datetime = models.DateTimeField(auto_now=True) + + +class FileLog(models.Model): + user = models.CharField(max_length=100) + host = models.TextField() + filename = models.TextField() + type = models.CharField(max_length=20) + remote_ip = models.CharField(max_length=100) + result = models.TextField(default='') + datetime = models.DateTimeField(auto_now=True) + + diff --git a/jlog/urls.py b/jlog/urls.py index deb2902b4..a490f46eb 100644 --- a/jlog/urls.py +++ b/jlog/urls.py @@ -3,11 +3,11 @@ from django.conf.urls import patterns, include, url from jlog.views import * urlpatterns = patterns('', - url(r'^$', log_list), - url(r'^log_list/(\w+)/$', log_list), - url(r'^history/$', log_history), - url(r'^log_kill/', log_kill), - url(r'^record/$', log_record), - url(r'^web_terminal/$', web_terminal), - url(r'^get_role_name/$', get_role_name), + (r'^$', log_list), + (r'^log_list/(\w+)/$', log_list), + (r'^log_detail/(\w+)/$', log_detail), + (r'^history/$', log_history), + (r'^log_kill/', log_kill), + (r'^record/$', log_record), + (r'^web_terminal/$', web_terminal), ) \ No newline at end of file diff --git a/jlog/views.py b/jlog/views.py index 729ececc7..3665e8968 100644 --- a/jlog/views.py +++ b/jlog/views.py @@ -8,7 +8,7 @@ from jperm.perm_api import user_have_perm from django.http import HttpResponseNotFound from jlog.log_api import renderTemplate -from models import Log +from jlog.models import Log, ExecLog, FileLog from jumpserver.settings import WEB_SOCKET_HOST @@ -21,9 +21,24 @@ def log_list(request, offset): username_list = request.GET.getlist('username', []) host_list = request.GET.getlist('host', []) cmd = request.GET.get('cmd', '') - print date_seven_day, date_now_str + if offset == 'online': + keyword = request.GET.get('keyword', '') posts = Log.objects.filter(is_finished=False).order_by('-start_time') + if keyword: + posts = posts.filter(Q(user__icontains=keyword) | Q(host__icontains=keyword) | + Q(login_type_icontains=keyword)) + + elif offset == 'exec': + posts = ExecLog.objects.all().order_by('-id') + keyword = request.GET.get('keyword', '') + if keyword: + posts = posts.filter(Q(user__icontains=keyword)|Q(host__icontains=keyword)|Q(cmd__icontains=keyword)) + elif offset == 'file': + posts = FileLog.objects.all().order_by('-id') + keyword = request.GET.get('keyword', '') + if keyword: + posts = posts.filter(Q(user__icontains=keyword)|Q(host__icontains=keyword)|Q(filename__icontains=keyword)) else: posts = Log.objects.filter(is_finished=True).order_by('-start_time') username_all = set([log.user for log in Log.objects.all()]) @@ -57,6 +72,11 @@ def log_list(request, offset): return render_to_response('jlog/log_%s.html' % offset, locals(), context_instance=RequestContext(request)) +@require_role('admin') +def log_detail(request): + return my_render('jlog/exec_detail.html', locals(), request) + + @require_role('admin') def log_kill(request): """ 杀掉connect进程 """ @@ -107,16 +127,6 @@ def log_record(request): return HttpResponse('无日志记录!') -@require_role('user') -def get_role_name(request): - asset_id = request.GET.get('id', 9999) - asset = get_object(Asset, id=asset_id) - if asset: - role = user_have_perm(request.user, asset=asset) - return HttpResponse(','.join([i.name for i in role])) - return HttpResponse('error') - - @require_role('user') def web_terminal(request): asset_id = request.GET.get('id') @@ -124,3 +134,21 @@ def web_terminal(request): web_terminal_uri = 'ws://%s/terminal?id=%s&role=%s' % (WEB_SOCKET_HOST, asset_id, role_name) return render_to_response('jlog/web_terminal.html', locals()) + +@require_role('admin') +def log_detail(request, offset): + log_id = request.GET.get('id') + if offset == 'exec': + log = get_object(ExecLog, id=log_id) + assets_hostname = log.host.split(' ') + result = eval(str(log.result)) + return my_render('jlog/exec_detail.html', locals(), request) + elif offset == 'file': + log = get_object(FileLog, id=log_id) + assets_hostname = log.host.split(' ') + file_list = log.filename.split(' ') + try: + result = eval(str(log.result)) + except (SyntaxError, NameError): + result = {} + return my_render('jlog/file_detail.html', locals(), request) diff --git a/jperm/ansible_api.py b/jperm/ansible_api.py index 3d292a67f..0fa0296b2 100644 --- a/jperm/ansible_api.py +++ b/jperm/ansible_api.py @@ -12,7 +12,11 @@ from ansible import utils from passlib.hash import sha512_crypt from utils import get_rand_pass +from jumpserver.api import logger +from tempfile import NamedTemporaryFile +from django.template.loader import get_template +from django.template import Context import os.path @@ -117,10 +121,10 @@ class MyRunner(MyInventory): """ def __init__(self, *args, **kwargs): super(MyRunner, self).__init__(*args, **kwargs) - self.results = {} + self.results_raw = {} - def run(self, module_name, module_args='', timeout=10, forks=10, pattern='', - sudo=False, sudo_user='root', sudo_pass=''): + def run(self, module_name='shell', module_args='', timeout=10, forks=10, pattern='*', + become=False, become_method='sudo', become_user='root', become_pass=''): """ run module from andible ad-hoc. module_name: ansible module_name @@ -132,13 +136,40 @@ class MyRunner(MyInventory): inventory=self.inventory, pattern=pattern, forks=forks, - become=sudo, - become_method='sudo', - become_user=sudo_user, - become_pass=sudo_pass + become=become, + become_method=become_method, + become_user=become_user, + become_pass=become_pass ) - self.results = hoc.run() - return self.results + self.results_raw = hoc.run() + logger.debug(self.results_raw) + return self.results_raw + + @property + def results(self): + """ + {'failed': {'localhost': ''}, 'ok': {'jumpserver': ''}} + """ + result = {'failed': {}, 'ok': {}} + dark = self.results_raw.get('dark') + contacted = self.results_raw.get('contacted') + if dark: + for host, info in dark.items(): + result['failed'][host] = info.get('msg') + + if contacted: + for host, info in contacted.items(): + if info.get('invocation').get('module_name') in ['raw', 'shell', 'command', 'script']: + if info.get('rc') == 0: + result['ok'][host] = info.get('stdout') + info.get('stderr') + else: + result['failed'][host] = info.get('stdout') + info.get('stderr') + else: + if info.get('failed'): + result['failed'][host] = info.get('msg') + else: + result['ok'][host] = info.get('changed') + return result class Command(MyInventory): @@ -147,9 +178,9 @@ class Command(MyInventory): """ def __init__(self, *args, **kwargs): super(Command, self).__init__(*args, **kwargs) - self.results = {} + self.results_raw = {} - def run(self, command, module_name="command", timeout=10, forks=10, pattern='*'): + def run(self, command, module_name="command", timeout=10, forks=10, pattern=''): """ run command from andible ad-hoc. command : 必须是一个需要执行的命令字符串, 比如 @@ -167,25 +198,34 @@ class Command(MyInventory): pattern=pattern, forks=forks, ) - self.results = hoc.run() - - ret = {} - if self.stdout: - data['ok'] = self.stdout - if self.stderr: - data['err'] = self.stderr - if self.dark: - data['dark'] = self.dark - - return data - + self.results_raw = hoc.run() @property - def raw_results(self): - """ - get the ansible raw results. - """ - return self.results + def result(self): + result = {} + for k, v in self.results_raw.items(): + if k == 'dark': + for host, info in v.items(): + result[host] = {'dark': info.get('msg')} + elif k == 'contacted': + for host, info in v.items(): + result[host] = {} + if info.get('stdout'): + result[host]['stdout'] = info.get('stdout') + elif info.get('stderr'): + result[host]['stderr'] = info.get('stderr') + return result + + @property + def state(self): + result = {} + if self.stdout: + result['ok'] = self.stdout + if self.stderr: + result['err'] = self.stderr + if self.dark: + result['dark'] = self.dark + return result @property def exec_time(self): @@ -193,7 +233,7 @@ class Command(MyInventory): get the command execute time. """ result = {} - all = self.results.get("contacted") + all = self.results_raw.get("contacted") for key, value in all.iteritems(): result[key] = { "start": value.get("start"), @@ -207,7 +247,7 @@ class Command(MyInventory): get the comamnd standard output. """ result = {} - all = self.results.get("contacted") + all = self.results_raw.get("contacted") for key, value in all.iteritems(): result[key] = value.get("stdout") return result @@ -218,7 +258,7 @@ class Command(MyInventory): get the command standard error. """ result = {} - all = self.results.get("contacted") + all = self.results_raw.get("contacted") for key, value in all.iteritems(): if value.get("stderr") or value.get("warnings"): result[key] = { @@ -231,64 +271,24 @@ class Command(MyInventory): """ get the dark results. """ - return self.results.get("dark") + return self.results_raw.get("dark") -class Tasks(Command): +class MyTask(MyRunner): """ this is a tasks object for include the common command. """ def __init__(self, *args, **kwargs): - super(Tasks, self).__init__(*args, **kwargs) - - def __run(self, - module_args, - module_name="command", - timeout=5, - forks=10, - group='default_group', - pattern='*', - become=False, - ): - """ - run command from andible ad-hoc. - command : 必须是一个需要执行的命令字符串, 比如 - 'uname -a' - """ - hoc = Runner(module_name=module_name, - module_args=module_args, - timeout=timeout, - inventory=self.inventory, - subset=group, - pattern=pattern, - forks=forks, - become=become, - ) - - self.results = hoc.run() - return {"msg": self.msg, "result": self.results} - - @property - def msg(self): - """ - get the contacted and dark msg - """ - msg = {} - for result in ["contacted", "dark"]: - all = self.results.get(result) - for key, value in all.iteritems(): - if value.get("msg"): - msg[key] = value.get("msg") - return msg + super(MyTask, self).__init__(*args, **kwargs) def push_key(self, user, key_path): """ push the ssh authorized key to target. """ module_args = 'user="%s" key="{{ lookup("file", "%s") }}" state=present' % (user, key_path) - self.__run(module_args, "authorized_key", become=True) + self.run("authorized_key", module_args, become=True) - return {"status": "failed", "msg": self.msg} if self.msg else {"status": "ok"} + return self.results def push_multi_key(self, **user_info): """ @@ -315,9 +315,9 @@ class Tasks(Command): push the ssh authorized key to target. """ module_args = 'user="%s" key="{{ lookup("file", "%s") }}" state="absent"' % (user, key_path) - self.__run(module_args, "authorized_key") + self.run("authorized_key", module_args, become=True) - return {"status": "failed", "msg": self.msg} if self.msg else {"status": "ok"} + return self.results def add_user(self, username, password=''): """ @@ -329,9 +329,10 @@ class Tasks(Command): module_args = 'name=%s shell=/bin/bash password=%s' % (username, encrypt_pass) else: module_args = 'name=%s shell=/bin/bash' % username - self.__run(module_args, "user", become=True) - return {"status": "failed", "msg": self.msg} if self.msg else {"status": "ok"} + self.run("user", module_args, become=True) + + return self.results def add_multi_user(self, **user_info): """ @@ -358,94 +359,38 @@ class Tasks(Command): """ delete a host user. """ - module_args = 'name=%s state=absent remove=yes move_home=yes force=yes' % (username) - self.__run(module_args, - "user",) + module_args = 'name=%s state=absent remove=yes move_home=yes force=yes' % username + self.run("user", module_args, become=True) + return self.results - return {"status": "failed","msg": self.msg} if self.msg else {"status": "ok"} - def add_init_users(self): - """ - add initail users: SA, DBA, DEV - """ - results = {} - action = results["action_info"] = {} - users = {"SA": get_rand_pass(), "DBA": get_rand_pass(), "DEV": get_rand_pass()} - for user, password in users.iteritems(): - ret = self.add_user(user, password) - action[user] = ret - results["user_info"] = users + @staticmethod + def gen_sudo_script(role_list, sudo_list): + # receive role_list = [role1, role2] sudo_list = [sudo1, sudo2] + # return sudo_alias={'NETWORK': '/sbin/ifconfig, /ls'} sudo_user={'user1': ['NETWORK', 'SYSTEM']} + sudo_alias = {} + sudo_user = {} + for sudo in sudo_list: + sudo_alias[sudo.name] = sudo.commands - return results + for role in role_list: + sudo_user[role.name] = ','.join(sudo_alias.keys()) - def del_init_users(self): - """ - delete initail users: SA, DBA, DEV - """ - results = {} - action = results["action_info"] = {} - for user in ["SA", "DBA", "DEV"]: - ret = self.del_user(user) - action[user] = ret - return results + sudo_j2 = get_template('jperm/role_sudo.j2') + sudo_content = sudo_j2.render(Context({"sudo_alias": sudo_alias, "sudo_user": sudo_user})) + sudo_file = NamedTemporaryFile(delete=False) + sudo_file.write(sudo_content) + sudo_file.close() + return sudo_file.name - def get_host_info(self): - """ - use the setup module get host informations - :return: - all_ip is list - processor_count is int - system_dist_version is string - system_type is string - disk is dict (device_name: device_size} - system_dist is string - processor_type is string - default_ip is string - hostname is string - product_sn is string - memory_total is int (MB) - default_mac is string - product_name is string - """ - self.__run('', 'setup', become=True) - - result = {} - all = self.results.get("contacted") - for key, value in all.iteritems(): - setup =value.get("ansible_facts") - # get disk informations - disk_all = setup.get("ansible_devices") - disk_need = {} - for disk_name, disk_info in disk_all.iteritems(): - if disk_name.startswith('sd') or disk_name.startswith('hd'): - disk_need[disk_name] = disk_info.get("size") - - result[key] = { - "all_ip": setup.get("ansible_all_ipv4_addresses"), - "hostname" : setup.get("ansible_hostname"), - "default_ip": setup.get("ansible_default_ipv4").get("address"), - "default_mac": setup.get("ansible_default_ipv4").get("macaddress"), - "product_name": setup.get("ansible_product_name"), - "processor_type": ' '.join(setup.get("ansible_processor")), - "processor_count": setup.get("ansible_processor_count"), - "memory_total": setup.get("ansible_memtotal_mb"), - "disk": disk_need, - "system_type": setup.get("ansible_system"), - "system_dist": setup.get("ansible_distribution"), - "system_dist_verion": setup.get("ansible_distribution_major_version"), - "product_sn": setup.get("ansible_product_serial") - } - - return {"failed": self.msg, "ok": result} - - def push_sudo_file(self, file_path): + def push_sudo_file(self, role_list, sudo_list): """ use template to render pushed sudoers file :return: """ - module_args1 = file_path - ret = self.__run(module_args1, "script") - return ret + module_args1 = self.gen_sudo_script(role_list, sudo_list) + self.run("script", module_args1, become=True) + return self.results class CustomAggregateStats(callbacks.AggregateStats): diff --git a/jperm/models.py b/jperm/models.py index 3a280762e..00f43a1ca 100644 --- a/jperm/models.py +++ b/jperm/models.py @@ -55,6 +55,6 @@ class PermPush(models.Model): is_public_key = models.BooleanField(default=False) is_password = models.BooleanField(default=False) success = models.BooleanField(default=False) - result = models.TextField() + result = models.TextField(default='') date_added = models.DateTimeField(auto_now=True) diff --git a/jperm/perm_api.py b/jperm/perm_api.py index 080f1cb39..9e4d014cd 100644 --- a/jperm/perm_api.py +++ b/jperm/perm_api.py @@ -43,13 +43,16 @@ def get_group_user_perm(ob): asset_groups = rule.asset_group.all() assets = rule.asset.all() perm_roles = rule.role.all() + group_assets = [] + for asset_group in asset_groups: + group_assets.extend(asset_group.asset_set.all()) # 获取一个规则授权的角色和对应主机 for role in perm_roles: - if perm_role.get('role'): - perm_role[role]['asset'] = perm_role[role].get('asset', set()).union(set(assets)) + if perm_role.get(role): + perm_role[role]['asset'] = perm_role[role].get('asset', set()).union(set(assets).union(set(group_assets))) perm_role[role]['asset_group'] = perm_role[role].get('asset_group', set()).union(set(asset_groups)) else: - perm_role[role] = {'asset': set(assets), 'asset_group': set(asset_groups)} + perm_role[role] = {'asset': set(assets).union(set(group_assets)), 'asset_group': set(asset_groups)} # 获取一个规则用户授权的资产 for asset in assets: @@ -161,23 +164,42 @@ def gen_resource(ob, perm=None): user = ob.get('user') if not perm: perm = get_group_user_perm(user) - roles = perm.get('role', {}).keys() - if role not in roles: - return {} - role_assets_all = perm.get('role').get(role).get('asset') - assets = set(role_assets_all) & set(asset_r) + if role: + roles = perm.get('role', {}).keys() # 获取用户所有授权角色 + if role not in roles: + return {} - for asset in assets: - asset_info = get_asset_info(asset) - info = {'hostname': asset.hostname, - 'ip': asset.ip, - 'port': asset_info.get('port', 22), - 'username': role.name, - 'password': CRYPTOR.decrypt(role.password), - 'ssh_key': get_role_key(user, role) - } - res.append(info) + role_assets_all = perm.get('role').get(role).get('asset') # 获取用户该角色所有授权主机 + assets = set(role_assets_all) & set(asset_r) # 获取用户提交中合法的主机 + + for asset in assets: + asset_info = get_asset_info(asset) + info = {'hostname': asset.hostname, + 'ip': asset.ip, + 'port': asset_info.get('port', 22), + 'username': role.name, + 'password': CRYPTOR.decrypt(role.password), + 'ssh_key': get_role_key(user, role) + } + res.append(info) + else: + for asset, asset_info in perm.get('asset').items(): + if asset not in asset_r: + continue + asset_info = get_asset_info(asset) + try: + role = sorted(list(perm.get('asset').get(asset).get('role')))[0] + except IndexError: + continue + info = {'hostname': asset.hostname, + 'ip': asset.ip, + 'port': asset_info.get('port', 22), + 'username': role.name, + 'password': CRYPTOR.decrypt(role.password), + 'ssh_key': get_role_key(user, role) + } + res.append(info) elif isinstance(ob, User): if not perm: @@ -198,6 +220,7 @@ def gen_resource(ob, perm=None): for asset in ob: info = get_asset_info(asset) res.append(info) + logger.debug('生成res: %s' % res) return res @@ -281,6 +304,7 @@ def get_role_push_host(role): asset_no_push = set(asset_all) - set(asset_pushed.keys()) return asset_pushed, asset_no_push + if __name__ == "__main__": print get_role_info(1) diff --git a/jperm/urls.py b/jperm/urls.py index 456cf5c87..0d26aad45 100644 --- a/jperm/urls.py +++ b/jperm/urls.py @@ -14,6 +14,7 @@ urlpatterns = patterns('jperm.views', (r'^role/perm_role_edit/$', perm_role_edit), (r'^role/push/$', perm_role_push), (r'^role/recycle/$', perm_role_recycle), + (r'^role/get/$', perm_role_get), (r'^sudo/$', perm_sudo_list), (r'^sudo/perm_sudo_add/$', perm_sudo_add), (r'^sudo/perm_sudo_delete/$', perm_sudo_delete), diff --git a/jperm/utils.py b/jperm/utils.py index 59b1c17ba..92506cdc1 100644 --- a/jperm/utils.py +++ b/jperm/utils.py @@ -10,11 +10,8 @@ from uuid import uuid4 from jumpserver.api import CRYPTOR from os import makedirs -from django.template.loader import get_template -from django.template import Context from tempfile import NamedTemporaryFile - from jumpserver.settings import KEY_DIR @@ -72,45 +69,6 @@ def gen_keys(key="", key_path_dir=""): return key_path_dir -def gen_sudo(role_custom, role_name, role_chosen): - """ - 生成sudo file, 仅测试了cenos7 - role_custom: 自定义支持的sudo 命令 格式: 'CMD1, CMD2, CMD3, ...' - role_name: role name - role_chosen: 选择那些sudo的命令别名: -     NETWORKING, SOFTWARE, SERVICES, STORAGE, -     DELEGATING, PROCESSES, LOCATE, DRIVERS - :return: - """ - sudo_file_basename = os.path.join(os.path.dirname(KEY_DIR), 'role_sudo_file') - makedirs(sudo_file_basename) - sudo_file_path = os.path.join(sudo_file_basename, role_name) - - t = get_template('role_sudo.j2') - content = t.render(Context({"role_custom": role_custom, - "role_name": role_name, - "role_chosen": role_chosen, - })) - with open(sudo_file_path, 'w') as f: - f.write(content) - return sudo_file_path - - -def get_add_sudo_script(role_chosen_aliase, sudo_alias): - """ - get the sudo file - :param kwargs: - :return: - """ - sudo_j2 = get_template('jperm/role_sudo.j2') - sudo_content = sudo_j2.render(Context({"role_chosen_aliase": role_chosen_aliase, - "sudo_alias": sudo_alias})) - sudo_file = NamedTemporaryFile(delete=False) - sudo_file.write(sudo_content) - sudo_file.close() - print(sudo_file.name) - return sudo_file.name - if __name__ == "__main__": print gen_keys() diff --git a/jperm/views.py b/jperm/views.py index 12ca395ba..44e59b6fe 100644 --- a/jperm/views.py +++ b/jperm/views.py @@ -10,10 +10,9 @@ from jasset.models import Asset, AssetGroup from jperm.models import PermRole, PermRule, PermSudo, PermPush from jumpserver.models import Setting -from jperm.utils import updates_dict, gen_keys, get_rand_pass, get_add_sudo_script -from jperm.ansible_api import Tasks +from jperm.utils import updates_dict, gen_keys, get_rand_pass +from jperm.ansible_api import MyTask from jperm.perm_api import get_role_info, get_role_push_host - from jumpserver.api import my_render, get_object, CRYPTOR @@ -24,12 +23,14 @@ def perm_rule_list(request): """ # 渲染数据 header_title, path1, path2 = "授权规则", "规则管理", "查看规则" - # 获取所有规则 rules_list = PermRule.objects.all() - + rule_id = request.GET.get('id') # TODO: 搜索和分页 keyword = request.GET.get('search', '') + if rule_id: + rules_list = rules_list.filter(id=rule_id) + if keyword: rules_list = rules_list.filter(Q(name=keyword)) @@ -78,24 +79,29 @@ def perm_rule_add(request): if request.method == 'POST': # 获取用户选择的 用户,用户组,资产,资产组,用户角色 - users_select = request.POST.getlist('user', []) - user_groups_select = request.POST.getlist('usergroup', []) - assets_select = request.POST.getlist('asset', []) - asset_groups_select = request.POST.getlist('assetgroup', []) - roles_select = request.POST.getlist('role', []) - rule_name = request.POST.get('rulename') - rule_comment = request.POST.get('rule_comment') + users_select = request.POST.getlist('user', []) # 需要授权用户 + user_groups_select = request.POST.getlist('user_group', []) # 需要授权用户组 + assets_select = request.POST.getlist('asset', []) # 需要授权资产 + asset_groups_select = request.POST.getlist('asset_group', []) # 需要授权资产组 + roles_select = request.POST.getlist('role', []) # 需要授权角色 + rule_name = request.POST.get('name') + rule_comment = request.POST.get('comment') try: rule = get_object(PermRule, name=rule_name) if rule: raise ServerError(u'授权规则 %s 已存在' % rule_name) + if not rule_name or not roles_select: + raise ServerError(u'角色名称和授权角色不能为空') + # 获取需要授权的主机列表 assets_obj = [Asset.objects.get(id=asset_id) for asset_id in assets_select] asset_groups_obj = [AssetGroup.objects.get(id=group_id) for group_id in asset_groups_select] - group_assets_obj = [asset for asset in [group.asset_set.all() for group in asset_groups_obj]] - calc_assets = set(group_assets_obj) | set(assets_obj) + group_assets_obj = [] + for asset_group in asset_groups_obj: + group_assets_obj.extend(list(asset_group.asset_set.all())) + calc_assets = set(group_assets_obj) | set(assets_obj) # 授权资产和资产组包含的资产 # 获取需要授权的用户列表 users_obj = [User.objects.get(id=user_id) for user_id in users_select] @@ -106,8 +112,9 @@ def perm_rule_add(request): # 获取授予的角色列表 roles_obj = [PermRole.objects.get(id=role_id) for role_id in roles_select] need_push_asset = set() + for role in roles_obj: - asset_no_push = get_role_push_host(role=role)[1] + asset_no_push = get_role_push_host(role=role)[0] # 获取某角色已经推送的资产 need_push_asset.update(set(calc_assets) - set(asset_no_push)) if need_push_asset: raise ServerError(u'没有推送角色 %s 的主机 %s' @@ -140,61 +147,68 @@ def perm_rule_edit(request): # 根据rule_id 取得rule对象 rule_id = request.GET.get("id") - rule = PermRule.objects.get(id=rule_id) + rule = get_object(PermRule, id=rule_id) - if request.method == 'GET' and rule_id: - # 渲染数据, 获取所选的rule对象 - rule_comment = rule.comment - users_select = rule.user.all() - user_groups_select = rule.user_group.all() - assets_select = rule.asset.all() - asset_groups_select = rule.asset_group.all() - roles_select = rule.role.all() + # 渲染数据, 获取所选的rule对象 - users = User.objects.all() - user_groups = UserGroup.objects.all() - assets = Asset.objects.all() - asset_groups = AssetGroup.objects.all() - roles = PermRole.objects.all() - return my_render('jperm/perm_rule_edit.html', locals(), request) + users = User.objects.all() + user_groups = UserGroup.objects.all() + assets = Asset.objects.all() + asset_groups = AssetGroup.objects.all() + roles = PermRole.objects.all() - elif request.method == 'POST' and rule_id: + if request.method == 'POST' and rule_id: # 获取用户选择的 用户,用户组,资产,资产组,用户角色 - rule_name = request.POST.get('rule_name') - rule_comment = request.POST.get("rule_comment") + rule_name = request.POST.get('name') + rule_comment = request.POST.get("comment") users_select = request.POST.getlist('user', []) - user_groups_select = request.POST.getlist('usergroup', []) + user_groups_select = request.POST.getlist('user_group', []) assets_select = request.POST.getlist('asset', []) - asset_groups_select = request.POST.getlist('assetgroup', []) + asset_groups_select = request.POST.getlist('asset_group', []) roles_select = request.POST.getlist('role', []) + print rule_name, roles_select + try: + if not rule_name or not roles_select: + raise ServerError(u'角色名称和授权角色不能为空') - assets_obj = [Asset.objects.get(id=asset_id) for asset_id in assets_select] - asset_groups_obj = [AssetGroup.objects.get(id=group_id) for group_id in asset_groups_select] - # group_assets_obj = [asset for asset in [group.asset_set.all() for group in asset_groups_obj]] - # calc_assets = set(group_assets_obj) | set(assets_obj) + assets_obj = [Asset.objects.get(id=asset_id) for asset_id in assets_select] + asset_groups_obj = [AssetGroup.objects.get(id=group_id) for group_id in asset_groups_select] + group_assets_obj = [] + for asset_group in asset_groups_obj: + group_assets_obj.extend(list(asset_group.asset_set.all())) + calc_assets = set(group_assets_obj) | set(assets_obj) # 授权资产和资产组包含的资产 - # 获取需要授权的用户列表 - users_obj = [User.objects.get(id=user_id) for user_id in users_select] - user_groups_obj = [UserGroup.objects.get(id=group_id) for group_id in user_groups_select] - # group_users_obj = [user for user in [group.user_set.all() for group in user_groups_obj]] - # calc_users = set(group_users_obj) | set(users_obj) + # 获取需要授权的用户列表 + users_obj = [User.objects.get(id=user_id) for user_id in users_select] + user_groups_obj = [UserGroup.objects.get(id=group_id) for group_id in user_groups_select] + # group_users_obj = [user for user in [group.user_set.all() for group in user_groups_obj]] + # calc_users = set(group_users_obj) | set(users_obj) - # 获取授予的角色列表 - roles_obj = [PermRole.objects.get(id=role_id) for role_id in roles_select] + # 获取授予的角色列表 + roles_obj = [PermRole.objects.get(id=role_id) for role_id in roles_select] + need_push_asset = set() + for role in roles_obj: + asset_no_push = get_role_push_host(role=role)[0] # 获取某角色已经推送的资产 + need_push_asset.update(set(calc_assets) - set(asset_no_push)) + if need_push_asset: + raise ServerError(u'没有推送角色 %s 的主机 %s' + % (role.name, ','.join([asset.hostname for asset in need_push_asset]))) - # 仅授权成功的,写回数据库(授权规则,用户,用户组,资产,资产组,用户角色) - rule.user = users_obj - rule.user_group = user_groups_obj - rule.asset = assets_obj - rule.asset_group = asset_groups_obj - rule.role = roles_obj - rule.name = rule_name - rule.comment = rule.comment - rule.save() + # 仅授权成功的,写回数据库(授权规则,用户,用户组,资产,资产组,用户角色) + rule.user = users_obj + rule.user_group = user_groups_obj + rule.asset = assets_obj + rule.asset_group = asset_groups_obj + rule.role = roles_obj + rule.name = rule_name + rule.comment = rule.comment + rule.save() + msg = u"更新授权规则:%s成功" % rule.name - msg = u"更新授权规则:%s" % rule.name + except ServerError, e: + error = e - return HttpResponseRedirect('/jperm/rule/') + return my_render('jperm/perm_rule_edit.html', locals(), request) @require_role('admin') @@ -208,8 +222,6 @@ def perm_rule_delete(request): # 根据rule_id 取得rule对象 rule_id = request.POST.get("id") rule_obj = PermRule.objects.get(id=rule_id) - print rule_id, rule_obj - print rule_obj.name rule_obj.delete() return HttpResponse(u"删除授权规则:%s" % rule_obj.name) else: @@ -226,12 +238,15 @@ def perm_role_list(request): # 获取所有系统角色 roles_list = PermRole.objects.all() - + role_id = request.GET.get('id') # TODO: 搜索和分页 keyword = request.GET.get('search', '') if keyword: roles_list = roles_list.filter(Q(name=keyword)) + if role_id: + roles_list = roles_list.filter(id=role_id) + roles_list, p, roles, page_range, current_page, show_first, show_end = pages(roles_list, request) return my_render('jperm/perm_role_list.html', locals(), request) @@ -396,9 +411,12 @@ def perm_role_push(request): # 渲染数据 header_title, path1, path2 = "系统角色", "角色管理", "角色推送" role_id = request.GET.get('id') + asset_ids = request.GET.get('asset_id') role = get_object(PermRole, id=role_id) assets = Asset.objects.all() asset_groups = AssetGroup.objects.all() + if asset_ids: + need_push_asset = [get_object(Asset, id=asset_id) for asset_id in asset_ids.split(',')] if request.method == "POST": # 获取推荐角色的名称列表 @@ -417,43 +435,48 @@ def perm_role_push(request): # 调用Ansible API 进行推送 password_push = True if request.POST.get("use_password") else False key_push = True if request.POST.get("use_publicKey") else False - task = Tasks(push_resource) + task = MyTask(push_resource) ret = {} - ret_failed = {} # 因为要先建立用户,所以password 是必选项,而push key是在 password也完成的情况下的 可选项 - # 1. 以password 方式推送角色 - if password_push: - ret["password_push"] = task.add_user(role.name, CRYPTOR.decrypt(role.password)) - if ret["password_push"].get("status") != "success": - ret_failed = ret["password_push"].get('msg') - - # 2. 以秘钥 方式推送角色 + # 1. 以秘钥 方式推送角色 if key_push: - ret["password_push"] = task.add_user(role.name) - if ret["password_push"].get("status") != "ok": - ret_failed = ret["password_push"].get('msg') + ret["pass_push"] = task.add_user(role.name, CRYPTOR.decrypt(role.password)) ret["key_push"] = task.push_key(role.name, os.path.join(role.key_path, 'id_rsa.pub')) - if ret["key_push"].get("status") != "ok": - ret_failed = ret["key_push"].get('msg') + + # 2. 推送账号密码 + elif password_push: + ret["pass_push"] = task.add_user(role.name, CRYPTOR.decrypt(role.password)) # 3. 推送sudo配置文件 if password_push or key_push: - role_chosen_aliase = {} # {'dev': 'NETWORKING, SHUTDOWN'} - sudo_alias = set([sudo for sudo in role.sudo.all()]) # set(sudo1, sudo2, sudo3) - role_chosen_aliase[role.name] = ','.join(sudo.name for sudo in sudo_alias) - add_sudo_script = get_add_sudo_script(role_chosen_aliase, sudo_alias) - ret['sudo'] = task.push_sudo_file(add_sudo_script) - - if ret['sudo'].get('msg'): - ret_failed = ret['sudo'].get('msg') - # os.remove(add_sudo_script) + sudo_list = set([sudo for sudo in role.sudo.all()]) # set(sudo1, sudo2, sudo3) + ret['sudo'] = task.push_sudo_file([role], sudo_list) logger.debug('推送role结果: %s' % ret) - logger.debug('推送role错误: %s' % ret_failed) + success_asset = {} + failed_asset = {} + logger.debug(ret) + for push_type, result in ret.items(): + if result.get('failed'): + for hostname, info in result.get('failed').items(): + if hostname in failed_asset.keys(): + if info in failed_asset.get(hostname): + failed_asset[hostname] += info + else: + failed_asset[hostname] = info + + for push_type, result in ret.items(): + if result.get('ok'): + for hostname, info in result.get('ok').items(): + if hostname in failed_asset.keys(): + continue + elif hostname in success_asset.keys(): + if str(info) in success_asset.get(hostname, ''): + success_asset[hostname] += str(info) + else: + success_asset[hostname] = str(info) - success_asset = [] - failed_asset = [] # 推送成功 回写push表 for asset in calc_assets: push_check = PermPush.objects.filter(role=role, asset=asset) @@ -463,20 +486,18 @@ def perm_role_push(request): def func(**kwargs): PermPush(**kwargs).save() - if ret_failed.get(asset.hostname): - failed_asset.append(asset) + if failed_asset.get(asset.hostname): func(is_password=password_push, is_public_key=key_push, role=role, asset=asset, success=False, - result=ret_failed.get(asset.hostname)) + result=failed_asset.get(asset.hostname)) else: - success_asset.append(asset) func(is_password=password_push, is_public_key=key_push, role=role, asset=asset, success=True) if not failed_asset: - msg = u'角色 %s 推送成功[ %s ]' % (role.name, ','.join([asset.hostname for asset in success_asset])) + msg = u'角色 %s 推送成功[ %s ]' % (role.name, ','.join(success_asset.keys())) else: error = u'角色 %s 推送失败 [ %s ], 推送成功 [ %s ]' % (role.name, - ','.join([asset.hostname for asset in failed_asset]), - ','.join([asset.hostname for asset in success_asset])) + ','.join(failed_asset.keys()), + ','.join(success_asset.keys())) return my_render('jperm/perm_role_push.html', locals(), request) @@ -515,14 +536,18 @@ def perm_sudo_add(request): if request.method == "POST": # 获取参数: name, comment - name = request.POST.get("sudo_name").strip() + name = request.POST.get("sudo_name").strip().upper() comment = request.POST.get("sudo_comment").strip() commands = request.POST.get("sudo_commands").strip() + pattern = re.compile(r'[ \n,\r]') + commands = ', '.join(list_drop_str(pattern.split(commands), u'')) + logger.debug(u'添加sudo %s: %s' % (name, commands)) + if get_object(PermSudo, name=name): error = 'Sudo别名 %s已经存在' % name else: - sudo = PermSudo(name=name.strip(), comment=comment, commands=commands.strip()) + sudo = PermSudo(name=name.strip(), comment=comment, commands=commands) sudo.save() msg = u"添加Sudo命令别名: %s" % name # 渲染数据 @@ -544,11 +569,16 @@ def perm_sudo_edit(request): sudo = PermSudo.objects.get(id=sudo_id) if request.method == "POST": - name = request.POST.get("sudo_name") + name = request.POST.get("sudo_name").upper() commands = request.POST.get("sudo_commands") comment = request.POST.get("sudo_comment") + + pattern = re.compile(r'[ \n,\r]') + commands = ', '.join(list_drop_str(pattern.split(commands), u'')).strip() + logger.debug(u'添加sudo %s: %s' % (name, commands)) + sudo.name = name.strip() - sudo.commands = commands.strip() + sudo.commands = commands sudo.comment = comment sudo.save() @@ -579,11 +609,31 @@ def perm_sudo_delete(request): def perm_role_recycle(request): role_id = request.GET.get('role_id') asset_ids = request.GET.get('asset_id').split(',') + assets = [] for asset_id in asset_ids: asset = get_object(Asset, id=asset_id) + assets.append(asset) role = get_object(PermRole, id=role_id) PermPush.objects.filter(asset=asset, role=role).delete() + + res = gen_resource(assets) + task = MyTask(res) + return HttpResponse('删除成功') +@require_role('user') +def perm_role_get(request): + asset_id = request.GET.get('id', 0) + if asset_id: + asset = get_object(Asset, id=asset_id) + if asset: + role = user_have_perm(request.user, asset=asset) + logger.debug('#' + ','.join([i.name for i in role]) + '#') + return HttpResponse(','.join([i.name for i in role])) + else: + roles = get_group_user_perm(request.user).get('role').keys() + return HttpResponse(','.join(i.name for i in roles)) + + return HttpResponse('error') diff --git a/jumpserver/api.py b/jumpserver/api.py index d36b306d2..723a4e9e7 100644 --- a/jumpserver/api.py +++ b/jumpserver/api.py @@ -9,6 +9,7 @@ import hashlib import datetime import random import subprocess +import uuid import json import logging @@ -47,9 +48,16 @@ def set_log(level): return logger_f +def list_drop_str(a_list, a_str): + for i in a_list: + if i == a_str: + a_list.remove(a_str) + return a_list + + def get_asset_info(asset): """ - 获取资产的相关账号端口信息 + 获取资产的相关管理账号端口等信息 """ default = get_object(Setting, name='default') info = {'hostname': asset.hostname, 'ip': asset.ip} @@ -70,17 +78,6 @@ def get_asset_info(asset): return info -def get_role(user, asset): - """ - 获取用户在这个资产上的授权角色列表 - """ - roles = [] - rules = PermRule.objects.filter(user=user, asset=asset) - for rule in rules: - roles.extend(list(rule.role.all())) - return roles - - def get_role_key(user, role): """ 由于role的key的权限是所有人可以读的, ansible执行命令等要求为600,所以拷贝一份到特殊目录 @@ -95,7 +92,7 @@ def get_role_key(user, role): with open(os.path.join(role.key_path, 'id_rsa')) as fk: with open(user_role_key_path, 'w') as fu: fu.write(fk.read()) - logger.debug("创建新的用户角色key %s" % user_role_key_path) + logger.debug(u"创建新的用户角色key %s, Owner: %s" % (user_role_key_path, user.username)) chown(user_role_key_path, user.username) os.chmod(user_role_key_path, 0600) return user_role_key_path @@ -482,5 +479,10 @@ def my_render(template, data, request): return render_to_response(template, data, context_instance=RequestContext(request)) +def get_tmp_dir(): + dir_name = os.path.join('/tmp', uuid.uuid4().hex) + mkdir(dir_name, mode=0777) + return dir_name + CRYPTOR = PyCrypt(KEY) logger = set_log(LOG_LEVEL) diff --git a/jumpserver/templatetags/mytags.py b/jumpserver/templatetags/mytags.py index 2adb62deb..a5d70d801 100644 --- a/jumpserver/templatetags/mytags.py +++ b/jumpserver/templatetags/mytags.py @@ -7,7 +7,7 @@ import time from django import template from jperm.models import PermPush from jumpserver.api import * -from jasset.models import AssetAlias +from jperm.perm_api import get_group_user_perm register = template.Library() @@ -237,7 +237,7 @@ def key_exist(username): """ ssh key is exist or not """ - if os.path.isfile(os.path.join(KEY_DIR, 'user', username)): + if os.path.isfile(os.path.join(KEY_DIR, 'user', username+'.pem')): return True else: return False @@ -272,3 +272,35 @@ def get_push_info(push_id, arg): return [role.name for role in push.role.all()] else: return [] + + +@register.filter(name='get_cpu_core') +def get_cpu_core(cpu_info): + cpu_core = cpu_info.split('* ')[1] if cpu_info and '*' in cpu_info else cpu_info + return cpu_core + + +@register.filter(name='get_disk_info') +def get_disk_info(disk_info): + try: + disk_size = 0 + if disk_info: + disk_dic = ast.literal_eval(disk_info) + for disk, size in disk_dic.items(): + disk_size += size + disk_size = int(disk_size) + else: + disk_size = '' + except Exception: + disk_size = '' + return disk_size + + +@register.filter(name='user_perm_asset_num') +def user_perm_asset_num(user_id): + user = get_object(User, id=user_id) + if user: + user_perm_info = get_group_user_perm(user) + return len(user_perm_info.get('asset').keys()) + else: + return 0 diff --git a/jumpserver/urls.py b/jumpserver/urls.py index 361338fb1..3be4e631a 100644 --- a/jumpserver/urls.py +++ b/jumpserver/urls.py @@ -6,19 +6,14 @@ urlpatterns = patterns('', (r'^$', 'jumpserver.views.index'), (r'^api/user/$', 'jumpserver.api.api_user'), (r'^skin_config/$', 'jumpserver.views.skin_config'), - (r'^install/$', 'jumpserver.views.install'), - (r'^base/$', 'jumpserver.views.base'), (r'^login/$', 'jumpserver.views.Login'), (r'^logout/$', 'jumpserver.views.Logout'), + (r'^exec_cmd/$', 'jumpserver.views.exec_cmd'), (r'^file/upload/$', 'jumpserver.views.upload'), (r'^file/download/$', 'jumpserver.views.download'), (r'^setting', 'jumpserver.views.setting'), - (r'^error/$', 'jumpserver.views.httperror'), (r'^juser/', include('juser.urls')), (r'^jasset/', include('jasset.urls')), (r'^jlog/', include('jlog.urls')), (r'^jperm/', include('jperm.urls')), - (r'^node_auth/', 'jumpserver.views.node_auth'), - (r'download/(\d{4}/\d\d/\d\d/.*)', 'jumpserver.views.download_file'), - (r'test2', 'jumpserver.views.test2'), ) diff --git a/jumpserver/views.py b/jumpserver/views.py index 4563a9fd4..86d0b34e8 100644 --- a/jumpserver/views.py +++ b/jumpserver/views.py @@ -15,8 +15,11 @@ from jumpserver.api import * from jumpserver.models import Setting from django.contrib.auth import authenticate, login, logout from django.contrib.auth.decorators import login_required -from jlog.models import Log -from jperm.perm_api import get_group_user_perm +from jlog.models import Log, FileLog +from jperm.perm_api import get_group_user_perm, gen_resource +from jasset.models import Asset, IDC +from jperm.ansible_api import MyRunner + def getDaysByNum(num): """ @@ -72,42 +75,11 @@ def get_count_by_date(date_li, item): return len(set(data_count_tmp)) -from jasset.models import Asset, IDC + @require_role(role='user') def index_cu(request): - # user_id = request.user.id - # user = get_object(User, id=user_id) - login_types = {'L': 'LDAP', 'M': 'MAP'} username = request.user.username - # TODO: need fix,liuzheng need Asset help - GUP = get_group_user_perm(request.user) - print GUP - assets = GUP.get('asset') - idcs = [] - for i in assets: - if i.idc_id: - idcs.append(i.idc_id) - idc_all = IDC.objects.filter(id__in=idcs) - for i in idc_all: - print i.name - # idc_all = [] - # for i in assets: - # idc_all.append(i.idc) - # print i.idc.name - asset_group_all = GUP.get('asset_group') - # posts = Asset.object.all() - # host_count = len(posts) - # - # new_posts = [] - # post_five = [] - # for post in posts: - # if len(post_five) < 5: - # post_five.append(post) - # else: - # new_posts.append(post_five) - # post_five = [] - # new_posts.append(post_five) - return render_to_response('index_cu.html', locals(), context_instance=RequestContext(request)) + return HttpResponseRedirect('/juser/user_detail/') @require_role(role='user') @@ -181,34 +153,6 @@ def skin_config(request): return render_to_response('skin_config.html') -# def pages(posts, r): -# """分页公用函数""" -# contact_list = posts -# p = paginator = Paginator(contact_list, 10) -# try: -# current_page = int(r.GET.get('page', '1')) -# except ValueError: -# current_page = 1 -# -# page_range = page_list_return(len(p.page_range), current_page) -# -# try: -# contacts = paginator.page(current_page) -# except (EmptyPage, InvalidPage): -# contacts = paginator.page(paginator.num_pages) -# -# if current_page >= 5: -# show_first = 1 -# else: -# show_first = 0 -# if current_page <= (len(p.page_range) - 3): -# show_end = 1 -# else: -# show_end = 0 -# -# return contact_list, p, contacts, page_range, current_page, show_first, show_end - - def is_latest(): node = uuid.getnode() jsn = uuid.UUID(int=node).hex[-12:] @@ -308,166 +252,91 @@ def setting(request): return my_render('setting.html', locals(), request) -def test2(request): - return my_render('test2.html', locals(), request) -# -# def filter_ajax_api(request): -# attr = request.GET.get('attr', 'user') -# value = request.GET.get('value', '') -# if attr == 'user': -# contact_list = User.objects.filter(name__icontains=value) -# elif attr == "user_group": -# contact_list = UserGroup.objects.filter(name__icontains=value) -# elif attr == "asset": -# contact_list = Asset.objects.filter(ip__icontains=value) -# elif attr == "asset": -# contact_list = BisGroup.objects.filter(name__icontains=value) -# -# return render_to_response('filter_ajax_api.html', locals()) -# -# -# def install(request): -# from juser.models import DEPT, User -# if User.objects.filter(id=5000): -# return http_error(request, 'Jumpserver已初始化,不能重复安装!') -# -# dept = DEPT(id=1, name="超管部", comment="超级管理部门") -# dept.save() -# dept2 = DEPT(id=2, name="默认", comment="默认部门") -# dept2.save() -# IDC(id=1, name="默认", comment="默认IDC").save() -# BisGroup(id=1, name="ALL", dept=dept, comment="所有主机组").save() -# -# User(id=5000, username="admin", password=PyCrypt.md5_crypt('admin'), -# name='admin', email='admin@jumpserver.org', role='SU', is_active=True, dept=dept).save() -# return http_success(request, u'Jumpserver初始化成功') -# -# -# def download(request): -# return render_to_response('download.html', locals(), context_instance=RequestContext(request)) -# -# -# def transfer(sftp, filenames): -# # pool = Pool(processes=5) -# for filename, file_path in filenames.items(): -# print filename, file_path -# sftp.put(file_path, '/tmp/%s' % filename) -# # pool.apply_async(transfer, (sftp, file_path, '/tmp/%s' % filename)) -# sftp.close() -# # pool.close() -# # pool.join() -# -# -# def upload(request): -# pass -# # user, dept = get_session_user_dept(request) -# # if request.method == 'POST': -# # hosts = request.POST.get('hosts') -# # upload_files = request.FILES.getlist('file[]', None) -# # upload_dir = "/tmp/%s" % user.username -# # is_dir(upload_dir) -# # date_now = datetime.datetime.now().strftime("%Y%m%d%H%M%S") -# # hosts_list = hosts.split(',') -# # user_hosts = [asset.ip for asset in user.get_asset()] -# # unperm_hosts = [] -# # filenames = {} -# # for ip in hosts_list: -# # if ip not in user_hosts: -# # unperm_hosts.append(ip) -# # -# # if not hosts: -# # return HttpResponseNotFound(u'地址不能为空') -# # -# # if unperm_hosts: -# # print hosts_list -# # return HttpResponseNotFound(u'%s 没有权限.' % ', '.join(unperm_hosts)) -# # -# # for upload_file in upload_files: -# # file_path = '%s/%s.%s' % (upload_dir, upload_file.name, date_now) -# # filenames[upload_file.name] = file_path -# # f = open(file_path, 'w') -# # for chunk in upload_file.chunks(): -# # f.write(chunk) -# # f.close() -# # -# # sftps = [] -# # for host in hosts_list: -# # username, password, host, port = get_connect_item(user.username, host) -# # try: -# # t = paramiko.Transport((host, port)) -# # t.connect(username=username, password=password) -# # sftp = paramiko.SFTPClient.from_transport(t) -# # sftps.append(sftp) -# # except paramiko.AuthenticationException: -# # return HttpResponseNotFound(u'%s 连接失败.' % host) -# # -# # # pool = Pool(processes=5) -# # for sftp in sftps: -# # transfer(sftp, filenames) -# # # pool.close() -# # # pool.join() -# # return HttpResponse('传送成功') -# # -# # return render_to_response('upload.html', locals(), context_instance=RequestContext(request)) -# -# -# def node_auth(request): -# username = request.POST.get('username', ' ') -# seed = request.POST.get('seed', ' ') -# filename = request.POST.get('filename', ' ') -# user = User.objects.filter(username=username, password=seed) -# auth = 1 -# if not user: -# auth = 0 -# if not filename.startswith('/opt/jumpserver/logs/connect/'): -# auth = 0 -# if auth: -# result = {'auth': {'username': username, 'result': 'success'}} -# else: -# result = {'auth': {'username': username, 'result': 'failed'}} -# -# return HttpResponse(json.dumps(result, sort_keys=True, indent=2), content_type='application/json') - - -####################### liuzheng's test(start) ######################## -from django.contrib.auth.decorators import login_required -from juser.models import Document - @login_required(login_url='/login') def upload(request): - if request.method == 'GET': - machines = [{'name':'aaa'}] - return render_to_response('upload.html', locals(), context_instance=RequestContext(request)) - elif request.method == 'POST': + user = request.user + assets = get_group_user_perm(user).get('asset').keys() + asset_select = [] + if request.method == 'POST': + remote_ip = request.META.get('REMOTE_ADDR') + asset_ids = request.POST.getlist('asset_ids', '') upload_files = request.FILES.getlist('file[]', None) - for file in upload_files: - print file - newdoc = Document(docfile=file, user_id=request.user.id) - newdoc.save() - return HttpResponse("success") - else: - return HttpResponse("ERROR") + date_now = datetime.datetime.now().strftime("%Y%m%d%H%M%S") + upload_dir = get_tmp_dir() + # file_dict = {} + for asset_id in asset_ids: + asset_select.append(get_object(Asset, id=asset_id)) + + if not set(asset_select).issubset(set(assets)): + illegal_asset = set(asset_select).issubset(set(assets)) + return HttpResponse('没有权限的服务器 %s' % ','.join([asset.hostname for asset in illegal_asset])) + + for upload_file in upload_files: + file_path = '%s/%s' % (upload_dir, upload_file.name) + with open(file_path, 'w') as f: + for chunk in upload_file.chunks(): + f.write(chunk) + + res = gen_resource({'user': user, 'asset': asset_select}) + runner = MyRunner(res) + runner.run('copy', module_args='src=%s dest=%s directory_mode' + % (upload_dir, upload_dir), pattern='*') + ret = runner.results + logger.debug(ret) + FileLog(user=request.user.username, host=' '.join([asset.hostname for asset in asset_select]), + filename=' '.join([f.name for f in upload_files]), type='upload', remote_ip=remote_ip, + result=ret).save() + if ret.get('failed'): + error = u'上传目录: %s
上传失败: [ %s ]
上传成功 [ %s ]' % (upload_dir, + ', '.join(ret.get('failed').keys()), + ', '.join(ret.get('ok').keys())) + return HttpResponse(error, status=500) + msg = u'上传目录: %s
传送成功 [ %s ]' % (upload_dir, ', '.join(ret.get('ok').keys())) + return HttpResponse(msg) + return my_render('upload.html', locals(), request) + @login_required(login_url='/login') def download(request): - documents = [] - for doc in Document.objects.filter(user_id=request.user.id).all(): - documents.append('/'.join(str(doc.docfile).split('/')[2:])) + user = request.user + assets = get_group_user_perm(user).get('asset').keys() + asset_select = [] + if request.method == 'POST': + remote_ip = request.META.get('REMOTE_ADDR') + asset_ids = request.POST.getlist('asset_ids', '') + file_path = request.POST.get('file_path') + date_now = datetime.datetime.now().strftime("%Y%m%d%H%M%S") + upload_dir = get_tmp_dir() + for asset_id in asset_ids: + asset_select.append(get_object(Asset, id=asset_id)) + + if not set(asset_select).issubset(set(assets)): + illegal_asset = set(asset_select).issubset(set(assets)) + return HttpResponse(u'没有权限的服务器 %s' % ','.join([asset.hostname for asset in illegal_asset])) + + res = gen_resource({'user': user, 'asset': asset_select}) + runner = MyRunner(res) + runner.run('fetch', module_args='src=%s dest=%s' % (file_path, upload_dir), pattern='*') + FileLog(user=request.user.username, host=' '.join([asset.hostname for asset in asset_select]), + filename=file_path, type='download', remote_ip=remote_ip, result=runner.results).save() + logger.debug(runner.results) + os.chdir('/tmp') + tmp_dir_name = os.path.basename(upload_dir) + tar_file = '%s.tar.gz' % upload_dir + bash('tar czf %s %s' % (tar_file, tmp_dir_name)) + f = open(tar_file) + data = f.read() + f.close() + response = HttpResponse(data, content_type='application/octet-stream') + response['Content-Disposition'] = 'attachment; filename=%s' % os.path.basename(tar_file) + return response + return render_to_response('download.html', locals(), context_instance=RequestContext(request)) -def download_file(request, path): - # TODO: get downlode file and make sure it is exist! - # by liuzheng - filepath = 'upload/' + str(request.user.id)+'/'+path - return HttpResponse(filepath) -def node_auth(request): - return HttpResponse('nothing') -def httperror(request): - return HttpResponse('nothing') -def base(request): - return HttpResponse('nothing') -def install(request): - return HttpResponse('nothing') - -####################### liuzheng's test(end) ######################## \ No newline at end of file +@login_required(login_url='/login') +def exec_cmd(request): + role = request.GET.get('role') + check_assets = request.GET.get('check_assets', '') + web_terminal_uri = 'ws://%s/exec?role=%s' % (WEB_SOCKET_HOST, role) + return my_render('exec_cmd.html', locals(), request) diff --git a/juser/urls.py b/juser/urls.py index bfd3a6270..098587770 100644 --- a/juser/urls.py +++ b/juser/urls.py @@ -25,9 +25,6 @@ urlpatterns = patterns('juser.views', (r'^forget_password/$', forget_password), (r'^change_info/$', 'change_info'), - (r'^change_role/$', 'chg_role'), (r'^regen_ssh_key/$', 'regen_ssh_key'), (r'^down_key/$', 'down_key'), - - (r'runcommand/$', 'RunCommand'), ) diff --git a/juser/user_api.py b/juser/user_api.py index 0248e5c6d..36bb9319a 100644 --- a/juser/user_api.py +++ b/juser/user_api.py @@ -129,8 +129,8 @@ def gen_ssh_key(username, password='', 生成一个用户ssh密钥对 """ logger.debug('生成ssh key, 并设置authorized_keys') - private_key_file = os.path.join(key_dir, username) - mkdir(key_dir, mode=777) + private_key_file = os.path.join(key_dir, username+'.pem') + mkdir(key_dir, mode=0700) if os.path.isfile(private_key_file): os.unlink(private_key_file) ret = bash('echo -e "y\n"|ssh-keygen -t rsa -f %s -b %s -P "%s"' % (private_key_file, length, password)) @@ -166,7 +166,7 @@ def user_add_mail(user, kwargs): mail_msg = u""" Hi, %s 您的用户名: %s - 您的角色: %s + 您的权限: %s 您的web登录密码: %s 您的ssh密钥文件密码: %s 密钥下载地址: %s/juser/down_key/?uuid=%s @@ -195,9 +195,9 @@ def get_display_msg(user, password, ssh_key_pwd, ssh_key_login_need, send_mail_n 用户名:%s 密码:%s 密钥密码:%s - 密钥下载url: %s/juser/down_key/?id=%s + 密钥下载url: %s/juser/down_key/?uuid=%s 该账号密码可以登陆web和跳板机。 - """ % (URL, user.username, password, ssh_key_pwd, URL, user.id) + """ % (URL, user.username, password, ssh_key_pwd, URL, user.uuid) else: msg = u""" 跳板机地址: %s \n diff --git a/juser/views.py b/juser/views.py index f204a53c5..a083c5d85 100644 --- a/juser/views.py +++ b/juser/views.py @@ -4,23 +4,15 @@ # import random # from Crypto.PublicKey import RSA -import uuid as uuid_r +import uuid from django.contrib.auth.decorators import login_required from django.db.models import Q from juser.user_api import * +from jperm.perm_api import get_group_user_perm MAIL_FROM = EMAIL_HOST_USER -@login_required(login_url='/login') -def chg_role(request): - role = {'SU': 2, 'GA': 1, 'CU': 0} - if request.session['role_id'] > 0: - request.session['role_id'] = 0 - elif request.session['role_id'] == 0: - request.session['role_id'] = role.get(request.user.role, 0) - return HttpResponseRedirect('/') - @require_role(role='super') def group_add(request): @@ -96,8 +88,8 @@ def group_edit(request): if request.method == 'GET': group_id = request.GET.get('id', '') - # user_group = get_object(UserGroup, id=group_id) - user_group = UserGroup.objects.get(id=group_id) + user_group = get_object(UserGroup, id=group_id) + # user_group = UserGroup.objects.get(id=group_id) users_selected = User.objects.filter(group=user_group) users_remain = User.objects.filter(~Q(group=user_group)) users_all = User.objects.all() @@ -126,8 +118,9 @@ def group_edit(request): if g == user_group: continue user.group.add(g) - - + user_group.name = group_name + user_group.comment = comment + user_group.save() except ServerError, e: error = e if not error: @@ -140,7 +133,6 @@ def group_edit(request): return my_render('juser/group_edit.html', locals(), request) -@login_required(login_url='/login') @require_role(role='super') def user_add(request): error = '' @@ -157,11 +149,11 @@ def user_add(request): groups = request.POST.getlist('groups', []) admin_groups = request.POST.getlist('admin_groups', []) role = request.POST.get('role', 'CU') - uuid = uuid_r.uuid1() + uuid_r = uuid.uuid4().get_hex() ssh_key_pwd = PyCrypt.gen_rand_pass(16) extra = request.POST.getlist('extra', []) is_active = False if '0' in extra else True - ssh_key_login_need = True if '1' in extra else False + ssh_key_login_need = True send_mail_need = True if '2' in extra else False try: @@ -179,7 +171,7 @@ def user_add(request): try: user = db_add_user(username=username, name=name, password=password, - email=email, role=role, uuid=uuid, + email=email, role=role, uuid=uuid_r, groups=groups, admin_groups=admin_groups, ssh_key_pwd=ssh_key_pwd, is_active=is_active, @@ -190,7 +182,7 @@ def user_add(request): user_groups = [] for user_group_id in groups: user_groups.extend(UserGroup.objects.filter(id=user_group_id)) - print user_groups + except IndexError, e: error = u'添加用户 %s 失败 %s ' % (username, e) try: @@ -230,24 +222,20 @@ def user_list(request): @require_role(role='user') def user_detail(request): header_title, path1, path2 = '用户详情', '用户管理', '用户详情' - # if request.session.get('role_id') == 0: - # user_id = request.user.id - # else: - # user_id = request.GET.get('id', '') - # if request.session.get('role_id') == 1: - # user, dept = get_session_user_dept(request) - # if not validate(request, user=[user_id]): - # return HttpResponseRedirect('/') - user_id = request.GET.get('id', '') - if not user_id: + if request.session.get('role_id') == 0: + user_id = request.user.id + else: + user_id = request.GET.get('id', '') + + user = get_object(User, id=user_id) + if not user: return HttpResponseRedirect('/juser/user_list/') - user = User.objects.get(id=user_id) - # if user: - # pass - # asset_group_permed = user.get_asset_group() - # logs_last = Log.objects.filter(user=user.name).order_by('-start_time')[0:10] - # logs_all = Log.objects.filter(user=user.name).order_by('-start_time') - # logs_num = len(logs_all) + + user_perm_info = get_group_user_perm(user) + role_assets = user_perm_info.get('role') + user_log_ten = Log.objects.filter(user=user.username).order_by('id')[0:10] + user_log_last = Log.objects.filter(user=user.username).order_by('id')[0:50] + user_log_last_num = len(user_log_last) return my_render('juser/user_detail.html', locals(), request) @@ -262,21 +250,20 @@ def user_del(request): user_id_list = user_ids.split(',') else: return HttpResponse('错误请求') + for user_id in user_id_list: user = get_object(User, id=user_id) - if user: - # TODO: annotation by liuzheng, because useless for me - # assets = user_permed(user) - # result = _public_perm_api({'type': 'del_user', 'user': user, 'asset': assets}) - # print result + if user and user.username != 'admin': + logger.debug(u"删除用户 %s " % user.username) + bash('userdel -r %s' % user.username) user.delete() return HttpResponse('删除成功') @require_role('admin') def send_mail_retry(request): - user_uuid = request.GET.get('uuid', '1') - user = get_object(User, uuid=user_uuid) + uuid_r = request.GET.get('uuid', '1') + user = get_object(User, uuid=uuid_r) msg = u""" 跳板机地址: %s 用户名:%s @@ -305,36 +292,38 @@ def forget_password(request): """ % (user.name, URL, user.uuid, timestamp, hash_encode) send_mail('忘记跳板机密码', msg, MAIL_FROM, [email], fail_silently=False) msg = u'请登陆邮箱,点击邮件重设密码' - return HttpResponse(msg) + return http_success(request, msg) else: error = u'用户不存在或邮件地址错误' return render_to_response('juser/forget_password.html', locals()) +@require_role('user') def reset_password(request): - uuid = request.GET.get('uuid', '') + uuid_r = request.GET.get('uuid', '') timestamp = request.GET.get('timestamp', '') hash_encode = request.GET.get('hash', '') - action = '/juser/reset_password/?uuid=%s×tamp=%s&hash=%s' % (uuid, timestamp, hash_encode) + action = '/juser/reset_password/?uuid=%s×tamp=%s&hash=%s' % (uuid_r, timestamp, hash_encode) if request.method == 'POST': password = request.POST.get('password') password_confirm = request.POST.get('password_confirm') + print password, password_confirm if password != password_confirm: return HttpResponse('密码不匹配') else: - user = get_object(User, uuid=uuid) + user = get_object(User, uuid=uuid_r) if user: user.password = PyCrypt.md5_crypt(password) user.save() - return HttpResponse('密码重设成功') + return http_success(request, u'密码重设成功') else: return HttpResponse('用户不存在') - if hash_encode == PyCrypt.md5_crypt(uuid + timestamp + KEY): + if hash_encode == PyCrypt.md5_crypt(uuid_r + timestamp + KEY): if int(time.time()) - int(timestamp) > 600: - return HttpResponse('链接已超时') + return http_error(request, u'链接已超时') else: return render_to_response('juser/reset_password.html', locals()) @@ -401,24 +390,15 @@ def user_edit(request): send_mail('您的信息已修改', msg, MAIL_FROM, [email], fail_silently=False) return HttpResponseRedirect('/juser/user_list/') - return my_render('juser/user_edit.html', locals(), request) -# @require_role(role='admin') -def user_edit_adm(request): - pass - - def profile(request): - a = request.user.id - a = request.user.groups - user_id = request.user.id if not user_id: return HttpResponseRedirect('/') user = User.objects.get(id=user_id) - return render_to_response('juser/profile.html', locals(), context_instance=RequestContext(request)) + return my_render('juser/profile.html', locals(), request) def change_info(request): @@ -436,26 +416,24 @@ def change_info(request): if '' in [name, email]: error = '不能为空' - if len(password) > 0 and len(password) < 6: + + if len(password) < 6: error = '密码须大于6位' if not error: - # if password != user.password: - # password = CRYPTOR.md5_crypt(password) - User.objects.filter(id=user_id).update(name=name, email=email) if len(password) > 0: user.set_password(password) user.save() msg = '修改成功' - return render_to_response('juser/change_info.html', locals(), context_instance=RequestContext(request)) + return my_render('juser/change_info.html', locals(), request) @require_role(role='user') def regen_ssh_key(request): - uuid = request.GET.get('uuid', '') - user = get_object(User, uuid=uuid) + uuid_r = request.GET.get('uuid', '') + user = get_object(User, uuid=uuid_r) if not user: return HttpResponse('没有该用户') @@ -467,18 +445,17 @@ def regen_ssh_key(request): @require_role(role='user') def down_key(request): - user_id = '' if is_role_request(request, 'super'): - user_id = request.GET.get('id') + uuid_r = request.GET.get('uuid', '') + else: + uuid_r = request.user.uuid - if is_role_request(request, 'user'): - user_id = request.user.id - - if user_id: - user = get_object(User, id=user_id) + if uuid_r: + user = get_object(User, uuid=uuid_r) if user: username = user.username - private_key_file = os.path.join(KEY_DIR, 'user', username) + private_key_file = os.path.join(KEY_DIR, 'user', username+'.pem') + print private_key_file if os.path.isfile(private_key_file): f = open(private_key_file) data = f.read() @@ -486,13 +463,5 @@ def down_key(request): response = HttpResponse(data, content_type='application/octet-stream') response['Content-Disposition'] = 'attachment; filename=%s' % os.path.basename(private_key_file) return response - return HttpResponse('No Key File. Contact Admin.') -from jperm.perm_api import get_group_user_perm -@require_role(role='user') -def RunCommand(request): - if request.method == 'GET': - GUP = get_group_user_perm(request.user) - print GUP - assets = GUP.get('asset') - return render_to_response('juser/run_command.html', locals(), context_instance=RequestContext(request)) \ No newline at end of file + diff --git a/run_websocket.py b/run_websocket.py index 742220c6d..f80df243b 100644 --- a/run_websocket.py +++ b/run_websocket.py @@ -8,7 +8,7 @@ import sys import os.path import threading import datetime -import urllib +import re import tornado.ioloop import tornado.options @@ -23,8 +23,8 @@ from tornado.options import define, options from pyinotify import WatchManager, Notifier, ProcessEvent, IN_DELETE, IN_CREATE, IN_MODIFY, AsyncNotifier import select -from connect import Tty, User, Asset, PermRole, logger, get_object -from connect import TtyLog, Log, Session, user_have_perm +from connect import Tty, User, Asset, PermRole, logger, get_object, PermRole, gen_resource +from connect import TtyLog, Log, Session, user_have_perm, get_group_user_perm, MyRunner, ExecLog try: import simplejson as json @@ -67,22 +67,6 @@ def require_auth(role='user'): except AttributeError: pass logger.warning('Websocket: Request auth failed.') - # asset_id = int(request.get_argument('id', 9999)) - # print asset_id - # asset = Asset.objects.filter(id=asset_id) - # if asset: - # asset = asset[0] - # request.asset = asset - # else: - # request.close() - # - # if user: - # user = user[0] - # request.user = user - # - # else: - # print("No session user.") - # request.close() return _deco2 return _deco @@ -138,6 +122,7 @@ class Application(tornado.web.Application): (r'/monitor', MonitorHandler), (r'/terminal', WebTerminalHandler), (r'/kill', WebTerminalKillHandler), + (r'/exec', ExecHandler), ] setting = { @@ -206,7 +191,6 @@ class MonitorHandler(tornado.websocket.WebSocketHandler): class WebTty(Tty): def __init__(self, *args, **kwargs): super(WebTty, self).__init__(*args, **kwargs) - self.login_type = 'web' self.ws = None self.data = '' self.input_mode = False @@ -225,6 +209,82 @@ class WebTerminalKillHandler(tornado.web.RequestHandler): logger.debug('Websocket: web terminal client num: %s' % len(WebTerminalHandler.clients)) +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 + + @require_auth('user') + def open(self): + logger.debug('Websocket: Open exec request') + role_name = self.get_argument('role', 'sb') + self.remote_ip = self.request.remote_ip + 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', '') + command = data.get('command', '') + asset_name_str = '' + if pattern and command: + for inv in self.runner.inventory.get_hosts(pattern=pattern): + asset_name_str += '%s ' % inv.name + self.write_message('匹配主机: ' + asset_name_str) + self.write_message('Ansible> %s\n\n' % command) + self.__class__.tasks.append(MyThread(target=self.run_cmd, args=(command, pattern))) + ExecLog(host=asset_name_str, cmd=command, user=self.user.username, remote_ip=self.remote_ip).save() + + 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) + for k, v in self.runner.results.items(): + for host, output in v.items(): + if k == 'ok': + header = "[ %s => %s]\n" % (host, 'Ok') + else: + header = "[ %s => %s]\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请求') + + class WebTerminalHandler(tornado.websocket.WebSocketHandler): clients = [] tasks = [] @@ -236,6 +296,8 @@ class WebTerminalHandler(tornado.websocket.WebSocketHandler): 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): @@ -250,7 +312,7 @@ class WebTerminalHandler(tornado.websocket.WebSocketHandler): if asset: roles = user_have_perm(self.user, asset) logger.debug(roles) - logger.debug('rolename: %s' % role_name) + logger.debug('角色: %s' % role_name) login_role = '' for role in roles: if role.name == role_name: @@ -267,10 +329,10 @@ class WebTerminalHandler(tornado.websocket.WebSocketHandler): return logger.debug('Websocket: request web terminal Host: %s User: %s Role: %s' % (asset.hostname, self.user.username, login_role.name)) - self.term = WebTty(self.user, asset, login_role) + self.term = WebTty(self.user, asset, login_role, login_type='web') self.term.remote_ip = self.request.remote_ip - self.term.get_connection() - self.term.channel = self.term.ssh.invoke_shell(term='xterm') + self.ssh = self.term.get_connection() + self.channel = self.ssh.invoke_shell(term='xterm') WebTerminalHandler.tasks.append(MyThread(target=self.forward_outbound)) WebTerminalHandler.clients.append(self) @@ -303,7 +365,7 @@ class WebTerminalHandler(tornado.websocket.WebSocketHandler): self.term.vim_data = '' self.term.data = '' self.term.input_mode = False - self.term.channel.send(data['data']) + self.channel.send(data['data']) def on_close(self): logger.debug('Websocket: Close request') @@ -326,9 +388,9 @@ class WebTerminalHandler(tornado.websocket.WebSocketHandler): data = '' pre_timestamp = time.time() while True: - r, w, e = select.select([self.term.channel, sys.stdin], [], []) - if self.term.channel in r: - recv = self.term.channel.recv(1024) + 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 @@ -347,8 +409,8 @@ class WebTerminalHandler(tornado.websocket.WebSocketHandler): data = '' except UnicodeDecodeError: pass - finally: - self.close() + except IndexError: + pass if __name__ == '__main__': tornado.options.parse_command_line() diff --git a/static/css/style.css b/static/css/style.css index b8a15d909..2dc7a2591 100644 --- a/static/css/style.css +++ b/static/css/style.css @@ -2822,7 +2822,9 @@ body.body-small .footer.fixed { .table > thead > tr > td, .table > tbody > tr > td, .table > tfoot > tr > td { - border-top: 1px solid #e7eaec; + /*border-top: 1px solid #e7eaec;*/ + border-bottom: 1px solid #e7eaec; + border-top: none; line-height: 1.42857; padding: 8px; vertical-align: top; diff --git a/static/files/excels/asset.xlsx b/static/files/excels/asset.xlsx new file mode 100644 index 000000000..76ff02165 Binary files /dev/null and b/static/files/excels/asset.xlsx differ diff --git a/static/js/base.js b/static/js/base.js index a8aa99b70..b872558f3 100644 --- a/static/js/base.js +++ b/static/js/base.js @@ -16,9 +16,9 @@ function check_all(form) { } } -function checkAll(){ - var checklist = document.getElementsByName ("checked"); - if(document.getElementById("check_all").checked) +function checkAll(id, name){ + var checklist = document.getElementsByName(name); + if(document.getElementById(id).checked) { for(var i=0;i - @@ -12,8 +11,6 @@ {% include 'link_css.html' %} {% include 'head_script.html' %} {% block self_head_css_js %} {% endblock %} - - diff --git a/templates/download.html b/templates/download.html index b1da555cf..3be809660 100644 --- a/templates/download.html +++ b/templates/download.html @@ -1,11 +1,16 @@ {% extends 'base.html' %} {% load mytags %} +{% block self_head_css_js %} + + + +{% endblock %} {% block content %} {% include 'nav_cat_bar.html' %}
-
+
下载文件
@@ -16,30 +21,62 @@ -
-

下载文件可联系管理员在服务器安装lrzsz,使用sz命令下载。

-
- {% for document in documents %} - {{ document }} - {% endfor %} -
- +
+ {% if error %} +
{{ error }}
+ {% endif %} + {% if msg %} +
{{ msg }}
+ {% endif %} +
+ +
+ +
+
+
+
+ +
+ +
+
+
+
+ + +
+
+
+{% endblock %} +{% block self_footer_js %} + {% endblock %} \ No newline at end of file diff --git a/templates/exec_cmd.html b/templates/exec_cmd.html new file mode 100644 index 000000000..b3a9d8ffe --- /dev/null +++ b/templates/exec_cmd.html @@ -0,0 +1,198 @@ + + + + + Jumpserver Exec Terminal + + + + +
+
+

+
+ + +
+ + + + + + +
+
+ \ No newline at end of file diff --git a/templates/footer.html b/templates/footer.html index 0f1f6f76e..dc2bed153 100644 --- a/templates/footer.html +++ b/templates/footer.html @@ -1,6 +1,6 @@