pull/71/head^2
yumaojun 2016-02-23 15:22:15 +08:00
commit 09e86f0a6b
31 changed files with 320 additions and 132 deletions

1
.gitignore vendored
View File

@ -37,6 +37,7 @@ nosetests.xml
.mr.developer.cfg .mr.developer.cfg
.project .project
.pydevproject .pydevproject
.settings
*.log *.log
logs/* logs/*
keys/* keys/*

View File

@ -1,3 +1,7 @@
## 写在前面
- 目前本版本处于beta阶段请不要用于生产环境除非你知道你在做什么
- 本版本暂时没加入LDAP接口稳定版会将LDAP和无Agent方式抽象成API2.x版本支持LDAP请移步release中下载
#欢迎使用Jumpserver #欢迎使用Jumpserver
**Jumpserver** 是一款由python编写开源的跳板机(堡垒机)系统实现了跳板机应有的功能。基于ssh协议来管理客户端无需安装agent。 **Jumpserver** 是一款由python编写开源的跳板机(堡垒机)系统实现了跳板机应有的功能。基于ssh协议来管理客户端无需安装agent。
支持常见系统: 支持常见系统:

View File

@ -21,7 +21,7 @@ from io import open as copen
import uuid import uuid
os.environ['DJANGO_SETTINGS_MODULE'] = 'jumpserver.settings' os.environ['DJANGO_SETTINGS_MODULE'] = 'jumpserver.settings'
if django.get_version() != '1.6': if not django.get_version().startswith('1.6'):
setup = django.setup() setup = django.setup()
from django.contrib.sessions.models import Session from django.contrib.sessions.models import Session
from jumpserver.api import ServerError, User, Asset, PermRole, AssetGroup, get_object, mkdir, get_asset_info 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 from jlog.models import ExecLog, FileLog
login_user = get_object(User, username=getpass.getuser()) 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: try:
import termios import termios
@ -412,7 +415,10 @@ class SshTty(Tty):
pass pass
if sys.stdin in r: 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 input_mode = True
if str(x) in ['\r', '\n', '\r\n']: if str(x) in ['\r', '\n', '\r\n']:
if self.vim_flag: if self.vim_flag:
@ -576,12 +582,15 @@ class Nav(object):
role = role_check[int(role_id)] role = role_check[int(role_id)]
elif len(roles) == 1: # 授权角色数为1 elif len(roles) == 1: # 授权角色数为1
role = roles[0] role = roles[0]
else:
color_print('当前用户未被授予角色,无法执行任何操作,如有疑问请联系管理员。')
return
assets = list(self.user_perm.get('role', {}).get(role).get('asset')) # 获取该用户,角色授权主机 assets = list(self.user_perm.get('role', {}).get(role).get('asset')) # 获取该用户,角色授权主机
print "授权包含该系统用户的所有主机" print "授权包含该系统用户的所有主机"
for asset in assets: for asset in assets:
print ' %s' % asset.hostname print ' %s' % asset.hostname
print print
print "请输入主机名或ansile支持的pattern, 多个主机:分隔, q退出" print "请输入主机名或ansible支持的pattern, 多个主机:分隔, q退出"
pattern = raw_input("\033[1;32mPattern>:\033[0m ").strip() pattern = raw_input("\033[1;32mPattern>:\033[0m ").strip()
if pattern == 'q': if pattern == 'q':
break break
@ -623,7 +632,7 @@ class Nav(object):
self.user_perm = get_group_user_perm(self.user) self.user_perm = get_group_user_perm(self.user)
try: try:
print "进入批量上传模式" print "进入批量上传模式"
print "请输入主机名或ansile支持的pattern, 多个主机:分隔 q退出" print "请输入主机名或ansible支持的pattern, 多个主机:分隔 q退出"
pattern = raw_input("\033[1;32mPattern>:\033[0m ").strip() pattern = raw_input("\033[1;32mPattern>:\033[0m ").strip()
if pattern == 'q': if pattern == 'q':
break break
@ -676,7 +685,7 @@ class Nav(object):
self.user_perm = get_group_user_perm(self.user) self.user_perm = get_group_user_perm(self.user)
try: try:
print "进入批量下载模式" print "进入批量下载模式"
print "请输入主机名或ansile支持的pattern, 多个主机:分隔,q退出" print "请输入主机名或ansible支持的pattern, 多个主机:分隔,q退出"
pattern = raw_input("\033[1;32mPattern>:\033[0m ").strip() pattern = raw_input("\033[1;32mPattern>:\033[0m ").strip()
if pattern == 'q': if pattern == 'q':
break break
@ -800,7 +809,7 @@ def main():
color_print('请输入正确ID', 'red') color_print('请输入正确ID', 'red')
except ServerError, e: except ServerError, e:
color_print(e, 'red') color_print(e, 'red')
except Exception, e: except IndexError, e:
color_print(e) color_print(e)
time.sleep(5) time.sleep(5)
pass pass

View File

@ -12,6 +12,8 @@ import socket
import fcntl import fcntl
import struct import struct
import readline import readline
import random
import string
jms_dir = os.path.dirname(os.path.abspath(os.path.dirname(__file__))) jms_dir = os.path.dirname(os.path.abspath(os.path.dirname(__file__)))
sys.path.append(jms_dir) sys.path.append(jms_dir)
@ -71,12 +73,15 @@ class PreSetup(object):
self.mail_addr = 'hello@jumpserver.org' self.mail_addr = 'hello@jumpserver.org'
self.mail_pass = '' self.mail_pass = ''
self.ip = '' 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')): def write_conf(self, conf_file=os.path.join(jms_dir, 'jumpserver.conf')):
color_print('开始写入配置文件', 'green') color_print('开始写入配置文件', 'green')
conf = ConfigParser.ConfigParser() conf = ConfigParser.ConfigParser()
conf.read(conf_file) conf.read(conf_file)
conf.set('base', 'url', 'http://%s' % self.ip) conf.set('base', 'url', 'http://%s' % self.ip)
conf.set('base', 'key', self.key)
conf.set('db', 'host', self.db_host) conf.set('db', 'host', self.db_host)
conf.set('db', 'port', self.db_port) conf.set('db', 'port', self.db_port)
conf.set('db', 'user', self.db_user) 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') color_print('默认用户名: %s 默认密码: %s' % (self.db_user, self.db_pass), 'green')
bash('yum -y install mysql-server') bash('yum -y install mysql-server')
bash('service mysqld start') bash('service mysqld start')
bash('chkconfig mysqld on')
bash('mysql -e "create database %s default charset=utf8"' % self.db) 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, bash('mysql -e "grant all on %s.* to \'%s\'@\'%s\' identified by \'%s\'"' % (self.db,
self.db_user, self.db_user,
@ -105,6 +111,7 @@ class PreSetup(object):
@staticmethod @staticmethod
def _set_env(): def _set_env():
color_print('开始关闭防火墙和selinux', 'green') 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') bash('service iptables stop && chkconfig iptables off && setenforce 0')
def _test_db_conn(self): def _test_db_conn(self):

View File

@ -18,7 +18,7 @@ if django.get_version() != '1.6':
from juser.user_api import db_add_user, get_object, User from juser.user_api import db_add_user, get_object, User
from install import color_print from install import color_print
from jumpserver.api import get_mac_address from jumpserver.api import get_mac_address, bash
socket.setdefaulttimeout(2) socket.setdefaulttimeout(2)
@ -81,9 +81,10 @@ class Setup(object):
os.system('id %s &> /dev/null || useradd %s' % (self.admin_user, self.admin_user)) os.system('id %s &> /dev/null || useradd %s' % (self.admin_user, self.admin_user))
@staticmethod @staticmethod
def _ensure_sh(): def _cp_zzsh():
jshell = os.path.join(jms_dir, 'connect.py') os.chdir(os.path.join(jms_dir, 'install'))
os.chmod(jshell, 0755) shutil.copy('zzjumpserver.sh', '/etc/profile.d/')
bash("sed -i 's#/opt/jumpserver#%s#g' /etc/profile.d/zzjumpserver.sh" % jms_dir)
@staticmethod @staticmethod
def _run_service(): def _run_service():
@ -97,7 +98,7 @@ class Setup(object):
self._sync_db() self._sync_db()
self._input_admin() self._input_admin()
self._create_admin() self._create_admin()
self._ensure_sh() self._cp_zzsh()
self._run_service() self._run_service()

13
install/zzjumpserver.sh Normal file
View File

@ -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

View File

@ -311,13 +311,12 @@ def excel_to_db(excel_file):
def get_ansible_asset_info(asset_ip, setup_info): def get_ansible_asset_info(asset_ip, setup_info):
print asset_ip print setup_info, '***'
disk_need = {} disk_need = {}
disk_all = setup_info.get("ansible_devices") disk_all = setup_info.get("ansible_devices")
if disk_all: if disk_all:
for disk_name, disk_info in disk_all.iteritems(): 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') or disk_name.startswith('xvd'):
if disk_name.startswith('sd') or disk_name.startswith('hd') or disk_name.startswith('vd'):
disk_size = disk_info.get("size", '') disk_size = disk_info.get("size", '')
if 'M' in disk_size: if 'M' in disk_size:
disk_format = round(float(disk_size[:-2]) / 1000, 0) 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") mac = setup_info.get("ansible_default_ipv4").get("macaddress")
brand = setup_info.get("ansible_product_name") brand = setup_info.get("ansible_product_name")
cpu_type = setup_info.get("ansible_processor")[1] 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) cpu = cpu_type + ' * ' + unicode(cpu_cores)
memory = setup_info.get("ansible_memtotal_mb") memory = setup_info.get("ansible_memtotal_mb")
try: try:

View File

@ -75,7 +75,7 @@ class Asset(models.Model):
brand = models.CharField(max_length=64, blank=True, null=True, verbose_name=u'硬件厂商型号') 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') 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'内存') 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_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_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"系统平台") system_arch = models.CharField(max_length=16, blank=True, null=True, verbose_name=u"系统平台")

View File

@ -227,7 +227,7 @@ def asset_edit(request):
if use_default_auth: if use_default_auth:
af_save.username = '' af_save.username = ''
af_save.password = '' af_save.password = ''
af_save.port = None # af_save.port = None
else: else:
if password: if password:
password_encode = CRYPTOR.encrypt(password) password_encode = CRYPTOR.encrypt(password)

View File

@ -6,7 +6,10 @@ from contextlib import closing
from io import open as copen from io import open as copen
from json import dumps from json import dumps
from math import ceil from math import ceil
import datetime
import time
import re import re
import os
from os.path import basename, dirname, exists, join from os.path import basename, dirname, exists, join
from struct import unpack from struct import unpack
from subprocess import Popen from subprocess import Popen
@ -17,6 +20,7 @@ from jinja2 import FileSystemLoader, Template
from jinja2.environment import Environment from jinja2.environment import Environment
from jumpserver.api import BASE_DIR from jumpserver.api import BASE_DIR
from jlog.models import Log
DEFAULT_TEMPLATE = join(BASE_DIR, 'templates', 'jlog', 'static.jinja2') 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 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()

View File

@ -66,7 +66,7 @@ def log_list(request, offset):
contact_list, p, contacts, page_range, current_page, show_first, show_end = pages(posts, request) contact_list, p, contacts, page_range, current_page, show_first, show_end = pages(posts, request)
web_monitor_uri = 'ws://%s/monitor' % WEB_SOCKET_HOST web_monitor_uri = '%s/monitor' % WEB_SOCKET_HOST
web_kill_uri = 'http://%s/kill' % WEB_SOCKET_HOST web_kill_uri = 'http://%s/kill' % WEB_SOCKET_HOST
session_id = request.session.session_key session_id = request.session.session_key
return render_to_response('jlog/log_%s.html' % offset, locals(), context_instance=RequestContext(request)) return render_to_response('jlog/log_%s.html' % offset, locals(), context_instance=RequestContext(request))

View File

@ -365,6 +365,16 @@ class MyTask(MyRunner):
self.run("user", module_args, become=True) self.run("user", module_args, become=True)
return self.results 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 @staticmethod
def gen_sudo_script(role_list, sudo_list): def gen_sudo_script(role_list, sudo_list):
# receive role_list = [role1, role2] sudo_list = [sudo1, sudo2] # receive role_list = [role1, role2] sudo_list = [sudo1, sudo2]

View File

@ -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 from jumpserver.api import my_render, get_object, CRYPTOR
# 设置PERM APP Log # 设置PERM APP Log
from jumpserver.settings import LOG_LEVEL from jumpserver.api import logger
logger = set_log(LOG_LEVEL, filename='jumpserver_perm.log') #logger = set_log(LOG_LEVEL, filename='jumpserver_perm.log')
@require_role('admin') @require_role('admin')
@ -277,7 +277,7 @@ def perm_role_add(request):
if request.method == "POST": if request.method == "POST":
# 获取参数: name, comment # 获取参数: name, comment
name = request.POST.get("role_name", "") name = request.POST.get("role_name", "").strip()
comment = request.POST.get("role_comment", "") comment = request.POST.get("role_comment", "")
password = request.POST.get("role_password", "") password = request.POST.get("role_password", "")
key_content = request.POST.get("role_key", "") key_content = request.POST.get("role_key", "")
@ -286,6 +286,8 @@ def perm_role_add(request):
try: try:
if get_object(PermRole, name=name): if get_object(PermRole, name=name):
raise ServerError(u'已经存在该用户 %s' % name) raise ServerError(u'已经存在该用户 %s' % name)
if name == "root":
raise ServerError(u'禁止使用root用户作为系统用户这样非常危险')
default = get_object(Setting, name='default') default = get_object(Setting, name='default')
if password: if password:
@ -317,14 +319,37 @@ def perm_role_delete(request):
""" """
delete role page 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": if request.method == "POST":
try: try:
# 获取参数删除的role对象 # 获取参数删除的role对象
role_id = request.POST.get("id") role_id = request.POST.get("id")
role = get_object(PermRole, id=role_id) role = get_object(PermRole, id=role_id)
if not role: if not role:
logger.warning(u"Delete Role: %s not exist" % role.name) logger.warning(u"Delete Role: role_id %s not exist" % role_id)
raise ServerError(u"%s 无数据记录" % role.name) raise ServerError(u"role_id %s 无数据记录" % role_id)
role_key = role.key_path role_key = role.key_path
# 删除推送到主机上的role # 删除推送到主机上的role
recycle_assets = [push.asset for push in role.perm_push.all() if push.success] 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) recycle_resource = gen_resource(recycle_assets)
task = MyTask(recycle_resource) task = MyTask(recycle_resource)
try: 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: except Exception, e:
logger.warning(u"Recycle Role failed: %s" % e) logger.warning(u"Recycle Role failed: %s" % e)
raise ServerError(u"回收已推送的系统用户失败: %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: 判断返回结果,处理异常 # TODO: 判断返回结果,处理异常
# 删除存储的秘钥,以及目录 # 删除存储的秘钥,以及目录
try: try:
@ -423,6 +450,9 @@ def perm_role_edit(request):
if not role: if not role:
raise ServerError('该系统用户不能存在') raise ServerError('该系统用户不能存在')
if role_name == "root":
raise ServerError(u'禁止使用root用户作为系统用户这样非常危险')
if role_password: if role_password:
encrypt_pass = CRYPTOR.encrypt(role_password) encrypt_pass = CRYPTOR.encrypt(role_password)
role.password = encrypt_pass role.password = encrypt_pass
@ -473,6 +503,7 @@ def perm_role_push(request):
for asset_group in asset_groups_obj: for asset_group in asset_groups_obj:
group_assets_obj.extend(asset_group.asset_set.all()) group_assets_obj.extend(asset_group.asset_set.all())
calc_assets = list(set(assets_obj) | set(group_assets_obj)) calc_assets = list(set(assets_obj) | set(group_assets_obj))
push_resource = gen_resource(calc_assets) push_resource = gen_resource(calc_assets)
# 调用Ansible API 进行推送 # 调用Ansible API 进行推送
@ -577,25 +608,28 @@ def perm_sudo_add(request):
""" """
# 渲染数据 # 渲染数据
header_title, path1, path2 = "Sudo命令", "别名管理", "添加别名" 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": if not name or not commands:
# 获取参数: name, comment raise ServerError(u"sudo name 和 commands是必填项!")
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]') pattern = re.compile(r'[\n,\r]')
commands = ', '.join(list_drop_str(pattern.split(commands), u'')) commands = ', '.join(list_drop_str(pattern.split(commands), u''))
logger.debug(u'添加sudo %s: %s' % (name, commands)) 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
# 渲染数据
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) 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_id = request.GET.get("id")
sudo = PermSudo.objects.get(id=sudo_id) sudo = PermSudo.objects.get(id=sudo_id)
if request.method == "POST": try:
name = request.POST.get("sudo_name").upper() if request.method == "POST":
commands = request.POST.get("sudo_commands") name = request.POST.get("sudo_name").upper()
comment = request.POST.get("sudo_comment") commands = request.POST.get("sudo_commands")
comment = request.POST.get("sudo_comment")
pattern = re.compile(r'[ \n,\r]') if not name or not commands:
commands = ', '.join(list_drop_str(pattern.split(commands), u'')).strip() raise ServerError(u"sudo name 和 commands是必填项!")
logger.debug(u'添加sudo %s: %s' % (name, commands))
sudo.name = name.strip() pattern = re.compile(r'[\n,\r]')
sudo.commands = commands commands = ', '.join(list_drop_str(pattern.split(commands), u'')).strip()
sudo.comment = comment logger.debug(u'添加sudo %s: %s' % (name, commands))
sudo.save()
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) return my_render('jperm/perm_sudo_edit.html', locals(), request)

View File

@ -64,7 +64,6 @@ def get_asset_info(asset):
info = {'hostname': asset.hostname, 'ip': asset.ip} info = {'hostname': asset.hostname, 'ip': asset.ip}
if asset.use_default_auth: if asset.use_default_auth:
if default: if default:
info['port'] = int(default.field2)
info['username'] = default.field1 info['username'] = default.field1
try: try:
info['password'] = CRYPTOR.decrypt(default.field3) info['password'] = CRYPTOR.decrypt(default.field3)
@ -73,9 +72,12 @@ def get_asset_info(asset):
if os.path.isfile(default.field4): if os.path.isfile(default.field4):
info['ssh_key'] = default.field4 info['ssh_key'] = default.field4
else: else:
info['port'] = int(asset.port)
info['username'] = asset.username info['username'] = asset.username
info['password'] = CRYPTOR.decrypt(asset.password) info['password'] = CRYPTOR.decrypt(asset.password)
try:
info['port'] = int(asset.port)
except TypeError:
info['port'] = int(default.field2)
return info return info

View File

@ -152,5 +152,6 @@ STATIC_URL = '/static/'
BOOTSTRAP_COLUMN_COUNT = 10 BOOTSTRAP_COLUMN_COUNT = 10
CRONJOBS = [ 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'),
] ]

View File

@ -344,7 +344,7 @@ def download(request):
def exec_cmd(request): def exec_cmd(request):
role = request.GET.get('role') role = request.GET.get('role')
check_assets = request.GET.get('check_assets', '') 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) return my_render('exec_cmd.html', locals(), request)
@ -356,7 +356,7 @@ def web_terminal(request):
if asset: if asset:
print asset print asset
hostname = asset.hostname 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()) return render_to_response('jlog/web_terminal.html', locals())

View File

@ -151,8 +151,8 @@ def server_add_user(username, password, ssh_key_pwd='', ssh_key_login_need=True)
add a system user in jumpserver add a system user in jumpserver
在jumpserver服务器上添加一个用户 在jumpserver服务器上添加一个用户
""" """
bash("useradd -s %s/connect.py '%s'; echo '%s'; echo '%s' | passwd --stdin '%s'" % bash("useradd '%s'; echo '%s'; echo '%s:%s' | chpasswd " %
(BASE_DIR, username, password, password, username)) (username, password, username, password))
if ssh_key_login_need: if ssh_key_login_need:
gen_ssh_key(username, ssh_key_pwd) gen_ssh_key(username, ssh_key_pwd)

View File

@ -6,7 +6,7 @@
# from Crypto.PublicKey import RSA # from Crypto.PublicKey import RSA
import uuid import uuid
from django.contrib.auth.decorators import login_required from django.contrib.auth.decorators import login_required
from django.shortcuts import get_object_or_404
from django.db.models import Q from django.db.models import Q
from juser.user_api import * from juser.user_api import *
from jperm.perm_api import get_group_user_perm 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: if len(UserGroup.objects.filter(name=group_name)) > 1:
raise ServerError(u'%s 用户组已存在' % group_name) raise ServerError(u'%s 用户组已存在' % group_name)
# add user group # 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): for user in User.objects.filter(id__in=users_selected):
user.group.add(UserGroup.objects.get(id=group_id)) 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.name = group_name
user_group.comment = comment user_group.comment = comment
user_group.save() user_group.save()
except ServerError, e: except ServerError, e:
error = e error = e
if not error: if not error:
return HttpResponseRedirect(reverse('user_group_list')) return HttpResponseRedirect(reverse('user_group_list'))
else: else:
@ -313,6 +309,12 @@ def reset_password(request):
hash_encode = request.GET.get('hash', '') hash_encode = request.GET.get('hash', '')
action = '/juser/password/reset/?uuid=%s&timestamp=%s&hash=%s' % (uuid_r, timestamp, hash_encode) action = '/juser/password/reset/?uuid=%s&timestamp=%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': if request.method == 'POST':
password = request.POST.get('password') password = request.POST.get('password')
password_confirm = request.POST.get('password_confirm') password_confirm = request.POST.get('password_confirm')
@ -328,11 +330,8 @@ def reset_password(request):
else: else:
return HttpResponse('用户不存在') return HttpResponse('用户不存在')
if hash_encode == PyCrypt.md5_crypt(uuid_r + timestamp + KEY): else:
if int(time.time()) - int(timestamp) > 600: return render_to_response('juser/reset_password.html', locals())
return http_error(request, u'链接已超时')
else:
return render_to_response('juser/reset_password.html', locals())
return http_error(request, u'错误请求') return http_error(request, u'错误请求')

View File

@ -230,15 +230,14 @@ class ExecHandler(tornado.websocket.WebSocketHandler):
def on_message(self, message): def on_message(self, message):
data = json.loads(message) data = json.loads(message)
pattern = data.get('pattern', '') pattern = data.get('pattern', '')
command = data.get('command', '') self.command = data.get('command', '')
asset_name_str = '' self.asset_name_str = ''
if pattern and command: if pattern and self.command:
for inv in self.runner.inventory.get_hosts(pattern=pattern): for inv in self.runner.inventory.get_hosts(pattern=pattern):
asset_name_str += '%s ' % inv.name self.asset_name_str += '%s ' % inv.name
self.write_message('匹配主机: ' + asset_name_str) self.write_message('匹配主机: ' + self.asset_name_str)
self.write_message('<span style="color: yellow">Ansible> %s</span>\n\n' % command) self.write_message('<span style="color: yellow">Ansible> %s</span>\n\n' % self.command)
self.__class__.tasks.append(MyThread(target=self.run_cmd, args=(command, pattern))) self.__class__.tasks.append(MyThread(target=self.run_cmd, args=(self.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: for t in self.__class__.tasks:
if t.is_alive(): if t.is_alive():
@ -251,11 +250,12 @@ class ExecHandler(tornado.websocket.WebSocketHandler):
def run_cmd(self, command, pattern): def run_cmd(self, command, pattern):
self.runner.run('shell', command, pattern=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') newline_pattern = re.compile(r'\n')
for k, v in self.runner.results.items(): for k, v in self.runner.results.items():
for host, output in v.items(): for host, output in v.items():
output = newline_pattern.sub('<br />', output) output = newline_pattern.sub('<br />', output)
logger.debug(output)
if k == 'ok': if k == 'ok':
header = "<span style='color: green'>[ %s => %s]</span>\n" % (host, 'Ok') header = "<span style='color: green'>[ %s => %s]</span>\n" % (host, 'Ok')
else: else:

View File

@ -23,7 +23,13 @@
<script type="text/javascript"> <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); var ws = new WebSocket(wsUri);
function createSystemMessage(message) { function createSystemMessage(message) {

View File

@ -224,6 +224,7 @@
} }
else if (dataArray.length == 1 && data != 'error' && navigator.platform == 'Win32'){ else if (dataArray.length == 1 && data != 'error' && navigator.platform == 'Win32'){
var title = 'Jumpserver Web Terminal' + '<span class="text-info"> '+ hostname +'</span>'; var title = 'Jumpserver Web Terminal' + '<span class="text-info"> '+ hostname +'</span>';
/*
layer.open({ layer.open({
type: 2, type: 2,
title: title, title: title,
@ -232,8 +233,11 @@
area: ['628px', '420px'], area: ['628px', '420px'],
content: new_url+data content: new_url+data
}); });
*/
window.open(new_url+data, '', 'width=628px, height=380px');
} else if (dataArray.length == 1 && data != 'error'){ } else if (dataArray.length == 1 && data != 'error'){
layer.open({ /*
layer.open({
type: 2, type: 2,
title: title, title: title,
maxmin: true, maxmin: true,
@ -241,6 +245,8 @@
area: ['628px', '452px'], area: ['628px', '452px'],
content: new_url+data content: new_url+data
}); });
*/
window.open(new_url+data, '', 'width=628px, height=452px')
} }
else { else {
aUrl = ''; aUrl = '';
@ -266,6 +272,7 @@
var hostname = $(a).attr('value'); var hostname = $(a).attr('value');
var title = 'Jumpserver Web Terminal - ' + '<span class="text-info"> '+ hostname +'</span>'; var title = 'Jumpserver Web Terminal - ' + '<span class="text-info"> '+ hostname +'</span>';
if (navigator.platform == 'Win32'){ if (navigator.platform == 'Win32'){
/*
layer.open({ layer.open({
type: 2, type: 2,
title: title, title: title,
@ -274,8 +281,11 @@
shade: false, shade: false,
content: new_url content: new_url
}); });
*/
window.open(new_url+data, '', 'width=628px, height=380px');
} else { } else {
/*
layer.open({ layer.open({
type: 2, type: 2,
title: title, title: title,
@ -284,6 +294,8 @@
shade: false, shade: false,
content: new_url content: new_url
}); });
*/
window.open(new_url+data, '', 'width=628px, height=452px');
} }
return false return false
@ -351,4 +363,4 @@
}); });
</script> </script>
{% endblock %} {% endblock %}

