mirror of
https://github.com/jumpserver/jumpserver.git
synced 2025-12-15 08:32:48 +00:00
Compare commits
114 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3533c01011 | ||
|
|
77ec2d5a86 | ||
|
|
8b707dc8f5 | ||
|
|
8dcc9c8d53 | ||
|
|
0ca0519ef1 | ||
|
|
8de50a0e83 | ||
|
|
64b7fb6a3d | ||
|
|
a114e173e0 | ||
|
|
402df81048 | ||
|
|
2a51eeef10 | ||
|
|
4b593e56f9 | ||
|
|
9f3c83b052 | ||
|
|
a864c38238 | ||
|
|
547408222d | ||
|
|
0ad26254ef | ||
|
|
b7e138f919 | ||
|
|
2ec415fd84 | ||
|
|
b3c12e8861 | ||
|
|
da8f30fc72 | ||
|
|
cddb0ce537 | ||
|
|
124e26fa32 | ||
|
|
59c689cd61 | ||
|
|
0cc36b1302 | ||
|
|
0ac9d19252 | ||
|
|
d9e497e8be | ||
|
|
e18ae7f82c | ||
|
|
e5286686b9 | ||
|
|
332316b0af | ||
|
|
8f666785d2 | ||
|
|
0ed4b84b63 | ||
|
|
97591e6f03 | ||
|
|
5d5d8ab32a | ||
|
|
5489f3ae36 | ||
|
|
ce7a3d1f33 | ||
|
|
270499a4fd | ||
|
|
27223a1883 | ||
|
|
e0ae7eeee7 | ||
|
|
b5fefb4687 | ||
|
|
ecfb2f3d40 | ||
|
|
1614dd5a4d | ||
|
|
cf3e89c374 | ||
|
|
0f0908d3f3 | ||
|
|
31c3def1a8 | ||
|
|
f75003461a | ||
|
|
49504e46ee | ||
|
|
699bdd1348 | ||
|
|
e608fbaad5 | ||
|
|
6a7d105484 | ||
|
|
6acda5fcd1 | ||
|
|
57ba8ed2fb | ||
|
|
d6c4017a2e | ||
|
|
b7be5d14e0 | ||
|
|
f130a78f0a | ||
|
|
a077053b68 | ||
|
|
c93c8de7fe | ||
|
|
3176156639 | ||
|
|
7072d16f00 | ||
|
|
f2dda35f28 | ||
|
|
0cc04ee20d | ||
|
|
7531a3ada7 | ||
|
|
32ab8a1646 | ||
|
|
d60562a034 | ||
|
|
93e08a6e29 | ||
|
|
d66ba9d6c6 | ||
|
|
1338d25b4e | ||
|
|
f994c4d1da | ||
|
|
5fab276c26 | ||
|
|
9f171da570 | ||
|
|
fed00d04a6 | ||
|
|
ecfaf9f02d | ||
|
|
d05e9d0b45 | ||
|
|
d4385c7e43 | ||
|
|
04fc9962ff | ||
|
|
2fd68845ce | ||
|
|
bd69339e22 | ||
|
|
c6404f7ed6 | ||
|
|
6ce948366d | ||
|
|
9e78fd3651 | ||
|
|
5afd135967 | ||
|
|
d5aa9324fa | ||
|
|
9096a6e5b8 | ||
|
|
cb58012a82 | ||
|
|
58bb3cc84f | ||
|
|
c2ff05201a | ||
|
|
9be13cf08f | ||
|
|
eb4ec47f7a | ||
|
|
e2eb9b72f8 | ||
|
|
c9ff235089 | ||
|
|
9af809a4f0 | ||
|
|
288a42663a | ||
|
|
cca15d4211 | ||
|
|
fe2081b407 | ||
|
|
fa323d4987 | ||
|
|
eeef4a2f95 | ||
|
|
2e49f51093 | ||
|
|
0481e83ec4 | ||
|
|
ff8b5bd6c0 | ||
|
|
aabab653d3 | ||
|
|
efe0b3acc0 | ||
|
|
35cc966132 | ||
|
|
777997202b | ||
|
|
1f09a40c77 | ||
|
|
d20fecadac | ||
|
|
fa430bf104 | ||
|
|
8bb66ac254 | ||
|
|
6518aa3670 | ||
|
|
c76d9ebd88 | ||
|
|
7f4d3ffdbc | ||
|
|
b908fdafc6 | ||
|
|
ef59cff44b | ||
|
|
f511802db5 | ||
|
|
30c74b8427 | ||
|
|
2dd16b91ec | ||
|
|
1959c685b9 |
37
Dockerfile
Normal file
37
Dockerfile
Normal file
@@ -0,0 +1,37 @@
|
||||
FROM alpine
|
||||
MAINTAINER xRain <xrain@simcu.com>
|
||||
RUN apk add --update openssh sshpass python py-mysqldb py-psutil py-crypto && \
|
||||
rm -rf /var/cache/apk/*
|
||||
COPY . /jumpserver
|
||||
WORKDIR /jumpserver
|
||||
RUN python /jumpserver/install/docker/get-pip.py && \
|
||||
pip install -r /jumpserver/install/docker/piprequires.txt && \
|
||||
rm -rf /jumpserver/docs && \
|
||||
cp /jumpserver/install/docker/run.sh /run.sh && \
|
||||
rm -rf /etc/motd && chmod +x /run.sh && \
|
||||
rm -rf /jumpserver/keys && \
|
||||
rm -rf /jumpserver/logs && \
|
||||
rm -rf /home && \
|
||||
rm -rf /etc/ssh && \
|
||||
rm -rf /etc/shadow && \
|
||||
rm -rf /etc/passwd && \
|
||||
cp -r /jumpserver/install/docker/useradd /usr/sbin/useradd && \
|
||||
cp -r /jumpserver/install/docker/userdel /usr/sbin/userdel && \
|
||||
chmod +x /usr/sbin/useradd && \
|
||||
chmod +x /usr/sbin/userdel && \
|
||||
mkdir -p /data/home && \
|
||||
mkdir -p /data/logs && \
|
||||
mkdir -p /data/keys && \
|
||||
mkdir -p /data/ssh && \
|
||||
cp -r /jumpserver/install/docker/shadow /data/shadow && \
|
||||
cp -r /jumpserver/install/docker/passwd /data/passwd && \
|
||||
ln -s /data/logs /jumpserver/logs && \
|
||||
ln -s /data/keys /jumpserver/keys && \
|
||||
ln -s /data/home /home && \
|
||||
ln -s /data/ssh /etc/ssh && \
|
||||
ln -s /data/passwd /etc/passwd && \
|
||||
ln -s /data/shadow /etc/shadow && \
|
||||
chmod -R 777 /jumpserver
|
||||
VOLUME /data
|
||||
EXPOSE 80 22
|
||||
CMD /run.sh
|
||||
10
README.md
10
README.md
@@ -69,12 +69,20 @@ Web批量执行命令
|
||||
|
||||
[demo站点](http://demo.jumpserver.org)
|
||||
|
||||
交流群: 399218702
|
||||
交流群: 552054376
|
||||
|
||||
### 团队
|
||||
|
||||

|
||||
|
||||
|
||||
### License & Copyright
|
||||
|
||||
Copyright (c) 2014-2017 Beijing Duizhan Tech, Inc., All rights reserved.
|
||||
|
||||
Licensed under The GNU General Public License version 2 (GPLv2) (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at
|
||||
|
||||
https://www.gnu.org/licenses/gpl-2.0.html
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
|
||||
|
||||
|
||||
128
connect.py
128
connect.py
@@ -29,7 +29,7 @@ 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 logger, Log, TtyLog, get_role_key, CRYPTOR, bash, get_tmp_dir
|
||||
from jperm.perm_api import gen_resource, get_group_asset_perm, get_group_user_perm, user_have_perm, PermRole
|
||||
from jumpserver.settings import LOG_DIR
|
||||
from jumpserver.settings import LOG_DIR, NAV_SORT_BY
|
||||
from jperm.ansible_api import MyRunner
|
||||
# from jlog.log_api import escapeString
|
||||
from jlog.models import ExecLog, FileLog
|
||||
@@ -93,9 +93,7 @@ class Tty(object):
|
||||
self.remote_ip = ''
|
||||
self.login_type = login_type
|
||||
self.vim_flag = False
|
||||
self.vim_end_flag = False
|
||||
self.vim_end_pattern = re.compile(r'\x1b\[\?1049', re.X)
|
||||
self.vim_pattern = re.compile(r'\W?vi[m]?\s.* | \W?fg\s.*', re.X)
|
||||
self.vim_data = ''
|
||||
self.stream = None
|
||||
self.screen = None
|
||||
@@ -117,7 +115,8 @@ class Tty(object):
|
||||
return True
|
||||
return False
|
||||
|
||||
def command_parser(self, command):
|
||||
@staticmethod
|
||||
def command_parser(command):
|
||||
"""
|
||||
处理命令中如果有ps1或者mysql的特殊情况,极端情况下会有ps1和mysql
|
||||
:param command:要处理的字符传
|
||||
@@ -157,14 +156,10 @@ class Tty(object):
|
||||
else:
|
||||
command = line_data
|
||||
break
|
||||
if command != '':
|
||||
# 判断用户输入的是否是vim 或者fg命令
|
||||
if self.vim_pattern.search(command):
|
||||
self.vim_flag = True
|
||||
# 虚拟屏幕清空
|
||||
self.screen.reset()
|
||||
except Exception:
|
||||
pass
|
||||
# 虚拟屏幕清空
|
||||
self.screen.reset()
|
||||
return command
|
||||
|
||||
def get_log(self):
|
||||
@@ -180,8 +175,8 @@ class Tty(object):
|
||||
log_file_path = os.path.join(today_connect_log_dir, '%s_%s_%s' % (self.username, self.asset_name, time_start))
|
||||
|
||||
try:
|
||||
mkdir(os.path.dirname(today_connect_log_dir), mode=0777)
|
||||
mkdir(today_connect_log_dir, mode=0777)
|
||||
mkdir(os.path.dirname(today_connect_log_dir), mode=777)
|
||||
mkdir(today_connect_log_dir, mode=777)
|
||||
except OSError:
|
||||
logger.debug('创建目录 %s 失败,请修改%s目录权限' % (today_connect_log_dir, tty_log_dir))
|
||||
raise ServerError('创建目录 %s 失败,请修改%s目录权限' % (today_connect_log_dir, tty_log_dir))
|
||||
@@ -254,7 +249,7 @@ class Tty(object):
|
||||
allow_agent=False,
|
||||
look_for_keys=False)
|
||||
|
||||
except paramiko.ssh_exception.AuthenticationException, paramiko.ssh_exception.SSHException:
|
||||
except (paramiko.ssh_exception.AuthenticationException, paramiko.ssh_exception.SSHException):
|
||||
raise ServerError('认证失败 Authentication Error.')
|
||||
except socket.error:
|
||||
raise ServerError('端口可能不对 Connect SSH Socket Port Error, Please Correct it.')
|
||||
@@ -305,7 +300,6 @@ class SshTty(Tty):
|
||||
old_tty = termios.tcgetattr(sys.stdin)
|
||||
pre_timestamp = time.time()
|
||||
data = ''
|
||||
input_str = ''
|
||||
input_mode = False
|
||||
try:
|
||||
tty.setraw(sys.stdin.fileno())
|
||||
@@ -325,8 +319,7 @@ class SshTty(Tty):
|
||||
x = self.channel.recv(10240)
|
||||
if len(x) == 0:
|
||||
break
|
||||
if self.vim_flag:
|
||||
self.vim_data += x
|
||||
|
||||
index = 0
|
||||
len_x = len(x)
|
||||
while index < len_x:
|
||||
@@ -338,7 +331,7 @@ class SshTty(Tty):
|
||||
if msg.errno == errno.EAGAIN:
|
||||
continue
|
||||
now_timestamp = time.time()
|
||||
termlog.write(x)
|
||||
#termlog.write(x)
|
||||
termlog.recoder = False
|
||||
log_time_f.write('%s %s\n' % (round(now_timestamp-pre_timestamp, 4), len(x)))
|
||||
log_time_f.flush()
|
||||
@@ -347,11 +340,10 @@ class SshTty(Tty):
|
||||
pre_timestamp = now_timestamp
|
||||
log_file_f.flush()
|
||||
|
||||
if input_mode and not self.is_output(x):
|
||||
self.vim_data += x
|
||||
if input_mode:
|
||||
data += x
|
||||
|
||||
input_str = ''
|
||||
|
||||
except socket.timeout:
|
||||
pass
|
||||
|
||||
@@ -362,25 +354,22 @@ class SshTty(Tty):
|
||||
pass
|
||||
termlog.recoder = True
|
||||
input_mode = True
|
||||
input_str += x
|
||||
if str(x) in ['\r', '\n', '\r\n']:
|
||||
# 这个是用来处理用户的复制操作
|
||||
if input_str != x:
|
||||
data += input_str
|
||||
if self.vim_flag:
|
||||
match = self.vim_end_pattern.findall(self.vim_data)
|
||||
if match:
|
||||
if self.vim_end_flag or len(match) == 2:
|
||||
self.vim_flag = False
|
||||
self.vim_end_flag = False
|
||||
else:
|
||||
self.vim_end_flag = True
|
||||
else:
|
||||
if self.is_output(str(x)):
|
||||
# 如果len(str(x)) > 1 说明是复制输入的
|
||||
if len(str(x)) > 1:
|
||||
data = x
|
||||
match = self.vim_end_pattern.findall(self.vim_data)
|
||||
if match:
|
||||
if self.vim_flag or len(match) == 2:
|
||||
self.vim_flag = False
|
||||
else:
|
||||
self.vim_flag = True
|
||||
elif not self.vim_flag:
|
||||
self.vim_flag = False
|
||||
data = self.deal_command(data)[0:200]
|
||||
if len(data) > 0:
|
||||
if data is not None:
|
||||
TtyLog(log=log, datetime=datetime.datetime.now(), cmd=data).save()
|
||||
data = ''
|
||||
input_str = ''
|
||||
self.vim_data = ''
|
||||
input_mode = False
|
||||
|
||||
@@ -406,7 +395,7 @@ class SshTty(Tty):
|
||||
"""
|
||||
# 发起ssh连接请求 Make a ssh connection
|
||||
ssh = self.get_connection()
|
||||
|
||||
|
||||
transport = ssh.get_transport()
|
||||
transport.set_keepalive(30)
|
||||
transport.use_compression(True)
|
||||
@@ -422,7 +411,7 @@ class SshTty(Tty):
|
||||
signal.signal(signal.SIGWINCH, self.set_win_size)
|
||||
except:
|
||||
pass
|
||||
|
||||
|
||||
self.posix_shell()
|
||||
|
||||
# Shutdown channel socket
|
||||
@@ -436,12 +425,22 @@ class Nav(object):
|
||||
"""
|
||||
def __init__(self, user):
|
||||
self.user = user
|
||||
self.search_result = None
|
||||
self.user_perm = get_group_user_perm(self.user)
|
||||
self.perm_assets = sorted(self.user_perm.get('asset', []).keys(),
|
||||
key=lambda x: [int(num) for num in x.ip.split('.') if num.isdigit()])
|
||||
if NAV_SORT_BY == 'ip':
|
||||
self.perm_assets = sorted(self.user_perm.get('asset', []).keys(),
|
||||
key=lambda x: [int(num) for num in x.ip.split('.') if num.isdigit()])
|
||||
elif NAV_SORT_BY == 'hostname':
|
||||
self.perm_assets = self.natural_sort_hostname(self.user_perm.get('asset', []).keys())
|
||||
else:
|
||||
self.perm_assets = tuple(self.user_perm.get('asset', []))
|
||||
self.search_result = self.perm_assets
|
||||
self.perm_asset_groups = self.user_perm.get('asset_group', [])
|
||||
|
||||
def natural_sort_hostname(self, list):
|
||||
convert = lambda text: int(text) if text.isdigit() else text.lower()
|
||||
alphanum_key = lambda x: [ convert(c) for c in re.split('([0-9]+)', x.hostname) ]
|
||||
return sorted(list, key = alphanum_key)
|
||||
|
||||
@staticmethod
|
||||
def print_nav():
|
||||
"""
|
||||
@@ -450,11 +449,11 @@ class Nav(object):
|
||||
"""
|
||||
msg = """\n\033[1;32m### 欢迎使用Jumpserver开源跳板机系统 ### \033[0m
|
||||
|
||||
1) 输入 \033[32mID\033[0m 直接登录.
|
||||
2) 输入 \033[32m/\033[0m + \033[32mIP, 主机名 or 备注 \033[0m搜索.
|
||||
1) 输入 \033[32mID\033[0m 直接登录 或 输入\033[32m部分 IP,主机名,备注\033[0m 进行搜索登录(如果唯一).
|
||||
2) 输入 \033[32m/\033[0m + \033[32mIP, 主机名 or 备注 \033[0m搜索. 如: /ip
|
||||
3) 输入 \033[32mP/p\033[0m 显示您有权限的主机.
|
||||
4) 输入 \033[32mG/g\033[0m 显示您有权限的主机组.
|
||||
5) 输入 \033[32mG/g\033[0m\033[0m + \033[32m组ID\033[0m 显示该组下主机.
|
||||
5) 输入 \033[32mG/g\033[0m\033[0m + \033[32m组ID\033[0m 显示该组下主机. 如: g1
|
||||
6) 输入 \033[32mE/e\033[0m 批量执行命令.
|
||||
7) 输入 \033[32mU/u\033[0m 批量上传文件.
|
||||
8) 输入 \033[32mD/d\033[0m 批量下载文件.
|
||||
@@ -470,7 +469,7 @@ class Nav(object):
|
||||
gid = int(str_r.lstrip('g'))
|
||||
# 获取资产组包含的资产
|
||||
asset_group = get_object(AssetGroup, id=gid)
|
||||
if asset_group:
|
||||
if asset_group and asset_group in self.perm_asset_groups:
|
||||
self.search_result = list(asset_group.asset_set.all())
|
||||
else:
|
||||
color_print('没有该资产组或没有权限')
|
||||
@@ -489,22 +488,46 @@ class Nav(object):
|
||||
|
||||
except (ValueError, TypeError):
|
||||
# 匹配 ip, hostname, 备注
|
||||
self.search_result = [asset for asset in self.perm_assets if str_r in str(asset.ip)
|
||||
or str_r in str(asset.hostname) or str_r in str(asset.comment)]
|
||||
str_r = str_r.lower()
|
||||
self.search_result = [asset for asset in self.perm_assets if str_r == str(asset.ip).lower()] or \
|
||||
[asset for asset in self.perm_assets if str_r in str(asset.ip).lower() \
|
||||
or str_r in str(asset.hostname).lower() \
|
||||
or str_r in str(asset.comment).lower()]
|
||||
else:
|
||||
# 如果没有输入就展现所有
|
||||
self.search_result = self.perm_assets
|
||||
|
||||
@staticmethod
|
||||
def truncate_str(str_, length=30):
|
||||
str_ = str_.decode('utf-8')
|
||||
if len(str_) > length:
|
||||
return str_[:14] + '..' + str_[-14:]
|
||||
else:
|
||||
return str_
|
||||
|
||||
@staticmethod
|
||||
def get_max_asset_property_length(assets, property_='hostname'):
|
||||
try:
|
||||
return max([len(getattr(asset, property_)) for asset in assets])
|
||||
except ValueError:
|
||||
return 30
|
||||
|
||||
def print_search_result(self):
|
||||
color_print('[%-3s] %-12s %-15s %-5s %-10s %s' % ('ID', '主机名', 'IP', '端口', '系统用户', '备注'), 'title')
|
||||
hostname_max_length = self.get_max_asset_property_length(self.search_result)
|
||||
line = '[%-3s] %-16s %-5s %-' + str(hostname_max_length) + 's %-10s %s'
|
||||
color_print(line % ('ID', 'IP', 'Port', 'Hostname', 'SysUser', 'Comment'), 'title')
|
||||
if hasattr(self.search_result, '__iter__'):
|
||||
for index, asset in enumerate(self.search_result):
|
||||
# 获取该资产信息
|
||||
asset_info = get_asset_info(asset)
|
||||
# 获取该资产包含的角色
|
||||
role = [str(role.name) for role in self.user_perm.get('asset').get(asset).get('role')]
|
||||
print '[%-3s] %-15s %-15s %-5s %-10s %s' % (index, asset.hostname, asset.ip, asset_info.get('port'),
|
||||
role, asset.comment)
|
||||
try:
|
||||
print line % (index, asset.ip, asset_info.get('port'),
|
||||
self.truncate_str(asset.hostname), str(role).replace("'", ''), asset.comment)
|
||||
except:
|
||||
print line % (index, asset.ip, asset_info.get('port'),
|
||||
self.truncate_str(asset.hostname), str(role).replace("'", ''), '')
|
||||
print
|
||||
|
||||
def try_connect(self):
|
||||
@@ -532,8 +555,8 @@ class Nav(object):
|
||||
color_print('没有映射用户', 'red')
|
||||
return
|
||||
|
||||
ssh_tty = SshTty(login_user, asset, role)
|
||||
print('Connecting %s ...' % asset.hostname)
|
||||
ssh_tty = SshTty(login_user, asset, role)
|
||||
ssh_tty.connect()
|
||||
except (KeyError, ValueError):
|
||||
color_print('请输入正确ID', 'red')
|
||||
@@ -566,7 +589,7 @@ class Nav(object):
|
||||
print "请输入运行命令所关联系统用户的ID, q退出"
|
||||
|
||||
try:
|
||||
role_id = raw_input("\033[1;32mRole>:\033[0m ").strip()
|
||||
role_id = int(raw_input("\033[1;32mRole>:\033[0m ").strip())
|
||||
if role_id == 'q':
|
||||
break
|
||||
except (IndexError, ValueError):
|
||||
@@ -781,6 +804,7 @@ def main():
|
||||
else:
|
||||
nav.search(option)
|
||||
if len(nav.search_result) == 1:
|
||||
print('Only match Host: %s ' % nav.search_result[0].hostname)
|
||||
nav.try_connect()
|
||||
else:
|
||||
nav.print_search_result()
|
||||
|
||||
25
docker-compose.yaml
Normal file
25
docker-compose.yaml
Normal file
@@ -0,0 +1,25 @@
|
||||
version: '2'
|
||||
services:
|
||||
jumpserver:
|
||||
build: .
|
||||
container_name: jumpserver
|
||||
restart: always
|
||||
ports:
|
||||
- "8888:80"
|
||||
- "2222:22"
|
||||
# environment:
|
||||
# - USE_MYSQL=true
|
||||
# - MYSQL_ENGINE=mysql
|
||||
# - MYSQL_HOST=192.168.50.143
|
||||
# - MYSQL_PORT=3306
|
||||
# - MYSQL_USER=jumpserver
|
||||
# - MYSQL_PASS=love1314
|
||||
# - MYSQL_NAME=jumpserver
|
||||
# - USE_MAIL=true
|
||||
# - MAIL_ENABLED=1
|
||||
# - MAIL_HOST=smtp.163.com
|
||||
# - MAIL_PORT=25
|
||||
# - MAIL_USER=jumpserver@163.com
|
||||
# - MAIL_PASS=123456
|
||||
# - MAIL_USE_TLS=False
|
||||
# - MAIL_USE_SSL=False
|
||||
26
install/docker/config_tmpl.conf
Normal file
26
install/docker/config_tmpl.conf
Normal file
@@ -0,0 +1,26 @@
|
||||
[base]
|
||||
url =
|
||||
key = 941enj9neshd1wes
|
||||
ip = 0.0.0.0
|
||||
port = 80
|
||||
log = debug
|
||||
|
||||
[db]
|
||||
engine = __MYSQL_ENGINE__
|
||||
host = __MYSQL_HOST__
|
||||
port = __MYSQL_PORT__
|
||||
user = __MYSQL_USER__
|
||||
password = __MYSQL_PASS__
|
||||
database = __MYSQL_NAME__
|
||||
|
||||
[mail]
|
||||
mail_enable = __MAIL_ENABLED__
|
||||
email_host = __MAIL_HOST__
|
||||
email_port = __MAIL_PORT__
|
||||
email_host_user = __MAIL_USER__
|
||||
email_host_password = __MAIL_PASS__
|
||||
email_use_tls = __MAIL_USE_TLS__
|
||||
email_use_ssl = __MAIL_USE_SSL__
|
||||
|
||||
[connect]
|
||||
nav_sort_by = ip
|
||||
17759
install/docker/get-pip.py
Normal file
17759
install/docker/get-pip.py
Normal file
File diff suppressed because it is too large
Load Diff
29
install/docker/passwd
Normal file
29
install/docker/passwd
Normal file
@@ -0,0 +1,29 @@
|
||||
root:x:0:0:root:/root:/bin/ash
|
||||
bin:x:1:1:bin:/bin:/sbin/nologin
|
||||
daemon:x:2:2:daemon:/sbin:/sbin/nologin
|
||||
adm:x:3:4:adm:/var/adm:/sbin/nologin
|
||||
lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin
|
||||
sync:x:5:0:sync:/sbin:/bin/sync
|
||||
shutdown:x:6:0:shutdown:/sbin:/sbin/shutdown
|
||||
halt:x:7:0:halt:/sbin:/sbin/halt
|
||||
mail:x:8:12:mail:/var/spool/mail:/sbin/nologin
|
||||
news:x:9:13:news:/usr/lib/news:/sbin/nologin
|
||||
uucp:x:10:14:uucp:/var/spool/uucppublic:/sbin/nologin
|
||||
operator:x:11:0:operator:/root:/bin/sh
|
||||
man:x:13:15:man:/usr/man:/sbin/nologin
|
||||
postmaster:x:14:12:postmaster:/var/spool/mail:/sbin/nologin
|
||||
cron:x:16:16:cron:/var/spool/cron:/sbin/nologin
|
||||
ftp:x:21:21::/var/lib/ftp:/sbin/nologin
|
||||
sshd:x:22:22:sshd:/dev/null:/sbin/nologin
|
||||
at:x:25:25:at:/var/spool/cron/atjobs:/sbin/nologin
|
||||
squid:x:31:31:Squid:/var/cache/squid:/sbin/nologin
|
||||
xfs:x:33:33:X Font Server:/etc/X11/fs:/sbin/nologin
|
||||
games:x:35:35:games:/usr/games:/sbin/nologin
|
||||
postgres:x:70:70::/var/lib/postgresql:/bin/sh
|
||||
nut:x:84:84:nut:/var/state/nut:/sbin/nologin
|
||||
cyrus:x:85:12::/usr/cyrus:/sbin/nologin
|
||||
vpopmail:x:89:89::/var/vpopmail:/sbin/nologin
|
||||
ntp:x:123:123:NTP:/var/empty:/sbin/nologin
|
||||
smmsp:x:209:209:smmsp:/var/spool/mqueue:/sbin/nologin
|
||||
guest:x:405:100:guest:/dev/null:/sbin/nologin
|
||||
nobody:x:65534:65534:nobody:/:/sbin/nologin
|
||||
19
install/docker/piprequires.txt
Normal file
19
install/docker/piprequires.txt
Normal file
@@ -0,0 +1,19 @@
|
||||
#sphinx-me==0.3
|
||||
django==1.6
|
||||
#pycrypto==2.4.1
|
||||
paramiko==1.16.0
|
||||
ecdsa==0.13
|
||||
#MySQL-python==1.2.5
|
||||
#django-uuidfield==0.5.0
|
||||
#psutil==3.3.0
|
||||
xlsxwriter==0.7.7
|
||||
xlrd==0.9.4
|
||||
django-bootstrap-form==3.2
|
||||
tornado==4.3
|
||||
ansible==1.9.4
|
||||
pyinotify==0.9.6
|
||||
passlib==1.6.5
|
||||
argparse==1.4.0
|
||||
django-crontab==0.6.0
|
||||
django-smtp-ssl==1.0
|
||||
pyte==0.5.2
|
||||
61
install/docker/run.sh
Normal file
61
install/docker/run.sh
Normal file
@@ -0,0 +1,61 @@
|
||||
#!/bin/sh
|
||||
cp -r /jumpserver/install/docker/config_tmpl.conf /jumpserver/jumpserver.conf
|
||||
if [ ! -n "${USE_MYSQL}" ]; then
|
||||
sed -i "s/__USE_MYSQL__/false/" /jumpserver/jumpserver.conf
|
||||
else
|
||||
sed -i "s/__MYSQL_ENGINE__/${MYSQL_ENGINE}/" /jumpserver/jumpserver.conf
|
||||
sed -i "s/__MYSQL_HOST__/${MYSQL_HOST}/" /jumpserver/jumpserver.conf
|
||||
sed -i "s/__MYSQL_PORT__/${MYSQL_PORT}/" /jumpserver/jumpserver.conf
|
||||
sed -i "s/__MYSQL_USER__/${MYSQL_USER}/" /jumpserver/jumpserver.conf
|
||||
sed -i "s/__MYSQL_PASS__/${MYSQL_PASS}/" /jumpserver/jumpserver.conf
|
||||
sed -i "s/__MYSQL_NAME__/${MYSQL_NAME}/" /jumpserver/jumpserver.conf
|
||||
fi
|
||||
|
||||
if [ ! -n "${USE_MAIL}" ]; then
|
||||
sed -i "s/__USE_MAIL__/false/" /jumpserver/jumpserver.conf
|
||||
else
|
||||
sed -i "s/__MAIL_ENABLED__/${MAIL_ENABLED}/" /jumpserver/jumpserver.conf
|
||||
sed -i "s/__MAIL_HOST__/${MAIL_HOST}/" /jumpserver/jumpserver.conf
|
||||
sed -i "s/__MAIL_PORT__/${MAIL_PORT}/" /jumpserver/jumpserver.conf
|
||||
sed -i "s/__MAIL_USER__/${MAIL_USER}/" /jumpserver/jumpserver.conf
|
||||
sed -i "s/__MAIL_PASS__/${MAIL_PASS}/" /jumpserver/jumpserver.conf
|
||||
fi
|
||||
if [ ! -n "${MAIL_USE_TLS}" ]; then
|
||||
sed -i "s/__MAIL_USE_TLS__/false/" /jumpserver/jumpserver.conf
|
||||
else
|
||||
sed -i "s/__MAIL_USE_TLS__/${MAIL_USE_TLS}/" /jumpserver/jumpserver.conf
|
||||
fi
|
||||
if [ ! -n "${MAIL_USE_SSL}" ]; then
|
||||
sed -i "s/__MAIL_USE_SSL__/false/" /jumpserver/jumpserver.conf
|
||||
else
|
||||
sed -i "s/__MAIL_USE_SSL__/${MAIL_USE_SSL}/" /jumpserver/jumpserver.conf
|
||||
fi
|
||||
if [ ! -f "/etc/ssh/sshd_config" ]; then
|
||||
cp -r /jumpserver/install/docker/sshd_config /etc/ssh/sshd_config
|
||||
fi
|
||||
if [ ! -f "/etc/ssh/ssh_host_rsa_key" ]; then
|
||||
ssh-keygen -t rsa -b 2048 -f /etc/ssh/ssh_host_rsa_key -N ''
|
||||
fi
|
||||
if [ ! -f "/etc/ssh/ssh_host_dsa_key" ]; then
|
||||
ssh-keygen -t dsa -b 1024 -f /etc/ssh/ssh_host_dsa_key -N ''
|
||||
fi
|
||||
if [ ! -f "/etc/ssh/ssh_host_ecdsa_key" ]; then
|
||||
ssh-keygen -t ecdsa -b 521 -f /etc/ssh/ssh_host_ecdsa_key -N ''
|
||||
fi
|
||||
if [ ! -f "/etc/ssh/ssh_host_ed25519_key" ]; then
|
||||
ssh-keygen -t ed25519 -b 1024 -f /etc/ssh/ssh_host_ed25519_key -N ''
|
||||
fi
|
||||
|
||||
# handle empty data directory
|
||||
mkdir -p /data/logs
|
||||
|
||||
/usr/sbin/sshd -E /data/logs/jumpserver.log
|
||||
python /jumpserver/manage.py syncdb --noinput
|
||||
if [ ! -f "/home/init.locked" ]; then
|
||||
python /jumpserver/manage.py loaddata install/initial_data.yaml
|
||||
date > /home/init.locked
|
||||
fi
|
||||
python /jumpserver/manage.py crontab add >> /data/logs/jumpserver.log &
|
||||
python /jumpserver/run_server.py >> /dev/null &
|
||||
chmod -R 777 /data/logs/jumpserver.log
|
||||
tail -f /data/logs/jumpserver.log
|
||||
29
install/docker/shadow
Normal file
29
install/docker/shadow
Normal file
@@ -0,0 +1,29 @@
|
||||
root:::0:::::
|
||||
bin:!::0:::::
|
||||
daemon:!::0:::::
|
||||
adm:!::0:::::
|
||||
lp:!::0:::::
|
||||
sync:!::0:::::
|
||||
shutdown:!::0:::::
|
||||
halt:!::0:::::
|
||||
mail:!::0:::::
|
||||
news:!::0:::::
|
||||
uucp:!::0:::::
|
||||
operator:!::0:::::
|
||||
man:!::0:::::
|
||||
postmaster:!::0:::::
|
||||
cron:!::0:::::
|
||||
ftp:!::0:::::
|
||||
sshd:!::0:::::
|
||||
at:!::0:::::
|
||||
squid:!::0:::::
|
||||
xfs:!::0:::::
|
||||
games:!::0:::::
|
||||
postgres:!::0:::::
|
||||
nut:!::0:::::
|
||||
cyrus:!::0:::::
|
||||
vpopmail:!::0:::::
|
||||
ntp:!::0:::::
|
||||
smmsp:!::0:::::
|
||||
guest:!::0:::::
|
||||
nobody:!::0:::::
|
||||
146
install/docker/sshd_config
Normal file
146
install/docker/sshd_config
Normal file
@@ -0,0 +1,146 @@
|
||||
# $OpenBSD: sshd_config,v 1.98 2016/02/17 05:29:04 djm Exp $
|
||||
|
||||
# This is the sshd server system-wide configuration file. See
|
||||
# sshd_config(5) for more information.
|
||||
|
||||
# This sshd was compiled with PATH=/bin:/usr/bin:/sbin:/usr/sbin
|
||||
|
||||
# The strategy used for options in the default sshd_config shipped with
|
||||
# OpenSSH is to specify options with their default value where
|
||||
# possible, but leave them commented. Uncommented options override the
|
||||
# default value.
|
||||
|
||||
#Port 22
|
||||
#AddressFamily any
|
||||
#ListenAddress 0.0.0.0
|
||||
#ListenAddress ::
|
||||
|
||||
# The default requires explicit activation of protocol 1
|
||||
#Protocol 2
|
||||
|
||||
# HostKey for protocol version 1
|
||||
#HostKey /etc/ssh/ssh_host_key
|
||||
# HostKeys for protocol version 2
|
||||
#HostKey /etc/ssh/ssh_host_rsa_key
|
||||
#HostKey /etc/ssh/ssh_host_dsa_key
|
||||
#HostKey /etc/ssh/ssh_host_ecdsa_key
|
||||
#HostKey /etc/ssh/ssh_host_ed25519_key
|
||||
|
||||
# Lifetime and size of ephemeral version 1 server key
|
||||
#KeyRegenerationInterval 1h
|
||||
#ServerKeyBits 1024
|
||||
|
||||
# Ciphers and keying
|
||||
#RekeyLimit default none
|
||||
|
||||
# Logging
|
||||
# obsoletes QuietMode and FascistLogging
|
||||
#SyslogFacility AUTH
|
||||
#LogLevel INFO
|
||||
|
||||
# Authentication:
|
||||
|
||||
#LoginGraceTime 2m
|
||||
#PermitRootLogin prohibit-password
|
||||
#StrictModes yes
|
||||
#MaxAuthTries 6
|
||||
#MaxSessions 10
|
||||
|
||||
#RSAAuthentication yes
|
||||
#PubkeyAuthentication yes
|
||||
PasswordAuthentication no
|
||||
ChallengeResponseAuthentication no
|
||||
|
||||
# The default is to check both .ssh/authorized_keys and .ssh/authorized_keys2
|
||||
# but this is overridden so installations will only check .ssh/authorized_keys
|
||||
AuthorizedKeysFile .ssh/authorized_keys
|
||||
|
||||
#AuthorizedPrincipalsFile none
|
||||
|
||||
#AuthorizedKeysCommand none
|
||||
#AuthorizedKeysCommandUser nobody
|
||||
|
||||
# For this to work you will also need host keys in /etc/ssh/ssh_known_hosts
|
||||
#RhostsRSAAuthentication no
|
||||
# similar for protocol version 2
|
||||
#HostbasedAuthentication no
|
||||
# Change to yes if you don't trust ~/.ssh/known_hosts for
|
||||
# RhostsRSAAuthentication and HostbasedAuthentication
|
||||
#IgnoreUserKnownHosts no
|
||||
# Don't read the user's ~/.rhosts and ~/.shosts files
|
||||
#IgnoreRhosts yes
|
||||
|
||||
# To disable tunneled clear text passwords, change to no here!
|
||||
#PasswordAuthentication yes
|
||||
#PermitEmptyPasswords no
|
||||
|
||||
# Change to no to disable s/key passwords
|
||||
#ChallengeResponseAuthentication yes
|
||||
|
||||
# Kerberos options
|
||||
#KerberosAuthentication no
|
||||
#KerberosOrLocalPasswd yes
|
||||
#KerberosTicketCleanup yes
|
||||
#KerberosGetAFSToken no
|
||||
|
||||
# GSSAPI options
|
||||
#GSSAPIAuthentication no
|
||||
#GSSAPICleanupCredentials yes
|
||||
|
||||
# Set this to 'yes' to enable PAM authentication, account processing,
|
||||
# and session processing. If this is enabled, PAM authentication will
|
||||
# be allowed through the ChallengeResponseAuthentication and
|
||||
# PasswordAuthentication. Depending on your PAM configuration,
|
||||
# PAM authentication via ChallengeResponseAuthentication may bypass
|
||||
# the setting of "PermitRootLogin without-password".
|
||||
# If you just want the PAM account and session checks to run without
|
||||
# PAM authentication, then enable this but set PasswordAuthentication
|
||||
# and ChallengeResponseAuthentication to 'no'.
|
||||
#UsePAM no
|
||||
|
||||
#AllowAgentForwarding yes
|
||||
#AllowTcpForwarding yes
|
||||
#GatewayPorts no
|
||||
#X11Forwarding no
|
||||
#X11DisplayOffset 10
|
||||
#X11UseLocalhost yes
|
||||
#PermitTTY yes
|
||||
#PrintMotd yes
|
||||
#PrintLastLog yes
|
||||
#TCPKeepAlive yes
|
||||
#UseLogin no
|
||||
#UsePrivilegeSeparation sandbox
|
||||
#PermitUserEnvironment no
|
||||
#Compression delayed
|
||||
#ClientAliveInterval 0
|
||||
#ClientAliveCountMax 3
|
||||
#UseDNS no
|
||||
#PidFile /run/sshd.pid
|
||||
#MaxStartups 10:30:100
|
||||
#PermitTunnel no
|
||||
#ChrootDirectory none
|
||||
#VersionAddendum none
|
||||
|
||||
# no default banner path
|
||||
#Banner none
|
||||
|
||||
# override default of no subsystems
|
||||
Subsystem sftp /usr/lib/ssh/sftp-server
|
||||
|
||||
# the following are HPN related configuration options
|
||||
# tcp receive buffer polling. disable in non autotuning kernels
|
||||
#TcpRcvBufPoll yes
|
||||
|
||||
# disable hpn performance boosts
|
||||
#HPNDisabled no
|
||||
|
||||
# buffer size for hpn to non-hpn connections
|
||||
#HPNBufferSize 2048
|
||||
|
||||
|
||||
# Example of overriding settings on a per-user basis
|
||||
#Match User anoncvs
|
||||
# X11Forwarding no
|
||||
# AllowTcpForwarding no
|
||||
# PermitTTY no
|
||||
# ForceCommand cvs server
|
||||
2
install/docker/useradd
Normal file
2
install/docker/useradd
Normal file
@@ -0,0 +1,2 @@
|
||||
#!/bin/sh
|
||||
adduser $@
|
||||
2
install/docker/userdel
Normal file
2
install/docker/userdel
Normal file
@@ -0,0 +1,2 @@
|
||||
#!/bin/sh
|
||||
deluser --remove-home $3
|
||||
@@ -80,17 +80,17 @@ class PreSetup(object):
|
||||
self.ip = ''
|
||||
self.key = ''.join(random.choice(string.ascii_lowercase + string.digits) \
|
||||
for _ in range(16))
|
||||
self.dist = platform.linux_distribution(supported_dists=['system'])[0].lower()
|
||||
self.version = platform.linux_distribution(supported_dists=['system'])[1]
|
||||
self.dist = platform.linux_distribution()[0].lower()
|
||||
self.version = platform.linux_distribution()[1]
|
||||
|
||||
@property
|
||||
def _is_redhat(self):
|
||||
if self.dist == "centos" or self.dist == "redhat" or self.dist == "fedora" or self.dist == "amazon linux ami":
|
||||
if self.dist.startswith("centos") or self.dist.startswith("red") or self.dist == "fedora" or self.dist == "oracle" or self.dist == "amazon linux ami":
|
||||
return True
|
||||
|
||||
@property
|
||||
def _is_centos7(self):
|
||||
if self.dist == "centos" and self.version.startswith("7"):
|
||||
if (self.dist.startswith("centos") or self.dist.startswith("centos linux")) and self.version.startswith("7"):
|
||||
return True
|
||||
|
||||
@property
|
||||
@@ -104,8 +104,8 @@ class PreSetup(object):
|
||||
return True
|
||||
|
||||
def check_platform(self):
|
||||
if not (self._is_redhat or self._is_ubuntu):
|
||||
print(u"支持的平台: CentOS, RedHat, Fedora, Debian, Ubuntu, Amazon Linux, 暂不支持其他平台安装.")
|
||||
if not (self._is_redhat or self._is_ubuntu or self._is_centos7):
|
||||
print(u"支持的平台: CentOS, RedHat, Fedora, Oracle Linux, Debian, Ubuntu, Amazon Linux, 暂不支持其他平台安装.")
|
||||
exit()
|
||||
|
||||
@staticmethod
|
||||
@@ -238,6 +238,7 @@ class PreSetup(object):
|
||||
def _require_pip(self):
|
||||
color_print('开始安装依赖pip包', 'green')
|
||||
bash('pip uninstall -y pycrypto')
|
||||
bash('rm -rf /usr/lib64/python2.6/site-packages/Crypto/')
|
||||
ret_code = bash('pip install -r requirements.txt')
|
||||
self.check_bash_return(ret_code, "安装JumpServer 依赖的python库失败!")
|
||||
|
||||
|
||||
@@ -96,7 +96,7 @@ class Setup(object):
|
||||
cmd = 'bash %s start' % os.path.join(jms_dir, 'service.sh')
|
||||
shlex.os.system(cmd)
|
||||
print
|
||||
color_print('安装成功,请访问web, 祝你使用愉快。\n请访问 https://github.com/jumpserver/jumpserver/wiki 查看文档', 'green')
|
||||
color_print('安装成功,Web登录请访问http://ip:8000, 祝你使用愉快。\n请访问 https://github.com/jumpserver/jumpserver/wiki 查看文档', 'green')
|
||||
|
||||
def start(self):
|
||||
print "开始安装Jumpserver ..."
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#sphinx-me==0.3
|
||||
PyYAML==3.10
|
||||
django==1.6
|
||||
pycrypto==2.4.1
|
||||
paramiko==1.16.0
|
||||
|
||||
@@ -67,7 +67,7 @@ class Asset(models.Model):
|
||||
port = models.IntegerField(blank=True, null=True, verbose_name=u"端口号")
|
||||
group = models.ManyToManyField(AssetGroup, blank=True, verbose_name=u"所属主机组")
|
||||
username = models.CharField(max_length=16, blank=True, null=True, verbose_name=u"管理用户名")
|
||||
password = models.CharField(max_length=64, blank=True, null=True, verbose_name=u"密码")
|
||||
password = models.CharField(max_length=256, blank=True, null=True, verbose_name=u"密码")
|
||||
use_default_auth = models.BooleanField(default=True, verbose_name=u"使用默认管理账号")
|
||||
idc = models.ForeignKey(IDC, blank=True, null=True, on_delete=models.SET_NULL, verbose_name=u'机房')
|
||||
mac = models.CharField(max_length=20, blank=True, null=True, verbose_name=u"MAC地址")
|
||||
|
||||
@@ -135,13 +135,16 @@ def asset_add(request):
|
||||
af_post = AssetForm(request.POST)
|
||||
ip = request.POST.get('ip', '')
|
||||
hostname = request.POST.get('hostname', '')
|
||||
|
||||
is_active = True if request.POST.get('is_active') == '1' else False
|
||||
use_default_auth = request.POST.get('use_default_auth', '')
|
||||
try:
|
||||
if Asset.objects.filter(hostname=unicode(hostname)):
|
||||
error = u'该主机名 %s 已存在!' % hostname
|
||||
raise ServerError(error)
|
||||
|
||||
if len(hostname) > 54:
|
||||
error = u"主机名长度不能超过53位!"
|
||||
raise ServerError(error)
|
||||
except ServerError:
|
||||
pass
|
||||
else:
|
||||
@@ -219,34 +222,38 @@ def asset_edit(request):
|
||||
if asset_test and asset_id != unicode(asset_test.id):
|
||||
emg = u'该主机名 %s 已存在!' % hostname
|
||||
raise ServerError(emg)
|
||||
except ServerError:
|
||||
pass
|
||||
else:
|
||||
if af_post.is_valid():
|
||||
af_save = af_post.save(commit=False)
|
||||
if use_default_auth:
|
||||
af_save.username = ''
|
||||
af_save.password = ''
|
||||
# af_save.port = None
|
||||
else:
|
||||
if password:
|
||||
password_encode = CRYPTOR.encrypt(password)
|
||||
af_save.password = password_encode
|
||||
else:
|
||||
af_save.password = password_old
|
||||
af_save.is_active = True if is_active else False
|
||||
af_save.save()
|
||||
af_post.save_m2m()
|
||||
# asset_new = get_object(Asset, id=asset_id)
|
||||
# asset_diff_one(asset_old, asset_new)
|
||||
info = asset_diff(af_post.__dict__.get('initial'), request.POST)
|
||||
db_asset_alert(asset, username, info)
|
||||
|
||||
smg = u'主机 %s 修改成功' % ip
|
||||
if len(hostname) > 54:
|
||||
emg = u'主机名长度不能超过54位!'
|
||||
raise ServerError(emg)
|
||||
else:
|
||||
emg = u'主机 %s 修改失败' % ip
|
||||
return my_render('jasset/error.html', locals(), request)
|
||||
return HttpResponseRedirect(reverse('asset_detail')+'?id=%s' % asset_id)
|
||||
if af_post.is_valid():
|
||||
af_save = af_post.save(commit=False)
|
||||
if use_default_auth:
|
||||
af_save.username = ''
|
||||
af_save.password = ''
|
||||
# af_save.port = None
|
||||
else:
|
||||
if password:
|
||||
password_encode = CRYPTOR.encrypt(password)
|
||||
af_save.password = password_encode
|
||||
else:
|
||||
af_save.password = password_old
|
||||
af_save.is_active = True if is_active else False
|
||||
af_save.save()
|
||||
af_post.save_m2m()
|
||||
# asset_new = get_object(Asset, id=asset_id)
|
||||
# asset_diff_one(asset_old, asset_new)
|
||||
info = asset_diff(af_post.__dict__.get('initial'), request.POST)
|
||||
db_asset_alert(asset, username, info)
|
||||
|
||||
smg = u'主机 %s 修改成功' % ip
|
||||
else:
|
||||
emg = u'主机 %s 修改失败' % ip
|
||||
raise ServerError(emg)
|
||||
except ServerError as e:
|
||||
error = e.message
|
||||
return my_render('jasset/asset_edit.html', locals(), request)
|
||||
return HttpResponseRedirect(reverse('asset_detail')+'?id=%s' % asset_id)
|
||||
|
||||
return my_render('jasset/asset_edit.html', locals(), request)
|
||||
|
||||
|
||||
@@ -224,7 +224,7 @@ class TermLogRecorder(object):
|
||||
Initializing the virtual screen and the character stream
|
||||
"""
|
||||
self._stream = pyte.ByteStream()
|
||||
self._screen = pyte.Screen(80, 24)
|
||||
self._screen = pyte.Screen(100, 35)
|
||||
self._stream.attach(self._screen)
|
||||
|
||||
def _command(self):
|
||||
@@ -240,6 +240,7 @@ class TermLogRecorder(object):
|
||||
TermLogRecorder.loglist[str(id)] = [self]
|
||||
|
||||
def write(self, msg):
|
||||
"""
|
||||
if self.recoder and (not self._in_vim):
|
||||
if self.commands.__len__() == 0:
|
||||
self._stream.feed(msg)
|
||||
@@ -256,6 +257,7 @@ class TermLogRecorder(object):
|
||||
self._screen.reset()
|
||||
else:
|
||||
self._command()
|
||||
"""
|
||||
try:
|
||||
self.write_message(msg)
|
||||
except:
|
||||
@@ -272,7 +274,7 @@ class TermLogRecorder(object):
|
||||
self.filename = filename
|
||||
filepath = os.path.join(path, 'tty', date, filename + '.zip')
|
||||
if not os.path.isdir(os.path.join(path, 'tty', date)):
|
||||
os.makedirs(os.path.join(path, 'tty', date), mode=0777)
|
||||
mkdir(os.path.join(path, 'tty', date), mode=777)
|
||||
while os.path.isfile(filepath):
|
||||
filename = str(uuid.uuid4())
|
||||
filepath = os.path.join(path, 'tty', date, filename + '.zip')
|
||||
|
||||
@@ -316,6 +316,8 @@ class MyTask(MyRunner):
|
||||
"""
|
||||
push the ssh authorized key to target.
|
||||
"""
|
||||
if user == 'root':
|
||||
return {"status": "failed", "msg": "root cann't be delete"}
|
||||
module_args = 'user="%s" key="{{ lookup("file", "%s") }}" state="absent"' % (user, key_path)
|
||||
self.run("authorized_key", module_args, become=True)
|
||||
|
||||
@@ -361,6 +363,8 @@ class MyTask(MyRunner):
|
||||
"""
|
||||
delete a host user.
|
||||
"""
|
||||
if username == 'root':
|
||||
return {"status": "failed", "msg": "root cann't be delete"}
|
||||
module_args = 'name=%s state=absent remove=yes move_home=yes force=yes' % username
|
||||
self.run("user", module_args, become=True)
|
||||
return self.results
|
||||
@@ -371,6 +375,8 @@ class MyTask(MyRunner):
|
||||
:param username:
|
||||
:return:
|
||||
"""
|
||||
if username == 'root':
|
||||
return {"status": "failed", "msg": "root cann't be delete"}
|
||||
module_args = "sed -i 's/^%s.*//' /etc/sudoers" % username
|
||||
self.run("command", module_args, become=True)
|
||||
return self.results
|
||||
@@ -403,6 +409,17 @@ class MyTask(MyRunner):
|
||||
self.run("script", module_args1, become=True)
|
||||
return self.results
|
||||
|
||||
def recyle_cmd_alias(self, role_name):
|
||||
"""
|
||||
recyle sudo cmd alias
|
||||
:return:
|
||||
"""
|
||||
if role_name == 'root':
|
||||
return {"status": "failed", "msg": "can't recyle root privileges"}
|
||||
module_args = "sed -i 's/^%s.*//' /etc/sudoers" % role_name
|
||||
self.run("command", module_args, become=True)
|
||||
return self.results
|
||||
|
||||
|
||||
class CustomAggregateStats(callbacks.AggregateStats):
|
||||
"""
|
||||
@@ -493,8 +510,8 @@ if __name__ == "__main__":
|
||||
# # "ansible_become_user": "root",
|
||||
# "ansible_become_pass": "yusky0902",
|
||||
}]
|
||||
cmd = Command(resource)
|
||||
print cmd.run('ls')
|
||||
cmd.run('ls',pattern='*')
|
||||
print cmd.results_raw
|
||||
|
||||
# resource = [{"hostname": "192.168.10.148", "port": "22", "username": "root", "password": "xxx"}]
|
||||
# task = Tasks(resource)
|
||||
|
||||
@@ -26,7 +26,7 @@ class PermSudo(models.Model):
|
||||
class PermRole(models.Model):
|
||||
name = models.CharField(max_length=100, unique=True)
|
||||
comment = models.CharField(max_length=100, null=True, blank=True, default='')
|
||||
password = models.CharField(max_length=128)
|
||||
password = models.CharField(max_length=512)
|
||||
key_path = models.CharField(max_length=100)
|
||||
date_added = models.DateTimeField(auto_now=True)
|
||||
sudo = models.ManyToManyField(PermSudo, related_name='perm_role')
|
||||
|
||||
@@ -42,7 +42,7 @@ def gen_keys(key="", key_path_dir=""):
|
||||
key_path_dir = os.path.join(KEY_DIR, 'role_key', key_basename)
|
||||
private_key = os.path.join(key_path_dir, 'id_rsa')
|
||||
public_key = os.path.join(key_path_dir, 'id_rsa.pub')
|
||||
mkdir(key_path_dir, mode=0755)
|
||||
mkdir(key_path_dir, mode=755)
|
||||
if not key:
|
||||
key = RSAKey.generate(2048)
|
||||
key.write_private_key_file(private_key)
|
||||
|
||||
@@ -16,6 +16,7 @@ from jperm.ansible_api import MyTask
|
||||
from jperm.perm_api import get_role_info, get_role_push_host
|
||||
from jumpserver.api import my_render, get_object, CRYPTOR
|
||||
|
||||
|
||||
# 设置PERM APP Log
|
||||
from jumpserver.api import logger
|
||||
#logger = set_log(LOG_LEVEL, filename='jumpserver_perm.log')
|
||||
@@ -38,7 +39,7 @@ def perm_rule_list(request):
|
||||
rules_list = rules_list.filter(id=rule_id)
|
||||
|
||||
if keyword:
|
||||
rules_list = rules_list.filter(Q(name=keyword))
|
||||
rules_list = rules_list.filter(Q(name__icontains=keyword))
|
||||
|
||||
rules_list, p, rules, page_range, current_page, show_first, show_end = pages(rules_list, request)
|
||||
|
||||
@@ -80,6 +81,7 @@ def perm_rule_detail(request):
|
||||
return my_render('jperm/perm_rule_detail.html', locals(), request)
|
||||
|
||||
|
||||
@require_role('admin')
|
||||
def perm_rule_add(request):
|
||||
"""
|
||||
add rule page
|
||||
@@ -290,6 +292,8 @@ def perm_role_add(request):
|
||||
if name == "root":
|
||||
raise ServerError(u'禁止使用root用户作为系统用户,这样非常危险!')
|
||||
default = get_object(Setting, name='default')
|
||||
if len(password) > 64:
|
||||
raise ServerError(u'密码长度不能超过64位!')
|
||||
|
||||
if password:
|
||||
encrypt_pass = CRYPTOR.encrypt(password)
|
||||
@@ -330,7 +334,6 @@ def perm_role_delete(request):
|
||||
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]
|
||||
@@ -414,6 +417,7 @@ def perm_role_detail(request):
|
||||
users = role_info.get("users")
|
||||
user_groups = role_info.get("user_groups")
|
||||
pushed_asset, need_push_asset = get_role_push_host(get_object(PermRole, id=role_id))
|
||||
|
||||
except ServerError, e:
|
||||
logger.warning(e)
|
||||
|
||||
@@ -446,6 +450,8 @@ def perm_role_edit(request):
|
||||
role_sudo_names = request.POST.getlist("sudo_name")
|
||||
role_sudos = [PermSudo.objects.get(id=sudo_id) for sudo_id in role_sudo_names]
|
||||
key_content = request.POST.get("role_key", "")
|
||||
if len(role_password) > 64:
|
||||
raise ServerError(u'密码长度不能超过64位!')
|
||||
|
||||
try:
|
||||
if not role:
|
||||
@@ -528,6 +534,8 @@ def perm_role_push(request):
|
||||
sudo_list = set([sudo for sudo in role.sudo.all()]) # set(sudo1, sudo2, sudo3)
|
||||
if sudo_list:
|
||||
ret['sudo'] = task.push_sudo_file([role], sudo_list)
|
||||
else:
|
||||
ret['sudo'] = task.recyle_cmd_alias(role.name)
|
||||
|
||||
logger.debug('推送role结果: %s' % ret)
|
||||
success_asset = {}
|
||||
@@ -571,7 +579,7 @@ def perm_role_push(request):
|
||||
if not failed_asset:
|
||||
msg = u'系统用户 %s 推送成功[ %s ]' % (role.name, ','.join(success_asset.keys()))
|
||||
else:
|
||||
error = u'系统用户 %s 推送失败 [ %s ], 推送成功 [ %s ] 进入系统用户详情,查看失败原因' % (role.name,
|
||||
error = u'系统用户 %s 推送失败 [ %s ], 推送成功 [ %s ] 请点系统用户->点对应名称->点失败,查看失败原因' % (role.name,
|
||||
','.join(failed_asset.keys()),
|
||||
','.join(success_asset.keys()))
|
||||
return my_render('jperm/perm_role_push.html', locals(), request)
|
||||
|
||||
@@ -6,6 +6,7 @@ port = 8000
|
||||
log = debug
|
||||
|
||||
[db]
|
||||
engine = mysql
|
||||
host = 127.0.0.1
|
||||
port = 3306
|
||||
user = jumpserver
|
||||
@@ -18,5 +19,8 @@ email_host =
|
||||
email_port = 587
|
||||
email_host_user =
|
||||
email_host_password =
|
||||
email_use_tls = True
|
||||
email_use_tls = False
|
||||
email_use_ssl = False
|
||||
|
||||
[connect]
|
||||
nav_sort_by = ip
|
||||
|
||||
@@ -91,7 +91,7 @@ def get_role_key(user, role):
|
||||
"""
|
||||
user_role_key_dir = os.path.join(KEY_DIR, 'user')
|
||||
user_role_key_path = os.path.join(user_role_key_dir, '%s_%s.pem' % (user.username, role.name))
|
||||
mkdir(user_role_key_dir, mode=0777)
|
||||
mkdir(user_role_key_dir, mode=777)
|
||||
if not os.path.isfile(user_role_key_path):
|
||||
with open(os.path.join(role.key_path, 'id_rsa')) as fk:
|
||||
with open(user_role_key_path, 'w') as fu:
|
||||
@@ -458,14 +458,13 @@ def bash(cmd):
|
||||
return subprocess.call(cmd, shell=True)
|
||||
|
||||
|
||||
def mkdir(dir_name, username='', mode=0755):
|
||||
def mkdir(dir_name, username='', mode=755):
|
||||
"""
|
||||
insure the dir exist and mode ok
|
||||
目录存在,如果不存在就建立,并且权限正确
|
||||
"""
|
||||
if not os.path.isdir(dir_name):
|
||||
os.makedirs(dir_name)
|
||||
os.chmod(dir_name, mode)
|
||||
cmd = '[ ! -d %s ] && mkdir -p %s && chmod %s %s' % (dir_name, dir_name, mode, dir_name)
|
||||
bash(cmd)
|
||||
if username:
|
||||
chown(dir_name, username)
|
||||
|
||||
@@ -486,7 +485,7 @@ def my_render(template, data, request):
|
||||
def get_tmp_dir():
|
||||
seed = uuid.uuid4().hex[:4]
|
||||
dir_name = os.path.join('/tmp', '%s-%s' % (datetime.datetime.now().strftime('%Y%m%d-%H%M%S'), seed))
|
||||
mkdir(dir_name, mode=0777)
|
||||
mkdir(dir_name, mode=777)
|
||||
return dir_name
|
||||
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ class Setting(models.Model):
|
||||
name = models.CharField(max_length=100)
|
||||
field1 = models.CharField(max_length=100, null=True, blank=True)
|
||||
field2 = models.CharField(max_length=100, null=True, blank=True)
|
||||
field3 = models.CharField(max_length=100, null=True, blank=True)
|
||||
field3 = models.CharField(max_length=256, null=True, blank=True)
|
||||
field4 = models.CharField(max_length=100, null=True, blank=True)
|
||||
field5 = models.CharField(max_length=100, null=True, blank=True)
|
||||
|
||||
|
||||
@@ -19,12 +19,6 @@ BASE_DIR = os.path.abspath(os.path.dirname(os.path.dirname(__file__)))
|
||||
config.read(os.path.join(BASE_DIR, 'jumpserver.conf'))
|
||||
KEY_DIR = os.path.join(BASE_DIR, 'keys')
|
||||
|
||||
|
||||
DB_HOST = config.get('db', 'host')
|
||||
DB_PORT = config.getint('db', 'port')
|
||||
DB_USER = config.get('db', 'user')
|
||||
DB_PASSWORD = config.get('db', 'password')
|
||||
DB_DATABASE = config.get('db', 'database')
|
||||
AUTH_USER_MODEL = 'juser.User'
|
||||
# mail config
|
||||
MAIL_ENABLE = config.get('mail', 'mail_enable')
|
||||
@@ -49,6 +43,12 @@ LOG_LEVEL = config.get('base', 'log')
|
||||
IP = config.get('base', 'ip')
|
||||
PORT = config.get('base', 'port')
|
||||
|
||||
# ======== Connect ==========
|
||||
try:
|
||||
NAV_SORT_BY = config.get('connect', 'nav_sort_by')
|
||||
except (ConfigParser.NoSectionError, ConfigParser.NoOptionError):
|
||||
NAV_SORT_BY = 'ip'
|
||||
|
||||
# Quick-start development settings - unsuitable for production
|
||||
# See https://docs.djangoproject.com/en/1.7/howto/deployment/checklist/
|
||||
|
||||
@@ -98,24 +98,37 @@ WSGI_APPLICATION = 'jumpserver.wsgi.application'
|
||||
|
||||
# Database
|
||||
# https://docs.djangoproject.com/en/1.7/ref/settings/#databases
|
||||
|
||||
DATABASES = {
|
||||
'default': {
|
||||
'ENGINE': 'django.db.backends.mysql',
|
||||
'NAME': DB_DATABASE,
|
||||
'USER': DB_USER,
|
||||
'PASSWORD': DB_PASSWORD,
|
||||
'HOST': DB_HOST,
|
||||
'PORT': DB_PORT,
|
||||
DATABASES = {}
|
||||
if config.get('db', 'engine') == 'mysql':
|
||||
DB_HOST = config.get('db', 'host')
|
||||
DB_PORT = config.getint('db', 'port')
|
||||
DB_USER = config.get('db', 'user')
|
||||
DB_PASSWORD = config.get('db', 'password')
|
||||
DB_DATABASE = config.get('db', 'database')
|
||||
DATABASES = {
|
||||
'default': {
|
||||
'ENGINE': 'django.db.backends.mysql',
|
||||
'NAME': DB_DATABASE,
|
||||
'USER': DB_USER,
|
||||
'PASSWORD': DB_PASSWORD,
|
||||
'HOST': DB_HOST,
|
||||
'PORT': DB_PORT,
|
||||
}
|
||||
}
|
||||
elif config.get('db', 'engine') == 'sqlite':
|
||||
DATABASES = {
|
||||
'default': {
|
||||
'ENGINE': 'django.db.backends.sqlite3',
|
||||
'NAME': config.get('db', 'database'),
|
||||
}
|
||||
}
|
||||
else:
|
||||
DATABASES = {
|
||||
'default': {
|
||||
'ENGINE': 'django.db.backends.sqlite3',
|
||||
'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# DATABASES = {
|
||||
# 'default': {
|
||||
# 'ENGINE': 'django.db.backends.sqlite3',
|
||||
# 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
|
||||
# }
|
||||
# }
|
||||
TEMPLATE_CONTEXT_PROCESSORS = (
|
||||
'django.contrib.auth.context_processors.auth',
|
||||
'django.core.context_processors.debug',
|
||||
|
||||
@@ -19,6 +19,7 @@ from jlog.models import Log, FileLog
|
||||
from jperm.perm_api import get_group_user_perm, gen_resource
|
||||
from jasset.models import Asset, IDC
|
||||
from jperm.ansible_api import MyRunner
|
||||
import zipfile
|
||||
|
||||
|
||||
def getDaysByNum(num):
|
||||
@@ -220,41 +221,46 @@ def setting(request):
|
||||
setting_default = get_object(Setting, name='default')
|
||||
|
||||
if request.method == "POST":
|
||||
setting_raw = request.POST.get('setting', '')
|
||||
if setting_raw == 'default':
|
||||
username = request.POST.get('username', '')
|
||||
port = request.POST.get('port', '')
|
||||
password = request.POST.get('password', '')
|
||||
private_key = request.POST.get('key', '')
|
||||
try:
|
||||
setting_raw = request.POST.get('setting', '')
|
||||
if setting_raw == 'default':
|
||||
username = request.POST.get('username', '')
|
||||
port = request.POST.get('port', '')
|
||||
password = request.POST.get('password', '')
|
||||
private_key = request.POST.get('key', '')
|
||||
|
||||
if '' in [username, port]:
|
||||
return HttpResponse('所填内容不能为空, 且密码和私钥填一个')
|
||||
else:
|
||||
private_key_dir = os.path.join(BASE_DIR, 'keys', 'default')
|
||||
private_key_path = os.path.join(private_key_dir, 'admin_user.pem')
|
||||
mkdir(private_key_dir)
|
||||
|
||||
if private_key:
|
||||
with open(private_key_path, 'w') as f:
|
||||
f.write(private_key)
|
||||
os.chmod(private_key_path, 0600)
|
||||
|
||||
if setting_default:
|
||||
if password:
|
||||
password_encode = CRYPTOR.encrypt(password)
|
||||
else:
|
||||
password_encode = password
|
||||
Setting.objects.filter(name='default').update(field1=username, field2=port,
|
||||
field3=password_encode,
|
||||
field4=private_key_path)
|
||||
if len(password) > 30:
|
||||
raise ServerError(u'秘密长度不能超过30位!')
|
||||
|
||||
if '' in [username, port]:
|
||||
return ServerError(u'所填内容不能为空, 且密码和私钥填一个')
|
||||
else:
|
||||
password_encode = CRYPTOR.encrypt(password)
|
||||
setting_r = Setting(name='default', field1=username, field2=port,
|
||||
field3=password_encode,
|
||||
field4=private_key_path).save()
|
||||
private_key_dir = os.path.join(BASE_DIR, 'keys', 'default')
|
||||
private_key_path = os.path.join(private_key_dir, 'admin_user.pem')
|
||||
mkdir(private_key_dir)
|
||||
|
||||
msg = "设置成功"
|
||||
if private_key:
|
||||
with open(private_key_path, 'w') as f:
|
||||
f.write(private_key)
|
||||
os.chmod(private_key_path, 0600)
|
||||
|
||||
if setting_default:
|
||||
if password:
|
||||
password_encode = CRYPTOR.encrypt(password)
|
||||
else:
|
||||
password_encode = password
|
||||
Setting.objects.filter(name='default').update(field1=username, field2=port,
|
||||
field3=password_encode,
|
||||
field4=private_key_path)
|
||||
|
||||
else:
|
||||
password_encode = CRYPTOR.encrypt(password)
|
||||
setting_r = Setting(name='default', field1=username, field2=port,
|
||||
field3=password_encode,
|
||||
field4=private_key_path).save()
|
||||
msg = "设置成功"
|
||||
except ServerError as e:
|
||||
error = e.message
|
||||
return my_render('setting.html', locals(), request)
|
||||
|
||||
|
||||
@@ -326,15 +332,19 @@ def download(request):
|
||||
FileLog(user=request.user.username, host=' '.join([asset.hostname for asset in asset_select]),
|
||||
filename=file_path, type='download', remote_ip=remote_ip, result=runner.results).save()
|
||||
logger.debug(runner.results)
|
||||
os.chdir('/tmp')
|
||||
tmp_dir_name = os.path.basename(upload_dir)
|
||||
tar_file = '%s.tar.gz' % upload_dir
|
||||
bash('tar czf %s %s' % (tar_file, tmp_dir_name))
|
||||
f = open(tar_file)
|
||||
file_zip = '/tmp/'+tmp_dir_name+'.zip'
|
||||
zf = zipfile.ZipFile(file_zip, "w", zipfile.ZIP_DEFLATED)
|
||||
for dirname, subdirs, files in os.walk(upload_dir):
|
||||
zf.write(dirname)
|
||||
for filename in files:
|
||||
zf.write(os.path.join(dirname, filename))
|
||||
zf.close()
|
||||
f = open(file_zip)
|
||||
data = f.read()
|
||||
f.close()
|
||||
response = HttpResponse(data, content_type='application/octet-stream')
|
||||
response['Content-Disposition'] = 'attachment; filename=%s' % os.path.basename(tar_file)
|
||||
response['Content-Disposition'] = 'attachment; filename=%s.zip' % tmp_dir_name
|
||||
return response
|
||||
|
||||
return render_to_response('download.html', locals(), context_instance=RequestContext(request))
|
||||
|
||||
@@ -130,14 +130,14 @@ def gen_ssh_key(username, password='',
|
||||
"""
|
||||
logger.debug('生成ssh key, 并设置authorized_keys')
|
||||
private_key_file = os.path.join(key_dir, username+'.pem')
|
||||
mkdir(key_dir, mode=0777)
|
||||
mkdir(key_dir, mode=777)
|
||||
if os.path.isfile(private_key_file):
|
||||
os.unlink(private_key_file)
|
||||
ret = bash('echo -e "y\n"|ssh-keygen -t rsa -f %s -b %s -P "%s"' % (private_key_file, length, password))
|
||||
|
||||
if authorized_keys:
|
||||
auth_key_dir = os.path.join(home, username, '.ssh')
|
||||
mkdir(auth_key_dir, username=username, mode=0700)
|
||||
mkdir(auth_key_dir, username=username, mode=700)
|
||||
authorized_key_file = os.path.join(auth_key_dir, 'authorized_keys')
|
||||
with open(private_key_file+'.pub') as pub_f:
|
||||
with open(authorized_key_file, 'w') as auth_f:
|
||||
@@ -181,6 +181,9 @@ def server_del_user(username):
|
||||
删除系统上的某用户
|
||||
"""
|
||||
bash('userdel -r -f %s' % username)
|
||||
logger.debug('rm -f %s/%s_*.pem' % (os.path.join(KEY_DIR, 'user'), username))
|
||||
bash('rm -f %s/%s_*.pem' % (os.path.join(KEY_DIR, 'user'), username))
|
||||
bash('rm -f %s/%s.pem*' % (os.path.join(KEY_DIR, 'user'), username))
|
||||
|
||||
|
||||
def get_display_msg(user, password='', ssh_key_pwd='', send_mail_need=False):
|
||||
|
||||
@@ -10,6 +10,7 @@ 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
|
||||
import re
|
||||
|
||||
MAIL_FROM = EMAIL_HOST_USER
|
||||
|
||||
@@ -142,7 +143,7 @@ def user_add(request):
|
||||
group_all = UserGroup.objects.all()
|
||||
|
||||
if request.method == 'POST':
|
||||
username = request.POST.get('username', '')
|
||||
username = request.POST.get('username', '')
|
||||
password = PyCrypt.gen_rand_pass(16)
|
||||
name = request.POST.get('name', '')
|
||||
email = request.POST.get('email', '')
|
||||
@@ -159,15 +160,23 @@ def user_add(request):
|
||||
if '' in [username, password, ssh_key_pwd, name, role]:
|
||||
error = u'带*内容不能为空'
|
||||
raise ServerError
|
||||
|
||||
check_user_is_exist = User.objects.filter(username=username)
|
||||
if check_user_is_exist:
|
||||
error = u'用户 %s 已存在' % username
|
||||
raise ServerError
|
||||
|
||||
if username in ['root']:
|
||||
error = u'用户不能为root'
|
||||
raise ServerError
|
||||
|
||||
except ServerError:
|
||||
pass
|
||||
else:
|
||||
try:
|
||||
if not re.match(r"^\w+$",username):
|
||||
error = u'用户名不合法'
|
||||
raise ServerError(error)
|
||||
user = db_add_user(username=username, name=name,
|
||||
password=password,
|
||||
email=email, role=role, uuid=uuid_r,
|
||||
@@ -254,7 +263,7 @@ def user_del(request):
|
||||
user = get_object(User, id=user_id)
|
||||
if user and user.username != 'admin':
|
||||
logger.debug(u"删除用户 %s " % user.username)
|
||||
bash('userdel -r %s' % user.username)
|
||||
server_del_user(user.username)
|
||||
user.delete()
|
||||
return HttpResponse('删除成功')
|
||||
|
||||
@@ -323,7 +332,7 @@ def reset_password(request):
|
||||
else:
|
||||
user = get_object(User, uuid=uuid_r)
|
||||
if user:
|
||||
user.password = PyCrypt.md5_crypt(password)
|
||||
user.set_password(password)
|
||||
user.save()
|
||||
return http_success(request, u'密码重设成功')
|
||||
else:
|
||||
@@ -419,7 +428,9 @@ def change_info(request):
|
||||
error = '不能为空'
|
||||
|
||||
if not error:
|
||||
User.objects.filter(id=user_id).update(name=name, email=email)
|
||||
user.name = name
|
||||
user.email = email
|
||||
user.save()
|
||||
if len(password) > 0:
|
||||
user.set_password(password)
|
||||
user.save()
|
||||
|
||||
@@ -364,24 +364,22 @@ class WebTerminalHandler(tornado.websocket.WebSocketHandler):
|
||||
return
|
||||
|
||||
if 'resize' in jsondata.get('data'):
|
||||
self.termlog.write(jsondata)
|
||||
self.termlog.write(message)
|
||||
self.channel.resize_pty(
|
||||
jsondata.get('data').get('resize').get('cols', 80),
|
||||
jsondata.get('data').get('resize').get('rows', 24)
|
||||
width=int(jsondata.get('data').get('resize').get('cols', 100)),
|
||||
height=int(jsondata.get('data').get('resize').get('rows', 35))
|
||||
)
|
||||
elif jsondata.get('data'):
|
||||
self.termlog.recoder = True
|
||||
self.term.input_mode = True
|
||||
if str(jsondata['data']) in ['\r', '\n', '\r\n']:
|
||||
if self.term.vim_flag:
|
||||
match = re.compile(r'\x1b\[\?1049', re.X).findall(self.term.vim_data)
|
||||
if match:
|
||||
if self.term.vim_end_flag or len(match) == 2:
|
||||
self.term.vim_flag = False
|
||||
self.term.vim_end_flag = False
|
||||
else:
|
||||
self.term.vim_end_flag = True
|
||||
else:
|
||||
match = re.compile(r'\x1b\[\?1049', re.X).findall(self.term.vim_data)
|
||||
if match:
|
||||
if self.term.vim_flag or len(match) == 2:
|
||||
self.term.vim_flag = False
|
||||
else:
|
||||
self.term.vim_flag = True
|
||||
elif not self.term.vim_flag:
|
||||
result = self.term.deal_command(self.term.data)[0:200]
|
||||
if len(result) > 0:
|
||||
TtyLog(log=self.log, datetime=datetime.datetime.now(), cmd=result).save()
|
||||
@@ -418,14 +416,13 @@ class WebTerminalHandler(tornado.websocket.WebSocketHandler):
|
||||
data = ''
|
||||
pre_timestamp = time.time()
|
||||
while True:
|
||||
r, w, e = select.select([self.channel, sys.stdin], [], [])
|
||||
r, w, e = select.select([self.channel], [], [])
|
||||
if self.channel in r:
|
||||
recv = self.channel.recv(1024)
|
||||
if not len(recv):
|
||||
return
|
||||
data += recv
|
||||
if self.term.vim_flag:
|
||||
self.term.vim_data += recv
|
||||
self.term.vim_data += recv
|
||||
try:
|
||||
self.write_message(data.decode('utf-8', 'replace'))
|
||||
self.termlog.write(data)
|
||||
@@ -436,7 +433,7 @@ class WebTerminalHandler(tornado.websocket.WebSocketHandler):
|
||||
pre_timestamp = now_timestamp
|
||||
self.log_file_f.flush()
|
||||
self.log_time_f.flush()
|
||||
if self.term.input_mode and not self.term.is_output(data):
|
||||
if self.term.input_mode:
|
||||
self.term.data += data
|
||||
data = ''
|
||||
except UnicodeDecodeError:
|
||||
@@ -497,7 +494,7 @@ def main():
|
||||
[
|
||||
(r'/ws/monitor', MonitorHandler),
|
||||
(r'/ws/terminal', WebTerminalHandler),
|
||||
(r'/kill', WebTerminalKillHandler),
|
||||
(r'/ws/kill', WebTerminalKillHandler),
|
||||
(r'/ws/exec', ExecHandler),
|
||||
(r"/static/(.*)", tornado.web.StaticFileHandler,
|
||||
dict(path=os.path.join(os.path.dirname(__file__), "static"))),
|
||||
@@ -505,7 +502,7 @@ def main():
|
||||
], **setting)
|
||||
|
||||
server = tornado.httpserver.HTTPServer(tornado_app)
|
||||
server.listen(options.port)
|
||||
server.listen(options.port, address=IP)
|
||||
|
||||
tornado.ioloop.IOLoop.instance().start()
|
||||
|
||||
|
||||
@@ -28,6 +28,11 @@ PROC_NAME="jumpserver"
|
||||
lockfile=/var/lock/subsys/${PROC_NAME}
|
||||
|
||||
start() {
|
||||
if [ $(whoami) != 'root' ];then
|
||||
echo "Sorry, JMS must be run as root"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
jump_start=$"Starting ${PROC_NAME} service:"
|
||||
if [ -f $lockfile ];then
|
||||
echo -n "jumpserver is running..."
|
||||
|
||||
@@ -57,8 +57,8 @@ NgApp.controller('TerminalRecordCtrl', function ($scope, $http) {
|
||||
for (; pos < timelist.length; pos++) {
|
||||
if (timelist[pos] * 1000 <= time) {
|
||||
try{
|
||||
var findResize = JSON.parse(data[timelist[pos]])['reszie'];
|
||||
term.resize(findResize['cols'], findResize['rows'])
|
||||
var findResize = JSON.parse(data[timelist[pos]])['data'];
|
||||
term.resize(findResize['resize']['cols'], findResize['resize']['rows'])
|
||||
} catch (err) {
|
||||
term.write(data[timelist[pos]]);
|
||||
}
|
||||
@@ -108,8 +108,8 @@ NgApp.controller('TerminalRecordCtrl', function ($scope, $http) {
|
||||
};
|
||||
|
||||
var term = new Terminal({
|
||||
rows: 24,
|
||||
cols: 80,
|
||||
rows: 35,
|
||||
cols: 100,
|
||||
useStyle: true,
|
||||
screenKeys: true
|
||||
});
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
|
||||
/**
|
||||
* Created by liuzheng on 3/3/16.
|
||||
*/
|
||||
@@ -14,7 +13,7 @@ WSSHClient.prototype._generateEndpoint = function (options) {
|
||||
var protocol = 'ws://';
|
||||
}
|
||||
|
||||
var endpoint = protocol + document.URL.match(RegExp('//(.*?)/'))[1] + '/ws/terminal' + document.URL.match(/(\?.*)/);
|
||||
var endpoint = protocol + document.URL.match(RegExp('//(.*?)/'))[1] + '/ws/terminal' + document.URL.match(/\?.*/);
|
||||
return endpoint;
|
||||
};
|
||||
WSSHClient.prototype.connect = function (options) {
|
||||
@@ -60,8 +59,16 @@ function openTerminal(options) {
|
||||
rowHeight = localStorage.getItem('term-row');
|
||||
colWidth = localStorage.getItem('term-col');
|
||||
} catch (err) {
|
||||
rowHeight = 24;
|
||||
colWidth = 80
|
||||
rowHeight = 35;
|
||||
colWidth = 100
|
||||
}
|
||||
if (rowHeight) {
|
||||
} else {
|
||||
rowHeight = 35
|
||||
}
|
||||
if (colWidth) {
|
||||
} else {
|
||||
colWidth = 100
|
||||
}
|
||||
|
||||
var term = new Terminal({
|
||||
@@ -126,8 +133,8 @@ $(document).ready(function () {
|
||||
$('#term-row')[0].value = localStorage.getItem('term-row');
|
||||
$('#term-col')[0].value = localStorage.getItem('term-col');
|
||||
} catch (err) {
|
||||
$('#term-row')[0].value = 24;
|
||||
$('#term-col')[0].value = 80;
|
||||
$('#term-row')[0].value = 35;
|
||||
$('#term-col')[0].value = 100;
|
||||
}
|
||||
$('#col-row').click(function () {
|
||||
var col = $('#term-col').val();
|
||||
@@ -137,6 +144,11 @@ $(document).ready(function () {
|
||||
term_client.term.resize(col, row);
|
||||
term_client.client.send({'resize': {'rows': row, 'cols': col}});
|
||||
$('#ssh').show();
|
||||
});
|
||||
$(".terminal").mouseleave(function () {
|
||||
$(".termChangBar").slideDown();
|
||||
});
|
||||
$(".terminal").mouseenter(function () {
|
||||
$(".termChangBar").slideUp();
|
||||
})
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta name="renderer" content="webkit">
|
||||
|
||||
<title>Jumpserver | 开源跳板机系统</title>
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<div class="footer fixed">
|
||||
<div class="pull-right">
|
||||
Version <strong>0.3.1</strong> GPL.
|
||||
Version <strong>0.3.2</strong> GPL.
|
||||
</div>
|
||||
<div>
|
||||
<strong>Copyright</strong> Jumpserver.org Team © 2014-2015
|
||||
|
||||
@@ -164,7 +164,7 @@
|
||||
msg: {required: "必须填写!"}
|
||||
},
|
||||
"hostname": {
|
||||
rule: "required",
|
||||
rule: "required;length[0~53]",
|
||||
tip: "填写主机名",
|
||||
ok: "",
|
||||
msg: {required: "必须填写!"}
|
||||
@@ -182,7 +182,7 @@
|
||||
msg: {required: "必须填写!"}
|
||||
},
|
||||
"password": {
|
||||
rule: "required(use_default_auth)",
|
||||
rule: "required(use_default_auth);length[0~64]",
|
||||
tip: "输入密码",
|
||||
ok: "",
|
||||
msg: {required: "必须填写!"}
|
||||
|
||||
@@ -206,7 +206,7 @@
|
||||
},
|
||||
fields: {
|
||||
"hostname": {
|
||||
rule: "required",
|
||||
rule: "required;length[0~53]",
|
||||
tip: "填写主机名",
|
||||
ok: "",
|
||||
msg: {required: "必须填写!"}
|
||||
@@ -218,17 +218,17 @@
|
||||
msg: {required: "必须填写!"}
|
||||
},
|
||||
"username": {
|
||||
rule: "required(use_default_auth)",
|
||||
rule: "required(use_default_auth);",
|
||||
tip: "输入用户名",
|
||||
ok: "",
|
||||
msg: {required: "必须填写!"}
|
||||
},
|
||||
{# "password": {#}
|
||||
{# rule: "required(use_default_auth)",#}
|
||||
{# tip: "输入密码",#}
|
||||
{# ok: "",#}
|
||||
{# msg: {required: "必须填写!"}#}
|
||||
{# }#}
|
||||
"password": {
|
||||
rule: "length[0~64]",
|
||||
tip: "输入密码",
|
||||
ok: "",
|
||||
empty: true
|
||||
}
|
||||
},
|
||||
valid: function(form) {
|
||||
form.submit();
|
||||
|
||||
@@ -245,7 +245,7 @@
|
||||
});
|
||||
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=380px');
|
||||
window.open(new_url+data, "_blank");
|
||||
} else if (dataArray.length == 1 && data != 'error'){
|
||||
/*layer.open({
|
||||
type: 2,
|
||||
@@ -256,8 +256,7 @@
|
||||
content: new_url+data
|
||||
});
|
||||
*/
|
||||
window.open(new_url+data, '_blank', 'toolbar=yes, location=yes, copyhistory=yes, scrollbars=yes, width=628, height=410');
|
||||
|
||||
window.open(new_url+data, '_blank');
|
||||
}
|
||||
else {
|
||||
aUrl = '';
|
||||
@@ -293,7 +292,7 @@
|
||||
content: new_url
|
||||
});
|
||||
*/
|
||||
window.open(new_url, '_blank', 'toolbar=yes, location=yes, copyhistory=yes, scrollbars=yes, width=628, height=380')
|
||||
window.open(new_url, '_blank')
|
||||
|
||||
} else {
|
||||
/*
|
||||
@@ -306,7 +305,7 @@
|
||||
content: new_url
|
||||
});
|
||||
*/
|
||||
window.open(new_url, '_blank', 'toolbar=yes, location=yes, copyhistory=yes, scrollbars=yes, width=628, height=410');
|
||||
window.open(new_url, '_blank');
|
||||
}
|
||||
|
||||
return false
|
||||
|
||||
@@ -57,7 +57,7 @@
|
||||
{% if smg %}
|
||||
<div class="alert alert-success text-center">{{ smg }}</div>
|
||||
{% endif %}
|
||||
<form id="assetForm" method="post" class="form-horizontal">
|
||||
<form id="assetForm" method="post" class="form-horizontal" onkeydown="if(event.keyCode==13){return false;}">
|
||||
<div class="form-group"><label class="col-sm-2 control-label"> 主机组名<span class="red-fonts">*</span></label>
|
||||
<div class="col-sm-8" name="group_id" value="{{ group.id }}"><input type="text" value="{{ group.name }}" placeholder="Name" name="name" class="form-control"></div>
|
||||
</div>
|
||||
@@ -153,6 +153,7 @@
|
||||
});
|
||||
|
||||
function on_submit(id){
|
||||
search_ip('', 'asset_select', 'asset_select_total') //提交之前清空过滤框
|
||||
$('#'+id+' option').each(
|
||||
function(){
|
||||
$(this).prop('selected', true)
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
<ul class="nav nav-tabs">
|
||||
<li><a href="{% url 'log_list' 'online' %}" class="text-center"><i class="fa fa-laptop"></i> 在线 </a></li>
|
||||
<li><a href="{% url 'log_list' 'offline' %}" class="text-center"><i class="fa fa-bar-chart-o"></i> 登录历史</a></li>
|
||||
<li class="active"><a href="{% url 'log_list' 'exec' %}" class="text-center"><i class="fa fa-bar-chart-o"></i> 命令记录 </a></li>
|
||||
<li class="active"><a href="{% url 'log_list' 'exec' %}" class="text-center"><i class="fa fa-bar-chart-o"></i> 批量命令 </a></li>
|
||||
<li><a href="{% url 'log_list' 'file' %}" class="text-center"><i class="fa fa-bar-chart-o"></i> 上传下载 </a></li>
|
||||
<div class="" style="float: right">
|
||||
<form id="search_form" method="get" action="" class="pull-right mail-search">
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
<ul class="nav nav-tabs">
|
||||
<li><a href="{% url 'log_list' 'online' %}" class="text-center"><i class="fa fa-laptop"></i> 在线 </a></li>
|
||||
<li><a href="{% url 'log_list' 'offline' %}" class="text-center"><i class="fa fa-bar-chart-o"></i> 登录历史</a></li>
|
||||
<li><a href="{% url 'log_list' 'exec' %}" class="text-center"><i class="fa fa-bar-chart-o"></i> 命令记录 </a></li>
|
||||
<li><a href="{% url 'log_list' 'exec' %}" class="text-center"><i class="fa fa-bar-chart-o"></i> 批量命令 </a></li>
|
||||
<li class="active"><a href="{% url 'log_list' 'file' %}" class="text-center"><i class="fa fa-bar-chart-o"></i> 上传下载 </a></li>
|
||||
<div class="" style="float: right">
|
||||
<form id="search_form" method="get" action="" class="pull-right mail-search">
|
||||
|
||||
@@ -59,7 +59,7 @@
|
||||
<ul class="nav nav-tabs">
|
||||
<li><a href="{% url 'log_list' 'online' %}" class="text-center"><i class="fa fa-laptop"></i> 在线 </a></li>
|
||||
<li class="active"><a href="{% url 'log_list' 'offline' %}" class="text-center"><i class="fa fa-bar-chart-o"></i> 登录历史</a></li>
|
||||
<li><a href="{% url 'log_list' 'exec' %}" class="text-center"><i class="fa fa-bar-chart-o"></i> 命令记录 </a></li>
|
||||
<li><a href="{% url 'log_list' 'exec' %}" class="text-center"><i class="fa fa-bar-chart-o"></i> 批量命令 </a></li>
|
||||
<li><a href="{% url 'log_list' 'file' %}" class="text-center"><i class="fa fa-bar-chart-o"></i> 上传下载 </a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
@@ -71,7 +71,7 @@
|
||||
<ul class="nav nav-tabs">
|
||||
<li class="active"><a href="{% url 'log_list' 'online' %}" class="text-center"><i class="fa fa-laptop"></i> 在线 </a></li>
|
||||
<li><a href="{% url 'log_list' 'offline' %}" class="text-center"><i class="fa fa-bar-chart-o"></i> 登录历史</a></li>
|
||||
<li><a href="{% url 'log_list' 'exec' %}" class="text-center"><i class="fa fa-bar-chart-o"></i> 命令记录 </a></li>
|
||||
<li><a href="{% url 'log_list' 'exec' %}" class="text-center"><i class="fa fa-bar-chart-o"></i> 批量命令 </a></li>
|
||||
<li><a href="{% url 'log_list' 'file' %}" class="text-center"><i class="fa fa-bar-chart-o"></i> 上传下载 </a></li>
|
||||
<div class="" style="float: right">
|
||||
<form id="search_form" method="get" action="" class="pull-right mail-search">
|
||||
@@ -174,8 +174,8 @@
|
||||
$('.terminal').show();
|
||||
socket.onmessage = function(evt){
|
||||
try {
|
||||
var findResize = JSON.parse(evt.data)['resize'];
|
||||
term.resize(findResize['cols'], findResize['rows'])
|
||||
var findResize = JSON.parse(evt.data)['data'];
|
||||
term.resize(findResize['resize']['cols'], findResize['resize']['rows'])
|
||||
} catch (err) {
|
||||
term.write(evt.data);
|
||||
}
|
||||
@@ -215,7 +215,7 @@
|
||||
|
||||
function cut(num, login_type){
|
||||
var protocol = window.location.protocol;
|
||||
var endpoint = protocol + '//' + document.URL.match(RegExp('//(.*?)/'))[1] + '/kill';
|
||||
var endpoint = protocol + '//' + document.URL.match(RegExp('//(.*?)/'))[1] + '/ws/kill';
|
||||
if (login_type=='web'){
|
||||
var g_url = endpoint + '?id=' + num;
|
||||
console.log(g_url);
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Jumpserver Web Terminal: {{ hostname }}</title>
|
||||
<title>{{ hostname }}</title>
|
||||
|
||||
<style>
|
||||
body {
|
||||
@@ -11,7 +11,7 @@
|
||||
|
||||
.terminal {
|
||||
border: #000 solid 5px;
|
||||
font-family: "Monaco", "Microsoft Yahei", "DejaVu Sans Mono", "Liberation Mono", monospace;
|
||||
font-family: Consolas, Monaco, monospace;
|
||||
font-size: 11px;
|
||||
color: #f0f0f0;
|
||||
background: #000;
|
||||
@@ -25,7 +25,7 @@
|
||||
background: #f0f0f0;
|
||||
}
|
||||
|
||||
.clock {
|
||||
.termChangBar {
|
||||
line-height: 1;
|
||||
margin: 0 auto;
|
||||
border: 1px solid #ffffff;
|
||||
@@ -43,15 +43,13 @@
|
||||
<div id="term">
|
||||
</div>
|
||||
</div>
|
||||
<div class="clock">
|
||||
<input type="number" min="80" placeholder="col" id="term-col"/>
|
||||
<input type="number" min="24" placeholder="row" id="term-row"/>
|
||||
<div class="termChangBar">
|
||||
<input type="number" min="100" value="100" placeholder="col" id="term-col"/>
|
||||
<input type="number" min="35" value="35" placeholder="row" id="term-row"/>
|
||||
<button id="col-row">修改窗口大小</button>
|
||||
</div>
|
||||
<script type="application/javascript" src="/static/js/jquery-2.1.1.js">
|
||||
</script>
|
||||
<script type="application/javascript" src="/static/js/term.js">
|
||||
</script>
|
||||
<script type="application/javascript" src="/static/js/jquery-2.1.1.js"></script>
|
||||
<script type="application/javascript" src="/static/js/term.js"></script>
|
||||
<script type="application/javascript" src="/static/js/webterminal.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -37,6 +37,7 @@
|
||||
<label for="role_name" class="col-sm-2 control-label">用户名称<span class="red-fonts">*</span></label>
|
||||
<div class="col-sm-8">
|
||||
<input id="role_name" name="role_name" placeholder="Role Name" type="text" class="form-control">
|
||||
<span class="help-block m-b-none">如果客户端是网络设备,填写已配置的SSH登录用户,支持SSH协议V2.0以上 </span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="hr-line-dashed"></div>
|
||||
@@ -44,7 +45,7 @@
|
||||
<label for="role_password" class="col-sm-2 control-label">用户密码</label>
|
||||
<div class="col-sm-8">
|
||||
<input id="role_password" name="role_password" placeholder="Role Password" type="password" class="form-control">
|
||||
<span class="help-block m-b-none">如果不添加密码,会自动生成</span>
|
||||
<span class="help-block m-b-none">如果客户端是网络设备这里必填,如果客户端是服务器,密码不会被推送,不会修改客户端原有的用户密码,请忽略这里</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
|
||||
@@ -30,13 +30,15 @@
|
||||
<div class="ibox-content">
|
||||
<div>
|
||||
<div class="text-left">
|
||||
<table class="table" id="ugedit" >
|
||||
<td class="text-navy text-left">时间</td>
|
||||
<td class="text-navy text-right">名称</td>
|
||||
<table class="table" id="ugedit">
|
||||
<td class="text-navy text-left">时间</td>
|
||||
<td class="text-navy text-right">名称</td>
|
||||
{% for rule in rules %}
|
||||
<tr class="gradeX">
|
||||
<td class="text-left"> {{ rule.date_added | date:"Y-m-d H:i:s"}} </td>
|
||||
<td class="text-right"> <a href="{% url 'rule_detail' %}?id={{ rule.id }}">{{ rule.name }}</a> </td>
|
||||
<td class="text-left"> {{ rule.date_added | date:"Y-m-d H:i:s" }} </td>
|
||||
<td class="text-right"><a
|
||||
href="{% url 'rule_detail' %}?id={{ rule.id }}">{{ rule.name }}</a>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
@@ -44,8 +46,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-4">
|
||||
|
||||
<div class="ibox float-e-margins">
|
||||
<div class="ibox-title">
|
||||
<span class="label label-primary"><b>授权用户/用户组</b></span>
|
||||
@@ -68,37 +69,39 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="ibox-content">
|
||||
<div class="text-center">
|
||||
<table class="table" id="agedit" >
|
||||
<td class="text-navy text-left">用户</td>
|
||||
<td class="text-navy text-right">用户组</td>
|
||||
<tr>
|
||||
<td>
|
||||
<table class="table progress-striped text-left">
|
||||
<div class="text-center">
|
||||
<table class="table" id="agedit">
|
||||
<td class="text-navy text-left">用户</td>
|
||||
<td class="text-navy text-right">用户组</td>
|
||||
<tr>
|
||||
<td>
|
||||
<table class="table progress-striped text-left">
|
||||
{% for user in users %}
|
||||
<tr class="gradeX">
|
||||
<td> <a href="{% url 'user_detail' %}?id={{ user.id }}">{{ user.name }}</a> </td>
|
||||
<td>
|
||||
<a href="{% url 'user_detail' %}?id={{ user.id }}">{{ user.name }}</a>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
</td>
|
||||
<td>
|
||||
<table class="table progress-striped text-right">
|
||||
</table>
|
||||
</td>
|
||||
<td>
|
||||
<table class="table progress-striped text-right">
|
||||
{% for group in user_groups %}
|
||||
<tr class="gradeX-">
|
||||
<td> <a href="{% url 'user_group_list' %}?id={{ group.id }}">{{ group.name }}</a> </td>
|
||||
<td>
|
||||
<a href="{% url 'user_group_list' %}?id={{ group.id }}">{{ group.name }}</a>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
</td>
|
||||
</table>
|
||||
</td>
|
||||
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-4">
|
||||
</div>
|
||||
<div class="ibox float-e-margins">
|
||||
<div class="ibox-title">
|
||||
<span class="label label-primary"><b>授权主机组/主机组</b></span>
|
||||
@@ -122,26 +125,30 @@
|
||||
</div>
|
||||
<div class="ibox-content">
|
||||
<div class="text-center">
|
||||
<table class="table" id="agedit" >
|
||||
<table class="table" id="agedit">
|
||||
<td class="text-navy text-left">主机</td>
|
||||
<td class="text-navy text-right">主机组</td>
|
||||
<tr>
|
||||
<td>
|
||||
<table class="table progress-striped text-left">
|
||||
{% for asset in assets %}
|
||||
<tr class="gradeX">
|
||||
<td> <a href="{% url 'asset_detail' %}?id={{ asset.id }}">{{ asset.ip }}</a> </td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
{% for asset in assets %}
|
||||
<tr class="gradeX">
|
||||
<td>
|
||||
<a href="{% url 'asset_detail' %}?id={{ asset.id }}">{{ asset.ip }}</a>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
</td>
|
||||
<td>
|
||||
<table class="table progress-striped text-right">
|
||||
{% for group in asset_groups %}
|
||||
<tr class="gradeX-">
|
||||
<td> <a href="{% url 'asset_list' %}?group_id={{ group.id }}">{{ group.name }}</a> </td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
{% for group in asset_groups %}
|
||||
<tr class="gradeX-">
|
||||
<td>
|
||||
<a href="{% url 'asset_list' %}?group_id={{ group.id }}">{{ group.name }}</a>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
@@ -150,9 +157,70 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm-6">
|
||||
<div class="col-sm-8">
|
||||
<div class="ibox float-e-margins">
|
||||
<div class="ibox-title">
|
||||
<span class="label label-danger"><b>{{ role.name }} - 推送失败主机</b></span>
|
||||
<div class="ibox-tools">
|
||||
<a class="collapse-link">
|
||||
<i class="fa fa-chevron-up"></i>
|
||||
</a>
|
||||
<a class="dropdown-toggle" data-toggle="dropdown" href="#">
|
||||
<i class="fa fa-wrench"></i>
|
||||
</a>
|
||||
<a class="close-link">
|
||||
<i class="fa fa-times"></i>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ibox-content">
|
||||
<div>
|
||||
<div class="text-left">
|
||||
<table class="table table-striped" id="ugedit">
|
||||
<a class="btn btn-xs btn-danger del_muti"> 删除 </a><span> </span>
|
||||
<a class="btn btn-xs btn-primary re_push"> 重新推送 </a>
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="text-center">
|
||||
<input type="checkbox" id="check_push"
|
||||
onclick="checkAll('check_push', 'asset_id')">
|
||||
</th>
|
||||
<th class="text-center">主机</th>
|
||||
<th class="text-center">密钥</th>
|
||||
<th class="text-center">结果</th>
|
||||
<th class="text-center">操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for asset, info in pushed_asset.items %}
|
||||
{% if not info.success %}
|
||||
<tr class="gradeX">
|
||||
<th class="text-center">
|
||||
<input type="checkbox" name="asset_id" value="{{ asset.id }}">
|
||||
</th>
|
||||
<td class="text-center"> {{ asset.hostname }} </td>
|
||||
<td class="text-center"> {{ info.key | yesno:"是,否,未知" }} </td>
|
||||
{% if info.success %}
|
||||
<td class="text-center"
|
||||
style="color: #1ab394;">{{ info.success | yesno:"成功,失败,未知" }} </td>
|
||||
{% else %}
|
||||
<td class="text-center push_failed" style="color: #ec4758;cursor: help"
|
||||
title="{{ info.result }}">{{ info.success | yesno:"成功,失败,未知" }} </td>
|
||||
{% endif %}
|
||||
<td class="text-center"><a class="fa fa-times del"
|
||||
href="{% url 'role_recycle' %}?role_id={{ role.id }}&asset_id={{ asset.id }}"
|
||||
style="color: #ec4758;"></a></td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="ibox float-e-margins">
|
||||
<div class="ibox-title">
|
||||
<span class="label label-primary"><b>{{ role.name }} - 推送主机</b></span>
|
||||
@@ -177,20 +245,20 @@
|
||||
<div class="ibox-content">
|
||||
<div>
|
||||
<div class="text-left">
|
||||
<table class="table table-striped" id="ugedit" >
|
||||
<table class="table table-striped" id="ugedit">
|
||||
<a class="btn btn-xs btn-danger del_muti"> 删除 </a><span> </span>
|
||||
<a class="btn btn-xs btn-primary re_push"> 重新推送 </a>
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="text-center">
|
||||
<input type="checkbox" id="check_push" onclick="checkAll('check_push', 'asset_id')">
|
||||
</th>
|
||||
<th class="text-center">主机</th>
|
||||
<th class="text-center">密钥</th>
|
||||
<th class="text-center">密码</th>
|
||||
<th class="text-center">结果</th>
|
||||
<th class="text-center">操作</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th class="text-center">
|
||||
<input type="checkbox" id="check_push"
|
||||
onclick="checkAll('check_push', 'asset_id')">
|
||||
</th>
|
||||
<th class="text-center">主机</th>
|
||||
<th class="text-center">密钥</th>
|
||||
<th class="text-center">结果</th>
|
||||
<th class="text-center">操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for asset, info in pushed_asset.items %}
|
||||
@@ -200,13 +268,19 @@
|
||||
</th>
|
||||
<td class="text-center"> {{ asset.hostname }} </td>
|
||||
<td class="text-center"> {{ info.key | yesno:"是,否,未知" }} </td>
|
||||
<td class="text-center"> {{ info.password | yesno:"是,否,未知" }} </td>
|
||||
{% if info.success %}
|
||||
<td class="text-center" style="color: #1ab394;" >{{ info.success | yesno:"成功,失败,未知" }} </td>
|
||||
<td class="text-center"
|
||||
style="color: #1ab394;">{{ info.success | yesno:"成功,失败,未知" }} </td>
|
||||
{% else %}
|
||||
<td class="text-center push_failed" style="color: #ec4758;cursor: help" title="{{ info.result }}">{{ info.success | yesno:"成功,失败,未知" }} </td>
|
||||
<td class="text-center push_failed" style="color: #ec4758;cursor: help"
|
||||
title="{{ info.result }}">{{ info.success | yesno:"成功,失败,未知" }} </td>
|
||||
{% endif %}
|
||||
<td class="text-center" ><a class="fa fa-times del" href="{% url 'role_recycle' %}?role_id={{ role.id }}&asset_id={{ asset.id }}" style="color: #ec4758;"></a></td>
|
||||
<td class="text-center">
|
||||
<a class="fa fa-times del"
|
||||
href="{% url 'role_recycle' %}?role_id={{ role.id }}&asset_id={{ asset.id }}"
|
||||
style="color: #ec4758;">
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
@@ -215,9 +289,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-sm-4">
|
||||
<div class="ibox float-e-margins">
|
||||
<div class="ibox-title">
|
||||
<span class="label label-danger"><b>{{ role.name }} - 未推送主机</b></span>
|
||||
@@ -236,16 +308,17 @@
|
||||
<div class="ibox-content">
|
||||
<div>
|
||||
<div class="text-left">
|
||||
<table class="table table-striped" >
|
||||
<table class="table table-striped">
|
||||
<a class="btn btn-xs btn-primary push_muti"> 推送 </a>
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="text-center">
|
||||
<input type="checkbox" id="check_no_push" onclick="checkAll('check_no_push', 'asset_no_push_id')">
|
||||
</th>
|
||||
<th class="text-center">主机</th>
|
||||
<th class="text-center">IP</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th class="text-center">
|
||||
<input type="checkbox" id="check_no_push"
|
||||
onclick="checkAll('check_no_push', 'asset_no_push_id')">
|
||||
</th>
|
||||
<th class="text-center">主机</th>
|
||||
<th class="text-center">IP</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for asset in need_push_asset %}
|
||||
@@ -272,66 +345,65 @@
|
||||
{% endblock %}
|
||||
{% block self_footer_js %}
|
||||
|
||||
<script>
|
||||
$(document).ready(function(){
|
||||
$('.del').click(function(){
|
||||
var url = $(this).attr('href');
|
||||
$.get(
|
||||
url,
|
||||
{},
|
||||
function(data){
|
||||
location.reload()
|
||||
<script>
|
||||
$(document).ready(function () {
|
||||
$('.del').click(function () {
|
||||
var url = $(this).attr('href');
|
||||
$.get(
|
||||
url,
|
||||
{},
|
||||
function (data) {
|
||||
location.reload()
|
||||
}
|
||||
);
|
||||
return false;
|
||||
});
|
||||
|
||||
$('.del_muti').click(function () {
|
||||
var check_array = [];
|
||||
if (confirm("确定删除")) {
|
||||
$(".gradeX input[name='asset_id']:checked").each(function () {
|
||||
check_array.push($(this).attr("value"))
|
||||
});
|
||||
var url = '/jperm/role/recycle/?role_id={{ role.id }}&asset_id=' + check_array.join(',');
|
||||
console.log(check_array);
|
||||
$.get(url,
|
||||
{},
|
||||
function (data) {
|
||||
location.reload()
|
||||
}
|
||||
)
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
);
|
||||
return false;
|
||||
});
|
||||
|
||||
$('.del_muti').click(function(){
|
||||
var check_array = [];
|
||||
if (confirm("确定删除")) {
|
||||
$(".gradeX input[name='asset_id']:checked").each(function() {
|
||||
$('.push_muti').click(function () {
|
||||
var check_array = [];
|
||||
$(".gradeX input[name='asset_no_push_id']:checked").each(function () {
|
||||
check_array.push($(this).attr("value"))
|
||||
});
|
||||
var url = '/jperm/role/recycle/?role_id={{ role.id }}&asset_id=' + check_array.join(',');
|
||||
console.log(check_array);
|
||||
$.get(url,
|
||||
{},
|
||||
function(data){
|
||||
location.reload()
|
||||
}
|
||||
)
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
$('.push_muti').click(function(){
|
||||
var check_array = [];
|
||||
$(".gradeX input[name='asset_no_push_id']:checked").each(function() {
|
||||
check_array.push($(this).attr("value"))
|
||||
var url = '/jperm/role/push/?id={{ role.id }}&asset_id=' + check_array.join(',');
|
||||
$(this).attr('href', url)
|
||||
});
|
||||
var url = '/jperm/role/push/?id={{ role.id }}&asset_id=' + check_array.join(',');
|
||||
$(this).attr('href', url)
|
||||
});
|
||||
|
||||
$('.re_push').click(function(){
|
||||
var check_array = [];
|
||||
$(".gradeX input[name='asset_id']:checked").each(function() {
|
||||
check_array.push($(this).attr("value"))
|
||||
$('.re_push').click(function () {
|
||||
var check_array = [];
|
||||
$(".gradeX input[name='asset_id']:checked").each(function () {
|
||||
check_array.push($(this).attr("value"))
|
||||
});
|
||||
var url = '/jperm/role/push/?id={{ role.id }}&asset_id=' + check_array.join(',');
|
||||
$(this).attr('href', url)
|
||||
});
|
||||
var url = '/jperm/role/push/?id={{ role.id }}&asset_id=' + check_array.join(',');
|
||||
$(this).attr('href', url)
|
||||
});
|
||||
|
||||
$('.push_failed').click(function() {
|
||||
var fail_reason = $(this).attr('title');
|
||||
layer.alert(fail_reason, {
|
||||
skin: 'layui-layer-molv',
|
||||
area: '500px'
|
||||
})
|
||||
});
|
||||
$('.push_failed').click(function () {
|
||||
var fail_reason = $(this).attr('title');
|
||||
layer.alert(fail_reason, {
|
||||
skin: 'layui-layer-molv',
|
||||
area: '500px'
|
||||
})
|
||||
});
|
||||
|
||||
|
||||
})
|
||||
</script>
|
||||
})
|
||||
</script>
|
||||
{% endblock %}
|
||||
@@ -94,7 +94,7 @@ $('#roleForm').validator({
|
||||
timely: 2,
|
||||
theme: "yellow_right_effect",
|
||||
rules: {
|
||||
check_name: [/(?!^root$)^\w{2,20}$/i, '大小写字母数字和下划线,2-20位,并且非root'],
|
||||
check_name: [/(?!^root$)^[\w.]{2,20}$/i, '大小写字母数字和下划线小数点,2-20位,并且非root'],
|
||||
check_begin: [/^[\-]+BEGIN RSA PRIVATE KEY[\-]+/gm, 'RSA Key填写有误,请检查'],
|
||||
|
||||
},
|
||||
|
||||
@@ -70,7 +70,7 @@
|
||||
<div class="row">
|
||||
<div class="col-sm-6">
|
||||
<div class="dataTables_info" id="editable_info" role="status" aria-live="polite">
|
||||
Showing {{ users.start_index }} to {{ users.end_index }} of {{ p.count }} entries
|
||||
Showing {{ roles.start_index }} to {{ roles.end_index }} of {{ p.count }} entries
|
||||
</div>
|
||||
</div>
|
||||
{% include 'paginator.html' %}
|
||||
@@ -80,8 +80,6 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<script>
|
||||
function remove_role(role_id){
|
||||
$.ajax({
|
||||
@@ -89,39 +87,37 @@ function remove_role(role_id){
|
||||
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)
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
var msg = data + " 资产上的系统用户会被删除, 包括其家目录,请谨慎操作!";
|
||||
layer.alert(msg, {
|
||||
title: '警告',
|
||||
closeBtn: 0
|
||||
}, function(){
|
||||
layer.confirm('危险动作, 除非你非常明白自己在做什么,否则请取消', {
|
||||
title: '再次确认',
|
||||
btn: ['确认', '取消']
|
||||
}, function(){
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
url: "{% url 'role_del' %}",
|
||||
data: "id=" + role_id,
|
||||
success: function(msg){
|
||||
layer.msg( "成功: " + msg );
|
||||
var del_row = $('tbody#edittbody>tr#' + role_id);
|
||||
del_row.remove()
|
||||
},
|
||||
error: function (msg) {
|
||||
console.log(msg);
|
||||
layer.alert("失败: " + msg.responseText)
|
||||
}
|
||||
});
|
||||
}, function(){
|
||||
layer.msg('取消', {icon: 1})
|
||||
})
|
||||
})},
|
||||
error: function(error) {
|
||||
alert(error)
|
||||
},
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
@@ -66,10 +66,11 @@
|
||||
<div class="row">
|
||||
<div class="form-group">
|
||||
<label for="j_group" class="col-sm-2 control-label">使用密钥</label>
|
||||
<div class="col-sm-1">
|
||||
<div class="col-sm-8">
|
||||
<div class="radio i-checks">
|
||||
<label>
|
||||
<input type="checkbox" value="1" id="use_publicKey" name="use_publicKey" checked>
|
||||
<span class="help-block m-b-none">如果资产是网络设备,请取消复选框(模拟推送),如果资产是服务器,要选中复选框 </span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -89,7 +89,7 @@
|
||||
<div class="row">
|
||||
<div class="col-sm-6">
|
||||
<div class="dataTables_info" id="editable_info" role="status" aria-live="polite">
|
||||
Showing {{ users.start_index }} to {{ users.end_index }} of {{ p.count }} entries
|
||||
Showing {{ rules.start_index }} to {{ rules.end_index }} of {{ p.count }} entries
|
||||
</div>
|
||||
</div>
|
||||
{% include 'paginator.html' %}
|
||||
|
||||
@@ -72,7 +72,7 @@
|
||||
<div class="row">
|
||||
<div class="col-sm-6">
|
||||
<div class="dataTables_info" id="editable_info" role="status" aria-live="polite">
|
||||
Showing {{ users.start_index }} to {{ users.end_index }} of {{ p.count }} entries
|
||||
Showing {{ sudos.start_index }} to {{ sudos.end_index }} of {{ p.count }} entries
|
||||
</div>
|
||||
</div>
|
||||
{% include 'paginator.html' %}
|
||||
|
||||
@@ -55,6 +55,7 @@
|
||||
<div class="col-sm-3">
|
||||
<div>
|
||||
<select id="users_selected" name="users_selected" class="form-control m-b" size="12" multiple>
|
||||
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
@@ -118,4 +119,4 @@ $(document).ready(function(){
|
||||
|
||||
|
||||
</script>
|
||||
{% endblock %}
|
||||
{% endblock %}
|
||||
|
||||
@@ -126,6 +126,7 @@ $('#userForm').validator({
|
||||
timely: 2,
|
||||
theme: "yellow_right_effect",
|
||||
rules: {
|
||||
check_name: [/(?!^root$)^[\w.]{2,20}$/i, '大小写字母数字和下划线小数点,2-20位,并且非root'],
|
||||
check_username: [/^[\w.]{3,20}$/, '大小写字母数字和下划线小数点'],
|
||||
type_m: function(element){
|
||||
return $("#M").is(":checked");
|
||||
@@ -133,7 +134,7 @@ $('#userForm').validator({
|
||||
},
|
||||
fields: {
|
||||
"username": {
|
||||
rule: "required;check_username",
|
||||
rule: "required;check_username;check_name",
|
||||
tip: "输入用户名",
|
||||
ok: "",
|
||||
msg: {required: "必须填写!"}
|
||||
@@ -174,4 +175,4 @@ $('#userForm').validator({
|
||||
{#})#}
|
||||
|
||||
</script>
|
||||
{% endblock %}
|
||||
{% endblock %}
|
||||
|
||||
@@ -49,7 +49,7 @@
|
||||
<input name="setting" value="default" style="display: none">
|
||||
<div class="col-sm-8">
|
||||
<input id="username" name="username" placeholder="Username" type="text" value="{{ setting_default.field1 }}" class="form-control">
|
||||
<span class="help-block m-b-none"> 管理账号是服务器存在的root等高权限账号(或拥有NOPASSWD: ALL sudo权限),用来推送新建系统用户</span>
|
||||
<span class="help-block m-b-none"> 管理用户是指客户端上的如root等高权限账号(或拥有NOPASSWD: ALL sudo权限),用来推送新建系统用户</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="hr-line-dashed"></div>
|
||||
@@ -128,6 +128,12 @@
|
||||
tip: "输入端口号",
|
||||
ok: "",
|
||||
msg: {required: "端口号必填"}
|
||||
},
|
||||
"password": {
|
||||
rule: "length[0~30]",
|
||||
tip: "输入密码",
|
||||
ok: "",
|
||||
empty: true
|
||||
}
|
||||
{# "key": {#}
|
||||
{# rule: "required(either)",#}
|
||||
@@ -141,4 +147,4 @@
|
||||
}
|
||||
})
|
||||
</script>
|
||||
{% endblock %}
|
||||
{% endblock %}
|
||||
|
||||
Reference in New Issue
Block a user