Compare commits

...

130 Commits
0.3.1 ... 0.3.3

Author SHA1 Message Date
老广
3533c01011 Update requirements.txt 2017-12-14 12:03:32 +08:00
老广
77ec2d5a86 Update connect.py
解决 index error的问题
2017-12-04 16:14:33 +08:00
ibuler
8b707dc8f5 修改文案说明 2017-10-09 20:32:45 +08:00
老广
8dcc9c8d53 Update web_terminal.html 2017-10-09 05:45:19 -05:00
老广
0ca0519ef1 Update install.py 2017-09-11 09:38:58 +08:00
老广
8de50a0e83 Update README.md 2017-09-08 07:19:16 +08:00
假想控
64b7fb6a3d 添加系统用户推送,使用密钥说明文案
添加系统用户推送,使用密钥说明文案
2017-09-02 18:31:41 +08:00
vincent927
a114e173e0 修复centos7安装报错-平台不支持 (#603) 2017-08-07 16:44:11 +08:00
Leon Chan
402df81048 make sure logs directory exists (#374) 2017-04-20 16:51:52 +08:00
老广
2a51eeef10 Update connect.py 2017-03-13 18:30:23 +08:00
假想控
4b593e56f9 添加系统用户推送,使用密钥说明文案
添加系统用户推送,使用密钥说明文案
2016-11-22 17:44:20 +08:00
紫川秀
9f3c83b052 修复普通用户能添加授权bug (#324)
* 修复sudo别名取消后不回收bug

* 修复普通用户能添加授权bug
2016-11-17 10:54:23 +08:00
紫川秀
a864c38238 修复sudo别名取消后不回收bug (#322) 2016-11-16 10:10:04 +08:00
假想控
547408222d 网页连接主机 js regx bug #292
ws://jumpserver.com/ws/terminal?id=1&role=admin,?id=1&role=admin
而期望的结果应该是ws://jumpserver.com/ws/terminal?id=1&role=admin
所以document.URL.match(/(\?.*)/)里的正则表达式应该去掉分组,变为 document.URL.match(/\?.*/)
2016-11-09 13:39:45 +08:00
假想控
0ad26254ef 修改系统用户推送失败, 进入系统用户详情说明文案
修改系统用户推送失败, 进入系统用户详情说明文案
2016-11-09 12:34:26 +08:00
假想控
b7e138f919 修改添加系统用户,用户名说明文案
修改添加系统用户,用户名说明文案
2016-11-09 11:28:16 +08:00
假想控
2ec415fd84 修改添加系统用户,密码说明文案
修改添加系统用户,密码说明文案
2016-11-09 10:59:34 +08:00
假想控
b3c12e8861 修复使用mysql未初始化问题
1.修复使用mysql未初始化问题
2.crontab add
2016-11-07 13:39:12 +08:00
假想控
da8f30fc72 修改用户密码说明文案
修改用户密码说明文案
2016-11-04 21:07:21 +08:00
假想控
cddb0ce537 Merge pull request #310 from MoonTails83/patch-1
Update install.py
2016-11-04 20:00:58 +08:00
peter17919
124e26fa32 fix #311 (#313)
* Update views.py

* Update views.py

* Update views.py

* Update views.py

* Update views.py
2016-11-03 18:33:38 +08:00
假想控
59c689cd61 Update run.sh
1.更改sed -i "s/__MYSQL_ENGINE__/${MYSQL_ENGINE}/" /jumpserver/jumpserver.conf
2.新增MAIL_USE_SSL
2016-11-03 14:33:16 +08:00
假想控
0cc36b1302 Update docker-compose.yaml
# environment:
- USE_MAIL=true
2016-11-03 14:14:10 +08:00
假想控
0ac9d19252 修改 [db]&[mail]
1.[db]
engine = __MYSQL_ENGINE__
database = __MYSQL_NAME__
2.[mail]
email_use_ssl = __MAIL_USE_SSL__
2016-11-03 14:03:52 +08:00
假想控
d9e497e8be 修改 environment
修改 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_ENABLED=true
      - MAIL_ENABLED=1
      - MAIL_HOST=smtp.163.com
      - MAIL_PORT=25
      - MAIL_USER=evilsmall@163.com
      - MAIL_PASS=Fuck123
      - MAIL_USE_TLS=False
      - MAIL_USE_SSL=False
2016-11-03 13:59:21 +08:00
假想控
e18ae7f82c Update user_add.html
禁止创建root用户
2016-10-27 17:53:43 +08:00
老广
e5286686b9 Update views.py
check valid user when username == 'root'
2016-10-27 17:08:46 +08:00
MoonTails83
332316b0af Update install.py 2016-10-09 11:25:25 +08:00
老广
8f666785d2 Modify select pop stdin
#283  More see this issue
2016-09-05 19:05:12 +08:00
假想控
0ed4b84b63 Update setting.html
修改管理用户说明将服务器改为客户端
2016-08-08 16:03:05 +08:00
老广
97591e6f03 Update service.sh 2016-08-05 13:39:34 +08:00
老广
5d5d8ab32a run service.sh using root user 2016-08-04 10:23:57 +08:00
老广
5489f3ae36 Modify server_add_user function
Change adduser to useradd.  Make compatible will debian, ubuntu
2016-08-04 10:08:46 +08:00
liuzheng712
ce7a3d1f33 change uri /kill to /ws/kill 2016-07-31 10:29:17 +08:00
老广
270499a4fd 修改删除系统用户的交互 (#276)
* fix(api) 修改建立目录的bug

使用bash代替python完成建立777目录的功能

* fix passwd input

* fix(mkdir) 修改mkdirs策略

修改原来导致的bug

* fix passwd input (#232)

修复记录敏感密码bug

* fix passwd input

* fix passwd input

* fix passwd input

* fix(connect) 输入role id时,输入了role名称异常

抓取后并处理

* Update perm_role_edit.html

* fix role delete
2016-07-26 18:41:58 +08:00
假想控
27223a1883 Update next.py
修改安装成功访问web提示信息
2016-07-23 18:14:26 +08:00
老广
e0ae7eeee7 Update user_api.py 2016-07-12 17:08:26 +08:00
老广
b5fefb4687 Update user_api.py 2016-07-12 17:00:20 +08:00
chnliyong
ecfb2f3d40 fix reset password bug (#274)
感谢PR
2016-07-08 03:22:38 -05:00
老广
1614dd5a4d Update README.md 2016-06-20 10:01:35 +08:00
liuzheng
cf3e89c374 Update run_server.py
because if someone need run jumpserver at 127.0.0.1 need this fix
2016-06-15 22:06:26 +08:00
老广
0f0908d3f3 Update jumpserver.conf 2016-06-14 17:32:28 +08:00
ibuler
31c3def1a8 Update group_add.html 2016-06-07 17:34:08 +08:00
ibuler
f75003461a Update settings.py 2016-06-07 16:39:35 +08:00
ibuler
49504e46ee Update docker-compose.yaml 2016-06-07 16:37:06 +08:00
ibuler
699bdd1348 Update config_tmpl.conf 2016-06-07 16:35:48 +08:00
ibuler
e608fbaad5 Update settings.py 2016-06-07 16:17:10 +08:00
ibuler
6a7d105484 Update jumpserver.conf 2016-06-07 16:12:38 +08:00
xRain
6acda5fcd1 add docker support and update the locally 2016-06-07 11:51:16 +08:00
xRain
57ba8ed2fb add docker support 2016-06-07 11:49:59 +08:00
kikiyou
d6c4017a2e 使示例可以正确运行 (#237)
为了安全pattern=空,后示例代码无法使用,运行示例时加上pattern='*',使示例可以返回正确的结果
2016-06-06 21:55:54 -05:00
lrqrun
b7be5d14e0 User object is not the user, must update at the user (#254)
修改信息保存后数据不是最新的而是之前的数据,因为在object的惰性查询不会获取到最新的数据,因此需要在缓存的对象基础上修改save后commit到数据库。
2016-06-06 21:55:28 -05:00
lrqrun
f130a78f0a users_selected keep new (#255)
用户组保存后数据显示的问题,在已选用户处显示选择的数据
2016-06-06 21:54:11 -05:00
__YoYO
a077053b68 Dev (#250)
* Update perm_role_edit.html
2016-06-06 21:52:41 -05:00
Kallen Ding
c93c8de7fe Replace os.makedirs to mkdir. (#251)
解决Tty Logs 日志跨天后目录权限不对的问题
2016-06-06 21:50:38 -05:00
ibuler
3176156639 Update connect.py 2016-06-03 10:03:12 +08:00
ibuler
7072d16f00 Update install.py 2016-05-31 19:25:10 +08:00
ibuler
f2dda35f28 Update footer.html 2016-05-19 17:47:50 +08:00
zheng
0cc04ee20d Group edit (#241)
* 修复主机组编辑时回车导致主机丢失问题

在主机组编辑页面,如果直接执行回车会导致主机组中主机信息丢失。
本修复方法是关闭回车提交

* 编辑主机组在移除过滤保存时数据会丢失

现象:在反向移除选择的主机时,用过滤框搜索移除主机此时保存的数据是当前过滤显示的数据
后果:会造成原有主机组数据丢失
修复:在保存之前触发一次空值搜索
2016-05-18 05:14:18 -05:00
ibuler
7531a3ada7 修复编辑系统用户,用户名jsbug (#240)
* Update perm_role_edit.html
2016-05-18 04:08:12 -05:00
ibuler
32ab8a1646 完美修复vim等交互式命令记录 (#236)
* fix(api) 修改建立目录的bug

使用bash代替python完成建立777目录的功能

* fix passwd input

* fix(mkdir) 修改mkdirs策略

修改原来导致的bug

* fix passwd input (#232)

修复记录敏感密码bug

* fix passwd input

* fix passwd input

* fix passwd input
2016-05-11 18:48:38 +08:00
ibuler
d60562a034 修复交互式记录密码bug,merge to master (#234)
* fix(api) 修改建立目录的bug

使用bash代替python完成建立777目录的功能

* fix passwd input

* fix(mkdir) 修改mkdirs策略

修改原来导致的bug

* fix passwd input (#232)

修复记录敏感密码bug

* fix passwd input

* fix passwd input
2016-05-11 17:44:22 +08:00
ibuler
93e08a6e29 修复创建tty日志文件失败, 请修改目录 bug (#231)
* fix(api) 修改建立目录的bug

使用bash代替python完成建立777目录的功能

* fix passwd input

* fix(mkdir) 修改mkdirs策略

修改原来导致的bug

* fix passwd input (#232)

修复记录敏感密码bug

* fix passwd input

* fix passwd input
2016-05-11 17:41:46 +08:00
zheng
d66ba9d6c6 修复主机组编辑时回车导致主机丢失问题 (#230)
在主机组编辑页面,如果直接执行回车会导致主机组中主机信息丢失。
本修复方法是关闭回车提交
2016-05-11 11:22:08 +08:00
ibuler
1338d25b4e fix bug 2016-05-10 13:55:06 +08:00
ibuler
f994c4d1da fix(connect.py) 修复max引起的异常
已经修复
2016-05-10 13:48:55 +08:00
ibuler
5fab276c26 fix(jperm) fix jperm role detail list.
* 1. Add a window to list pushed error asset
* 2. Fix old bug for pagninator
2016-05-10 12:19:54 +08:00
ibuler
9f171da570 修复cli 端资产列表显示 (#226)
* modify(jperm) 授权列表模糊搜索

修改授权规则搜索为模糊搜索

* fix(cli nav align) Max Hostname length 30, else will be truncate.
2016-05-10 10:11:32 +08:00
ibuler
fed00d04a6 fix(cli nav align) Max Hostname length 30, else will be truncate. 2016-05-09 20:19:01 +08:00
ibuler
ecfaf9f02d modify(jperm) 授权列表模糊搜索 (#225)
修改授权规则搜索为模糊搜索
2016-05-09 18:54:33 +08:00
ibuler
d05e9d0b45 modify(jperm) 授权列表模糊搜索
修改授权规则搜索为模糊搜索
2016-05-09 18:53:04 +08:00
ibuler
d4385c7e43 Merge branch 'master' into dev 2016-04-28 15:56:22 +08:00
ibuler
04fc9962ff Merge branch 'dev' 2016-04-28 15:55:08 +08:00
ibuler
2fd68845ce Merge branch 'dev' of github.com:jumpserver/jumpserver into dev 2016-04-28 15:54:33 +08:00
yumaojun03
bd69339e22 Bug fix hostname (#216)
* fix (jasset):   修复资产hostname过长和密码过长引起的bug

1. 修改password字段的长度,对称加密过后的字符串会变长,所有设置得比较大(256)
2. 添加check hostname 和 password的 长度校验

* fix (jumpserver/jasset):   修复setting时,秘密过长问题。

1. 修改password字段的长度,对称加密过后的字符串会变长,所有设置得比较大(256)
2. 后端修复views秘密超过30位不保存
3.前段使用js限制秘密长多不能超过30位

* fix (jumpserver/jasset):   setting and asset hostname password  too long.

1. 添加setting password字段长度验证
2. 添加资产主机名和密码长度验证

* fix (jumpserver/jasset):   setting and asset hostname password  too long.

1. 修正setting时的 输入密码的提示错误.
2016-04-28 15:44:48 +08:00
liuzheng
c6404f7ed6 Static bug (#208)
* 紧急修复下载文件后静态文件404问题

* 紧急修复监控白屏问题

* 紧急修复下载文件后静态文件404问题

* 紧急修复下载文件后静态文件404问题

* 修复zip包为空问题
2016-04-24 20:42:02 +08:00
huangguozhen
6ce948366d Update base.html (#213)
fix(frontend): use webkit default in multi kernel browsers
2016-04-23 10:35:28 +08:00
ibuler
9e78fd3651 Merge master to dev (#212)
* Update install.py

修改centos7支持

* Update install.py

* Support resize web terminal size

Support resize web terminal size.

Change new windows to a new tab.
May be more hommization

* Static bug (#204)

* 紧急修复下载文件后静态文件404问题

* 紧急修复监控白屏问题

* bugfix(upload web) When download file, static file will unreachable.

Didn't change dir

fixed

* bugfix(upload web) When download file, static file will unreachable. (#206) (#207)

* Update install.py

修改centos7支持

* Update install.py

* Support resize web terminal size

Support resize web terminal size.

Change new windows to a new tab.
May be more hommization

* Static bug (#204)

* 紧急修复下载文件后静态文件404问题

* 紧急修复监控白屏问题

* bugfix(upload web) When download file, static file will unreachable.

Didn't change dir

fixed

* fix bug index out of range (#210)
2016-04-22 11:51:36 +08:00
kelianchun
5afd135967 fix bug index out of range (#210) 2016-04-22 11:49:50 +08:00
ibuler
d5aa9324fa Merge branch 'dev' of github.com:jumpserver/jumpserver into dev 2016-04-21 18:12:58 +08:00
ibuler
9096a6e5b8 bugfix(upload web) When download file, static file will unreachable. (#206) (#207)
* Update install.py

修改centos7支持

* Update install.py

* Support resize web terminal size

Support resize web terminal size.

Change new windows to a new tab.
May be more hommization

* Static bug (#204)

* 紧急修复下载文件后静态文件404问题

* 紧急修复监控白屏问题

* bugfix(upload web) When download file, static file will unreachable.

Didn't change dir

fixed
2016-04-20 15:21:55 +08:00
ibuler
cb58012a82 bugfix(upload web) When download file, static file will unreachable. (#206)
* Update install.py

修改centos7支持

* Update install.py

* Support resize web terminal size

Support resize web terminal size.

Change new windows to a new tab.
May be more hommization

* Static bug (#204)

* 紧急修复下载文件后静态文件404问题

* 紧急修复监控白屏问题

* bugfix(upload web) When download file, static file will unreachable.

Didn't change dir

fixed
2016-04-20 15:20:11 +08:00
ibuler
58bb3cc84f Merge branch 'dev'
fix download error

static file lost
2016-04-20 15:17:04 +08:00
ibuler
c2ff05201a Merge branch 'dev' of github.com:jumpserver/jumpserver into dev 2016-04-20 14:44:06 +08:00
yumaojun03
9be13cf08f fix (jperm): 修复密码添加和更新 role时 密码过长引起的bug (#202)
1. 修改password字段的长度,对称加密过后的字符串会变长,所有设置得比较大(512)
2. 修改后端检查密码长度,并触发异常。
2016-04-20 14:31:52 +08:00
ibuler
eb4ec47f7a bugfix(upload web) When download file, static file will unreachable.
Didn't change dir

fixed
2016-04-20 14:29:11 +08:00
liuzheng
e2eb9b72f8 Static bug (#204)
* 紧急修复下载文件后静态文件404问题

* 紧急修复监控白屏问题
2016-04-20 13:08:26 +08:00
ibuler
c9ff235089 fix(connect) input exact ip for connect
modify search strategy

if some ip match pass
2016-04-16 16:27:15 +08:00
ibuler
9af809a4f0 Merge branch 'dev' of github.com:jumpserver/jumpserver into dev 2016-04-12 11:12:32 +08:00
Astraeux
288a42663a TTY nav sort by ip / hostname / none (#198)
* TTY nav sort by ip / hostname / none

* add newline at end of file
2016-04-12 11:11:19 +08:00
liuzheng
cca15d4211 Support resize web terminal size
Support resize web terminal size.

Change new windows to a new tab.
May be more hommization
2016-04-07 15:45:48 +08:00
jiaxiangkong
fe2081b407 Update install.py 2016-04-07 15:07:03 +08:00
ibuler
fa323d4987 Update install.py
修改centos7支持
2016-04-07 11:56:00 +08:00
ibuler
eeef4a2f95 Merge branch 'windowResize' of github.com:jumpserver/jumpserver into windowResize 2016-04-06 13:04:47 +08:00
liuzheng712
2e49f51093 update 2016-04-06 12:58:00 +08:00
ibuler
0481e83ec4 Merge branch 'dev' into windowResize 2016-04-06 12:57:54 +08:00
liuzheng712
ff8b5bd6c0 默认terminal100x35 2016-04-06 10:26:30 +08:00
liuzheng712
aabab653d3 默认terminal大小100x35 2016-04-06 10:24:18 +08:00
ibuler
efe0b3acc0 Fix nav info and delete user key when delete a user
Fix nav info and delete user key when delete a user

reviewd by ibuler <ibuler@qq.com>
2016-04-05 23:47:27 +08:00
ibuler
35cc966132 change(info) Modify some nav info
May be clearly.
2016-04-05 23:44:01 +08:00
ibuler
777997202b patch again with 1f09a40c77
print => debug
2016-04-05 22:37:55 +08:00
ibuler
1f09a40c77 fix(user manage and connect first login)
When delete a user, but didn't delete the user sysuser key. When create
a user with same username, error occur.

When user login tty, and type a num first, it will search a host, but
login the asset with the id.

fixed
2016-04-05 22:34:37 +08:00
ibuler
d20fecadac Merge branch 'dev' 2016-04-05 16:53:06 +08:00
ibuler
fa430bf104 Merge branch 'dev' of github.com:jumpserver/jumpserver into dev 2016-04-05 16:52:44 +08:00
ibuler
8bb66ac254 fix(install.py) delete old pycrypto module
fixed
2016-04-05 16:51:58 +08:00
liuzheng712
6518aa3670 bugfix 2016-04-05 13:05:07 +08:00
liuzheng712
c76d9ebd88 bugfix 2016-04-05 12:56:37 +08:00
liuzheng712
7f4d3ffdbc bug_fix 2016-04-05 12:54:37 +08:00
liuzheng712
b908fdafc6 udpate 2016-04-05 12:50:09 +08:00
liuzheng712
ef59cff44b bug_fix 2016-04-05 12:42:44 +08:00
ibuler
f511802db5 fix asset group judge
reviewed by: ibuler <ibuler@qq.com>
2016-04-05 11:22:31 +08:00
ibuler
30c74b8427 Merge pull request #192 from jumpserver/group_judge
new feature (connect) Ignore case in searching
2016-04-05 11:19:55 +08:00
ibuler
2dd16b91ec Merge branch 'dev' of github.com:jumpserver/jumpserver into dev 2016-04-05 11:18:54 +08:00
liuzheng712
bb7a3ea053 Merge branch 'master' of github.com:jumpserver/jumpserver 2016-04-05 10:53:47 +08:00
liuzheng712
0499a7265a 手动修改窗口大小问题 2016-04-05 10:53:35 +08:00
ibuler
1959c685b9 new feature (connect) Ignore case in searching
finshed
2016-04-01 17:52:47 +08:00
ibuler
cd80fbcdbb Merge pull request #190 from jumpserver/dev
Dev
2016-04-01 17:24:45 +08:00
ibuler
5814b833ed Merge pull request #189 from jumpserver/webexec_log
Webexec log
2016-04-01 16:44:35 +08:00
ibuler
cda0b9c90a fix(web exec) Web execute command log didn't get the real ip if behind the lb proxy.
fixed
2016-04-01 16:42:01 +08:00
ibuler
609bba569e fix(web exec) Web execute command log didn't get the real ip if behind the lb proxy.
fixed
2016-04-01 16:40:53 +08:00
ibuler
77c7f8fb54 fix(web exec) Web execute command log didn't get the real ip if behind the lb proxy.
fixed
2016-04-01 16:38:37 +08:00
ibuler
f65290ef38 Merge pull request #188 from jumpserver/dev
新增功能
2016-04-01 13:49:28 +08:00
ibuler
de594aebd5 Merge branch 'dev' of github.com:jumpserver/jumpserver into dev 2016-04-01 12:12:34 +08:00
ibuler
2be81c0322 fix(connect) delete debug
fixed
2016-04-01 12:11:31 +08:00
ibuler
e902fccd39 Merge pull request #187 from jumpserver/search_sort
feat (sort search) 对搜索结果排序
2016-04-01 11:54:28 +08:00
ibuler
61d162312f Merge pull request #185 from Astraeux/support_amazon_linux
支持 Amazon Linux
2016-04-01 11:53:55 +08:00
ibuler
30a3cd2911 feat (sort search) 对搜索结果排序
tty登陆连接,搜索时对结果进行排序

finished
2016-04-01 11:50:35 +08:00
ibuler
2dfe9337a2 Merge pull request #186 from jumpserver/conn_search
fix(connect) 增加模糊搜索
2016-03-31 23:50:37 +08:00
ibuler
c188696328 fix(connect) 增加模糊搜索
之前只是输入id登陆,增加了模糊搜索登陆

如果搜索唯一则登陆
2016-03-31 23:45:01 +08:00
Astraeux
f8fac06e1b 支持 Amazon Linux 2016-03-31 15:14:41 +08:00
56 changed files with 18899 additions and 497 deletions

37
Dockerfile Normal file
View 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

View File

@@ -4,10 +4,10 @@
#欢迎使用Jumpserver
**Jumpserver** 是一款由python编写开源的跳板机(堡垒机)系统实现了跳板机应有的功能。基于ssh协议来管理客户端无需安装agent。
支持常见系统:
1. redhat centos
2. debian
3. suse ubuntu
4. freebsd
1. CentOS, RedHat, Fedora, Amazon Linux
2. Debian
3. SUSE, Ubuntu
4. FreeBSD
5. 其他ssh协议硬件设备
###截图:
@@ -69,12 +69,20 @@ Web批量执行命令
[demo站点](http://demo.jumpserver.org)
交流群: 399218702
交流群: 552054376
### 团队
![](https://github.com/ibuler/static/raw/master/jumpserver3/team.jpg)
### 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.

View File

@@ -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,8 +425,21 @@ class Nav(object):
"""
def __init__(self, user):
self.user = user
self.search_result = {}
self.user_perm = {}
self.user_perm = get_group_user_perm(self.user)
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():
@@ -447,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 批量下载文件.
@@ -460,46 +462,107 @@ class Nav(object):
"""
print textwrap.dedent(msg)
def search(self, str_r=''):
def get_asset_group_member(self, str_r):
gid_pattern = re.compile(r'^g\d+$')
# 获取用户授权的所有主机信息
if not self.user_perm:
self.user_perm = get_group_user_perm(self.user)
user_asset_all = self.user_perm.get('asset').keys()
# 搜索结果保存
user_asset_search = []
if str_r:
# 资产组组id匹配
if gid_pattern.match(str_r):
gid = int(str_r.lstrip('g'))
# 获取资产组包含的资产
asset_group = get_object(AssetGroup, id=gid)
if asset_group:
user_asset_search = asset_group.asset_set.all()
else:
color_print('没有该资产组或没有权限')
return
if gid_pattern.match(str_r):
gid = int(str_r.lstrip('g'))
# 获取资产组包含的资产
asset_group = get_object(AssetGroup, id=gid)
if asset_group and asset_group in self.perm_asset_groups:
self.search_result = list(asset_group.asset_set.all())
else:
color_print('没有该资产组或没有权限')
return
def search(self, str_r=''):
# 搜索结果保存
if str_r:
try:
id_ = int(str_r)
if id_ < len(self.search_result):
self.search_result = [self.search_result[id_]]
return
else:
raise ValueError
except (ValueError, TypeError):
# 匹配 ip, hostname, 备注
for asset in user_asset_all:
if str_r in asset.ip or str_r in str(asset.hostname) or str_r in str(asset.comment):
user_asset_search.append(asset)
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:
# 如果没有输入就展现所有
user_asset_search = user_asset_all
self.search_result = self.perm_assets
self.search_result = dict(zip(range(len(user_asset_search)), user_asset_search))
color_print('[%-3s] %-12s %-15s %-5s %-10s %s' % ('ID', '主机名', 'IP', '端口', '系统用户', '备注'), 'title')
for index, asset in self.search_result.items():
# 获取该资产信息
asset_info = get_asset_info(asset)
# 获取该资产包含的角色
role = [str(role.name) for role in self.user_perm.get('asset').get(asset).get('role')]
print '[%-3s] %-15s %-15s %-5s %-10s %s' % (index, asset.hostname, asset.ip, asset_info.get('port'),
role, asset.comment)
@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):
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')]
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):
try:
asset = self.search_result[0]
roles = list(self.user_perm.get('asset').get(asset).get('role'))
if len(roles) == 1:
role = roles[0]
elif len(roles) > 1:
print "\033[32m[ID] 系统用户\033[0m"
for index, role in enumerate(roles):
print "[%-2s] %s" % (index, role.name)
print
print "授权系统用户超过1个请输入ID, q退出"
try:
role_index = raw_input("\033[1;32mID>:\033[0m ").strip()
if role_index == 'q':
return
else:
role = roles[int(role_index)]
except IndexError:
color_print('请输入正确ID', 'red')
return
else:
color_print('没有映射用户', 'red')
return
print('Connecting %s ...' % asset.hostname)
ssh_tty = SshTty(login_user, asset, role)
ssh_tty.connect()
except (KeyError, ValueError):
color_print('请输入正确ID', 'red')
except ServerError, e:
color_print(e, 'red')
def print_asset_group(self):
"""
打印用户授权的资产组
@@ -515,9 +578,6 @@ class Nav(object):
批量执行命令
"""
while True:
if not self.user_perm:
self.user_perm = get_group_user_perm(self.user)
roles = self.user_perm.get('role').keys()
if len(roles) > 1: # 授权角色数大于1
color_print('[%-2s] %-15s' % ('ID', '系统用户'), 'info')
@@ -529,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):
@@ -587,8 +647,6 @@ class Nav(object):
def upload(self):
while True:
if not self.user_perm:
self.user_perm = get_group_user_perm(self.user)
try:
print "进入批量上传模式"
print "请输入主机名或ansible支持的pattern, 多个主机:分隔 q退出"
@@ -640,8 +698,6 @@ class Nav(object):
def download(self):
while True:
if not self.user_perm:
self.user_perm = get_group_user_perm(self.user)
try:
print "进入批量下载模式"
print "请输入主机名或ansible支持的pattern, 多个主机:分隔,q退出"
@@ -723,9 +779,14 @@ def main():
sys.exit(0)
if option in ['P', 'p', '\n', '']:
nav.search()
nav.print_search_result()
continue
if option.startswith('/') or gid_pattern.match(option):
if option.startswith('/'):
nav.search(option.lstrip('/'))
nav.print_search_result()
elif gid_pattern.match(option):
nav.get_asset_group_member(str_r=option)
nav.print_search_result()
elif option in ['G', 'g']:
nav.print_asset_group()
continue
@@ -741,36 +802,13 @@ def main():
elif option in ['Q', 'q', 'exit']:
sys.exit()
else:
try:
asset = nav.search_result[int(option)]
roles = nav.user_perm.get('asset').get(asset).get('role')
if len(roles) > 1:
role_check = dict(zip(range(len(roles)), roles))
print "\033[32m[ID] 系统用户\033[0m"
for index, role in role_check.items():
print "[%-2s] %s" % (index, role.name)
print
print "授权系统用户超过1个请输入ID, q退出"
try:
role_index = raw_input("\033[1;32mID>:\033[0m ").strip()
if role_index == 'q':
continue
else:
role = role_check[int(role_index)]
except IndexError:
color_print('请输入正确ID', 'red')
continue
elif len(roles) == 1:
role = list(roles)[0]
else:
color_print('没有映射用户', 'red')
continue
ssh_tty = SshTty(login_user, asset, role)
ssh_tty.connect()
except (KeyError, ValueError):
color_print('请输入正确ID', 'red')
except ServerError, e:
color_print(e, 'red')
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()
except IndexError, e:
color_print(e)
time.sleep(5)

25
docker-compose.yaml Normal file
View 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

View File

@@ -1,4 +1,4 @@
#!/bin/bash
#!/bin/sh
#
trap '' SIGINT

View 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

File diff suppressed because it is too large Load Diff

29
install/docker/passwd Normal file
View 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

View 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
View 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
View 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
View 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
View File

@@ -0,0 +1,2 @@
#!/bin/sh
adduser $@

2
install/docker/userdel Normal file
View File

@@ -0,0 +1,2 @@
#!/bin/sh
deluser --remove-home $3

View File

@@ -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.dist()[0].lower()
self.version = platform.dist()[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":
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, 暂不支持其他平台安装.")
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库失败")

View File

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

View File

@@ -1,4 +1,5 @@
#sphinx-me==0.3
PyYAML==3.10
django==1.6
pycrypto==2.4.1
paramiko==1.16.0

View File

@@ -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地址")

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -233,7 +233,9 @@ class ExecHandler(tornado.websocket.WebSocketHandler):
def open(self):
logger.debug('Websocket: Open exec request')
role_name = self.get_argument('role', 'sb')
self.remote_ip = self.request.remote_ip
self.remote_ip = self.request.headers.get("X-Real-IP")
if not self.remote_ip:
self.remote_ip = self.request.remote_ip
logger.debug('Web执行命令: 请求系统用户 %s' % role_name)
self.role = get_object(PermRole, name=role_name)
self.perm = get_group_user_perm(self.user)
@@ -362,23 +364,22 @@ class WebTerminalHandler(tornado.websocket.WebSocketHandler):
return
if 'resize' in jsondata.get('data'):
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()
@@ -415,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)
@@ -433,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:
@@ -494,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"))),
@@ -502,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()

View File

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

View File

@@ -56,7 +56,12 @@ NgApp.controller('TerminalRecordCtrl', function ($scope, $http) {
document.getElementById("beforeScrubberText").innerHTML = buildTimeString(time);
for (; pos < timelist.length; pos++) {
if (timelist[pos] * 1000 <= time) {
term.write(data[timelist[pos]]);
try{
var findResize = JSON.parse(data[timelist[pos]])['data'];
term.resize(findResize['resize']['cols'], findResize['resize']['rows'])
} catch (err) {
term.write(data[timelist[pos]]);
}
} else {
break;
}
@@ -103,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
});

View File

@@ -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) {
@@ -36,7 +35,7 @@ WSSHClient.prototype.connect = function (options) {
};
this._connection.onmessage = function (evt) {
try{
try {
options.onData(evt.data);
} catch (e) {
var data = JSON.parse(evt.data.toString());
@@ -55,6 +54,23 @@ WSSHClient.prototype.send = function (data) {
function openTerminal(options) {
var client = new WSSHClient();
var rowHeight, colWidth;
try {
rowHeight = localStorage.getItem('term-row');
colWidth = localStorage.getItem('term-col');
} catch (err) {
rowHeight = 35;
colWidth = 100
}
if (rowHeight) {
} else {
rowHeight = 35
}
if (colWidth) {
} else {
colWidth = 100
}
var term = new Terminal({
rows: rowHeight,
cols: colWidth,
@@ -66,7 +82,7 @@ function openTerminal(options) {
client.send(data)
});
$('.terminal').detach().appendTo('#term');
term.resize(80, 24);
//term.resize(colWidth, rowHeight);
term.write('Connecting...');
client.connect($.extend(options, {
onError: function (error) {
@@ -74,6 +90,7 @@ function openTerminal(options) {
},
onConnect: function () {
// Erase our connecting message
client.send({'resize': {'rows': rowHeight, 'cols': colWidth}});
term.write('\r');
},
onClose: function () {
@@ -83,20 +100,20 @@ function openTerminal(options) {
term.write(data);
}
}));
rowHeight = 0.0 + 1.00 * $('.terminal').height() / 24;
colWidth = 0.0 + 1.00 * $('.terminal').width() / 80;
//rowHeight = 0.0 + 1.00 * $('.terminal').height() / 24;
//colWidth = 0.0 + 1.00 * $('.terminal').width() / 80;
return {'term': term, 'client': client};
}
function resize() {
$('.terminal').css('width', window.innerWidth - 25);
console.log(window.innerWidth);
console.log(window.innerWidth - 10);
var rows = Math.floor(window.innerHeight / rowHeight) - 2;
var cols = Math.floor(window.innerWidth / colWidth) - 1;
return {rows: rows, cols: cols};
}
//function resize() {
// $('.terminal').css('width', window.innerWidth - 25);
// console.log(window.innerWidth);
// console.log(window.innerWidth - 10);
// var rows = Math.floor(window.innerHeight / rowHeight) - 2;
// var cols = Math.floor(window.innerWidth / colWidth) - 1;
//
// return {rows: rows, cols: cols};
//}
$(document).ready(function () {
var options = {};
@@ -112,5 +129,26 @@ $(document).ready(function () {
// term_client.client.send({'resize': {'rows': geom.rows, 'cols': geom.cols}});
// $('#ssh').show();
//}
});
try {
$('#term-row')[0].value = localStorage.getItem('term-row');
$('#term-col')[0].value = localStorage.getItem('term-col');
} catch (err) {
$('#term-row')[0].value = 35;
$('#term-col')[0].value = 100;
}
$('#col-row').click(function () {
var col = $('#term-col').val();
var row = $('#term-row').val();
localStorage.setItem('term-col', col);
localStorage.setItem('term-row', row);
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();
})
});

View File

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

View File

@@ -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 &copy; 2014-2015

View File

@@ -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: "必须填写!"}

View File

@@ -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();

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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">
@@ -173,8 +173,14 @@
$('.terminal').detach().appendTo('#term');
$('.terminal').show();
socket.onmessage = function(evt){
term.write(evt.data);
}}, 1000);
try {
var findResize = JSON.parse(evt.data)['data'];
term.resize(findResize['resize']['cols'], findResize['resize']['rows'])
} catch (err) {
term.write(evt.data);
}
}
}, 1000);
return tag[0];
} ,
@@ -209,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);

View File

@@ -1,43 +1,55 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Jumpserver Web Terminal: {{ hostname }}</title>
<head>
<meta charset="utf-8">
<title>{{ hostname }}</title>
<style>
body {
padding-bottom: 40px;
}
<style>
body {
padding-bottom: 40px;
}
.terminal {
border: #000 solid 5px;
font-family: "Monaco", "Microsoft Yahei", "DejaVu Sans Mono", "Liberation Mono", monospace;
font-size: 11px;
color: #f0f0f0;
background: #000;
width: 600px;
box-shadow: rgba(0, 0, 0, 0.8) 2px 2px 20px;
white-space: nowrap;
display: inline-block;
}
.terminal {
border: #000 solid 5px;
font-family: Consolas, Monaco, monospace;
font-size: 11px;
color: #f0f0f0;
background: #000;
box-shadow: rgba(0, 0, 0, 0.8) 2px 2px 20px;
white-space: nowrap;
display: inline-block;
}
.reverse-video {
color: #000;
background: #f0f0f0;
}
</style>
</head>
.reverse-video {
color: #000;
background: #f0f0f0;
}
<body>
<div class="container">
<div id="term">
</div>
</div>
.termChangBar {
line-height: 1;
margin: 0 auto;
border: 1px solid #ffffff;
color: #fff;
background-color: #ffffff;
position: fixed;
right: 0;
top: 0;
}
</style>
</head>
<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>
<body>
<div class="container">
<div id="term">
</div>
</div>
<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/webterminal.js"></script>
</body>
</html>

View File

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

View File

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

View File

@@ -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填写有误请检查'],
},

View File

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

View File

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

View File

@@ -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' %}

View File

@@ -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' %}

View File

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

View File

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

View File

@@ -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>
@@ -109,7 +109,7 @@
timely: 2,
theme: "yellow_right_effect",
rules: {
check_name: [/^\w{2,20}$/, '大小写字母数字和下划线,2-20位'],
check_name: [/^(\w|\-){2,20}$/, '大小写字母数字、中划线和下划线2-20位'],
check_port: [/^\d{1,5}$/, '端口号不正确'],
either: function(){
return $('#password').val() == ''
@@ -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 %}