mirror of https://github.com/jumpserver/jumpserver
Merge branch 'master' into dev
commit
2691cc0b6d
|
@ -37,6 +37,7 @@ nosetests.xml
|
|||
.mr.developer.cfg
|
||||
.project
|
||||
.pydevproject
|
||||
.settings
|
||||
*.log
|
||||
logs/*
|
||||
keys/*
|
||||
|
|
|
@ -1,3 +1,7 @@
|
|||
## 写在前面
|
||||
- 目前本版本处于beta阶段,请不要用于生产环境,除非你知道你在做什么
|
||||
- 本版本暂时没加入LDAP接口,稳定版会将LDAP和无Agent方式抽象成API,2.x版本支持LDAP,请移步release中下载
|
||||
|
||||
#欢迎使用Jumpserver
|
||||
**Jumpserver** 是一款由python编写开源的跳板机(堡垒机)系统,实现了跳板机应有的功能。基于ssh协议来管理,客户端无需安装agent。
|
||||
支持常见系统:
|
||||
|
|
23
connect.py
23
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
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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()
|
||||
|
||||
|
||||
|
|
|
@ -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
|
|
@ -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:
|
||||
|
|
|
@ -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"系统平台")
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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()
|
||||
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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]
|
||||
|
|
109
jperm/views.py
109
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)
|
||||
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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'),
|
||||
]
|
||||
|
|
|
@ -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())
|
||||
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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'错误请求')
|
||||
|
||||
|
|
|
@ -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('<span style="color: yellow">Ansible> %s</span>\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('<span style="color: yellow">Ansible> %s</span>\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('<br />', output)
|
||||
logger.debug(output)
|
||||
if k == 'ok':
|
||||
header = "<span style='color: green'>[ %s => %s]</span>\n" % (host, 'Ok')
|
||||
else:
|
||||
|
|
|
@ -23,7 +23,13 @@
|
|||
|
||||
|
||||
<script type="text/javascript">
|
||||
var wsUri = "{{ web_terminal_uri }}"; //请求的websocket url
|
||||
|
||||
var protocol = "ws://";
|
||||
if (window.location.protocol == 'https:') {
|
||||
protocol = 'wss://';
|
||||
}
|
||||
|
||||
var wsUri = protocol + "{{ web_terminal_uri }}"; //请求的websocket url
|
||||
var ws = new WebSocket(wsUri);
|
||||
|
||||
function createSystemMessage(message) {
|
||||
|
|
|
@ -224,6 +224,7 @@
|
|||
}
|
||||
else if (dataArray.length == 1 && data != 'error' && navigator.platform == 'Win32'){
|
||||
var title = 'Jumpserver Web Terminal' + '<span class="text-info"> '+ hostname +'</span>';
|
||||
/*
|
||||
layer.open({
|
||||
type: 2,
|
||||
title: title,
|
||||
|
@ -232,8 +233,11 @@
|
|||
area: ['628px', '420px'],
|
||||
content: new_url+data
|
||||
});
|
||||
*/
|
||||
window.open(new_url+data, '', 'width=628px, height=380px');
|
||||
} else if (dataArray.length == 1 && data != 'error'){
|
||||
layer.open({
|
||||
/*
|
||||
layer.open({
|
||||
type: 2,
|
||||
title: title,
|
||||
maxmin: true,
|
||||
|
@ -241,6 +245,8 @@
|
|||
area: ['628px', '452px'],
|
||||
content: new_url+data
|
||||
});
|
||||
*/
|
||||
window.open(new_url+data, '', 'width=628px, height=452px')
|
||||
}
|
||||
else {
|
||||
aUrl = '';
|
||||
|
@ -266,6 +272,7 @@
|
|||
var hostname = $(a).attr('value');
|
||||
var title = 'Jumpserver Web Terminal - ' + '<span class="text-info"> '+ hostname +'</span>';
|
||||
if (navigator.platform == 'Win32'){
|
||||
/*
|
||||
layer.open({
|
||||
type: 2,
|
||||
title: title,
|
||||
|
@ -274,8 +281,11 @@
|
|||
shade: false,
|
||||
content: new_url
|
||||
});
|
||||
*/
|
||||
window.open(new_url+data, '', 'width=628px, height=380px');
|
||||
|
||||
} else {
|
||||
/*
|
||||
layer.open({
|
||||
type: 2,
|
||||
title: title,
|
||||
|
@ -284,6 +294,8 @@
|
|||
shade: false,
|
||||
content: new_url
|
||||
});
|
||||
*/
|
||||
window.open(new_url+data, '', 'width=628px, height=452px');
|
||||
}
|
||||
|
||||
return false
|
||||
|
@ -351,4 +363,4 @@
|
|||
});
|
||||
</script>
|
||||
|
||||
{% endblock %}
|
||||
{% endblock %}
|
||||
|
|
|
@ -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 @@
|
|||
});
|
||||
</script>
|
||||
|
||||
{% endblock %}
|
||||
{% endblock %}
|
||||
|
|
|
@ -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 = $('<div id="term" style="height:500px; overflow: auto;background-color: rgba(0, 0, 0, 0);border: none"></div>');
|
||||
|
|
|
@ -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};
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -84,22 +84,43 @@
|
|||
|
||||
<script>
|
||||
function remove_role(role_id){
|
||||
if (confirm("确认删除")) {
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
url: "{% url 'role_del' %}",
|
||||
data: "id=" + role_id,
|
||||
success: function(msg){
|
||||
alert( "成功: " + msg );
|
||||
var del_row = $('tbody#edittbody>tr#' + role_id);
|
||||
del_row.remove()
|
||||
},
|
||||
error: function (msg) {
|
||||
console.log(msg);
|
||||
alert("失败: " + msg.responseText)
|
||||
}
|
||||
});
|
||||
}
|
||||
$.ajax({
|
||||
type: "GET",
|
||||
url: "{% url 'role_del' %}",
|
||||
data: {id: role_id, filter_type: 'recycle_assets'},
|
||||
success: function(data) {
|
||||
console.log(data)
|
||||
if (data) {
|
||||
msg = data + "的系统用户会被删除,包括其家目录,请谨慎操作!"
|
||||
}
|
||||
else {
|
||||
msg = "该角色无已推送的系统用户, 可以安全删除"
|
||||
}
|
||||
if (confirm(msg)) {
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
url: "{% url 'role_del' %}",
|
||||
data: "id=" + role_id,
|
||||
success: function(msg){
|
||||
alert( "成功: " + msg );
|
||||
var del_row = $('tbody#edittbody>tr#' + role_id);
|
||||
del_row.remove()
|
||||
},
|
||||
error: function (msg) {
|
||||
console.log(msg);
|
||||
alert("失败: " + msg.responseText)
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
error: function(error) {
|
||||
alert(error)
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
</script>
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
<script src="/static/js/cropper/cropper.min.js"></script>
|
||||
<script src="/static/js/datapicker/bootstrap-datepicker.js"></script>
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
|
|
|
@ -99,6 +99,7 @@
|
|||
autoProcessQueue: false,
|
||||
uploadMultiple: true,
|
||||
parallelUploads: 100,
|
||||
maxFilesize: 2048,
|
||||
maxFiles: 100,
|
||||
url: '/file/upload/',
|
||||
|
||||
|
|
Loading…
Reference in New Issue