View File

@ -243,8 +243,9 @@
area: ['628px', '420px'], area: ['628px', '420px'],
content: new_url+data 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'){ } else if (dataArray.length == 1 && data != 'error'){
/*layer.open({ /*layer.open({
type: 2, type: 2,
@ -255,7 +256,7 @@
content: new_url+data 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 { else {
@ -292,7 +293,7 @@
content: new_url 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 { } else {
/* /*
@ -305,7 +306,7 @@
content: new_url 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 return false
@ -429,4 +430,4 @@
}); });
</script> </script>
{% endblock %} {% endblock %}

View File

@ -136,14 +136,20 @@
{# })#} {# })#}
{# });#} {# });#}
function init(obj){ function init(obj){
var protocol = "ws://";
if (window.location.protocol == 'https:') {
protocol = 'wss://';
}
var file_path = obj.attr('file_path'); 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 socket = new WebSocket(wsUri + '?file_path=' + file_path);
var term = new Terminal({ var term = new Terminal({
cols: 80, cols: 80,
rows: 24, rows: 24,
screenKeys: false 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>'); var tag = $('<div id="term" style="height:500px; overflow: auto;background-color: rgba(0, 0, 0, 0);border: none"></div>');

View File

@ -43,7 +43,12 @@
} }
WSSHClient.prototype.connect = function(options) { 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) { if (window.WebSocket) {
this._connection = new WebSocket(endpoint); this._connection = new WebSocket(endpoint);
@ -112,7 +117,7 @@
$('.terminal').css('width', window.innerWidth-25); $('.terminal').css('width', window.innerWidth-25);
console.log(window.innerWidth); console.log(window.innerWidth);
console.log(window.innerWidth-10); 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; var cols = Math.floor(window.innerWidth/colWidth) - 1;
return {rows: rows, cols: cols}; return {rows: rows, cols: cols};

View File

@ -92,11 +92,9 @@ $('#roleForm').validator({
timely: 2, timely: 2,
theme: "yellow_right_effect", theme: "yellow_right_effect",
rules: { 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填写有误请检查'], check_begin: [/^[\-]+BEGIN R|DSA PRIVATE KEY[\-]+/gm, 'RSA|DSA Key填写有误请检查']
{# either: function(){#}
{# return $('#role_password').val() == ''#}
{# }#}
}, },
fields: { fields: {
@ -110,13 +108,8 @@ $('#roleForm').validator({
rule: "check_begin", rule: "check_begin",
ok: "", ok: "",
empty: true empty: true
}, }
{# "role_key": {#}
{# rule: "required(either)",#}
{# tip: "输入密钥",#}
{# ok: "",#}
{# msg: {required: "密码和密钥必填一个!"}#}
{# }#}
}, },
valid: function(form) { valid: function(form) {
form.submit(); form.submit();

View File

@ -94,16 +94,24 @@ $('#roleForm').validator({
timely: 2, timely: 2,
theme: "yellow_right_effect", theme: "yellow_right_effect",
rules: { 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: { fields: {
"role_name": { "role_name": {
rule: "required;check_name", rule: "required;check_name;check_name_root",
tip: "输入系统用户名称", tip: "输入系统用户名称",
ok: "", ok: "",
msg: {required: "系统用户名称必填"} msg: {required: "系统用户名称必填"}
} },
"role_key": {
rule: "check_begin",
ok: "",
empty: true
},
}, },
valid: function(form) { valid: function(form) {
form.submit(); form.submit();

View File

@ -84,22 +84,43 @@
<script> <script>
function remove_role(role_id){ function remove_role(role_id){
if (confirm("确认删除")) { $.ajax({
$.ajax({ type: "GET",
type: "POST", url: "{% url 'role_del' %}",
url: "{% url 'role_del' %}", data: {id: role_id, filter_type: 'recycle_assets'},
data: "id=" + role_id, success: function(data) {
success: function(msg){ console.log(data)
alert( "成功: " + msg ); if (data) {
var del_row = $('tbody#edittbody>tr#' + role_id); msg = data + "的系统用户会被删除,包括其家目录,请谨慎操作!"
del_row.remove() }
}, else {
error: function (msg) { msg = "该角色无已推送的系统用户, 可以安全删除"
console.log(msg); }
alert("失败: " + msg.responseText) 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> </script>

View File

@ -112,7 +112,28 @@ var config = {
for (var selector in config) { for (var selector in config) {
$(selector).chosen(config[selector]); $(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>
<script src="/static/js/cropper/cropper.min.js"></script> <script src="/static/js/cropper/cropper.min.js"></script>
<script src="/static/js/datapicker/bootstrap-datepicker.js"></script> <script src="/static/js/datapicker/bootstrap-datepicker.js"></script>

View File

@ -51,7 +51,7 @@ add_role_chosen() {
check_syntax(){ 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 cp $real_file $tmp_file && add_cmd_alias $tmp_file && add_role_chosen $tmp_file || exit 1

View File

@ -131,7 +131,7 @@ $('#userForm').validator({
timely: 2, timely: 2,
theme: "yellow_right_effect", theme: "yellow_right_effect",
rules: { rules: {
check_username: [/^\w{3,20}$/, '大小写字母数字和下划线'], check_username: [/^[\w.]{3,20}$/, '大小写字母数字和下划线小数点'],
type_m: function(element){ type_m: function(element){
return $("#M").is(":checked"); return $("#M").is(":checked");
} }

View File

@ -99,6 +99,7 @@
autoProcessQueue: false, autoProcessQueue: false,
uploadMultiple: true, uploadMultiple: true,
parallelUploads: 100, parallelUploads: 100,
maxFilesize: 2048,
maxFiles: 100, maxFiles: 100,
url: '/file/upload/', url: '/file/upload/',