diff --git a/.gitignore b/.gitignore index 983fedd49..e300831e4 100644 --- a/.gitignore +++ b/.gitignore @@ -37,6 +37,7 @@ nosetests.xml .mr.developer.cfg .project .pydevproject +.settings *.log logs/* keys/* diff --git a/README.md b/README.md index 448ce6711..947b4b5e9 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,7 @@ +## 写在前面 + - 目前本版本处于beta阶段,请不要用于生产环境,除非你知道你在做什么 + - 本版本暂时没加入LDAP接口,稳定版会将LDAP和无Agent方式抽象成API,2.x版本支持LDAP,请移步release中下载 + #欢迎使用Jumpserver **Jumpserver** 是一款由python编写开源的跳板机(堡垒机)系统,实现了跳板机应有的功能。基于ssh协议来管理,客户端无需安装agent。 支持常见系统: diff --git a/connect.py b/connect.py index 37c9a126c..0be5abdfb 100755 --- a/connect.py +++ b/connect.py @@ -21,7 +21,7 @@ from io import open as copen import uuid os.environ['DJANGO_SETTINGS_MODULE'] = 'jumpserver.settings' -if django.get_version() != '1.6': +if not django.get_version().startswith('1.6'): setup = django.setup() from django.contrib.sessions.models import Session from jumpserver.api import ServerError, User, Asset, PermRole, AssetGroup, get_object, mkdir, get_asset_info @@ -33,7 +33,10 @@ from jperm.ansible_api import MyRunner from jlog.models import ExecLog, FileLog login_user = get_object(User, username=getpass.getuser()) -remote_ip = os.popen("who -m | awk '{ print $NF }'").read().strip('()\n') +try: + remote_ip = os.environ.get('SSH_CLIENT').split()[0] +except (IndexError, AttributeError): + remote_ip = os.popen("who -m | awk '{ print $NF }'").read().strip('()\n') try: import termios @@ -412,7 +415,10 @@ class SshTty(Tty): pass if sys.stdin in r: - x = os.read(sys.stdin.fileno(), 4096) + try: + x = os.read(sys.stdin.fileno(), 4096) + except OSError: + pass input_mode = True if str(x) in ['\r', '\n', '\r\n']: if self.vim_flag: @@ -576,12 +582,15 @@ class Nav(object): role = role_check[int(role_id)] elif len(roles) == 1: # 授权角色数为1 role = roles[0] + else: + color_print('当前用户未被授予角色,无法执行任何操作,如有疑问请联系管理员。') + return assets = list(self.user_perm.get('role', {}).get(role).get('asset')) # 获取该用户,角色授权主机 print "授权包含该系统用户的所有主机" for asset in assets: print ' %s' % asset.hostname print - print "请输入主机名或ansile支持的pattern, 多个主机:分隔, q退出" + print "请输入主机名或ansible支持的pattern, 多个主机:分隔, q退出" pattern = raw_input("\033[1;32mPattern>:\033[0m ").strip() if pattern == 'q': break @@ -623,7 +632,7 @@ class Nav(object): self.user_perm = get_group_user_perm(self.user) try: print "进入批量上传模式" - print "请输入主机名或ansile支持的pattern, 多个主机:分隔 q退出" + print "请输入主机名或ansible支持的pattern, 多个主机:分隔 q退出" pattern = raw_input("\033[1;32mPattern>:\033[0m ").strip() if pattern == 'q': break @@ -676,7 +685,7 @@ class Nav(object): self.user_perm = get_group_user_perm(self.user) try: print "进入批量下载模式" - print "请输入主机名或ansile支持的pattern, 多个主机:分隔,q退出" + print "请输入主机名或ansible支持的pattern, 多个主机:分隔,q退出" pattern = raw_input("\033[1;32mPattern>:\033[0m ").strip() if pattern == 'q': break @@ -800,7 +809,7 @@ def main(): color_print('请输入正确ID', 'red') except ServerError, e: color_print(e, 'red') - except Exception, e: + except IndexError, e: color_print(e) time.sleep(5) pass diff --git a/install/install.py b/install/install.py index 5552d3c20..c5765ff12 100755 --- a/install/install.py +++ b/install/install.py @@ -12,6 +12,8 @@ import socket import fcntl import struct import readline +import random +import string jms_dir = os.path.dirname(os.path.abspath(os.path.dirname(__file__))) sys.path.append(jms_dir) @@ -71,12 +73,15 @@ class PreSetup(object): self.mail_addr = 'hello@jumpserver.org' self.mail_pass = '' self.ip = '' + self.key = ''.join(random.choice(string.ascii_lowercase + string.digits) \ + for _ in range(16)) def write_conf(self, conf_file=os.path.join(jms_dir, 'jumpserver.conf')): color_print('开始写入配置文件', 'green') conf = ConfigParser.ConfigParser() conf.read(conf_file) conf.set('base', 'url', 'http://%s' % self.ip) + conf.set('base', 'key', self.key) conf.set('db', 'host', self.db_host) conf.set('db', 'port', self.db_port) conf.set('db', 'user', self.db_user) @@ -96,6 +101,7 @@ class PreSetup(object): color_print('默认用户名: %s 默认密码: %s' % (self.db_user, self.db_pass), 'green') bash('yum -y install mysql-server') bash('service mysqld start') + bash('chkconfig mysqld on') bash('mysql -e "create database %s default charset=utf8"' % self.db) bash('mysql -e "grant all on %s.* to \'%s\'@\'%s\' identified by \'%s\'"' % (self.db, self.db_user, @@ -105,6 +111,7 @@ class PreSetup(object): @staticmethod def _set_env(): color_print('开始关闭防火墙和selinux', 'green') + os.system("export LANG='en_US.UTF-8' && sed -i 's/LANG=.*/LANG=en_US.UTF-8/g' /etc/sysconfig/i18n") bash('service iptables stop && chkconfig iptables off && setenforce 0') def _test_db_conn(self): diff --git a/install/next.py b/install/next.py index a68eb66bc..e3dc312fa 100755 --- a/install/next.py +++ b/install/next.py @@ -18,7 +18,7 @@ if django.get_version() != '1.6': from juser.user_api import db_add_user, get_object, User from install import color_print -from jumpserver.api import get_mac_address +from jumpserver.api import get_mac_address, bash socket.setdefaulttimeout(2) @@ -81,9 +81,10 @@ class Setup(object): os.system('id %s &> /dev/null || useradd %s' % (self.admin_user, self.admin_user)) @staticmethod - def _ensure_sh(): - jshell = os.path.join(jms_dir, 'connect.py') - os.chmod(jshell, 0755) + def _cp_zzsh(): + os.chdir(os.path.join(jms_dir, 'install')) + shutil.copy('zzjumpserver.sh', '/etc/profile.d/') + bash("sed -i 's#/opt/jumpserver#%s#g' /etc/profile.d/zzjumpserver.sh" % jms_dir) @staticmethod def _run_service(): @@ -97,7 +98,7 @@ class Setup(object): self._sync_db() self._input_admin() self._create_admin() - self._ensure_sh() + self._cp_zzsh() self._run_service() diff --git a/install/zzjumpserver.sh b/install/zzjumpserver.sh new file mode 100644 index 000000000..516b0466e --- /dev/null +++ b/install/zzjumpserver.sh @@ -0,0 +1,13 @@ +#!/bin/bash + +export LANG='zh_CN.UTF-8' + +if [ "$USER" != "admin" ] && [ "$USER" != "root" ];then + python /opt/jumpserver/connect.py + if [ $USER == 'guanghongwei' ];then + echo + else + exit 3 + echo + fi +fi diff --git a/jasset/asset_api.py b/jasset/asset_api.py index ca733cad0..7cc49fa66 100644 --- a/jasset/asset_api.py +++ b/jasset/asset_api.py @@ -311,13 +311,12 @@ def excel_to_db(excel_file): def get_ansible_asset_info(asset_ip, setup_info): - print asset_ip + print setup_info, '***' disk_need = {} 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'): + if disk_name.startswith('sd') or disk_name.startswith('hd') or disk_name.startswith('vd') or disk_name.startswith('xvd'): disk_size = disk_info.get("size", '') if 'M' in disk_size: disk_format = round(float(disk_size[:-2]) / 1000, 0) @@ -334,7 +333,7 @@ def get_ansible_asset_info(asset_ip, setup_info): mac = setup_info.get("ansible_default_ipv4").get("macaddress") brand = setup_info.get("ansible_product_name") cpu_type = setup_info.get("ansible_processor")[1] - cpu_cores = setup_info.get("ansible_processor_count") + cpu_cores = setup_info.get("ansible_processor_vcpus") cpu = cpu_type + ' * ' + unicode(cpu_cores) memory = setup_info.get("ansible_memtotal_mb") try: diff --git a/jasset/models.py b/jasset/models.py index 2851b7d56..82c5f54a6 100644 --- a/jasset/models.py +++ b/jasset/models.py @@ -75,7 +75,7 @@ class Asset(models.Model): brand = models.CharField(max_length=64, blank=True, null=True, verbose_name=u'硬件厂商型号') cpu = models.CharField(max_length=64, blank=True, null=True, verbose_name=u'CPU') memory = models.CharField(max_length=128, blank=True, null=True, verbose_name=u'内存') - disk = models.CharField(max_length=128, blank=True, null=True, verbose_name=u'硬盘') + disk = models.CharField(max_length=1024, 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"系统平台") diff --git a/jasset/views.py b/jasset/views.py index aabe0665d..779306580 100644 --- a/jasset/views.py +++ b/jasset/views.py @@ -227,7 +227,7 @@ def asset_edit(request): if use_default_auth: af_save.username = '' af_save.password = '' - af_save.port = None + # af_save.port = None else: if password: password_encode = CRYPTOR.encrypt(password) diff --git a/jlog/log_api.py b/jlog/log_api.py index b10325643..6f2d6edbe 100644 --- a/jlog/log_api.py +++ b/jlog/log_api.py @@ -6,7 +6,10 @@ from contextlib import closing from io import open as copen from json import dumps from math import ceil +import datetime +import time import re +import os from os.path import basename, dirname, exists, join from struct import unpack from subprocess import Popen @@ -17,6 +20,7 @@ from jinja2 import FileSystemLoader, Template from jinja2.environment import Environment from jumpserver.api import BASE_DIR +from jlog.models import Log DEFAULT_TEMPLATE = join(BASE_DIR, 'templates', 'jlog', 'static.jinja2') @@ -75,3 +79,28 @@ def renderTemplate(script_path, time_file_path, dimensions=(24, 80), templatenam return rendered +def kill_invalid_connection(): + long_time_logs = [] + unfinished_logs = Log.objects.filter(is_finished=False) + now = datetime.datetime.now() + now_timestamp = int(time.mktime(now.timetuple())) + for log in unfinished_logs: + if (now - log.start_time).days > 1: + long_time_logs.append(log) + + for log in long_time_logs: + try: + log_file_mtime = int(os.stat(log.log_path).st_mtime) + except OSError: + log_file_mtime = 0 + + if (now_timestamp - log_file_mtime) > 3600: + try: + os.kill(int(log.pid), 9) + except OSError: + pass + + log.is_finished = True + log.end_time = now + log.save() + diff --git a/jlog/views.py b/jlog/views.py index ff0eaf1c0..e2565250e 100644 --- a/jlog/views.py +++ b/jlog/views.py @@ -66,7 +66,7 @@ def log_list(request, offset): contact_list, p, contacts, page_range, current_page, show_first, show_end = pages(posts, request) - web_monitor_uri = 'ws://%s/monitor' % WEB_SOCKET_HOST + web_monitor_uri = '%s/monitor' % WEB_SOCKET_HOST web_kill_uri = 'http://%s/kill' % WEB_SOCKET_HOST session_id = request.session.session_key return render_to_response('jlog/log_%s.html' % offset, locals(), context_instance=RequestContext(request)) diff --git a/jperm/ansible_api.py b/jperm/ansible_api.py index 953271917..20725908c 100644 --- a/jperm/ansible_api.py +++ b/jperm/ansible_api.py @@ -365,6 +365,16 @@ class MyTask(MyRunner): self.run("user", module_args, become=True) return self.results + def del_user_sudo(self, username): + """ + delete a role sudo item + :param username: + :return: + """ + module_args = "sed -i 's/^%s.*//' /etc/sudoers" % username + self.run("command", module_args, become=True) + return self.results + @staticmethod def gen_sudo_script(role_list, sudo_list): # receive role_list = [role1, role2] sudo_list = [sudo1, sudo2] diff --git a/jperm/views.py b/jperm/views.py index db05783bd..a8a66128b 100644 --- a/jperm/views.py +++ b/jperm/views.py @@ -16,8 +16,8 @@ from jperm.perm_api import get_role_info, get_role_push_host from jumpserver.api import my_render, get_object, CRYPTOR # 设置PERM APP Log -from jumpserver.settings import LOG_LEVEL -logger = set_log(LOG_LEVEL, filename='jumpserver_perm.log') +from jumpserver.api import logger +#logger = set_log(LOG_LEVEL, filename='jumpserver_perm.log') @require_role('admin') @@ -277,7 +277,7 @@ def perm_role_add(request): if request.method == "POST": # 获取参数: name, comment - name = request.POST.get("role_name", "") + name = request.POST.get("role_name", "").strip() comment = request.POST.get("role_comment", "") password = request.POST.get("role_password", "") key_content = request.POST.get("role_key", "") @@ -286,6 +286,8 @@ def perm_role_add(request): try: if get_object(PermRole, name=name): raise ServerError(u'已经存在该用户 %s' % name) + if name == "root": + raise ServerError(u'禁止使用root用户作为系统用户,这样非常危险!') default = get_object(Setting, name='default') if password: @@ -317,14 +319,37 @@ def perm_role_delete(request): """ delete role page """ + if request.method == "GET": + try: + # 获取参数删除的role对象 + role_id = request.GET.get("id") + role = get_object(PermRole, id=role_id) + if not role: + logger.warning(u"Delete Role: role_id %s not exist" % role_id) + raise ServerError(u"role_id %s 无数据记录" % role_id) + # 删除推送到主机上的role + filter_type = request.GET.get("filter_type") + print filter_type + if filter_type: + if filter_type == "recycle_assets": + recycle_assets = [push.asset for push in role.perm_push.all() if push.success] + print recycle_assets + recycle_assets_ip = ','.join([asset.ip for asset in recycle_assets]) + return HttpResponse(recycle_assets_ip) + else: + return HttpResponse("no such filter_type: %s" % filter_type) + else: + return HttpResponse("filter_type: ?") + except ServerError, e: + return HttpResponse(e) if request.method == "POST": try: # 获取参数删除的role对象 role_id = request.POST.get("id") role = get_object(PermRole, id=role_id) if not role: - logger.warning(u"Delete Role: %s not exist" % role.name) - raise ServerError(u"%s 无数据记录" % role.name) + logger.warning(u"Delete Role: role_id %s not exist" % role_id) + raise ServerError(u"role_id %s 无数据记录" % role_id) role_key = role.key_path # 删除推送到主机上的role recycle_assets = [push.asset for push in role.perm_push.all() if push.success] @@ -333,11 +358,13 @@ def perm_role_delete(request): recycle_resource = gen_resource(recycle_assets) task = MyTask(recycle_resource) try: - msg = task.del_user(get_object(PermRole, id=role_id).name) + msg_del_user = task.del_user(get_object(PermRole, id=role_id).name) + msg_del_sudo = task.del_user_sudo(get_object(PermRole, id=role_id).name) except Exception, e: logger.warning(u"Recycle Role failed: %s" % e) raise ServerError(u"回收已推送的系统用户失败: %s" % e) - logger.info(u"delete role %s - execute delete user: %s" % (role.name, msg)) + logger.info(u"delete role %s - execute delete user: %s" % (role.name, msg_del_user)) + logger.info(u"delete role %s - execute delete sudo: %s" % (role.name, msg_del_sudo)) # TODO: 判断返回结果,处理异常 # 删除存储的秘钥,以及目录 try: @@ -423,6 +450,9 @@ def perm_role_edit(request): if not role: raise ServerError('该系统用户不能存在') + if role_name == "root": + raise ServerError(u'禁止使用root用户作为系统用户,这样非常危险!') + if role_password: encrypt_pass = CRYPTOR.encrypt(role_password) role.password = encrypt_pass @@ -473,6 +503,7 @@ def perm_role_push(request): for asset_group in asset_groups_obj: group_assets_obj.extend(asset_group.asset_set.all()) calc_assets = list(set(assets_obj) | set(group_assets_obj)) + push_resource = gen_resource(calc_assets) # 调用Ansible API 进行推送 @@ -577,25 +608,28 @@ def perm_sudo_add(request): """ # 渲染数据 header_title, path1, path2 = "Sudo命令", "别名管理", "添加别名" + try: + if request.method == "POST": + # 获取参数: name, comment + name = request.POST.get("sudo_name").strip().upper() + comment = request.POST.get("sudo_comment").strip() + commands = request.POST.get("sudo_commands").strip() - if request.method == "POST": - # 获取参数: name, comment - name = request.POST.get("sudo_name").strip().upper() - comment = request.POST.get("sudo_comment").strip() - commands = request.POST.get("sudo_commands").strip() + if not name or not commands: + raise ServerError(u"sudo name 和 commands是必填项!") - 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) - sudo.save() - msg = u"添加Sudo命令别名: %s" % name - # 渲染数据 + 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) + sudo.save() + msg = u"添加Sudo命令别名: %s" % name + except ServerError, e: + error = e return my_render('jperm/perm_sudo_add.html', locals(), request) @@ -612,22 +646,27 @@ def perm_sudo_edit(request): sudo_id = request.GET.get("id") sudo = PermSudo.objects.get(id=sudo_id) - if request.method == "POST": - name = request.POST.get("sudo_name").upper() - commands = request.POST.get("sudo_commands") - comment = request.POST.get("sudo_comment") + try: + if request.method == "POST": + 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)) + if not name or not commands: + raise ServerError(u"sudo name 和 commands是必填项!") - sudo.name = name.strip() - sudo.commands = commands - sudo.comment = comment - sudo.save() + pattern = re.compile(r'[\n,\r]') + commands = ', '.join(list_drop_str(pattern.split(commands), u'')).strip() + logger.debug(u'添加sudo %s: %s' % (name, commands)) - msg = u"更新命令别名: %s" % name + sudo.name = name.strip() + sudo.commands = commands + sudo.comment = comment + sudo.save() + msg = u"更新命令别名: %s" % name + except ServerError, e: + error = e return my_render('jperm/perm_sudo_edit.html', locals(), request) diff --git a/jumpserver/api.py b/jumpserver/api.py index 68a013870..691aafb11 100644 --- a/jumpserver/api.py +++ b/jumpserver/api.py @@ -64,7 +64,6 @@ def get_asset_info(asset): info = {'hostname': asset.hostname, 'ip': asset.ip} if asset.use_default_auth: if default: - info['port'] = int(default.field2) info['username'] = default.field1 try: info['password'] = CRYPTOR.decrypt(default.field3) @@ -73,9 +72,12 @@ def get_asset_info(asset): if os.path.isfile(default.field4): info['ssh_key'] = default.field4 else: - info['port'] = int(asset.port) info['username'] = asset.username info['password'] = CRYPTOR.decrypt(asset.password) + try: + info['port'] = int(asset.port) + except TypeError: + info['port'] = int(default.field2) return info diff --git a/jumpserver/settings.py b/jumpserver/settings.py index fa8431272..d1c8a8f40 100644 --- a/jumpserver/settings.py +++ b/jumpserver/settings.py @@ -152,5 +152,6 @@ STATIC_URL = '/static/' BOOTSTRAP_COLUMN_COUNT = 10 CRONJOBS = [ - ('0 1 * * *', 'jasset.asset_api.asset_ansible_update_all') + ('0 1 * * *', 'jasset.asset_api.asset_ansible_update_all'), + ('1 * * * *', 'jlog.log_api.kill_invalid_connection'), ] diff --git a/jumpserver/views.py b/jumpserver/views.py index 766ae021f..afe5ddd38 100644 --- a/jumpserver/views.py +++ b/jumpserver/views.py @@ -344,7 +344,7 @@ def download(request): 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) + web_terminal_uri = '%s/exec?role=%s' % (WEB_SOCKET_HOST, role) return my_render('exec_cmd.html', locals(), request) @@ -356,7 +356,7 @@ def web_terminal(request): if asset: print asset hostname = asset.hostname - web_terminal_uri = 'ws://%s/terminal?id=%s&role=%s' % (WEB_SOCKET_HOST, asset_id, role_name) + web_terminal_uri = '%s/terminal?id=%s&role=%s' % (WEB_SOCKET_HOST, asset_id, role_name) return render_to_response('jlog/web_terminal.html', locals()) diff --git a/juser/user_api.py b/juser/user_api.py index 2c16c3dc7..a6fc7648a 100644 --- a/juser/user_api.py +++ b/juser/user_api.py @@ -151,8 +151,8 @@ def server_add_user(username, password, ssh_key_pwd='', ssh_key_login_need=True) add a system user in jumpserver 在jumpserver服务器上添加一个用户 """ - bash("useradd -s %s/connect.py '%s'; echo '%s'; echo '%s' | passwd --stdin '%s'" % - (BASE_DIR, username, password, password, username)) + bash("useradd '%s'; echo '%s'; echo '%s:%s' | chpasswd " % + (username, password, username, password)) if ssh_key_login_need: gen_ssh_key(username, ssh_key_pwd) diff --git a/juser/views.py b/juser/views.py index ed2c11f2a..41baa7536 100644 --- a/juser/views.py +++ b/juser/views.py @@ -6,7 +6,7 @@ # from Crypto.PublicKey import RSA import uuid from django.contrib.auth.decorators import login_required - +from django.shortcuts import get_object_or_404 from django.db.models import Q from juser.user_api import * from jperm.perm_api import get_group_user_perm @@ -111,22 +111,18 @@ def group_edit(request): if len(UserGroup.objects.filter(name=group_name)) > 1: raise ServerError(u'%s 用户组已存在' % group_name) # add user group + user_group = get_object_or_404(UserGroup, id=group_id) + user_group.user_set.clear() + for user in User.objects.filter(id__in=users_selected): user.group.add(UserGroup.objects.get(id=group_id)) - # delete user group - user_group = UserGroup.objects.get(id=group_id) - for user in [user for user in User.objects.filter(group=user_group) if user not in User.objects.filter(id__in=users_selected)]: - user_group_all = user.group.all() - user.group.clear() - for g in user_group_all: - 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: return HttpResponseRedirect(reverse('user_group_list')) else: @@ -313,6 +309,12 @@ def reset_password(request): hash_encode = request.GET.get('hash', '') action = '/juser/password/reset/?uuid=%s×tamp=%s&hash=%s' % (uuid_r, timestamp, hash_encode) + if hash_encode == PyCrypt.md5_crypt(uuid_r + timestamp + KEY): + if int(time.time()) - int(timestamp) > 600: + return http_error(request, u'链接已超时') + else: + return HttpResponse('hash校验失败') + if request.method == 'POST': password = request.POST.get('password') password_confirm = request.POST.get('password_confirm') @@ -328,11 +330,8 @@ def reset_password(request): else: return HttpResponse('用户不存在') - if hash_encode == PyCrypt.md5_crypt(uuid_r + timestamp + KEY): - if int(time.time()) - int(timestamp) > 600: - return http_error(request, u'链接已超时') - else: - return render_to_response('juser/reset_password.html', locals()) + else: + return render_to_response('juser/reset_password.html', locals()) return http_error(request, u'错误请求') diff --git a/run_websocket.py b/run_websocket.py index 4a1a185dc..ec6b2f73f 100755 --- a/run_websocket.py +++ b/run_websocket.py @@ -230,15 +230,14 @@ class ExecHandler(tornado.websocket.WebSocketHandler): def on_message(self, message): data = json.loads(message) pattern = data.get('pattern', '') - command = data.get('command', '') - asset_name_str = '' - if pattern and command: + self.command = data.get('command', '') + self.asset_name_str = '' + if pattern and self.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() + self.asset_name_str += '%s ' % inv.name + self.write_message('匹配主机: ' + self.asset_name_str) + self.write_message('Ansible> %s\n\n' % self.command) + self.__class__.tasks.append(MyThread(target=self.run_cmd, args=(self.command, pattern))) for t in self.__class__.tasks: if t.is_alive(): @@ -251,11 +250,12 @@ class ExecHandler(tornado.websocket.WebSocketHandler): def run_cmd(self, command, pattern): self.runner.run('shell', command, pattern=pattern) + ExecLog(host=self.asset_name_str, cmd=self.command, user=self.user.username, + remote_ip=self.remote_ip, result=self.runner.results).save() newline_pattern = re.compile(r'\n') for k, v in self.runner.results.items(): for host, output in v.items(): output = newline_pattern.sub('
', output) - logger.debug(output) if k == 'ok': header = "[ %s => %s]\n" % (host, 'Ok') else: diff --git a/templates/exec_cmd.html b/templates/exec_cmd.html index 754105cee..d1eda2fcb 100644 --- a/templates/exec_cmd.html +++ b/templates/exec_cmd.html @@ -23,7 +23,13 @@ -{% endblock %} \ No newline at end of file +{% endblock %} diff --git a/templates/jasset/asset_list.html b/templates/jasset/asset_list.html index 1da808c3c..b87b6e177 100644 --- a/templates/jasset/asset_list.html +++ b/templates/jasset/asset_list.html @@ -243,8 +243,9 @@ area: ['628px', '420px'], content: new_url+data }); + window.open(new_url+data, '_blank', 'toolbar=yes, location=yes, scrollbars=yes, resizable=yes, copyhistory=yes, width=628, height=400') */ - window.open(new_url+data, '', 'width=628px, height=420px') + window.open(new_url+data, '', 'width=628px, height=380px'); } else if (dataArray.length == 1 && data != 'error'){ /*layer.open({ type: 2, @@ -255,7 +256,7 @@ content: new_url+data }); */ - window.open(new_url+data, '', 'width=628px, height=440px') + window.open(new_url+data, '_blank', 'toolbar=yes, location=yes, copyhistory=yes, scrollbars=yes, width=628, height=410'); } else { @@ -292,7 +293,7 @@ content: new_url }); */ - window.open(new_url, '', 'height=628px, width=420px') + window.open(new_url, '_blank', 'toolbar=yes, location=yes, copyhistory=yes, scrollbars=yes, width=628, height=400') } else { /* @@ -305,7 +306,7 @@ content: new_url }); */ - window.open(new_url, '', 'height=628px, width=452px') + window.open(new_url, '_blank', 'toolbar=yes, location=yes, copyhistory=yes, scrollbars=yes, width=628, height=410'); } return false @@ -429,4 +430,4 @@ }); -{% endblock %} \ No newline at end of file +{% endblock %} diff --git a/templates/jlog/log_online.html b/templates/jlog/log_online.html index 14e55f957..f82969156 100644 --- a/templates/jlog/log_online.html +++ b/templates/jlog/log_online.html @@ -136,14 +136,20 @@ {# })#} {# });#} function init(obj){ + var protocol = "ws://"; + if (window.location.protocol == 'https:') { + protocol = 'wss://'; + } + var file_path = obj.attr('file_path'); - var wsUri = '{{ web_monitor_uri }}'; + var wsUri = protocol + '{{ web_monitor_uri }}'; var socket = new WebSocket(wsUri + '?file_path=' + file_path); var term = new Terminal({ - cols: 80, - rows: 24, - screenKeys: false + cols: 80, + rows: 24, + screenKeys: false, + handler: function(){return false} }); var tag = $('
'); diff --git a/templates/jlog/web_terminal.html b/templates/jlog/web_terminal.html index e5b864a79..420249a10 100644 --- a/templates/jlog/web_terminal.html +++ b/templates/jlog/web_terminal.html @@ -43,7 +43,12 @@ } WSSHClient.prototype.connect = function(options) { - var endpoint = '{{ web_terminal_uri }}'; + var protocol = "ws://"; + if (window.location.protocol == 'https:') { + protocol = 'wss://'; + } + + var endpoint = protocol + '{{ web_terminal_uri }}'; if (window.WebSocket) { this._connection = new WebSocket(endpoint); @@ -112,7 +117,7 @@ $('.terminal').css('width', window.innerWidth-25); console.log(window.innerWidth); console.log(window.innerWidth-10); - var rows = Math.floor(window.innerHeight/rowHeight) - 1; + var rows = Math.floor(window.innerHeight/rowHeight) - 2; var cols = Math.floor(window.innerWidth/colWidth) - 1; return {rows: rows, cols: cols}; diff --git a/templates/jperm/perm_role_add.html b/templates/jperm/perm_role_add.html index 70ad918d8..59ec3b3b6 100644 --- a/templates/jperm/perm_role_add.html +++ b/templates/jperm/perm_role_add.html @@ -92,11 +92,9 @@ $('#roleForm').validator({ timely: 2, theme: "yellow_right_effect", rules: { - check_name: [/^\w{2,20}$/, '大小写字母数字和下划线,2-20位'], - check_begin: [/^[\-]+BEGIN RSA PRIVATE KEY[\-]+/gm, 'RSA Key填写有误,请检查'], -{# either: function(){#} -{# return $('#role_password').val() == ''#} -{# }#} + check_name: [/(?!^root$)^[\w.]{2,20}$/i, '大小写字母数字和下划线小数点,2-20位,并且非root'], + check_begin: [/^[\-]+BEGIN R|DSA PRIVATE KEY[\-]+/gm, 'RSA|DSA Key填写有误,请检查'] + }, fields: { @@ -110,13 +108,8 @@ $('#roleForm').validator({ rule: "check_begin", ok: "", empty: true - }, -{# "role_key": {#} -{# rule: "required(either)",#} -{# tip: "输入密钥",#} -{# ok: "",#} -{# msg: {required: "密码和密钥必填一个!"}#} -{# }#} + } + }, valid: function(form) { form.submit(); diff --git a/templates/jperm/perm_role_edit.html b/templates/jperm/perm_role_edit.html index 33ab47e0e..420eb300a 100644 --- a/templates/jperm/perm_role_edit.html +++ b/templates/jperm/perm_role_edit.html @@ -94,16 +94,24 @@ $('#roleForm').validator({ timely: 2, theme: "yellow_right_effect", rules: { - check_name: [/^\w{2,20}$/, '大小写字母数字和下划线,2-20位'] + check_name: [/(?!^root$)^\w{2,20}$/i, '大小写字母数字和下划线,2-20位,并且非root'], + check_begin: [/^[\-]+BEGIN RSA PRIVATE KEY[\-]+/gm, 'RSA Key填写有误,请检查'], + }, fields: { "role_name": { - rule: "required;check_name", + rule: "required;check_name;check_name_root", tip: "输入系统用户名称", ok: "", msg: {required: "系统用户名称必填"} - } + }, + "role_key": { + rule: "check_begin", + ok: "", + empty: true + }, + }, valid: function(form) { form.submit(); diff --git a/templates/jperm/perm_role_list.html b/templates/jperm/perm_role_list.html index aafa94053..b371880ac 100644 --- a/templates/jperm/perm_role_list.html +++ b/templates/jperm/perm_role_list.html @@ -84,22 +84,43 @@ diff --git a/templates/jperm/perm_sudo_edit.html b/templates/jperm/perm_sudo_edit.html index b90fb151d..248289a40 100644 --- a/templates/jperm/perm_sudo_edit.html +++ b/templates/jperm/perm_sudo_edit.html @@ -112,7 +112,28 @@ var config = { for (var selector in config) { $(selector).chosen(config[selector]); } +$('#sudoForm').validator({ + timely: 2, + theme: "yellow_right_effect", + rules: { + check_name: [/^\w{2,20}$/, '大写字母,2-20位'] + }, + fields: { + "sudo_name": { + rule: "required;check_name" + }, + "sudo_runas": { + rule: "required;check_name" + }, + "sudo_commands": { + rule: "required" + } + }, + valid: function(form) { + form.submit(); + } +}); diff --git a/templates/jperm/role_sudo.j2 b/templates/jperm/role_sudo.j2 index c48d354c1..4fe2e82da 100644 --- a/templates/jperm/role_sudo.j2 +++ b/templates/jperm/role_sudo.j2 @@ -46,7 +46,7 @@ add_role_chosen() { check_syntax(){ - visudo -c -f $1 + /usr/sbin/visudo -c -f $1 } cp $real_file $tmp_file && add_cmd_alias $tmp_file && add_role_chosen $tmp_file || exit 1 diff --git a/templates/juser/user_add.html b/templates/juser/user_add.html index 2e4619043..621d18107 100644 --- a/templates/juser/user_add.html +++ b/templates/juser/user_add.html @@ -131,7 +131,7 @@ $('#userForm').validator({ timely: 2, theme: "yellow_right_effect", rules: { - check_username: [/^\w{3,20}$/, '大小写字母数字和下划线'], + check_username: [/^[\w.]{3,20}$/, '大小写字母数字和下划线小数点'], type_m: function(element){ return $("#M").is(":checked"); } diff --git a/templates/upload.html b/templates/upload.html index 2a5d712d9..068e59c11 100644 --- a/templates/upload.html +++ b/templates/upload.html @@ -99,6 +99,7 @@ autoProcessQueue: false, uploadMultiple: true, parallelUploads: 100, + maxFilesize: 2048, maxFiles: 100, url: '/file/upload/',