Compare commits

...

640 Commits
v3.5 ... v3.9

Author SHA1 Message Date
fit2bot
43dc58c06c perf: dockerfile 添加 freerdp2-dev 依赖 (#12387)
Co-authored-by: feng <1304903146@qq.com>
2023-12-20 18:49:01 +08:00
ibuler
2a23af5030 perf: 优化 applet 账号选择 2023-12-05 16:19:19 +08:00
wangruidong
0fbbdceec2 fix: 双机主备部署sftp备份文件找不到 2023-12-05 14:01:26 +08:00
wangruidong
b10ee436e8 fix: sftp不能设置为默认存储 2023-11-28 15:19:00 +08:00
ibuler
24987c7f60 perf: 优化 queryset count 2023-11-28 12:53:32 +08:00
feng
73bed4d33d fix: mysql 开始ssl后 再关闭测试失败 2023-11-20 15:39:06 +08:00
feng
4c389c05c1 fix: mysql 开启ssl 再关闭 测试可连接性失败 2023-11-20 10:50:56 +08:00
ibuler
e744c4c8af perf: 优化延迟运行
fix: 延迟执行设置超时

perf: 修改 delay run

perf: 优化 delay_run 执行

perf: 修改 delay run
2023-11-20 10:31:20 +08:00
feng
06d1c9f420 fix: redis 开启 ssl websocket连接失败 2023-11-20 10:23:03 +08:00
ibuler
a92023840a fix: 修复自动禁用非活跃用户任务 2023-11-17 15:41:12 +08:00
Aaron3S
2d1bf866fa fix: 删除debug信息 2023-11-17 11:34:58 +08:00
吴小白
c606c3eb21 perf: 优化 Dockerfile 2023-11-17 10:38:41 +08:00
老广
dabbb45f6e Merge pull request #12144 from jumpserver/dev
v3.9.0
2023-11-16 18:23:05 +08:00
ibuler
ded1b4bba1 perf: 优化 api key 认证记录用户的时间 2023-11-16 18:17:22 +08:00
fit2bot
2630ea39a1 perf: windows 改密推送添加新的方式 最后测试可连接性的时候采用rdp的方式测试 (#12141)
Co-authored-by: feng <1304903146@qq.com>
2023-11-16 18:12:22 +08:00
Bryan
9e10029bdd Revert "fix: 修复平台自动化翻译 (#12078)" (#12138)
This reverts commit 69c0eb2f50.
2023-11-16 16:35:08 +08:00
Aaron3S
d1391cb5d5 fix: 修复 sqlserver 命令执行问题 2023-11-16 16:24:39 +08:00
Aaron3S
44f029774d fix: 修复playbook部分不可执行问题 2023-11-16 16:07:31 +08:00
fit2bot
23fce9e426 perf: 翻译 (#12135)
Co-authored-by: feng <1304903146@qq.com>
2023-11-16 15:35:34 +08:00
fit2bot
0778a39894 perf: 在线会话添加活跃状态过滤 (#12134)
Co-authored-by: feng <1304903146@qq.com>
2023-11-16 14:41:35 +08:00
fit2bot
9cc6d6a9af perf: dockerfile add libx11-dev (#12133)
Co-authored-by: feng <1304903146@qq.com>
2023-11-16 13:21:16 +08:00
fit2bot
8f309dee92 fix: 资产测试可连接性选错账号 (#12130)
Co-authored-by: feng <1304903146@qq.com>
2023-11-16 11:26:05 +08:00
Bai
d166b26252 perf: 优化处理telnet协议资产端点的端口问题 2023-11-16 11:13:37 +08:00
fit2bot
1ef51563b5 perf: account 迁移文件 (#12128)
Co-authored-by: feng <1304903146@qq.com>
2023-11-16 10:18:54 +08:00
老广
3e7b4682e4 Merge pull request #12124 from jumpserver/pr@dev@perf_device_icon
perf: 修改 tree 硬件设备的 icon
2023-11-15 17:02:05 +08:00
ibuler
994b42aa93 perf: 修改 tree 硬件设备的 icon 2023-11-15 17:00:12 +08:00
fit2bot
d6aea54722 fix: 账号收集未同步资产时 变更数据错误 (#12123)
Co-authored-by: feng <1304903146@qq.com>
2023-11-15 16:44:35 +08:00
ibuler
88afabdd1d perf: 设置 winrm 用户端不可以连接 2023-11-15 15:34:38 +08:00
fit2bot
b2327c0c5a fix: 账号改密 root密钥无法替换 (#12121)
Co-authored-by: feng <1304903146@qq.com>
2023-11-15 15:33:10 +08:00
Aaron3S
7610f64433 perf: 优化获取当前 python 执行路径的方式 2023-11-15 15:21:56 +08:00
fit2bot
b15c314384 fix: 资产多协议时 计算协议端口错误 (#12120)
Co-authored-by: feng <1304903146@qq.com>
2023-11-15 14:59:40 +08:00
wangruidong
7a5cffac91 fix: 对象存储下拉无法自动加载 2023-11-15 14:58:33 +08:00
feng
8667943443 fix: celery事物 数据库未保存 2023-11-14 19:42:21 +08:00
Aaron3S
7c51d90a3d fix: 修复快捷命令找不到mssql module 的问题 2023-11-14 19:28:46 +08:00
wangruidong
9996b200f9 fix: 作业执行历史日志未按配置天数清理 2023-11-14 19:22:14 +08:00
wangruidong
ae364ac373 fix: 录像存储下载报错 2023-11-14 19:21:33 +08:00
wangruidong
fef4a97931 fix: 作业日志筛选用户出错 2023-11-14 19:20:52 +08:00
fit2bot
d63c4d6cc4 fix: mysql 测试可连接性失败 (#12104)
Co-authored-by: feng <1304903146@qq.com>
2023-11-14 17:03:20 +08:00
fit2bot
4e5a44bd98 fix: 账号收集通知 同步资产时 计算新增账号错误 (#12101)
Co-authored-by: feng <1304903146@qq.com>
2023-11-14 14:50:33 +08:00
fit2bot
fcce03f7bd fix: 改密记录搜索失败 (#12098)
Co-authored-by: feng <1304903146@qq.com>
2023-11-14 12:48:02 +08:00
fit2bot
5f121934a7 perf: 交换机切换至卡住 (#12096)
Co-authored-by: feng <1304903146@qq.com>
2023-11-14 10:58:57 +08:00
fit2bot
521c1f0dfa perf: 修改授权动作翻译 (#12095)
Co-authored-by: feng <1304903146@qq.com>
2023-11-14 10:41:00 +08:00
ibuler
5673698a57 perf: 修改账号选择 2023-11-14 10:18:24 +08:00
fit2bot
d6b75ac700 perf: 修改默认 ansible_python_interpreter (#12093)
Co-authored-by: feng <1304903146@qq.com>
2023-11-13 18:09:09 +08:00
fit2bot
0ee14e6d85 perf: 修改翻译 (#12092)
Co-authored-by: feng <1304903146@qq.com>
2023-11-13 17:50:10 +08:00
wangruidong
9babe977d8 fix: 修改sftp账号备份文件名及任务日志提示 2023-11-13 17:05:21 +08:00
fit2bot
0f9223331c perf: 修改 m2m json filter (#12087)
* perf: 修改 m2m json filter

* perf: 修复 json 过滤问题

---------

Co-authored-by: ibuler <ibuler@qq.com>
2023-11-13 15:04:27 +08:00
fit2bot
f8a4a0e108 fix: 修复UserOtpDisableView 视图函数获取模版错误 (#12084)
Co-authored-by: feng <1304903146@qq.com>
2023-11-10 17:59:28 +08:00
ibuler
ba76f30af9 perf: 修改 applet option 2023-11-10 17:18:40 +08:00
Eric_Lee
e5e0c841a2 Revert "perf: 调整 secret 长度为32位"
This reverts commit c41fdf1786.
2023-11-10 15:27:57 +08:00
Eric
c41fdf1786 perf: 调整 secret 长度为32位 2023-11-10 15:03:51 +08:00
fit2bot
69c0eb2f50 fix: 修复平台自动化翻译 (#12078)
Co-authored-by: feng <1304903146@qq.com>
2023-11-09 17:25:32 +08:00
Bryan
e077afe2cc Update README.md 2023-11-09 14:53:49 +08:00
wangruidong
c1f572df05 fix: 【账号备份】创建账号备份存储,选择SFTP,发送服务器为空。修改执行任务的日志提示 2023-11-09 14:22:04 +08:00
fit2bot
d60fe464ca fix:修复es6.8查询不到数据问题 (#12069)
Co-authored-by: feng <1304903146@qq.com>
2023-11-09 14:18:49 +08:00
fit2bot
f47895b8a8 perf: 优化仪表盘查询sftp数量sql (#12075)
Co-authored-by: feng <1304903146@qq.com>
2023-11-09 14:16:41 +08:00
Eric
3eb1583c69 perf: 增加分享权限位 2023-11-08 19:05:51 +08:00
feng
5ab8ff4fde perf: 在线用户根据websocket添加用户是否活跃状态 2023-11-08 17:02:47 +08:00
feng
7746491e19 perf: 在线用户添加是否活跃的属性 2023-11-08 17:02:47 +08:00
Eric
5e54792d94 perf: 优化发布机终端名称 2023-11-08 13:53:24 +08:00
Eric
621c7a31fe fix: 修复发布机名称因含特殊字符部署失败的问题 2023-11-08 13:26:04 +08:00
fit2bot
75bab70ccf fix: 账号迁移文件 (#12059)
Co-authored-by: feng <1304903146@qq.com>
2023-11-08 10:33:49 +08:00
halo
30683ed859 perf: 优化连接信息超长,客户端拉起无响应问题 2023-11-07 15:47:22 +08:00
Bai
7c52cec5fb perf: Upgrade requements jms-storage-sdk==0.0.53 2023-11-07 15:46:48 +08:00
fit2bot
f01bfc44b8 perf: 账号备份增加sftp方式 (#12032)
* perf: 添加sftp支持

* perf: 账号备份增加sftp方式

---------

Co-authored-by: wangruidong <940853815@qq.com>
Co-authored-by: Bryan <jiangjie.bai@fit2cloud.com>
2023-11-07 15:10:46 +08:00
fit2bot
54b89f6fee feat: 账号收集添加资产账号信息变化通知 (#12009)
Co-authored-by: feng <1304903146@qq.com>
2023-11-07 13:00:09 +08:00
Bai
c0de0b0d8e fix: Remove repetition code 2023-11-07 11:30:53 +08:00
huailei
06275a09ac Merge pull request #12042 from jumpserver/pr@dev@ansible
perf: 密码中支持特殊字符比如"
2023-11-06 18:19:34 +08:00
feng
7b86938b58 perf: 密码中支持特殊字符比如" 2023-11-06 17:53:18 +08:00
fit2bot
44624d0ce0 feat: 工作台支持配置显示系统工具 (#12013)
Co-authored-by: halo <wuyihuangw@gmail.com>
2023-11-03 17:33:44 +08:00
wangruidong
9b8c817a16 perf: 修改字段翻译 2023-11-03 10:45:17 +08:00
ibuler
927fe1f128 perf: 修改资产协议 xpack 2023-11-03 10:43:34 +08:00
fit2bot
eee119eba1 feat: 个人设置 rdp smart size可配置 (#12021)
Co-authored-by: feng <1304903146@qq.com>
2023-11-02 18:51:17 +08:00
老广
53d8f716eb Merge pull request #12007 from jumpserver/pr@dev@json_field_support_m2m_all
perf: JSONManyToMany 中的 m2m 方式支持包含所有
2023-11-02 10:35:28 +08:00
吴小白
f48aec2bcb Merge pull request #12011 from jumpserver/pr@dev@perf_tinker_chrome
perf: 更新 chrome 和 chromedriver
2023-11-01 20:34:53 +08:00
吴小白
78e9f51786 perf: 移除旧版本 Chrome 文件 2023-11-01 18:49:38 +08:00
吴小白
af33ad6631 perf: 移除 python3 环境变量 2023-11-01 18:35:10 +08:00
吴小白
864da49ae6 perf: 更新 chrome 和 chromedriver 2023-11-01 18:10:03 +08:00
huailei
e6b8b3982d Merge pull request #12010 from jumpserver/pr@dev@perf_mobile_login
perf: 优化登录页样式
2023-11-01 17:02:28 +08:00
“huailei000”
49b3df218e perf: 优化登录页样式 2023-11-01 17:01:14 +08:00
ibuler
0858d67098 fix: 修改可能迁移的问题 2023-11-01 03:11:47 -05:00
ibuler
ffa242e635 perf: JSONManyToMany 中的 m2m 方式支持包含所有 2023-11-01 15:38:03 +08:00
wangruidong
4021b1955e fix: 组件启动失败 2023-10-31 19:18:35 +08:00
Bryan
204258f058 Update README.md 2023-10-31 18:20:01 +08:00
wangruidong
dc841650cf perf: AKSK添加访问IP控制 2023-10-31 02:43:33 -05:00
feng
bc54685a31 feat: 改密记录 推送记录可单独执行 2023-10-31 00:57:47 -05:00
ibuler
ee586954f8 feat: 发布机支持使用同名账号连接 2023-10-31 10:18:30 +08:00
ibuler
e56a37afd2 fix: 优化选择发布机 2023-10-30 16:07:02 +08:00
老广
7669744312 Merge pull request #11981 from jumpserver/pr@dev@feat_perm_add_protocols
perf: 资产授权添加协议
2023-10-30 10:12:45 +08:00
ibuler
ad8aba88a3 perf: 资产授权添加协议 2023-10-30 10:11:36 +08:00
wangruidong
7659846df4 perf: 兼容SERVER_NAME值多种情况 2023-10-27 16:45:42 +08:00
ibuler
f93979eb2d perf: 资产授权添加协议 2023-10-27 16:15:59 +08:00
fit2bot
badf83c560 perf: 命令存储为本地数据库时 搜索资产时支持模糊搜索 (#11978)
Co-authored-by: feng <1304903146@qq.com>
2023-10-26 17:10:27 +08:00
halo
f6466a3a20 fix: 修复DB2平台已经存在的问题 2023-10-26 01:25:47 -05:00
ibuler
996394ba29 perf: 优化 profile field 2023-10-25 05:09:15 -05:00
fit2bot
09f8470d34 fix: 改密校验可连接性失败 (#11964)
Co-authored-by: feng <1304903146@qq.com>
2023-10-25 16:21:45 +08:00
Bai
fdb3f6409c fix: 修复登录日志和在线用户会话的 IP 地址获取方式 2023-10-25 01:40:16 -05:00
ibuler
73b0b23910 perf: 修改rsa key 默认长度 2023-10-25 10:05:58 +08:00
ibuler
c1185e989a perf: 修复资产类型的 bug 2023-10-24 16:19:08 +08:00
fit2bot
1239082649 fix: change secret perm 没有生成 (#11948)
Co-authored-by: feng <1304903146@qq.com>
2023-10-24 14:07:07 +08:00
fit2bot
ff073185f1 fix: 改密切换至检测可连接性 失败 (#11946)
Co-authored-by: feng <1304903146@qq.com>
2023-10-24 11:30:26 +08:00
老广
d7a682b462 Merge pull request #11945 from jumpserver/pr@dev@perf_oauth2_access_token_content_type
perf: 优化OAuth2.0获取Access_token的content_type
2023-10-24 11:29:10 +08:00
Eric_Lee
4df2bdd9b6 Merge pull request #11944 from jumpserver/pr@dev@upgrade_tinker_python
perf: 更新 tinker python 版本
2023-10-24 10:39:36 +08:00
吴小白
2437072768 perf: 清理旧版本 chromedriver PATH 2023-10-24 10:29:14 +08:00
jiangweidong
08a2d96213 perf: 优化OAuth2.0获取Access_token的content_type 2023-10-24 10:26:38 +08:00
吴小白
de7d7b41c0 perf: 更新 tinker python 版本 2023-10-24 08:46:17 +08:00
jiangweidong
b04c7f022f perf: 使用scan命令扫描在线用户 2023-10-23 04:34:12 -05:00
feng
bf0d9f4b80 fix: 删除错误的改密权限 2023-10-23 04:32:00 -05:00
wangruidong
314257f790 perf: 作业中心执行历史增加保留天数配置 2023-10-23 04:13:35 -05:00
ibuler
6d2a62e413 fix: 优化替换 DOMAINS 中端口 的问题 2023-10-22 22:32:04 -05:00
老广
1734ddc2bd Merge pull request #11926 from jumpserver/pr@dev@database_list
fix: 资产数据库 不分页时list接口错误
2023-10-20 03:51:07 -05:00
feng
7c796e8201 fix: 资产数据库 不分页时list接口错误 2023-10-20 16:35:39 +08:00
老广
62a74418ea Merge pull request #11852 from jumpserver/pr@dev@perf_core
perf: 按照需求添加 core-ce 镜像
2023-10-19 21:35:23 -05:00
fit2bot
32461078fe perf: ticket 迁移文件 (#11920)
Co-authored-by: feng <1304903146@qq.com>
2023-10-19 20:00:47 +08:00
Bai
939b517e34 fix: 修复账号改密密码规则提交不生效的问题 2023-10-19 04:30:49 -05:00
jiangweidong
66eac762ff fix: 可以清空云同步中的策略 2023-10-19 03:57:00 -05:00
Bryan
ce24c1c3fd Merge pull request #11914 from jumpserver/dev
v3.8.0
2023-10-19 03:37:39 -05:00
fit2bot
db9ee71ab3 perf: 翻译 (#11913)
Co-authored-by: feng <1304903146@qq.com>
2023-10-19 16:24:25 +08:00
fit2bot
db2331521d fix: 修复工单复合通知无账号信息 (#11912)
Co-authored-by: feng <1304903146@qq.com>
2023-10-19 15:56:14 +08:00
fit2bot
4aa4c6854b perf: 更新ops ticket announcement settings 权限 (#11911)
Co-authored-by: feng <1304903146@qq.com>
2023-10-19 15:15:02 +08:00
fit2bot
26a18a1f5c perf: 批量创建资产 账号格式错误提醒 (#11909)
Co-authored-by: feng <1304903146@qq.com>
2023-10-19 13:44:24 +08:00
fit2bot
6870df6d75 fix: cas ldap 登录失败 (#11908)
Co-authored-by: feng <1304903146@qq.com>
2023-10-19 12:24:56 +08:00
jiangweidong
03d1a187df perf: 工单直接审批者访问链接无认证跳转到登录页面 (#11902)
* perf: 工单直接审批者访问链接无认证跳转到登录页面

* perf: 修改重定向登录地址

* perf: 跳转字段标识更新
2023-10-18 22:21:08 -05:00
Eric_Lee
ca0dca26c7 Merge pull request #11900 from jumpserver/pr@dev@perf_task
perf: 完善僵尸会话清理
2023-10-18 19:17:04 +08:00
Eric
25a1989157 perf: 完善僵尸会话清理 2023-10-18 19:13:42 +08:00
ibuler
fef26c38fe perf: 去掉创建记录报错 2023-10-18 06:06:50 -05:00
fit2bot
a2fcc47436 fix: cas oidc 登录失败 (#11899)
Co-authored-by: feng <1304903146@qq.com>
2023-10-18 18:51:28 +08:00
fit2bot
00450121bc perf: 命令组加命令过滤搜索 (#11898)
Co-authored-by: feng <1304903146@qq.com>
2023-10-18 18:46:49 +08:00
ibuler
bdd885069f perf: 优化登录时创建 activity 的问题 2023-10-18 04:10:20 -05:00
老广
25d0c021e1 Merge pull request #11894 from jumpserver/pr@dev@perf_window_default_verify_account_by_rdp
perf: 修改windows校验账号的默认方式
2023-10-18 03:46:08 -05:00
ibuler
095c23ea4f perf: 修改windows校验账号的默认方式 2023-10-18 16:44:38 +08:00
fit2bot
3c3c112b07 perf: 更新appletpublication applethostdeployment 权限位 (#11893)
Co-authored-by: feng <1304903146@qq.com>
2023-10-18 16:35:08 +08:00
老广
d95a44fe44 Merge pull request #11892 from jumpserver/pr@dev@perf_bind_wecom_logout
perf: 企业微信绑定后退出
2023-10-18 03:10:41 -05:00
ibuler
e713bdab0b perf: 企业微信绑定后退出 2023-10-18 16:09:17 +08:00
fit2bot
78f1b2b002 perf: user session 表去掉过期时间字段 (#11890)
Co-authored-by: feng <1304903146@qq.com>
2023-10-18 16:04:02 +08:00
fit2bot
e0762573ae perf: 在线用户动态过期时间 (#11889)
Co-authored-by: feng <1304903146@qq.com>
2023-10-18 15:50:38 +08:00
老广
16e8c7faba Merge pull request #11888 from jumpserver/pr@dev@perf_protocols_lose
perf: 修复协议丢失的问题
2023-10-18 02:42:12 -05:00
ibuler
9b019e45ae perf: 修复协议丢失的问题 2023-10-18 15:40:52 +08:00
fit2bot
71d70501d6 perf: 优化数据库必填 (#11887)
Co-authored-by: ibuler <ibuler@qq.com>
2023-10-18 02:22:17 -05:00
老广
5cd44ebfce Merge pull request #11865 from jumpserver/pr@dev@fix_corntab
fix: 修复crontab语义
2023-10-18 01:13:15 -05:00
老广
03c27ab5b8 Merge pull request #11875 from jumpserver/pr@dev@perf_update_clients_version
perf: 更新clients版本
2023-10-18 01:12:36 -05:00
fit2bot
d3a283232f perf: 优化 xpack license 检查 (#11885)
Co-authored-by: ibuler <ibuler@qq.com>
2023-10-18 01:10:24 -05:00
fit2bot
f088bbce12 perf: 连接方式,动作去掉: 通知 (#11878)
Co-authored-by: feng <1304903146@qq.com>
2023-10-17 19:21:59 +08:00
fit2bot
b313598227 fix: 修复账号批量添加模版账号时name没同步过来,资产创建时使用模版账号没有切换自,资产克隆时生成的账号没有切换自 (#11877)
Co-authored-by: feng <1304903146@qq.com>
2023-10-17 19:15:46 +08:00
halo
3a118b6753 perf: 更新clients版本 2023-10-17 17:13:30 +08:00
Eric_Lee
578c2af57c Merge pull request #11870 from jumpserver/pr@dev@perf_acl_ip
perf: 增加针对 ip 的获取
2023-10-17 16:25:24 +08:00
Eric
b5ef239c6c perf: 增加针对 ip 的获取 2023-10-17 15:57:15 +08:00
fit2bot
e88e4438ba fix: acl 记录操作日志 账号信息为空 (#11869)
Co-authored-by: feng <1304903146@qq.com>
2023-10-17 15:01:21 +08:00
fit2bot
73b75df524 perf: 资产acl拒绝后没记录操作日志 (#11868)
Co-authored-by: feng <1304903146@qq.com>
2023-10-17 14:28:19 +08:00
Bai
772684d24c fix: 修复crontab语义 2023-10-17 11:19:13 +08:00
fit2bot
741705b85b perf: 修改定期清理日志默认时间180天 (#11864)
Co-authored-by: feng <1304903146@qq.com>
2023-10-17 11:11:55 +08:00
fit2bot
f5176bcc6f perf: 修改迁移文件 (#11863)
Co-authored-by: feng <1304903146@qq.com>
2023-10-17 11:04:05 +08:00
fit2bot
c917d8f346 fix: 系统设置 安全设置权限为错误 (#11860)
Co-authored-by: feng <1304903146@qq.com>
2023-10-16 19:33:13 +08:00
fit2bot
5c0905b3b5 fix: 操作日志全局组织数量不对 (#11859)
Co-authored-by: feng <1304903146@qq.com>
2023-10-16 18:51:26 +08:00
fit2bot
bda23b3d2a fix: 调API创建与父节点同名的子节点报错:同级别节点名称不能重复 (#11858)
Co-authored-by: feng <1304903146@qq.com>
2023-10-16 18:15:53 +08:00
fit2bot
8b6526211c perf: 工单动作添加操作日志 (#11857)
Co-authored-by: feng <1304903146@qq.com>
2023-10-16 16:40:21 +08:00
fit2bot
86e8f3a80b fix: 组织管理员不能创建用户 (#11856)
Co-authored-by: feng <1304903146@qq.com>
2023-10-16 15:06:47 +08:00
fit2bot
70661242c1 fix: 在线用户 下线权限错误 导致审计员无权限下线用户 (#11853)
Co-authored-by: feng <1304903146@qq.com>
2023-10-16 14:22:20 +08:00
吴小白
6f4082f800 fix: 修正 actions 测试构建任务 2023-10-16 14:00:40 +08:00
吴小白
edd65f965b perf: 按照需求添加 core-ce 镜像 2023-10-16 13:30:51 +08:00
fit2bot
7dcae1e05a perf: 命令过滤中 去掉通知动作 (#11851)
Co-authored-by: feng <1304903146@qq.com>
2023-10-16 13:27:37 +08:00
fit2bot
0a28c5650c perf: 三方用户登录通知 (#11846)
Co-authored-by: feng <1304903146@qq.com>
2023-10-16 11:28:53 +08:00
fit2bot
f55c84ce3b fix: 发布机500 (#11841)
Co-authored-by: feng <1304903146@qq.com>
2023-10-13 17:19:05 +08:00
fit2bot
ac11790192 perf: 替换iphone mfa 二维码图片 (#11839)
Co-authored-by: feng <1304903146@qq.com>
2023-10-13 16:51:34 +08:00
老广
f80ff279d0 perf: 用户确认和access key
Merge branch 'dev' of github.com:jumpserver/jumpserver into dev
2023-10-13 16:37:45 +08:00
ibuler
d7ac08f6d9 perf: 去掉 debug 2023-10-13 16:36:23 +08:00
ibuler
b5714f7e14 Merge branch 'pr@dev@perf_user_confirm' into pr@dev@perf_change_access_key_create 2023-10-13 16:34:19 +08:00
ibuler
d6b450f32a perf: 修改 ak 2023-10-13 16:33:25 +08:00
ibuler
1daf1acaf3 perf: 修改 access key 2023-10-13 16:31:05 +08:00
fit2bot
ea0e852412 fix: rdp 测试可连接性失败 (#11837)
Co-authored-by: feng <1304903146@qq.com>
2023-10-13 16:21:20 +08:00
ibuler
ce976f215f Merge branch 'dev' of github.com:jumpserver/jumpserver into dev 2023-10-13 16:10:40 +08:00
fit2bot
ffc057f844 fix: 账号改密-执行列表-详情-任务记录:模糊搜索500 (#11835)
Co-authored-by: feng <1304903146@qq.com>
2023-10-13 15:33:35 +08:00
fit2bot
588723a76c perf: 优化资产登录通知信息 (#11834)
Co-authored-by: feng <1304903146@qq.com>
2023-10-13 15:08:56 +08:00
ibuler
1ca912373f perf: 修改用户确认 2023-10-13 14:59:58 +08:00
ibuler
452ee1224c perf: 修改用户确认 2023-10-13 14:40:40 +08:00
fit2bot
7eb497f9d3 fix: 资产登录被限制,没有记录到当前组织的操作日志,而是记录到全局组织 (#11827)
Co-authored-by: feng <1304903146@qq.com>
2023-10-12 20:13:21 +08:00
fit2bot
58fd578ddd perf: 资产登录提示添加账号信息 (#11826)
Co-authored-by: feng <1304903146@qq.com>
2023-10-12 20:04:28 +08:00
fit2bot
e1278360af fix: 资产创建失败 (#11824)
Co-authored-by: feng <1304903146@qq.com>
2023-10-12 19:46:04 +08:00
fit2bot
c0de27ff7a perf: 资产批量更新平台字段,根据平台约束协议自动生效 (#11818)
Co-authored-by: feng <1304903146@qq.com>
2023-10-12 18:11:51 +08:00
ibuler
116d0ba5c6 perf: 优化任务记录 activity 2023-10-12 17:06:12 +08:00
fit2bot
9f042cfa04 Merge branch 'dev' into pr@dev@change_import (#11815)
* perf: 修改获取 ip

* perf: 修改导入

---------

Co-authored-by: ibuler <ibuler@qq.com>
2023-10-12 03:17:32 -05:00
Eric_Lee
ce63ea7528 Merge pull request #11812 from jumpserver/pr@dev@fix_get_real_ip
perf: 修改获取 ip
2023-10-12 16:13:57 +08:00
ibuler
8b3fd2c117 perf: 修改获取 ip 2023-10-12 16:09:22 +08:00
ibuler
23ccd6df8c perf: mysql mariadb 数据库不再必填 2023-10-12 14:50:09 +08:00
Bai
614e019f14 fix: 修改迁移文件choices和翻译文件 2023-10-11 21:15:47 -05:00
ibuler
38aa828eb8 perf: passkey 只允许本地用户开启 2023-10-11 04:52:54 -05:00
Bai
7cd2736e82 perf: 优化用户传递的 phone 处理 2023-10-11 04:50:45 -05:00
ibuler
443f6d25e8 perf: Windows 默认使用 rdping 测试 2023-10-11 04:22:08 -05:00
Eric
e8652af054 perf: 更新格式 2023-10-11 04:21:00 -05:00
Eric
fd6a8dd807 perf: 增加错误类型 2023-10-11 04:21:00 -05:00
Eric
499eedd83e perf: 会话新增 error_reason 字段 2023-10-11 04:21:00 -05:00
feng
ca7d164034 perf: 账号模版信息同步到所关联的账号 2023-10-11 04:20:11 -05:00
Bai
3ef8e9603a perf: 优化 otp windows 最小支持设置 0 2023-10-11 04:06:17 -05:00
Bai
09f71d80eb perf: 优化LDAP用户导入列表时任务状态设置 2023-10-11 04:05:51 -05:00
jiangweidong
73db1bf50c feat: 支持LDAP用户组变更时,JS同步变更 2023-10-10 06:37:28 -05:00
fit2bot
6017f804a6 perf: 用户 phone wechat 加密 (#11789)
Co-authored-by: feng <1304903146@qq.com>
2023-10-10 19:11:08 +08:00
ibuler
affa562384 perf: 优化禁用用户 2023-10-10 19:00:00 +08:00
fit2bot
0d101bc5ad perf: 不活跃了用户默认90天检测一下 (#11790)
Co-authored-by: feng <1304903146@qq.com>
2023-10-10 18:48:35 +08:00
jiangweidong
70f0f55ddb feat: 支持自定义短信认证(文件) (#11784)
* feat: 支持自定义短信认证(文件)

* perf: 翻译

* perf: 还原注释
2023-10-10 05:23:54 -05:00
fit2bot
333746e7c4 perf: 优化用户 access key 的使用和创建 (#11776)
* perf: 优化用户 access key 的使用和创建

* perf: 优化 access key api

---------

Co-authored-by: ibuler <ibuler@qq.com>
2023-10-10 04:52:52 -05:00
fit2bot
30b19d31eb fix: 账号批量更新失败 (#11785)
Co-authored-by: feng <1304903146@qq.com>
2023-10-10 17:24:56 +08:00
Eric
a844ce23e4 perf: 调整格式 2023-10-10 07:40:48 +05:00
Eric
d6c0139fef perf: 支持持久化设置个人终端主题 2023-10-10 07:40:48 +05:00
jiangweidong
11157563ba perf: 优化跳转接口参数 2023-10-09 16:00:36 +05:00
jiangweidong
95e7bde5d7 perf: 优化翻译 2023-10-09 16:00:36 +05:00
jiangweidong
814350ab80 perf: 翻译 2023-10-09 16:00:36 +05:00
jiangweidong
3ac35eec68 perf: 优化OIDC用户未激活时,会循环跳转登录页面 2023-10-09 16:00:36 +05:00
fit2bot
3d27986c96 perf: asset login 消息通知添加操作日志记录 (#11774)
Co-authored-by: feng <1304903146@qq.com>
2023-10-09 17:16:38 +08:00
fit2bot
c981e9cd9f perf: 主机名包含/ 可以执行ansible任务 (#11772)
Co-authored-by: feng <1304903146@qq.com>
2023-10-09 16:05:42 +08:00
Bai
e00c804a5a perf: 优化校验登录城市名1 2023-10-09 14:43:48 +08:00
fit2bot
ef2b7b464e perf: ansible 用户切换至 (#11766)
Co-authored-by: feng <1304903146@qq.com>
2023-10-09 14:35:21 +08:00
feng
ae5d4257ad fix: 修复账号批量更新失败问题 2023-10-09 10:05:50 +08:00
halo
b42014d58e feat: 支持DB2数据库 2023-10-09 10:04:14 +08:00
feng
e71e8cd595 perf: 优化清除日志天数系统配置校验逻辑 2023-10-08 07:39:19 +05:00
fit2bot
dd50044b89 perf: 翻译 (#11748)
Co-authored-by: feng <1304903146@qq.com>
2023-10-07 17:58:42 +08:00
fit2bot
68707085fa perf: 日志保存时间不少于6个月 (#11742)
Co-authored-by: feng <1304903146@qq.com>
2023-10-07 17:41:45 +08:00
fit2bot
60399fae29 feat: 登录资产消息提醒 (#11747)
Co-authored-by: feng <1304903146@qq.com>
2023-10-07 17:41:20 +08:00
ibuler
f206d963a0 perf: 优化会话 api 2023-10-07 15:57:15 +08:00
fit2bot
42b4e7697d feat: 资产登录acl动作增加操作日志 (#11741)
Co-authored-by: feng <1304903146@qq.com>
2023-10-07 15:50:28 +08:00
fit2bot
0c1f4d99f8 fix: 修复工单引入html错误问题 (#11744)
Co-authored-by: feng <1304903146@qq.com>
2023-10-07 15:28:40 +08:00
ibuler
2aed3fcaea perf: 修改随机数生成,避免使用 random 库 2023-10-07 11:30:45 +05:00
ibuler
28196573bb perf: 修改随机使用secrets 2023-10-07 11:30:45 +05:00
ibuler
27c505853b perf: 优化忘记密码 2023-10-07 11:18:55 +05:00
jiangweidong
896d42c53e perf: 更新jms-storage版本 2023-09-28 18:08:39 +05:00
feng
f79084c2df fix: 账号授权过滤指定账号api 失效问题 2023-09-27 13:11:48 +05:00
ibuler
15a5dda9e0 perf: 修改默认的邮箱地址 2023-09-27 15:15:15 +08:00
ibuler
2069fee795 perf: 优化发送邮件 2023-09-27 08:27:12 +05:00
feng
56a26481a4 perf: 账号模版 生成随机密码密钥及账号批量更新500 2023-09-26 12:55:54 +08:00
ibuler
cbe3d66b39 fix: pubkey auth require svc sign 2023-09-25 23:29:42 +08:00
Bryan
7c67d882aa Revert "fix: pubkey auth require svc sign"
This reverts commit 9bde2ff6e1.
2023-09-25 23:24:52 +08:00
ibuler
9bde2ff6e1 fix: pubkey auth require svc sign 2023-09-25 23:08:55 +08:00
Bai
1f00c00183 fix: 修复验证码校验逻辑和报错信息 2023-09-25 23:03:32 +08:00
ibuler
c369b5478c fix: 修复暴力校验验证码 2023-09-25 22:06:57 +08:00
fit2bot
10363dcc5b fix: 修复用户username 中文 登录失败问题 (#11692)
Co-authored-by: feng <1304903146@qq.com>
2023-09-25 21:39:16 +08:00
jiangweidong
42bdb2cf14 perf: 优化找回密码时区号带加号无法匹配的问题 2023-09-25 16:42:30 +08:00
fit2bot
d64e77db30 perf: 去掉print (#11687)
Co-authored-by: feng <1304903146@qq.com>
2023-09-25 16:37:00 +08:00
fit2bot
4065baf785 feat: 用户登录堡垒机时通知管理员 (#11686)
Co-authored-by: feng <1304903146@qq.com>
2023-09-25 16:25:44 +08:00
Bai
0f3ddc3bf1 fix: 修复系统用户同步同时包含pwd/ssh-key导致创建账号id冲突报错的问题 2023-09-25 16:22:47 +08:00
吴小白
138adeff76 perf: 添加 ping 命令 2023-09-25 10:50:53 +08:00
ibuler
0cf17310e1 fix: 修复 DOMAINS 添加 80和443 不生效的问题 2023-09-22 17:47:43 +08:00
吴小白
43dbb4c226 perf: 添加 patch 命令 2023-09-22 15:20:49 +08:00
Bai
cefd9f4ab2 fix: 解决节点资产数量方法计算不准确的问题 2023-09-22 15:18:22 +08:00
fit2bot
7128593502 perf: CeleryTaskExecution 添加默认排序 (#11663)
Co-authored-by: feng <1304903146@qq.com>
2023-09-22 15:06:58 +08:00
maninhill
5d4fa22058 chore: 优化 README 文案 2023-09-22 10:24:21 +08:00
Bryan
3c54c82ce9 Merge pull request #11636 from jumpserver/dev
v3.7.0
2023-09-21 17:02:48 +08:00
fit2bot
91dce82b38 fix: 安全设置开启仅已存在用户登录,企业微信等扫描登录,如果用户不存在,还是会自动创建用户登录成功。 (#11651)
Co-authored-by: feng <1304903146@qq.com>
2023-09-21 17:01:03 +08:00
Bryan
d102db7a7b Merge pull request #11650 from jumpserver/pr@dev@dev_master
fix: 解决 master 冲突
2023-09-21 16:53:09 +08:00
Bai
1de7af4984 fix: 解决 master 冲突 2023-09-21 16:51:54 +08:00
Aaron3S
9892ff7dd6 feat: 代码片段支持 oracle 和 mariadb 2023-09-21 16:37:16 +08:00
老广
4cb499953c Revert "perf: 修复事务中任务执行"
This reverts commit cdbe5d31e9.
2023-09-21 15:40:39 +08:00
老广
0397bdeb46 Revert "perf: 修复 task id 不对的问题"
This reverts commit 1d6d92c160.
2023-09-21 15:39:29 +08:00
ibuler
1d6d92c160 perf: 修复 task id 不对的问题 2023-09-21 15:20:16 +08:00
ibuler
cdbe5d31e9 perf: 修复事务中任务执行 2023-09-21 15:04:58 +08:00
fit2bot
b023ca0c69 fix: saml 用户没现在记录 (#11641)
Co-authored-by: feng <1304903146@qq.com>
2023-09-21 14:02:09 +08:00
ibuler
803d590096 perf: 修改生成 applet accounts 2023-09-21 13:06:02 +08:00
ibuler
e11367088a perf: 修改 acl 登录限制问题 2023-09-21 11:33:28 +08:00
jiangweidong
1c74dd00ba fix: 解决sqlserver无法推送和改密的问题 (#11637) 2023-09-20 21:45:21 +08:00
Aaron3S
ed832af631 fix: 修复运行job 组织切换问题 2023-09-20 18:26:47 +08:00
fit2bot
948c499d9e fix: 修复仪表板图表时间范围不准 (#11633)
Co-authored-by: feng <1304903146@qq.com>
2023-09-20 17:41:35 +08:00
fit2bot
a51549cf1c perf: ansible任务 未激活的时候关闭定时任务 (#11631)
Co-authored-by: feng <1304903146@qq.com>
2023-09-20 15:30:29 +08:00
fit2bot
39baf88055 fix: ansible postgresql (#11629)
Co-authored-by: feng <1304903146@qq.com>
2023-09-20 14:29:53 +08:00
fit2bot
90131db55a perf: 修改任务检查 (#11609)
* perf: 修改任务检查

* perf: 修改翻译

---------

Co-authored-by: ibuler <ibuler@qq.com>
2023-09-20 11:12:15 +08:00
“huailei000”
ea3ff1ebcb perf: 优化登录页面移动端布局 2023-09-19 20:20:28 +08:00
Aaron3S
f3ca45aa74 perf: 优化 Playbook 文件创建逻辑 2023-09-19 18:49:16 +08:00
老广
74cc174d7a Merge pull request #11622 from jumpserver/pr@dev@perf_random_error
fix: 修复 random error
2023-09-19 18:15:50 +08:00
ibuler
0eba6d2175 fix: 修复 random error 2023-09-19 18:11:27 +08:00
fit2bot
58592a13e3 fix: 解锁ip失败问题 (#11611)
Co-authored-by: feng <1304903146@qq.com>
2023-09-19 17:38:46 +08:00
fit2bot
b8fb23a0a0 perf: user setting (#11610)
Co-authored-by: feng <1304903146@qq.com>
2023-09-19 16:30:48 +08:00
Bai
f5c43488fd perf: 优化 es host 中包含 # 字符时提示错误 2023-09-19 15:31:02 +08:00
Eric
19c76ba01c perf: 删除发布机执行的任务目录 2023-09-19 15:02:02 +08:00
Eric
68c4cd5928 perf: 修复发布机安装应用的报错 2023-09-19 15:01:37 +08:00
fit2bot
e5bfa29c7b fix: 创建用户推送失败问题 (#11606)
Co-authored-by: feng <1304903146@qq.com>
2023-09-19 14:53:43 +08:00
fit2bot
cbb772def7 fix: 修复connection token 获取user错误 (#11603)
Co-authored-by: feng <1304903146@qq.com>
2023-09-19 11:09:58 +08:00
fit2bot
e6fe7c489e perf: 修改账号生成 (#11591)
* perf: 修改账号生成

* perf: 修改账号模版支持策略

* perf: 修改特殊字符数量

* perf: 修改 model 继承

* perf: 修改顺序

* perf: 修改 requirements

* perf: 修改翻译

* perf: 修改随机生成密码

* perf: 修改密钥生成

* perf: 修复 bug

---------

Co-authored-by: ibuler <ibuler@qq.com>
2023-09-19 10:59:33 +08:00
fit2bot
0b30f5cf88 perf: 翻译 (#11602)
Co-authored-by: feng <1304903146@qq.com>
2023-09-19 10:36:03 +08:00
fit2bot
018f1a0e8d perf: 删除管理用户错误提醒 (#11596)
Co-authored-by: feng <1304903146@qq.com>
2023-09-18 18:42:02 +08:00
fit2bot
24ed57b98a fix: 三方登录用户无法下线 (#11592)
Co-authored-by: feng <1304903146@qq.com>
2023-09-18 16:20:55 +08:00
ibuler
04a790c4ee perf: 优化 account template platform required 2023-09-18 14:29:41 +08:00
ibuler
2d9a3ef7d4 perf: 修改 migrations,不生成新的迁移文件 2023-09-18 14:29:24 +08:00
ibuler
0d2adeccf2 perf: 优化 applet account delete 2023-09-18 14:18:24 +08:00
Eric
886f977311 perf: 修复 chrome 部分元素定位失败的问题 2023-09-18 14:10:24 +08:00
fit2bot
9367e79bcf perf: 翻译 (#11583)
Co-authored-by: feng <1304903146@qq.com>
2023-09-18 11:11:12 +08:00
fit2bot
af733ecbad fix: 修改平台id序列化属性 改为非只读 (#11581)
Co-authored-by: feng <1304903146@qq.com>
2023-09-17 16:07:05 +08:00
fit2bot
09f9775eab fix: 平台无category type 过滤 (#11580)
Co-authored-by: feng <1304903146@qq.com>
2023-09-17 12:50:31 +08:00
feng
1c2a362beb perf: 修改usersession 模块位置 2023-09-15 17:25:06 +08:00
Eric_Lee
bb1e674367 Merge pull request #11578 from jumpserver/pr@dev@perf_host_deploy_log
perf: 修复发布机历史执行任务日志无法查看的问题
2023-09-15 17:18:45 +08:00
Eric
a75677ab08 perf: 修复发布机历史执行任务日志无法查看的问题 2023-09-15 17:15:12 +08:00
fit2bot
b1daa4d357 fix: 修改不常登录用户锁定逻辑 (#11576)
Co-authored-by: feng <1304903146@qq.com>
2023-09-15 16:39:49 +08:00
fit2bot
c32271ec6f fix: mysql 没配置ssl ansible 连接失败问题 (#11574)
Co-authored-by: feng <1304903146@qq.com>
2023-09-15 16:16:04 +08:00
Aaron3S
beb4f14be9 perf: 优化 jobexecution 创建 2023-09-15 14:39:16 +08:00
fit2bot
e719904874 fix: 修复工单回复报500 (#11571)
Co-authored-by: feng <1304903146@qq.com>
2023-09-15 11:26:13 +08:00
Eric_Lee
664bc2a4d9 Merge pull request #11568 from jumpserver/pr@dev@perf_deplay_task
perf: 优化推送部署任务,事务提交后再执行
2023-09-14 18:30:16 +08:00
ibuler
b91db8c146 perf: 优化推送部署任务,事务提交后再执行 2023-09-14 18:17:22 +08:00
fit2bot
500aeeb77f perf: 升级flower (#11567)
Co-authored-by: feng <1304903146@qq.com>
2023-09-14 18:15:25 +08:00
feng
3abc8bddfa feat: 用户在线session控制 2023-09-14 16:21:57 +08:00
老广
5cbbf9e737 Merge pull request #11561 from jumpserver/pr@dev@perf_i18n
perf: 优化翻译
2023-09-14 14:30:53 +08:00
ibuler
7204a86f87 perf: 优化翻译 2023-09-14 14:26:17 +08:00
老广
829194420a Merge pull request #11559 from jumpserver/pr@dev@limit_super_privilege
feat: 限制超级权限
2023-09-14 13:54:20 +08:00
老广
61dc95d9ae Merge pull request #11560 from jumpserver/pr@dev@perf_i18n
perf: 优化翻译
2023-09-14 11:29:28 +08:00
ibuler
a9f60a9117 perf: 优化翻译 2023-09-14 11:26:12 +08:00
ibuler
82f96d6ed2 feat: 限制超级权限 2023-09-14 10:42:16 +08:00
feng
f6c56d4979 perf: 网络设备 ansible enables true 2023-09-13 19:29:01 +08:00
老广
54d0a1b871 Merge pull request #11554 from jumpserver/pr@dev@perf_add_tip
perf: 添加tips
2023-09-13 17:44:45 +08:00
老广
5b4a267ccd Merge pull request #11553 from jumpserver/pr@dev@feat_support_ansbile_raw
feat: 作业中心支持 raw (网络设备使用)
2023-09-13 17:44:10 +08:00
ibuler
a6d78834e7 perf: 添加tips 2023-09-13 17:43:29 +08:00
Aaron3S
07da98e438 feat: 作业中心支持 raw (网络设备使用) 2023-09-13 17:25:42 +08:00
老广
7c973616cd Merge pull request #11552 from jumpserver/pr@dev@add_api_check_for_unauth
perf: 添加 check api,检测所有 api
2023-09-13 17:24:40 +08:00
ibuler
b9997b07db perf: 去掉不用的 backend 2023-09-13 17:22:50 +08:00
ibuler
bcda879f3b perf: 修改 ticket 认证的 2023-09-13 17:19:13 +08:00
ibuler
d0f79c2df2 perf: 添加 check api 避免未认证 2023-09-13 17:05:01 +08:00
ibuler
1249935bab perf: 优化设置项名称 2023-09-13 10:09:56 +08:00
ibuler
5fa1ae9ee5 perf: 修改说明 2023-09-12 15:59:25 +08:00
Bai
d0755c4719 fix: 修复系统任务支持通过 id、name 进行搜索 2023-09-12 15:35:11 +08:00
fit2bot
72b215ed03 feat: 支持 passkey 登录 (#11519)
* perf: 基本完成功能

* perf: 优化 passkey

* perf: 优化 passkey

* perf: 完成 passkey

---------

Co-authored-by: ibuler <ibuler@qq.com>
2023-09-11 18:15:03 +08:00
fit2bot
d7ca1a09d4 perf: connectiontoken 添加 connect_options file_name_conflict_resolution参数 给koko处理冲冲突文件名 (#11535)
Co-authored-by: feng <1304903146@qq.com>
2023-09-11 16:22:11 +08:00
fit2bot
04e341a1bb perf: 翻译 (#11534)
Co-authored-by: feng <1304903146@qq.com>
2023-09-11 15:26:50 +08:00
fit2bot
a41909ec8d feat: 个人设置 (#11494)
Co-authored-by: feng <1304903146@qq.com>
2023-09-11 14:38:07 +08:00
ibuler
f9d6de9c39 fix: 修复 private storage permission 2023-09-11 11:20:12 +08:00
halo
816b284a51 perf: 支持windows客户端msi格式 2023-09-11 11:15:17 +08:00
Eric
d4c5dcf069 perf: 修改变更时间 2023-09-07 19:30:59 +08:00
Eric
73037c21e8 perf: chrome 代填进度条最大 30s 超时 2023-09-07 19:30:59 +08:00
halo
c7f9259a2e perf: 更新客户端 v2.0.1 2023-09-07 19:30:25 +08:00
feng
8632bd2480 fix: 修复ip被锁定列表展示数据不准问题 2023-09-07 19:28:11 +08:00
ibuler
23723f4eda perf: 优化 ftp log 索引 2023-09-07 19:27:49 +08:00
Bai
38601a84c2 perf: 优化 GitHub Labels 2023-09-06 16:42:42 +08:00
fit2bot
e50189e284 fix: 修复工单审计员切换其他资产,原资产未删除问题 (#11511)
Co-authored-by: feng <1304903146@qq.com>
2023-09-06 15:13:02 +08:00
jiangweidong
da9bd11db5 feat: 系统工具支持traceroute (#11474) 2023-09-06 10:30:55 +08:00
Bai
9acb7d6183 perf: 优化 GitHub 默认 Assignees 2023-09-04 14:43:18 +05:00
Bai
dbd9a9fdac perf: 优化 GitHub 默认 Assignees 2023-09-04 12:03:16 +05:00
fit2bot
25301aa396 perf: 修改 sftp 的说明文案 (#11490)
* perf: 修改 sftp 的说明文案

* perf: 修改翻译问题

---------

Co-authored-by: ibuler <ibuler@qq.com>
2023-09-04 13:48:53 +08:00
老广
8cc1ca2770 Merge pull request #11483 from jumpserver/pr@dev@perf_db_cli
perf: 修改数据库 cli 连接方式的支持
2023-08-31 19:03:20 +08:00
Eric
bad01aefa2 perf: 修改数据库 cli 连接方式的支持 2023-08-31 18:23:30 +08:00
老广
56a989bfb9 Merge pull request #11481 from jumpserver/pr@dev@perf_online_num
perf: 修改在线数量
2023-08-31 17:44:25 +08:00
fit2bot
578f66d5e2 fix: 账号推送定时任务不执行 (#11482)
Co-authored-by: feng <1304903146@qq.com>
2023-08-31 17:43:52 +08:00
ibuler
8d6083bfb2 perf: 修改在线数量 2023-08-31 17:42:21 +08:00
fit2bot
1138cd3334 perf: 添加 session 在线数量 (#11464)
* perf: 添加 session 在线数量

* perf: 优化会话数量

* perf: 优化会话数量

---------

Co-authored-by: ibuler <ibuler@qq.com>
2023-08-31 17:00:03 +08:00
fit2bot
db0b43ee84 perf: 优化 dashboard api (#11478)
Co-authored-by: feng <1304903146@qq.com>
2023-08-31 15:05:05 +08:00
Bai
40a460870a fix: 优化 db_port 日志显示 2023-08-31 10:52:41 +05:00
fit2bot
51910ea2c1 fix: 修复历史会话为负数的情况 (#11472)
Co-authored-by: feng <1304903146@qq.com>
2023-08-30 18:18:08 +08:00
fit2bot
266a360a97 feat: 可查看全局被限制的ip 并且可以解锁 (#11394)
Co-authored-by: feng <1304903146@qq.com>
2023-08-30 17:31:59 +08:00
fit2bot
24194b4e4d perf: 翻译 (#11468)
Co-authored-by: feng <1304903146@qq.com>
2023-08-30 16:01:16 +08:00
fit2bot
992e34d652 feat: mysql 证书 (#11465)
Co-authored-by: feng <1304903146@qq.com>
2023-08-30 15:15:49 +08:00
老广
894249a3d1 Merge pull request #11452 from jumpserver/pr@dev@feat_audit_view_download_replay
feat: 查看/下载录像记录在操作及活动日志中
2023-08-30 13:48:53 +08:00
老广
21c6fe19a1 Merge pull request #11459 from jumpserver/pr@dev@metics
perf: dashboard date metrics
2023-08-30 13:26:31 +08:00
老广
e4e4f82143 Merge pull request #11461 from jumpserver/pr@dev@fix_cas_login_failed
fix: 解决CAS无法登陆问题
2023-08-30 13:25:30 +08:00
jiangweidong
2a5c635dc5 fix: 修改日志内容 2023-08-30 11:32:54 +08:00
jiangweidong
7dbaa28539 fix: 解决CAS无法登陆问题 2023-08-30 11:28:17 +08:00
feng
5bae4cde58 perf: dashboard date metrics 2023-08-29 22:04:08 +08:00
老广
35c0d7be35 Merge pull request #11455 from jumpserver/pr@dev@feat_settings_tool_ping_telnet_multi
feat: telnet、ping支持批量测试
2023-08-29 19:05:56 +08:00
jiangweidong
1f2a4b0fb5 feat: telnet、ping支持批量测试 2023-08-29 17:02:51 +08:00
jiangweidong
7c3a3d599b perf: 参数修改 2023-08-29 15:18:51 +08:00
jiangweidong
d70770775a perf: 翻译 2023-08-29 15:16:02 +08:00
jiangweidong
bc217e1bad Merge branch 'dev' of https://github.com/jumpserver/jumpserver into pr@dev@feat_audit_view_download_replay 2023-08-29 14:21:11 +08:00
jiangweidong
d4469aeaf7 feat: 查看/下载录像被记录在活动日志中 2023-08-29 14:21:06 +08:00
老广
904406c5c1 Merge pull request #11442 from jumpserver/pr@dev@fix_migrate_sftp
fix: 修复迁移的 sftp 数量不对
2023-08-28 19:03:28 +08:00
ibuler
09db2ad3e1 fix: 修复迁移的 sftp 数量不对 2023-08-28 16:48:22 +08:00
fit2bot
859268f7f3 perf: 优化账号创建 (#11440)
* feat: 支持账号模版自动推送
* perf: 修改模版
* perf: 优化账号创建

---------

Co-authored-by: ibuler <ibuler@qq.com>
2023-08-28 15:43:45 +08:00
老广
72bb5a4037 Merge pull request #11439 from jumpserver/pr@dev@change_tips
perf: 修改同名账号的提示
2023-08-28 11:25:55 +08:00
ibuler
6f3871d5fe perf: 修改同名账号的提示 2023-08-28 11:24:54 +08:00
“huailei000”
2f0c346365 perf: 优化不能生成MFA二维码问题 2023-08-25 12:01:51 +05:00
老广
e9c090f656 Merge pull request #11410 from hoilc/hoilc-patch-1
perf: 通过网域连接k8s时支持默认端口
2023-08-24 18:25:14 +08:00
老广
7b0b07cf52 Merge pull request #11415 from jumpserver/pr@dev@perf_select_host
perf: 优化 applet 发布机选择
2023-08-24 18:14:08 +08:00
ibuler
bebb90f688 perf: 优化 applet 发布机选择 2023-08-24 18:00:19 +08:00
hoilc
ac14a70c51 perf: 通过网域连接k8s时支持默认端口 2023-08-24 16:10:29 +08:00
jiangweidong
642f92c0a3 fix: saml2无法登陆问题 2023-08-24 11:05:50 +05:00
fit2bot
04f4ecb3d1 perf: 优化文案 (#11405)
Co-authored-by: ibuler <ibuler@qq.com>
2023-08-24 10:58:27 +08:00
老广
60703c920c Merge pull request #11381 from jumpserver/pr@dev@sqlserver_add_version
perf: sql server 添加驱动标识
2023-08-24 10:55:48 +08:00
ibuler
9634f397df perf: 不允许修改自己的角色 2023-08-23 16:11:05 +05:00
ibuler
f9a7a95191 fix: 修复 Host name 中包含 [ 导致 ansible 错误的问题 2023-08-23 16:07:58 +05:00
ibuler
bced33fd93 perf: sql server 添加驱动标识 2023-08-22 13:40:41 +08:00
老广
1044ff004b Merge pull request #11372 from jumpserver/pr@dev@device_add_sftp
perf: 网络设备支持 sftp
2023-08-21 15:40:30 +08:00
ibuler
e11c7a264e perf: 网络设备支持 sftp 2023-08-21 15:20:58 +08:00
老广
3c497aa81e Merge pull request #11361 from jumpserver/pr@dev@perf_login_csrf
perf: 修改 csrf 登录时判断
2023-08-18 20:44:25 +08:00
ibuler
c8a1f4b092 perf: 修改 csrf 登录时判断 2023-08-18 20:36:58 +08:00
老广
9dd2dc8907 Merge pull request #11358 from jumpserver/pr@dev@perf_csrf_token_error
perf: 修改 csrf token 提示
2023-08-18 18:42:50 +08:00
ibuler
56285d906f perf: 修改 csrf token 提示 2023-08-18 18:41:10 +08:00
ibuler
44b536a23b perf: 去掉 migrate 提示 2023-08-18 15:17:41 +05:00
老广
a97003a03a Merge pull request #11353 from jumpserver/pr@dev@perf_login_info
perf: 优化登录页面提示判断,可能没有端口
2023-08-18 18:00:26 +08:00
ibuler
4315cbe6d0 perf: 优化登录页面提示判断,可能没有端口
perf: 修改 login 检测
2023-08-18 17:59:13 +08:00
老广
b2d9670721 Merge pull request #11349 from jumpserver/pr@dev@perf_info
perf: 修改说明
2023-08-18 17:01:31 +08:00
ibuler
78f66c46e8 perf: 修改说明 2023-08-18 16:59:07 +08:00
老广
f3af9c3108 Merge pull request #11346 from jumpserver/pr@dev@fix_sessionshare
fix: 修复创建会话分享不填写用户报错的问题
2023-08-18 16:52:07 +08:00
ibuler
822a124dbc perf: 优化登录提示 2023-08-18 13:51:27 +05:00
Bai
20799ece93 fix: 修复创建会话分享不填写用户报错的问题 2023-08-18 08:46:14 +00:00
老广
4e2c7d7aab Merge pull request #11343 from jumpserver/pr@dev@allow_hosts_to_all
perf: 修改 allowed hosts
2023-08-18 16:17:25 +08:00
ibuler
75e4895314 perf: 修改 allowed hosts 2023-08-18 16:15:25 +08:00
Bai
ea7b409a7f fix: 修复资产树子节点创建后没有获取到的问题 2023-08-18 13:03:54 +05:00
老广
01d10a25e9 Merge pull request #11337 from jumpserver/pr@dev@perf_change_depends
perf: 修改依赖
2023-08-18 15:27:57 +08:00
ibuler
61ce39b4ba perf: 修改依赖 2023-08-18 15:19:26 +08:00
feng
7506c7ea43 fix: 修复密钥校验ansible不支持{% 2023-08-17 16:16:34 +05:00
老广
f6f162ec3a Merge pull request #11324 from jumpserver/pr@master@perf_django_ca_version
perf: 修改 django cas version
2023-08-17 17:43:23 +08:00
老广
2e840e3b05 Merge pull request #11323 from jumpserver/pr@dev@perf_django_ca_version
perf: 修改 django cas version
2023-08-17 17:43:04 +08:00
ibuler
ff4560c2a7 perf: 修改 django cas version 2023-08-17 09:42:27 +00:00
ibuler
deeb8da226 perf: 修改 django cas version 2023-08-17 17:39:58 +08:00
Bryan
03273b2ec4 Merge pull request #11322 from jumpserver/dev
v3.6.0
2023-08-17 13:56:25 +05:00
老广
737cae8d03 Merge pull request #11320 from jumpserver/pr@dev@fix_operatelog_not_record_component
fix: 操作日志判断is_service_account为匿名用户会报错
2023-08-17 16:29:04 +08:00
jiangweidong
cf6ce0fa2e fix: 操作日志判断is_service_account为匿名用户会报错 2023-08-17 16:21:30 +08:00
fit2bot
7dd6ee5f1a perf: translate (#11319)
Co-authored-by: feng <1304903146@qq.com>
2023-08-17 15:34:50 +08:00
老广
91432f0e8f Merge pull request #11318 from jumpserver/pr@dev@update_poetry_lock
perf: 更新 poetry lock
2023-08-17 15:28:51 +08:00
ibuler
6c36b5be92 perf: 更新 poetry lock 2023-08-17 15:25:44 +08:00
Bai
7b89055fbf fix: 账号备份参数控制 2023-08-17 11:50:17 +05:00
jiangweidong
c0f3769f9f perf: 优化组件的操作行为不记录到操作日志中 2023-08-17 11:49:57 +05:00
fit2bot
b20abb494f perf: 优化 vault 配置 (#11313)
Co-authored-by: feng <1304903146@qq.com>
2023-08-17 12:12:58 +08:00
老广
a084bc9962 Merge pull request #11310 from jumpserver/pr@dev@perf_applet_deploy
perf: 优化发布机的注册名称,避免重复
2023-08-17 10:59:53 +08:00
老广
cbb615e2ce Merge pull request #11311 from jumpserver/pr@dev@perf_applet_enterprise
perf: applet 上传检查版本
2023-08-17 10:57:53 +08:00
ibuler
769d5fbd96 perf: applet 上传检查版本 2023-08-17 10:54:35 +08:00
Eric
bbd36fea03 perf: 优化发布机的注册名称,避免重复 2023-08-17 10:33:59 +08:00
老广
9317d9e35e Merge pull request #11307 from jumpserver/pr@dev@perf_add_xframe_option
perf: add iframe option
2023-08-17 10:21:53 +08:00
ibuler
f697033252 perf: add iframe option 2023-08-17 10:18:27 +08:00
老广
eb8d80d417 Merge pull request #11302 from jumpserver/pr@dev@fix_ops_shell_run_failed
fix: 修复 shell 批量命令无法执行的问题
2023-08-16 18:43:35 +08:00
老广
d5ac8b16f1 Merge pull request #11305 from jumpserver/pr@dev@perf_task_err
perf: 修复发布机任务执行失败的问题
2023-08-16 18:43:02 +08:00
老广
ed54cc8507 Merge pull request #11306 from jumpserver/pr@dev@perf_chrome_ext
fix: 修复 chrome 插件不生效的问题
2023-08-16 18:33:14 +08:00
ibuler
40248077cd fix: 修复 chrome 插件不生效的问题 2023-08-16 18:30:29 +08:00
Eric
45e1723aa9 perf: 修复发布机任务执行失败的问题 2023-08-16 18:17:32 +08:00
Aaron3S
af9f7060be fix: 修复 shell 批量命令无法执行的问题 2023-08-16 17:01:35 +08:00
Eric
8f10b84e94 perf: 修复 Chrome 执行脚本失败,页面卡在进度条界面的问题 2023-08-16 13:48:00 +05:00
halo
d02cbcc3a3 perf: linux客户端文件后缀 2023-08-16 13:47:30 +05:00
ibuler
689fd12141 perf: windows 可以添加 sftp 2023-08-16 12:24:56 +05:00
Eric
3c9c494979 perf: 修复发布机因同名账号创建造成的部署异常 2023-08-16 12:15:02 +05:00
老广
16ceb79427 Merge pull request #11292 from jumpserver/pr@dev@k8s_add_icon
perf: 修改 k8s icon
2023-08-16 13:44:51 +08:00
老广
cd5e53e3dc Merge pull request #11293 from jumpserver/pr@dev@oracledb_thin_mode
perf: python-oracledb Thin Mode
2023-08-16 13:44:26 +08:00
吴小白
df1aa73723 perf: python-oracledb Thin Mode 2023-08-16 13:11:48 +08:00
ibuler
ceee2e1633 perf: 修改 k8s icon 2023-08-16 11:42:36 +08:00
吴小白
91867fa01d Merge pull request #11291 from jumpserver/pr@dev@perf_Dockerfile
perf: 优化构建企业版本镜像
2023-08-16 11:24:18 +08:00
吴小白
dfde9258c7 perf: 优化构建企业版本镜像 2023-08-16 11:17:53 +08:00
fit2bot
fc595bc4e4 perf: 启动 ssh 隧道错误处理优化 (#11287)
Co-authored-by: feng <1304903146@qq.com>
2023-08-15 18:50:48 +08:00
老广
48aa48e7a3 Merge pull request #11262 from jumpserver/pr@dev@revert_dockerfile
revert: 还原构建
2023-08-15 18:37:56 +08:00
老广
479378aa46 Merge branch 'dev' into pr@dev@revert_dockerfile 2023-08-15 18:37:38 +08:00
fit2bot
362c2a9509 perf: 修改翻译 账号模版批量添加 config配置文件 (#11286)
Co-authored-by: feng <1304903146@qq.com>
2023-08-15 18:24:01 +08:00
老广
a423d241a5 Merge pull request #11285 from jumpserver/pr@dev@perf_settings
perf: 再次修改 setting
2023-08-15 17:00:14 +08:00
ibuler
9e6221443e perf: 再次修改 setting 2023-08-15 16:58:41 +08:00
fit2bot
12744a08af perf: vault 日志 (#11282)
Co-authored-by: feng <1304903146@qq.com>
2023-08-15 15:09:25 +08:00
老广
5e29c7e7bf Merge pull request #11275 from jumpserver/pr@dev@perf_setting
perf: 优化设置布局
2023-08-15 13:52:54 +08:00
ibuler
02f38fe37a perf: merge with dev 2023-08-15 13:51:59 +08:00
ibuler
663ccbca6f perf: 修改翻译 2023-08-15 13:49:56 +08:00
ibuler
c4528612d5 perf: 修改完成 2023-08-15 13:45:44 +08:00
Bai
7707101379 perf: 优化飞书信息通知文案 2023-08-15 08:17:24 +05:00
BoringCat
873e6d1ab9 修复飞书markdown信息渲染问题 2023-08-15 07:47:21 +05:00
fit2bot
7ba261c4f0 perf: vault 同步日志 (#11278)
Co-authored-by: feng <1304903146@qq.com>
2023-08-15 10:32:03 +08:00
fit2bot
1f8428ac1c perf: vault 同步速度问题 (#11277)
Co-authored-by: feng <1304903146@qq.com>
2023-08-14 22:32:53 +08:00
ibuler
8e0c04c84c perf: 优化设置布局 2023-08-14 19:40:21 +08:00
Bai
a6e49b730b fix: 修复忘记密码不包含左侧 + 字符 2023-08-14 15:42:32 +05:00
fit2bot
c11ba16e4e perf: oidc 替换原有的is_ajax方法,优化accountbackupexecution 迁移文件 (#11274)
Co-authored-by: feng <1304903146@qq.com>
2023-08-14 18:37:28 +08:00
Eric
efe57b3ebe perf: 修复手动登陆账号密码无法赋值问题 2023-08-14 14:46:51 +05:00
Eric
4899f6bb69 fix: 修复发布机网关选择 2023-08-14 14:45:37 +05:00
jiangweidong
ef0c2f41ac perf: 翻译 2023-08-14 14:38:47 +05:00
jiangweidong
98b4f51cbb fix: 修复云同步策略权限位置显示不正常问 2023-08-14 14:38:47 +05:00
fit2bot
da52180976 perf: 组织角色添加connectiontoken权限 (#11268)
Co-authored-by: feng <1304903146@qq.com>
2023-08-14 16:37:56 +08:00
fit2bot
bd642a0281 perf: 翻译 (#11266)
Co-authored-by: feng <1304903146@qq.com>
2023-08-14 14:47:51 +08:00
吴小白
dc88e4f420 fix: 添加 nmap 包 2023-08-14 14:25:08 +08:00
老广
7a3a0b2d8e Merge pull request #11264 from jumpserver/pr@dev@fix_recursive_expansion
fix: 解决类型树展开全部时,根节点无限递归展开问题
2023-08-14 11:26:59 +08:00
老广
eac1b287e4 Merge pull request #11265 from jumpserver/pr@dev@perf_jms-storage
perf: jms-storage==0.0.51
2023-08-14 11:25:54 +08:00
Bai
d2f7396689 perf: jms-storage==0.0.51 2023-08-14 11:20:58 +08:00
jiangweidong
db4f05afbe fix: 解决类型树展开全部时,根节点无限递归展开问题 2023-08-14 11:07:28 +08:00
吴小白
339fe1b73b revert: 还原构建 2023-08-14 11:06:04 +08:00
fit2bot
237c71f921 perf: vault 同步日志优化 (#11261)
Co-authored-by: feng <1304903146@qq.com>
2023-08-14 10:57:59 +08:00
吴小白
bd7c5f8e65 revert: 还原构建 2023-08-14 10:57:40 +08:00
“huailei000”
c3ea5300a3 perf: 优化任务日志页面时间显示兼容问题 2023-08-14 07:11:11 +05:00
fit2bot
e2de744398 perf: 优化vault 配置 (#11254)
Co-authored-by: feng <1304903146@qq.com>
2023-08-11 16:01:05 +08:00
Bai
a890a8d535 perf: 发布机获取账号API移除日志 2023-08-11 12:17:01 +05:00
老广
c39e134834 Merge pull request #11250 from jumpserver/pr@dev@perf_applet_gen_private_account
perf: 账号生成时,排除 [ 开头的
2023-08-10 18:28:36 +08:00
ibuler
e9e5fbb4c2 perf: 账号生成时,排除 [ 开头的 2023-08-10 18:23:53 +08:00
Bai
3203c298e5 perf: 发布机获取账号API增加日志 2023-08-10 14:57:50 +05:00
老广
e416a5d5d7 Merge pull request #11247 from jumpserver/pr@dev@perf_change_edition
perf: 修改翻译
2023-08-10 17:32:06 +08:00
ibuler
7ea61c0f22 perf: 修改翻译 2023-08-10 17:30:04 +08:00
老广
b2108ec624 Merge pull request #11245 from jumpserver/pr@dev@perf_account_perm
perf: 修复账号权限问题
2023-08-10 16:03:22 +08:00
ibuler
433324ec8c perf: 修复账号权限问题 2023-08-10 15:56:31 +08:00
老广
ac20bfe024 Merge pull request #11243 from jumpserver/pr@dev@perf_update_clients_version
perf: 更新clients版本
2023-08-10 15:18:09 +08:00
老广
a116c7db39 Merge pull request #11244 from jumpserver/pr@dev@perf_merge_migrate
perf: 合并 migrations
2023-08-10 15:13:48 +08:00
ibuler
71e69782b7 perf: 合并 migrations 2023-08-10 15:11:52 +08:00
老广
7611d4e7ce Merge pull request #11242 from jumpserver/pr@dev@perf_applet_enterprise
perf: 修改 applet 企业版
2023-08-10 14:42:52 +08:00
ibuler
a778a40b21 perf: 修改 applet 企业版 2023-08-10 14:41:43 +08:00
老广
4e254493bc Merge pull request #11241 from jumpserver/pr@dev@perf_core_host
perf: 优化 CORE_HOST
2023-08-10 13:07:14 +08:00
ibuler
07530bc56b perf: 优化 CORE_HOST 2023-08-10 12:23:40 +08:00
老广
259daaab38 Merge pull request #11240 from jumpserver/pr@dev@perf_i18n
perf: 修改翻译
2023-08-10 12:22:31 +08:00
老广
c769c06202 Merge pull request #11239 from jumpserver/pr@dev@default_add_core
perf: 修改默认添加 core 到 allow hosts
2023-08-10 11:24:31 +08:00
ibuler
e0463420fa perf: 修改默认添加 core 到 allow hosts 2023-08-10 11:23:42 +08:00
ibuler
1944e80418 perf: 修改翻译 2023-08-10 11:19:17 +08:00
fit2bot
4b72099053 perf: 连接方式新增 guide 模式 (#11237)
Co-authored-by: ibuler <ibuler@qq.com>
2023-08-09 19:59:53 +05:00
Aaron3S
dcf113b87c feat: 增加作业中心 sql 支持 2023-08-09 17:32:35 +08:00
Bai
ab6d0d2484 perf: 优化账号 API 支持 comment 模糊搜索 2023-08-09 17:05:21 +08:00
Eric
7bef4b07ff feat: 增加会话最大连接时长设置 2023-08-09 10:37:38 +08:00
fit2bot
f486c843bf feat: 支持拉起本地客户端 (#10865)
* perf: 拉起本地客户端应用接口提供更多数据

* fix: rdp客户端拉起后窗口标题中文乱码

* perf: ssh客户端连接选项显示优化

* feat: 增加本地sftp客户端选项

* perf: 合并支持sftp协议

* perf: sftp与ssh使用相同端口

---------

Co-authored-by: halo <wuyihuangw@gmail.com>
2023-08-09 10:36:54 +08:00
halo
90038e41f9 perf: 更新clients版本 2023-08-08 19:09:24 +08:00
fit2bot
33ee84633f perf: 修改terminal metrics接口 加入terminal name (#11228)
Co-authored-by: feng <1304903146@qq.com>
2023-08-08 18:45:10 +08:00
ibuler
419806aa57 perf: 去掉 requirements.txt 2023-08-08 17:52:44 +08:00
fit2bot
8ea3c3288b perf: 改密替换校验可连接性方法 (#11224)
Co-authored-by: feng <1304903146@qq.com>
2023-08-08 17:26:29 +08:00
老广
99ce2bc946 Merge pull request #11222 from jumpserver/pr@dev@perf_change_help_text
perf: 优化 applet 选择账号调度
2023-08-08 16:50:17 +08:00
ibuler
9bf76ae07a perf: 优化 applet 选择账号调度 2023-08-08 16:15:44 +08:00
ibuler
a33540710e perf: 优化 applet 选择账号调度 2023-08-08 15:58:24 +08:00
ibuler
680d31dad2 perf: 优化 applet 账号选择 2023-08-08 15:58:24 +08:00
Bai
a297355a0d fix: 修复 accounts 迁移文件编号冲突 2023-08-08 14:07:08 +08:00
ibuler
e891283925 perf: System 组织不允许删除 2023-08-08 10:33:28 +08:00
ibuler
c72ec5ea78 perf: 组织属性添加 internal 2023-08-08 10:33:28 +08:00
fit2bot
b764827003 perf: 虚拟账号增加密码选项 (#11201)
* perf: 修改账号配置

* perf: 修改 account

* perf: 修改 virtual account

* perf: 虚拟账号增加密码选项

* perf: 修改获取虚拟账号

* perf: 修改 virtual account

* perf: 修改一些写法

* perf: 添加说明

---------

Co-authored-by: ibuler <ibuler@qq.com>
2023-08-08 10:16:23 +08:00
Eric
a261b2de3c perf: 优化用户个人 ssh 公钥校验 2023-08-07 18:52:45 +08:00
Eric
e939776da0 chore: 更新 poetry.lock 2023-08-07 18:48:38 +08:00
fit2bot
0a9726d845 feat: 账号备份密钥拆分 (#11199)
Co-authored-by: feng <1304903146@qq.com>
2023-08-07 15:50:09 +08:00
fit2bot
c21fcacf70 perf: 检测不常用账号 (#11205)
Co-authored-by: feng <1304903146@qq.com>
2023-08-07 14:55:17 +08:00
jiangweidong
f588a112fb perf: 修改nmap位置 2023-08-07 14:01:58 +08:00
jiangweidong
ecca64ef42 perf: Dockerfile中安装nmap工具 2023-08-07 14:01:58 +08:00
吴小白
56a657827a Merge pull request #11210 from jumpserver/pr@dev@fix_huaweicloud_sdk
fix: 添加华为云依赖包
2023-08-07 12:18:59 +08:00
jiangweidong
38803518fc perf: 类型树右击可以获取节点下所有的资产 2023-08-07 12:15:50 +08:00
jiangweidong
c2f1e4f4f6 fix: 添加华为云依赖包 2023-08-07 11:07:28 +08:00
Eric
49662b308d feat: Chrome 应用通过平台的安全模式动态加载扩展 2023-08-07 11:03:18 +08:00
jiangweidong
7636255533 feat: 系统工具改为异步,增加tcpdump工具 2023-08-07 10:18:51 +08:00
吴小白
8accd296b8 Merge pull request #11202 from jumpserver/pr@dev@perf_dockerfile
perf: 优化 Dockerfile
2023-08-05 14:34:42 +08:00
吴小白
e424e3c311 perf: 优化 Dockerfile 2023-08-05 14:18:27 +08:00
老广
e38dd96d6f Merge pull request #11191 from jumpserver/pr@dev@perf_http_support_unsafe_mode
perf: 修改 safe mode
2023-08-04 14:02:42 +08:00
吴小白
170f1e40d6 Merge pull request #11190 from jumpserver/pr@dev@perf_dockerfile
perf: 优化构建
2023-08-03 20:29:20 +08:00
Bai
2aacb07b15 fix: 修复 MAX_LIMIT_PER_PAGE, 默认值以及数据类型转换 2023-08-03 18:38:58 +08:00
ibuler
6b9f40d5c1 perf: 修改 safe mode 2023-08-03 16:52:21 +08:00
ibuler
27c4e1d895 perf: web 平台增加高级选项,可以控制是否安全模式 2023-08-03 16:09:54 +08:00
吴小白
65916a469c perf: 优化构建 2023-08-03 14:33:22 +08:00
jiangweidong
ff2aace569 feat: ssh_ping及custom_command支持sudo及su切换用户 (#11180) 2023-08-03 14:09:13 +08:00
fit2bot
8cfec07faa fix: 修复 在AWS公有云环境中,rds等资产的域名解析长度超过JumpServer资产限制的128字节导致连接失败问题 (#11188)
Co-authored-by: feng <1304903146@qq.com>
2023-08-03 11:21:30 +08:00
老广
4dc6bd3660 Merge pull request #11186 from jumpserver/pr@dev@perf_merge_migrations
perf: 合并 migrations
2023-08-03 10:53:25 +08:00
ibuler
ee874f3ddc perf: 合并 migrations 2023-08-03 10:52:13 +08:00
老广
9691125c7a Merge pull request #11182 from jumpserver/pr@dev@perf_telnet_prompt
perf: 修改 telnet 平台 setting
2023-08-02 18:27:10 +08:00
ibuler
41fa1d65ff perf: 修改 telnet 平台 setting 2023-08-02 17:54:11 +08:00
fit2bot
6d2e7cf7f4 perf: 任务添加过滤项 (#11181)
Co-authored-by: feng <1304903146@qq.com>
2023-08-02 17:51:58 +08:00
ibuler
4ef05a1cd4 perf: 修改 telnet 平台,支持自定义 prompt 2023-08-02 16:53:47 +08:00
老广
207d015497 Merge pull request #11177 from jumpserver/pr@dev@perf_del_remote
perf: 不能 remove
2023-08-02 15:49:53 +08:00
ibuler
85058f8599 perf: 不能 remove 2023-08-02 15:45:13 +08:00
老广
55dad53934 Merge pull request #11175 from jumpserver/pr@dev@no_virtual_env
perf: 不创建 venv
2023-08-02 15:40:43 +08:00
ibuler
958290529a perf: 不创建 venv 2023-08-02 15:37:30 +08:00
老广
ba128e99f9 perf: 添加清华源 (#11174) 2023-08-02 15:30:20 +08:00
fit2bot
89c4a8d5c4 perf: 去掉 lock 中的 source (#11173)
* perf: 去掉 lock 中的 source

* perf: 去掉格式化

---------

Co-authored-by: ibuler <ibuler@qq.com>
2023-08-02 15:17:21 +08:00
fit2bot
6d758bdb59 fix: k8s 支持网关 (#11171)
Co-authored-by: feng <1304903146@qq.com>
2023-08-02 15:07:22 +08:00
老广
eb8e7c5f8a Merge pull request #11170 from jumpserver/pr@dev@add_mirror
perf: using mirror
2023-08-02 14:54:56 +08:00
ibuler
ef4f1ddb74 perf: using mirror 2023-08-02 14:52:12 +08:00
老广
e14e5b523a Merge pull request #11166 from jumpserver/pr@dev@using_poetry_requirements
perf: 使用 poetry 管理依赖
2023-08-02 13:51:35 +08:00
ibuler
99ae0066ae perf: 使用 poetry 管理依赖 2023-08-02 13:45:15 +08:00
fit2bot
d486dfc7f7 fix: 修复因vault 改密500 问题 (#11168)
Co-authored-by: feng <1304903146@qq.com>
2023-08-02 13:11:46 +08:00
fit2bot
93ba4443dd perf: windows ssh 协议 默认开启 (#11158)
Co-authored-by: feng <1304903146@qq.com>
2023-08-01 19:48:32 +08:00
fit2bot
d182d14e26 perf: 账号备份日志优化 (#11151)
Co-authored-by: feng <1304903146@qq.com>
2023-08-01 18:17:02 +08:00
fit2bot
8ed823d587 feat: 批量不是发布机 (#11150)
Co-authored-by: feng <1304903146@qq.com>
2023-08-01 17:42:16 +08:00
fit2bot
44397caad4 perf: 支持在线会话暂停操作 (#11146)
* perf: 支持在线会话暂停操作

* perf: 优化代码

---------

Co-authored-by: Eric <xplzv@126.com>
2023-08-01 16:40:38 +08:00
fit2bot
d17e2cde06 feat: 终端会话增加字段: cmd_amount(命令数量) (#11136)
* feat: 终端会话增加字段: command_amount(命令数量)

* perf: 优化已产生会话的命令数量计算方式

* Update 0065_session_command_amount.py

* Update session.py

* Update session.py

* perf: 优化会话命令数量的计算逻辑

* perf: 优化命令数量获取

---------

Co-authored-by: fangfang.dong <fangfang.dong@fit2cloud.com>
Co-authored-by: Bai <baijiangjie@gmail.com>
2023-08-01 16:14:40 +08:00
feng
681988f450 fix: ansible task 500 2023-08-01 16:07:07 +08:00
ibuler
6b333adc05 perf: 修改 ansible version 2023-08-01 10:50:54 +08:00
ibuler
5207b99696 perf: 修改 inventory 2023-08-01 10:49:40 +08:00
fangfang.dong
b93b64255b perf: 统一用户名称的label显示 2023-07-31 20:11:44 +08:00
Aaron3S
f9c9c9d525 fix: 禁止一些 ansible 变量 2023-07-31 19:46:33 +08:00
fit2bot
1ad0a20627 fix: 启动500 (#11133)
Co-authored-by: feng <1304903146@qq.com>
2023-07-31 18:31:11 +08:00
老广
0ed929a3b2 Merge pull request #11129 from jumpserver/pr@dev@fix_common_elasticsearch
fix: 修复es7创建index的错误
2023-07-31 17:54:13 +08:00
nut
2ffadcb9bc Update es.py 2023-07-31 17:53:08 +08:00
fit2bot
3b615719fe feat: 账号密钥用vault储存 (#10830)
* feat: 账号密钥用vault储存

* perf: 优化 Vault

* perf: 重构 Vault Backend 设计架构 (未完成)

* perf: 重构 Vault Backend 设计架构 (未完成2)

* perf: 重构 Vault Backend 设计架构 (未完成3)

* perf: 重构 Vault Backend 设计架构 (未完成4)

* perf: 重构 Vault Backend 设计架构 (未完成5)

* perf: 重构 Vault Backend 设计架构 (已完成)

* perf: 重构 Vault Backend 设计架构 (已完成)

* perf: 重构 Vault Backend 设计架构 (已完成)

* perf: 小优化

* perf: 优化

---------

Co-authored-by: feng <1304903146@qq.com>
Co-authored-by: Bai <baijiangjie@gmail.com>
Co-authored-by: feng626 <57284900+feng626@users.noreply.github.com>
2023-07-31 17:39:30 +08:00
ibuler
7776158279 perf: 修改 django_cas_ng 的版本 2023-07-31 17:25:32 +08:00
fangfang.dong
47dd73eb4c fix: 修复es7创建index的错误 2023-07-31 14:54:35 +08:00
老广
bf30be2084 Merge pull request #11122 from jumpserver/pr@dev@fix_rdpfilemultimon
feat: rdp file 支持设置多屏显示 (multimon)
2023-07-31 09:22:40 +08:00
老广
39d651dd9b Merge pull request #11123 from jumpserver/pr@dev@fix_rdpfile
feat: rdp file 支持设置多屏显示
2023-07-31 09:22:27 +08:00
Bai
07f4fdd92d feat: rdp file 支持设置多屏显示 2023-07-28 18:06:38 +08:00
Bai
53c8c2d9ea feat: rdp file 支持设置多屏显示 (multimon) 2023-07-28 17:45:20 +08:00
fit2bot
c201914bc8 perf: change secret perf (#11120)
Co-authored-by: feng <1304903146@qq.com>
2023-07-28 17:00:55 +08:00
老广
83917cb440 Merge pull request #11118 from jumpserver/pr@dev@perf_filter_and_default_group
perf: 添加到默认组织中
2023-07-28 16:52:00 +08:00
ibuler
b55eb1236f perf: 添加到默认组织中 2023-07-28 16:15:12 +08:00
fit2bot
38cee8eaa4 fix: 修复migrations文件错误 (#11116)
Co-authored-by: fangfang.dong <fangfang.dong@fit2cloud.com>
2023-07-28 15:14:49 +08:00
jiangweidong
e339a56042 feat: 云同步增加同步策略 (#11001) 2023-07-28 14:34:38 +08:00
fit2bot
384b639dd3 perf: 优化隐藏 Chrome 的代填操作 (#11114)
Co-authored-by: Eric <xplzv@126.com>
2023-07-28 14:33:09 +08:00
jiangweidong
c86b28a305 feat: 支持批量审批工单 (#11014) 2023-07-28 14:32:31 +08:00
老广
dbfb9db5c5 Merge pull request #11113 from jumpserver/pr@dev@perf_account_select
perf: 修改发布机账号选择
2023-07-28 11:17:02 +08:00
ibuler
93350faa08 perf: 修改账号选择 2023-07-28 11:15:24 +08:00
ibuler
107fda0f99 perf: 修改发布机账号选择 2023-07-28 11:13:48 +08:00
老广
58124af1ce Merge pull request #11111 from jumpserver/pr@dev@perf_applet_host_account_create
perf: 修改应用发布机账号创建
2023-07-28 11:07:18 +08:00
ibuler
1a4c5dca33 perf: 修改翻译 2023-07-28 11:06:01 +08:00
ibuler
5380dc0c2d perf: 修改翻译 2023-07-28 11:02:21 +08:00
ibuler
2c22396093 perf: 修改去掉冲突 2023-07-28 10:49:33 +08:00
ibuler
31da139eb3 merge: with dev 2023-07-28 10:46:34 +08:00
ibuler
962354c50d perf: 修改应用发布机账号创建 2023-07-28 10:41:37 +08:00
jiangweidong
1907c795c3 feat: 系统工具增加服务器时间及nmap工具 (#11078) 2023-07-28 10:40:48 +08:00
fangfang.dong
1239ffd4c8 perf: 优化会话分享url的构造 2023-07-28 10:22:47 +08:00
nut
7a37f91964 Update sharing.py 2023-07-28 10:22:47 +08:00
fangfang.dong
2741d7cbdc feat: 终端会话分享增加消息通知功能 2023-07-28 10:22:47 +08:00
fit2bot
99adb6ab7a perf: 改造username_suggestions api 改为post请求 (#11110)
Co-authored-by: feng <1304903146@qq.com>
2023-07-27 14:04:29 +08:00
Bai
665c833479 fix: 修复创建 ES 存储 get_mapping index 使用位置参数 2023-07-27 10:43:21 +08:00
Bai
77944cc91b fix: 修复创建资产 is_valid 使用kw参数 2023-07-27 10:21:22 +08:00
ibuler
b5fc865cc6 perf: Oracle 支持 2023-07-26 19:27:34 +08:00
ibuler
3b6c2fc0c0 perf: 修改 sftp 的一些处理 2023-07-26 19:25:39 +08:00
Bai
114645732a perf: 用户授权账号 API 返回 id 字段 2023-07-26 19:24:58 +08:00
老广
1b338a9cd3 Merge pull request #11093 from jumpserver/pr@dev@fix_user_account
fix: 修复同名账号用户名代填问题
2023-07-26 19:16:45 +08:00
老广
59f12a3c14 Merge pull request #11091 from jumpserver/pr@dev@ssh_to_sftp
perf: 修改 sftp 协议
2023-07-26 18:21:45 +08:00
Eric
3fc52cbb68 fix: 修复同名账号用户名代填问题 2023-07-26 17:13:38 +08:00
ibuler
b0b6d19bc0 perf: 修改 sftp 协议 2023-07-26 15:31:02 +08:00
老广
9deb48b16b Merge pull request #11080 from jumpserver/pr@dev@fix_bulk_update_asset_error
perf: 修复批量更新资产导致的错误
2023-07-26 09:51:45 +08:00
ibuler
48510e98a2 merge: with dev 2023-07-25 17:13:38 +08:00
ibuler
c135837372 perf: 修改 connect method 2023-07-25 17:12:06 +08:00
老广
92ed189453 Merge pull request #11083 from jumpserver/pr@dev@perf_koko_support
perf: 移除 Koko 的部分数据库支持
2023-07-25 16:33:58 +08:00
Eric
418ac5a5ba perf: 移除 Koko 的部分数据库支持 2023-07-25 15:45:48 +08:00
fit2bot
539a6161e6 perf: 翻译 (#11082)
Co-authored-by: feng <1304903146@qq.com>
2023-07-25 15:40:57 +08:00
ibuler
806baeb136 perf: 修复批量更新资产导致的错误 2023-07-25 14:45:24 +08:00
老广
ae0daddbea Merge pull request #11077 from jumpserver/pr@dev@change_ansible_pkg
perf: 使用瘦身后的 ansible
2023-07-25 11:22:29 +08:00
ibuler
76903977eb perf: 使用瘦身后的 ansible 2023-07-25 11:21:01 +08:00
老广
c9fffa50a8 Merge pull request #11076 from jumpserver/pr@dev@perf_django_version
perf: 降级 Django 版本
2023-07-25 10:57:53 +08:00
ibuler
6478727cd2 perf: 修改依赖包 2023-07-25 10:53:14 +08:00
ibuler
a20b210514 perf: 降级 Django 版本 2023-07-25 10:41:16 +08:00
老广
04a34e8456 Merge pull request #11075 from jumpserver/pr@dev@perf_domains_get
perf: 优化 domains 获取
2023-07-25 10:23:35 +08:00
ibuler
4d2c4a9602 perf: 优化 domains 获取 2023-07-25 10:11:57 +08:00
老广
2a24fcc1bb Merge pull request #11073 from jumpserver/pr@dev@perf_req
perf: 修改 uvicon  的版本
2023-07-24 23:28:33 +08:00
ibuler
366693783c perf: 修改 uvicon 的版本 2023-07-24 23:27:25 +08:00
老广
0a611a4ce9 Merge pull request #11072 from jumpserver/pr@dev@perf_ws_asgi
perf: 优化 asgi 的位置
2023-07-24 23:23:36 +08:00
ibuler
5fedb5440c perf: 设置 application 到 __all__ 2023-07-24 23:23:04 +08:00
ibuler
160c99a01a perf: 修改 requirements 2023-07-24 23:21:30 +08:00
ibuler
089d769eb0 perf: 优化 asgi 的位置 2023-07-24 23:20:05 +08:00
老广
9195d4c43d Merge pull request #11071 from jumpserver/pr@dev@remove_unuse_app
perf: 去掉不用的 app
2023-07-24 22:54:08 +08:00
ibuler
f1d984898b perf: 去掉不用的 app 2023-07-24 22:53:10 +08:00
老广
ecfd9449f2 Merge pull request #11070 from jumpserver/pr@dev@remove_loong64
perf: 拆分 loong64 架构
2023-07-24 21:22:53 +08:00
吴小白
94d40efcad perf: 预构建 ansible-core 2023-07-24 21:17:53 +08:00
吴小白
d5461fe66f perf: 拆分 loong64 架构 2023-07-24 21:09:02 +08:00
老广
00f4ae97ed Merge pull request #11068 from jumpserver/pr@dev@perf_deps
perf: 修改版本以来
2023-07-24 19:31:34 +08:00
ibuler
554c1da38b perf: 修改版本以来 2023-07-24 19:30:27 +08:00
老广
f1a68ebd70 Merge pull request #11064 from jumpserver/pr@dev@change_python_version
perf: 修改 Python 的版本
2023-07-24 18:23:52 +08:00
ibuler
b443a89cb5 perf: 修改 Python 的版本 2023-07-24 18:22:48 +08:00
老广
5b1ae46153 Merge pull request #11062 from jumpserver/pr@dev@for_django4
perf: 修改写法
2023-07-24 18:10:04 +08:00
ibuler
98fd209498 perf: 修改为 Domain 2023-07-24 18:09:10 +08:00
ibuler
7af769f7d3 perf: es 修改导入 2023-07-24 18:05:28 +08:00
老广
89ec01003c Merge pull request #11057 from jumpserver/pr@dev@for_django4
perf: 修改支持 Django4
2023-07-24 17:59:30 +08:00
ibuler
148bf3b894 perf: 修改写法 2023-07-24 17:55:17 +08:00
ibuler
38e8e8734d perf: 添加 DEBUG 日志 2023-07-24 17:49:32 +08:00
ibuler
d8d487f770 perf: 修改 ALLOW_HOSTS 2023-07-24 15:32:30 +08:00
ibuler
e3aaba4798 perf: 去掉不用的 2023-07-24 14:57:49 +08:00
ibuler
95e92a45d5 perf: 修改 xpack requirements 2023-07-24 14:46:48 +08:00
ibuler
86a17b9955 perf: 支持 ws 2023-07-24 14:32:13 +08:00
ibuler
7ae52eb941 perf: 修改 gettext 2023-07-24 14:09:22 +08:00
ibuler
b4b9c805ff perf: 修改支持 Django4 2023-07-24 11:52:25 +08:00
老广
16660575b7 Merge pull request #11054 from jumpserver/pr@dev@change_req_version
perf: 修改 mssql
2023-07-24 10:16:01 +08:00
老广
e9c2351f83 Merge pull request #11048 from huiserwang/dev_huiserwang
fix a latent bug when field_type belongs to int, bool and list.
2023-07-24 10:15:28 +08:00
ibuler
ed49216625 perf: 修改 mssql 2023-07-24 10:14:26 +08:00
ibuler
2417a0930f perf: 修改依赖库版本 2023-07-24 10:07:32 +08:00
老广
c9ba3f4f05 Merge pull request #11045 from jumpserver/pr@dev@feat_python_v3.11
feat: python 支持使用 3.11 版本
2023-07-24 10:07:03 +08:00
Huiser WANG
78d8e410db fix a latent bug when field_type belongs to int, bool and list. 2023-07-22 14:04:21 +08:00
feng
1f25eaf413 perf: update requirements.txt 2023-07-21 19:58:01 +08:00
Eric
54e6200ffe feat: python 支持使用 3.11 版本 2023-07-21 18:21:24 +08:00
老广
bad8400e77 Merge pull request #11042 from jumpserver/pr@dev@chrome_change_readme
chore: 修改 README
2023-07-21 14:11:19 +08:00
ibuler
0fb01bd7fb chore: 还原 requirements 2023-07-21 14:10:21 +08:00
ibuler
34e7671f65 chore: 修改 README 2023-07-21 14:04:34 +08:00
老广
2d99fddaf8 Merge pull request #10842 from jumpserver/pr@dev@perf_support_tidb
perf: 修改支持 tidb
2023-07-21 10:25:36 +08:00
老广
5df4efa5a8 Merge pull request #11037 from jumpserver/pr@dev@chore_change_readme
chore: 修改 readme
2023-07-20 19:43:04 +08:00
ibuler
e2207cf8f1 chore: 修改 readme 2023-07-20 19:41:42 +08:00
ibuler
bf29158be9 perf: 修改支持 tidb 2023-06-28 15:01:25 +08:00
630 changed files with 23186 additions and 6866 deletions

View File

@@ -6,7 +6,6 @@ labels: 类型:需求
assignees:
- ibuler
- baijiangjie
- wojiushixiaobai
---
**请描述您的需求或者改进建议.**

View File

@@ -2,11 +2,9 @@
name: Bug 提交
about: 提交产品缺陷帮助我们更好的改进
title: "[Bug] "
labels: 类型:bug
labels: 类型:Bug
assignees:
- wojiushixiaobai
- baijiangjie
---
**JumpServer 版本( v2.28 之前的版本不再支持 )**

View File

@@ -4,9 +4,7 @@ about: 提出针对本项目安装部署、使用及其他方面的相关问题
title: "[Question] "
labels: 类型:提问
assignees:
- wojiushixiaobai
- baijiangjie
---
**请描述您的问题.**

View File

@@ -19,8 +19,8 @@ jobs:
with:
context: .
push: false
tags: jumpserver/core:test
file: Dockerfile
tags: jumpserver/core-ce:test
file: Dockerfile-ce
build-args: |
APT_MIRROR=http://deb.debian.org
PIP_MIRROR=https://pypi.org/simple

View File

@@ -1,4 +1,4 @@
FROM jumpserver/python:3.9-slim-buster as stage-build
FROM python:3.11-slim-bullseye as stage-1
ARG TARGETARCH
ARG VERSION
@@ -6,11 +6,11 @@ ENV VERSION=$VERSION
WORKDIR /opt/jumpserver
ADD . .
RUN cd utils && bash -ixeu build.sh
RUN echo > /opt/jumpserver/config.yml \
&& cd utils && bash -ixeu build.sh
FROM jumpserver/python:3.9-slim-buster
FROM python:3.11-slim-bullseye as stage-2
ARG TARGETARCH
MAINTAINER JumpServer Team <ibuler@qq.com>
ARG BUILD_DEPENDENCIES=" \
g++ \
@@ -22,6 +22,7 @@ ARG DEPENDENCIES=" \
libpq-dev \
libffi-dev \
libjpeg-dev \
libkrb5-dev \
libldap2-dev \
libsasl2-dev \
libssl-dev \
@@ -36,19 +37,15 @@ ARG TOOLS=" \
curl \
default-libmysqlclient-dev \
default-mysql-client \
locales \
openssh-client \
procps \
sshpass \
telnet \
unzip \
vim \
git \
git-lfs \
unzip \
xz-utils \
wget"
ARG APT_MIRROR=http://mirrors.ustc.edu.cn
RUN --mount=type=cache,target=/var/cache/apt,sharing=locked,id=core \
RUN --mount=type=cache,target=/var/cache/apt,sharing=locked,id=core-apt \
--mount=type=cache,target=/var/lib/apt,sharing=locked,id=core-apt \
sed -i "s@http://.*.debian.org@${APT_MIRROR}@g" /etc/apt/sources.list \
&& rm -f /etc/apt/apt.conf.d/docker-clean \
&& ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime \
@@ -56,59 +53,72 @@ RUN --mount=type=cache,target=/var/cache/apt,sharing=locked,id=core \
&& apt-get -y install --no-install-recommends ${BUILD_DEPENDENCIES} \
&& apt-get -y install --no-install-recommends ${DEPENDENCIES} \
&& apt-get -y install --no-install-recommends ${TOOLS} \
&& echo "no" | dpkg-reconfigure dash
WORKDIR /opt/jumpserver
ARG PIP_MIRROR=https://pypi.tuna.tsinghua.edu.cn/simple
RUN --mount=type=cache,target=/root/.cache \
--mount=type=bind,source=poetry.lock,target=/opt/jumpserver/poetry.lock \
--mount=type=bind,source=pyproject.toml,target=/opt/jumpserver/pyproject.toml \
set -ex \
&& python3 -m venv /opt/py3 \
&& pip install poetry -i ${PIP_MIRROR} \
&& poetry config virtualenvs.create false \
&& . /opt/py3/bin/activate \
&& poetry install
FROM python:3.11-slim-bullseye
ARG TARGETARCH
ENV LANG=zh_CN.UTF-8 \
PATH=/opt/py3/bin:$PATH
ARG DEPENDENCIES=" \
libjpeg-dev \
libx11-dev \
freerdp2-dev \
libxmlsec1-openssl"
ARG TOOLS=" \
ca-certificates \
curl \
default-libmysqlclient-dev \
default-mysql-client \
iputils-ping \
locales \
nmap \
openssh-client \
patch \
sshpass \
telnet \
vim \
wget"
ARG APT_MIRROR=http://mirrors.ustc.edu.cn
RUN --mount=type=cache,target=/var/cache/apt,sharing=locked,id=core-apt \
--mount=type=cache,target=/var/lib/apt,sharing=locked,id=core-apt \
sed -i "s@http://.*.debian.org@${APT_MIRROR}@g" /etc/apt/sources.list \
&& rm -f /etc/apt/apt.conf.d/docker-clean \
&& ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime \
&& apt-get update \
&& apt-get -y install --no-install-recommends ${DEPENDENCIES} \
&& apt-get -y install --no-install-recommends ${TOOLS} \
&& mkdir -p /root/.ssh/ \
&& echo "Host *\n\tStrictHostKeyChecking no\n\tUserKnownHostsFile /dev/null\n\tCiphers +aes128-cbc\n\tKexAlgorithms +diffie-hellman-group1-sha1\n\tHostKeyAlgorithms +ssh-rsa" > /root/.ssh/config \
&& echo "set mouse-=a" > ~/.vimrc \
&& echo "no" | dpkg-reconfigure dash \
&& echo "zh_CN.UTF-8" | dpkg-reconfigure locales \
&& sed -i "s@# export @export @g" ~/.bashrc \
&& sed -i "s@# alias @alias @g" ~/.bashrc \
&& rm -rf /var/lib/apt/lists/*
&& sed -i "s@# alias @alias @g" ~/.bashrc
ARG DOWNLOAD_URL=https://download.jumpserver.org
RUN set -ex \
&& \
if [ "${TARGETARCH}" == "amd64" ] || [ "${TARGETARCH}" == "arm64" ]; then \
mkdir -p /opt/oracle; \
cd /opt/oracle; \
wget ${DOWNLOAD_URL}/public/instantclient-basiclite-linux.${TARGETARCH}-19.10.0.0.0.zip; \
unzip instantclient-basiclite-linux.${TARGETARCH}-19.10.0.0.0.zip; \
echo "/opt/oracle/instantclient_19_10" > /etc/ld.so.conf.d/oracle-instantclient.conf; \
ldconfig; \
rm -f instantclient-basiclite-linux.${TARGETARCH}-19.10.0.0.0.zip; \
fi
WORKDIR /tmp/build
COPY ./requirements ./requirements
ARG PIP_MIRROR=https://pypi.douban.com/simple
ARG PIP_JMS_MIRROR=https://pypi.douban.com/simple
RUN --mount=type=cache,target=/root/.cache/pip \
set -ex \
&& pip config set global.index-url ${PIP_MIRROR} \
&& pip install --upgrade pip \
&& pip install --upgrade setuptools wheel \
&& \
if [ "${TARGETARCH}" == "loong64" ]; then \
pip install https://download.jumpserver.org/pypi/simple/cryptography/cryptography-38.0.4-cp39-cp39-linux_loongarch64.whl; \
pip install https://download.jumpserver.org/pypi/simple/greenlet/greenlet-1.1.2-cp39-cp39-linux_loongarch64.whl; \
pip install https://download.jumpserver.org/pypi/simple/PyNaCl/PyNaCl-1.5.0-cp39-cp39-linux_loongarch64.whl; \
pip install https://download.jumpserver.org/pypi/simple/grpcio/grpcio-1.54.2-cp39-cp39-linux_loongarch64.whl; \
fi \
&& pip install $(grep -E 'jms|jumpserver' requirements/requirements.txt) -i ${PIP_JMS_MIRROR} \
&& pip install -r requirements/requirements.txt
COPY --from=stage-build /opt/jumpserver/release/jumpserver /opt/jumpserver
RUN echo > /opt/jumpserver/config.yml \
&& rm -rf /tmp/build
COPY --from=stage-2 /opt/py3 /opt/py3
COPY --from=stage-1 /opt/jumpserver/release/jumpserver /opt/jumpserver
WORKDIR /opt/jumpserver
VOLUME /opt/jumpserver/data
VOLUME /opt/jumpserver/logs
ENV LANG=zh_CN.UTF-8
ARG VERSION
ENV VERSION=$VERSION
VOLUME /opt/jumpserver/data
EXPOSE 8080

View File

@@ -1,10 +1,5 @@
ARG VERSION
FROM registry.fit2cloud.com/jumpserver/xpack:${VERSION} as build-xpack
FROM jumpserver/core:${VERSION}
COPY --from=build-xpack /opt/xpack /opt/jumpserver/apps/xpack
FROM registry.fit2cloud.com/jumpserver/core-ce:${VERSION}
WORKDIR /opt/jumpserver
RUN --mount=type=cache,target=/root/.cache/pip \
set -ex \
&& pip install -r requirements/requirements_xpack.txt
COPY --from=build-xpack /opt/xpack /opt/jumpserver/apps/xpack

View File

@@ -12,11 +12,10 @@
<p align="center">
JumpServer <a href="https://github.com/jumpserver/jumpserver/releases/tag/v3.0.0">v3.0</a> 正式发布。
<br>
9 年时间,倾情投入,用心做好一款开源堡垒机。
</p>
------------------------------
JumpServer 是广受欢迎的开源堡垒机,是符合 4A 规范的专业运维安全审计系统。
JumpServer 堡垒机帮助企业以更安全的方式管控和登录各种类型的资产,包括:
@@ -62,6 +61,7 @@ JumpServer 堡垒机帮助企业以更安全的方式管控和登录各种类型
## 案例研究
- [腾讯音乐娱乐集团基于JumpServer的安全运维审计解决方案](https://blog.fit2cloud.com/?p=a04cdf0d-6704-4d18-9b40-9180baecd0e2)
- [腾讯海外游戏基于JumpServer构建游戏安全运营能力](https://blog.fit2cloud.com/?p=3704)
- [万华化学通过JumpServer管理全球化分布式IT资产并且实现与云管平台的联动](https://blog.fit2cloud.com/?p=3504)
- [雪花啤酒JumpServer堡垒机使用体会](https://blog.fit2cloud.com/?p=3412)
@@ -83,9 +83,7 @@ JumpServer 堡垒机帮助企业以更安全的方式管控和登录各种类型
### 参与贡献
欢迎提交 PR 参与贡献。感谢以下贡献者,他们让 JumpServer 变的越来越好。
<a href="https://github.com/jumpserver/jumpserver/graphs/contributors"><img src="https://opencollective.com/jumpserver/contributors.svg?width=890&button=false" /></a>
欢迎提交 PR 参与贡献。 参考 [CONTRIBUTING.md](https://github.com/jumpserver/jumpserver/blob/dev/CONTRIBUTING.md)
## 组件项目
@@ -100,7 +98,7 @@ JumpServer 堡垒机帮助企业以更安全的方式管控和登录各种类型
| [Magnus](https://github.com/jumpserver/magnus-release) | <a href="https://github.com/jumpserver/magnus-release/releases"><img alt="Magnus release" src="https://img.shields.io/github/release/jumpserver/magnus-release.svg" /> | JumpServer 数据库代理 Connector 项目 |
| [Chen](https://github.com/jumpserver/chen-release) | <a href="https://github.com/jumpserver/chen-release/releases"><img alt="Chen release" src="https://img.shields.io/github/release/jumpserver/chen-release.svg" /> | JumpServer Web DB 项目,替代原来的 OmniDB |
| [Kael](https://github.com/jumpserver/kael) | <a href="https://github.com/jumpserver/kael/releases"><img alt="Kael release" src="https://img.shields.io/github/release/jumpserver/kael.svg" /> | JumpServer 连接 GPT 资产的组件项目 |
| [Wisp](https://github.com/jumpserver/wisp) | <a href="https://github.com/jumpserver/wisp/releases"><img alt="Magnus release" src="https://img.shields.io/github/release/jumpserver/wisp.svg" /> | JumpServer 各系统终端组件和 Core Api 通信的组件项目 |
| [Wisp](https://github.com/jumpserver/wisp) | <a href="https://github.com/jumpserver/wisp/releases"><img alt="Magnus release" src="https://img.shields.io/github/release/jumpserver/wisp.svg" /> | JumpServer 各系统终端组件和 Core API 通信的组件项目 |
| [Clients](https://github.com/jumpserver/clients) | <a href="https://github.com/jumpserver/clients/releases"><img alt="Clients release" src="https://img.shields.io/github/release/jumpserver/clients.svg" /> | JumpServer 客户端 项目 |
| [Installer](https://github.com/jumpserver/installer) | <a href="https://github.com/jumpserver/installer/releases"><img alt="Installer release" src="https://img.shields.io/github/release/jumpserver/installer.svg" /> | JumpServer 安装包 项目 |

View File

@@ -1,3 +1,4 @@
from .account import *
from .task import *
from .template import *
from .virtual import *

View File

@@ -6,11 +6,12 @@ from rest_framework.status import HTTP_200_OK
from accounts import serializers
from accounts.filters import AccountFilterSet
from accounts.mixins import AccountRecordViewLogMixin
from accounts.models import Account
from assets.models import Asset, Node
from common.api import ExtraFilterFieldsMixin
from common.permissions import UserConfirmation, ConfirmType, IsValidUser
from common.views.mixins import RecordViewLogMixin
from authentication.permissions import UserConfirmation, ConfirmType
from common.api.mixin import ExtraFilterFieldsMixin
from common.permissions import IsValidUser
from orgs.mixins.api import OrgBulkModelViewSet
from rbac.permissions import RBACPermission
@@ -22,10 +23,11 @@ __all__ = [
class AccountViewSet(OrgBulkModelViewSet):
model = Account
search_fields = ('username', 'name', 'asset__name', 'asset__address')
search_fields = ('username', 'name', 'asset__name', 'asset__address', 'comment')
filterset_class = AccountFilterSet
serializer_classes = {
'default': serializers.AccountSerializer,
'retrieve': serializers.AccountDetailSerializer,
}
rbac_perms = {
'partial_update': ['accounts.change_account'],
@@ -52,22 +54,23 @@ class AccountViewSet(OrgBulkModelViewSet):
return Response(data=serializer.data)
@action(
methods=['get'], detail=False, url_path='username-suggestions',
methods=['post'], detail=False, url_path='username-suggestions',
permission_classes=[IsValidUser]
)
def username_suggestions(self, request, *args, **kwargs):
asset_ids = request.query_params.get('assets')
node_keys = request.query_params.get('keys')
username = request.query_params.get('username')
asset_ids = request.data.get('assets', [])
node_ids = request.data.get('nodes', [])
username = request.data.get('username', '')
accounts = Account.objects.all()
if node_ids:
nodes = Node.objects.filter(id__in=node_ids)
node_asset_ids = Node.get_nodes_all_assets(*nodes).values_list('id', flat=True)
asset_ids.extend(node_asset_ids)
assets = Asset.objects.all()
if asset_ids:
assets = assets.filter(id__in=asset_ids.split(','))
if node_keys:
patten = Node.get_node_all_children_key_pattern(node_keys.split(','))
assets = assets.filter(nodes__key__regex=patten)
accounts = accounts.filter(asset_id__in=list(set(asset_ids)))
accounts = Account.objects.filter(asset__in=assets)
if username:
accounts = accounts.filter(username__icontains=username)
usernames = list(accounts.values_list('username', flat=True).distinct()[:10])
@@ -84,7 +87,7 @@ class AccountViewSet(OrgBulkModelViewSet):
return Response(status=HTTP_200_OK)
class AccountSecretsViewSet(RecordViewLogMixin, AccountViewSet):
class AccountSecretsViewSet(AccountRecordViewLogMixin, AccountViewSet):
"""
因为可能要导出所有账号,所以单独建立了一个 viewset
"""
@@ -113,7 +116,7 @@ class AssetAccountBulkCreateApi(CreateAPIView):
return Response(data=serializer.data, status=HTTP_200_OK)
class AccountHistoriesSecretAPI(ExtraFilterFieldsMixin, RecordViewLogMixin, ListAPIView):
class AccountHistoriesSecretAPI(ExtraFilterFieldsMixin, AccountRecordViewLogMixin, ListAPIView):
model = Account.history.model
serializer_class = serializers.AccountHistorySerializer
http_method_names = ['get', 'options']
@@ -132,11 +135,12 @@ class AccountHistoriesSecretAPI(ExtraFilterFieldsMixin, RecordViewLogMixin, List
def get_queryset(self):
account = self.get_object()
histories = account.history.all()
last_history = account.history.first()
if not last_history:
latest_history = account.history.first()
if not latest_history:
return histories
if account.secret == last_history.secret \
and account.secret_type == last_history.secret_type:
histories = histories.exclude(history_id=last_history.history_id)
if account.secret != latest_history.secret:
return histories
if account.secret_type != latest_history.secret_type:
return histories
histories = histories.exclude(history_id=latest_history.history_id)
return histories

View File

@@ -19,7 +19,9 @@ class AccountsTaskCreateAPI(CreateAPIView):
code = 'accounts.push_account'
else:
code = 'accounts.verify_account'
return request.user.has_perm(code)
has = request.user.has_perm(code)
if not has:
self.permission_denied(request)
def perform_create(self, serializer):
data = serializer.validated_data
@@ -44,6 +46,6 @@ class AccountsTaskCreateAPI(CreateAPIView):
def get_exception_handler(self):
def handler(e, context):
return Response({"error": str(e)}, status=400)
return Response({"error": str(e)}, status=401)
return handler

View File

@@ -1,13 +1,15 @@
from django_filters import rest_framework as drf_filters
from rest_framework import status
from rest_framework.decorators import action
from rest_framework.response import Response
from accounts import serializers
from accounts.mixins import AccountRecordViewLogMixin
from accounts.models import AccountTemplate
from accounts.tasks import template_sync_related_accounts
from assets.const import Protocol
from authentication.permissions import UserConfirmation, ConfirmType
from common.drf.filters import BaseFilterSet
from common.permissions import UserConfirmation, ConfirmType
from common.views.mixins import RecordViewLogMixin
from orgs.mixins.api import OrgBulkModelViewSet
from rbac.permissions import RBACPermission
@@ -44,6 +46,7 @@ class AccountTemplateViewSet(OrgBulkModelViewSet):
}
rbac_perms = {
'su_from_account_templates': 'accounts.view_accounttemplate',
'sync_related_accounts': 'accounts.change_account',
}
@action(methods=['get'], detail=False, url_path='su-from-account-templates')
@@ -54,8 +57,15 @@ class AccountTemplateViewSet(OrgBulkModelViewSet):
serializer = self.get_serializer(templates, many=True)
return Response(data=serializer.data)
@action(methods=['patch'], detail=True, url_path='sync-related-accounts')
def sync_related_accounts(self, request, *args, **kwargs):
instance = self.get_object()
user_id = str(request.user.id)
task = template_sync_related_accounts.delay(str(instance.id), user_id)
return Response({'task': task.id}, status=status.HTTP_200_OK)
class AccountTemplateSecretsViewSet(RecordViewLogMixin, AccountTemplateViewSet):
class AccountTemplateSecretsViewSet(AccountRecordViewLogMixin, AccountTemplateViewSet):
serializer_classes = {
'default': serializers.AccountTemplateSecretSerializer,
}

View File

@@ -0,0 +1,20 @@
from django.shortcuts import get_object_or_404
from accounts.models import VirtualAccount
from accounts.serializers import VirtualAccountSerializer
from common.utils import is_uuid
from orgs.mixins.api import OrgBulkModelViewSet
class VirtualAccountViewSet(OrgBulkModelViewSet):
serializer_class = VirtualAccountSerializer
search_fields = ('alias',)
filterset_fields = ('alias',)
def get_queryset(self):
return VirtualAccount.get_or_init_queryset()
def get_object(self, ):
pk = self.kwargs.get('pk')
kwargs = {'pk': pk} if is_uuid(pk) else {'alias': pk}
return get_object_or_404(VirtualAccount, **kwargs)

View File

@@ -26,8 +26,8 @@ class AccountBackupPlanViewSet(OrgBulkModelViewSet):
class AccountBackupPlanExecutionViewSet(viewsets.ModelViewSet):
serializer_class = serializers.AccountBackupPlanExecutionSerializer
search_fields = ('trigger',)
filterset_fields = ('trigger', 'plan_id')
search_fields = ('trigger', 'plan__name')
filterset_fields = ('trigger', 'plan_id', 'plan__name')
http_method_names = ['get', 'post', 'options']
def get_queryset(self):

View File

@@ -1,5 +1,5 @@
from django.shortcuts import get_object_or_404
from django.utils.translation import ugettext_lazy as _
from django.utils.translation import gettext_lazy as _
from rest_framework import status, mixins, viewsets
from rest_framework.response import Response
@@ -95,8 +95,8 @@ class AutomationExecutionViewSet(
mixins.CreateModelMixin, mixins.ListModelMixin,
mixins.RetrieveModelMixin, viewsets.GenericViewSet
):
search_fields = ('trigger',)
filterset_fields = ('trigger', 'automation_id')
search_fields = ('trigger', 'automation__name')
filterset_fields = ('trigger', 'automation_id', 'automation__name')
serializer_class = serializers.AutomationExecutionSerializer
tp: str

View File

@@ -1,12 +1,13 @@
# -*- coding: utf-8 -*-
#
from rest_framework import mixins
from rest_framework import status, mixins
from rest_framework.decorators import action
from rest_framework.response import Response
from accounts import serializers
from accounts.const import AutomationTypes
from accounts.models import ChangeSecretAutomation, ChangeSecretRecord, AutomationExecution
from common.utils import get_object_or_none
from accounts.models import ChangeSecretAutomation, ChangeSecretRecord
from accounts.tasks import execute_automation_record_task
from orgs.mixins.api import OrgBulkModelViewSet, OrgGenericViewSet
from .base import (
AutomationAssetsListApi, AutomationRemoveAssetApi, AutomationAddAssetApi,
@@ -30,21 +31,27 @@ class ChangeSecretAutomationViewSet(OrgBulkModelViewSet):
class ChangeSecretRecordViewSet(mixins.ListModelMixin, OrgGenericViewSet):
serializer_class = serializers.ChangeSecretRecordSerializer
filter_fields = ['asset', 'execution_id']
search_fields = ['asset__hostname']
filterset_fields = ('asset_id', 'execution_id')
search_fields = ('asset__address',)
tp = AutomationTypes.change_secret
rbac_perms = {
'execute': 'accounts.add_changesecretexecution',
}
def get_queryset(self):
return ChangeSecretRecord.objects.filter(
execution__automation__type=AutomationTypes.change_secret
)
return ChangeSecretRecord.objects.all()
def filter_queryset(self, queryset):
queryset = super().filter_queryset(queryset)
eid = self.request.query_params.get('execution_id')
execution = get_object_or_none(AutomationExecution, pk=eid)
if execution:
queryset = queryset.filter(execution=execution)
return queryset
@action(methods=['post'], detail=False, url_path='execute')
def execute(self, request, *args, **kwargs):
record_id = request.data.get('record_id')
record = self.get_queryset().filter(pk=record_id)
if not record:
return Response(
{'detail': 'record not found'},
status=status.HTTP_404_NOT_FOUND
)
task = execute_automation_record_task.delay(record_id, self.tp)
return Response({'task': task.id}, status=status.HTTP_200_OK)
class ChangSecretExecutionViewSet(AutomationExecutionViewSet):

View File

@@ -42,6 +42,7 @@ class PushAccountExecutionViewSet(AutomationExecutionViewSet):
class PushAccountRecordViewSet(ChangeSecretRecordViewSet):
serializer_class = serializers.ChangeSecretRecordSerializer
tp = AutomationTypes.push_account
def get_queryset(self):
return ChangeSecretRecord.objects.filter(

View File

@@ -6,6 +6,5 @@ class AccountsConfig(AppConfig):
name = 'accounts'
def ready(self):
from . import signal_handlers
from . import tasks
__all__ = signal_handlers
from . import signal_handlers # noqa
from . import tasks # noqa

View File

@@ -1,26 +1,28 @@
import os
import time
from openpyxl import Workbook
from collections import defaultdict, OrderedDict
from django.conf import settings
from django.db.models import F
from openpyxl import Workbook
from rest_framework import serializers
from accounts.models import Account
from assets.const import AllTypes
from accounts.const.automation import AccountBackupType
from accounts.notifications import AccountBackupExecutionTaskMsg, AccountBackupByObjStorageExecutionTaskMsg
from accounts.serializers import AccountSecretSerializer
from accounts.notifications import AccountBackupExecutionTaskMsg
from accounts.models.automations.backup_account import AccountBackupAutomation
from assets.const import AllTypes
from common.utils.file import encrypt_and_compress_zip_file, zip_files
from common.utils.timezone import local_now_filename, local_now_display
from terminal.models.component.storage import ReplayStorage
from users.models import User
from common.utils import get_logger
from common.utils.timezone import local_now_display
from common.utils.file import encrypt_and_compress_zip_file
logger = get_logger(__file__)
PATH = os.path.join(os.path.dirname(settings.BASE_DIR), 'tmp')
class RecipientsNotFound(Exception):
pass
class BaseAccountHandler:
@classmethod
def unpack_data(cls, serializer_data, data=None):
@@ -72,12 +74,26 @@ class AssetAccountHandler(BaseAccountHandler):
@staticmethod
def get_filename(plan_name):
filename = os.path.join(
PATH, f'{plan_name}-{local_now_display()}-{time.time()}.xlsx'
PATH, f'{plan_name}-{local_now_filename()}-{time.time()}.xlsx'
)
return filename
@staticmethod
def handler_secret(data, section):
for account_data in data:
secret = account_data.get('secret')
if not secret:
continue
length = len(secret)
index = length // 2
if section == "front":
secret = secret[:index] + '*' * (length - index)
elif section == "back":
secret = '*' * (length - index) + secret[index:]
account_data['secret'] = secret
@classmethod
def create_data_map(cls, accounts):
def create_data_map(cls, accounts, section):
data_map = defaultdict(list)
if not accounts.exists():
@@ -97,9 +113,10 @@ class AssetAccountHandler(BaseAccountHandler):
for tp, _accounts in account_type_map.items():
sheet_name = type_dict.get(tp, tp)
data = AccountSecretSerializer(_accounts, many=True).data
cls.handler_secret(data, section)
data_map.update(cls.add_rows(data, header_fields, sheet_name))
logger.info('\n\033[33m- 共备份 {} 条账号\033[0m'.format(accounts.count()))
print('\n\033[33m- 共备份 {} 条账号\033[0m'.format(accounts.count()))
return data_map
@@ -109,8 +126,8 @@ class AccountBackupHandler:
self.plan_name = self.execution.plan.name
self.is_frozen = False # 任务状态冻结标志
def create_excel(self):
logger.info(
def create_excel(self, section='complete'):
print(
'\n'
'\033[32m>>> 正在生成资产或应用相关备份信息文件\033[0m'
''
@@ -119,7 +136,7 @@ class AccountBackupHandler:
time_start = time.time()
files = []
accounts = self.execution.backup_accounts
data_map = AssetAccountHandler.create_data_map(accounts)
data_map = AssetAccountHandler.create_data_map(accounts, section)
if not data_map:
return files
@@ -133,16 +150,16 @@ class AccountBackupHandler:
wb.save(filename)
files.append(filename)
timedelta = round((time.time() - time_start), 2)
logger.info('步骤完成: 用时 {}s'.format(timedelta))
print('创建备份文件完成: 用时 {}s'.format(timedelta))
return files
def send_backup_mail(self, files, recipients):
if not files:
return
recipients = User.objects.filter(id__in=list(recipients))
logger.info(
print(
'\n'
'\033[32m>>> 发送备份邮件\033[0m'
'\033[32m>>> 开始发送备份邮件\033[0m'
''
)
plan_name = self.plan_name
@@ -151,11 +168,35 @@ class AccountBackupHandler:
attachment_list = []
else:
password = user.secret_key.encode('utf8')
attachment = os.path.join(PATH, f'{plan_name}-{local_now_display()}-{time.time()}.zip')
attachment = os.path.join(PATH, f'{plan_name}-{local_now_filename()}-{time.time()}.zip')
encrypt_and_compress_zip_file(attachment, password, files)
attachment_list = [attachment, ]
AccountBackupExecutionTaskMsg(plan_name, user).publish(attachment_list)
logger.info('邮件已发送至{}({})'.format(user, user.email))
print('邮件已发送至{}({})'.format(user, user.email))
for file in files:
os.remove(file)
def send_backup_obj_storage(self, files, recipients, password):
if not files:
return
recipients = ReplayStorage.objects.filter(id__in=list(recipients))
print(
'\n'
'\033[32m>>> 开始发送备份文件到sftp服务器\033[0m'
''
)
plan_name = self.plan_name
for rec in recipients:
attachment = os.path.join(PATH, f'{plan_name}-{local_now_filename()}-{time.time()}.zip')
if password:
print('\033[32m>>> 使用加密密码对文件进行加密中\033[0m')
password = password.encode('utf8')
encrypt_and_compress_zip_file(attachment, password, files)
else:
zip_files(attachment, files)
attachment_list = attachment
AccountBackupByObjStorageExecutionTaskMsg(plan_name, rec).publish(attachment_list)
print('备份文件将发送至{}({})'.format(rec.name, rec.id))
for file in files:
os.remove(file)
@@ -163,33 +204,29 @@ class AccountBackupHandler:
self.execution.reason = reason[:1024]
self.execution.is_success = is_success
self.execution.save()
logger.info('已完成对任务状态的更新')
print('\n已完成对任务状态的更新\n')
def step_finished(self, is_success):
@staticmethod
def step_finished(is_success):
if is_success:
logger.info('任务执行成功')
print('任务执行成功')
else:
logger.error('任务执行失败')
print('任务执行失败')
def _run(self):
is_success = False
error = '-'
try:
recipients = self.execution.plan_snapshot.get('recipients')
if not recipients:
logger.info(
'\n'
'\033[32m>>> 该备份任务未分配收件人\033[0m'
''
)
else:
files = self.create_excel()
self.send_backup_mail(files, recipients)
backup_type = self.execution.snapshot.get('backup_type', AccountBackupType.email.value)
if backup_type == AccountBackupType.email.value:
self.backup_by_email()
elif backup_type == AccountBackupType.object_storage.value:
self.backup_by_obj_storage()
except Exception as e:
self.is_frozen = True
logger.error('任务执行被异常中断')
logger.info('下面打印发生异常的 Traceback 信息 : ')
logger.error(e, exc_info=True)
print('任务执行被异常中断')
print('下面打印发生异常的 Traceback 信息 : ')
print(e)
error = str(e)
else:
is_success = True
@@ -198,16 +235,62 @@ class AccountBackupHandler:
self.step_perform_task_update(is_success, reason)
self.step_finished(is_success)
def backup_by_obj_storage(self):
object_id = self.execution.snapshot.get('id')
zip_encrypt_password = AccountBackupAutomation.objects.get(id=object_id).zip_encrypt_password
obj_recipients_part_one = self.execution.snapshot.get('obj_recipients_part_one', [])
obj_recipients_part_two = self.execution.snapshot.get('obj_recipients_part_two', [])
if not obj_recipients_part_one and not obj_recipients_part_two:
print(
'\n'
'\033[31m>>> 该备份任务未分配sftp服务器\033[0m'
''
)
raise RecipientsNotFound('Not Found Recipients')
if obj_recipients_part_one and obj_recipients_part_two:
print('\033[32m>>> 账号的密钥将被拆分成前后两部分发送\033[0m')
files = self.create_excel(section='front')
self.send_backup_obj_storage(files, obj_recipients_part_one, zip_encrypt_password)
files = self.create_excel(section='back')
self.send_backup_obj_storage(files, obj_recipients_part_two, zip_encrypt_password)
else:
recipients = obj_recipients_part_one or obj_recipients_part_two
files = self.create_excel()
self.send_backup_obj_storage(files, recipients, zip_encrypt_password)
def backup_by_email(self):
recipients_part_one = self.execution.snapshot.get('recipients_part_one', [])
recipients_part_two = self.execution.snapshot.get('recipients_part_two', [])
if not recipients_part_one and not recipients_part_two:
print(
'\n'
'\033[31m>>> 该备份任务未分配收件人\033[0m'
''
)
raise RecipientsNotFound('Not Found Recipients')
if recipients_part_one and recipients_part_two:
print('\033[32m>>> 账号的密钥将被拆分成前后两部分发送\033[0m')
files = self.create_excel(section='front')
self.send_backup_mail(files, recipients_part_one)
files = self.create_excel(section='back')
self.send_backup_mail(files, recipients_part_two)
else:
recipients = recipients_part_one or recipients_part_two
files = self.create_excel()
self.send_backup_mail(files, recipients)
def run(self):
logger.info('任务开始: {}'.format(local_now_display()))
print('任务开始: {}'.format(local_now_display()))
time_start = time.time()
try:
self._run()
except Exception as e:
logger.error('任务运行出现异常')
logger.error('下面显示异常 Traceback 信息: ')
logger.error(e, exc_info=True)
print('任务运行出现异常')
print('下面显示异常 Traceback 信息: ')
print(e)
finally:
logger.info('\n任务结束: {}'.format(local_now_display()))
print('\n任务结束: {}'.format(local_now_display()))
timedelta = round((time.time() - time_start), 2)
logger.info('用时: {}'.format(timedelta))
print('用时: {}s'.format(timedelta))

View File

@@ -4,13 +4,9 @@ import time
from django.utils import timezone
from common.utils import get_logger
from common.utils.timezone import local_now_display
from .handlers import AccountBackupHandler
logger = get_logger(__name__)
class AccountBackupManager:
def __init__(self, execution):
@@ -23,7 +19,7 @@ class AccountBackupManager:
def do_run(self):
execution = self.execution
logger.info('\n\033[33m# 账号备份计划正在执行\033[0m')
print('\n\033[33m# 账号备份计划正在执行\033[0m')
handler = AccountBackupHandler(execution)
handler.run()
@@ -35,10 +31,10 @@ class AccountBackupManager:
self.time_end = time.time()
self.date_end = timezone.now()
logger.info('\n\n' + '-' * 80)
logger.info('计划执行结束 {}\n'.format(local_now_display()))
print('\n\n' + '-' * 80)
print('计划执行结束 {}\n'.format(local_now_display()))
self.timedelta = self.time_end - self.time_start
logger.info('用时: {}s'.format(self.timedelta))
print('用时: {}s'.format(self.timedelta))
self.execution.timedelta = self.timedelta
self.execution.save()

View File

@@ -1,27 +1,40 @@
- hosts: custom
gather_facts: no
vars:
asset_port: "{{ jms_asset.protocols | selectattr('name', 'equalto', 'ssh') | map(attribute='port') | first }}"
ansible_connection: local
ansible_become: false
tasks:
- name: Test privileged account
- name: Test privileged account (paramiko)
ssh_ping:
login_host: "{{ jms_asset.address }}"
login_port: "{{ jms_asset.port }}"
login_port: "{{ asset_port }}"
login_user: "{{ jms_account.username }}"
login_password: "{{ jms_account.secret }}"
login_secret_type: "{{ jms_account.secret_type }}"
login_private_key_path: "{{ jms_account.private_key_path }}"
become: "{{ custom_become | default(False) }}"
become_method: "{{ custom_become_method | default('su') }}"
become_user: "{{ custom_become_user | default('') }}"
become_password: "{{ custom_become_password | default('') }}"
become_private_key_path: "{{ custom_become_private_key_path | default(None) }}"
register: ping_info
delegate_to: localhost
- name: Change asset password
- name: Change asset password (paramiko)
custom_command:
login_user: "{{ jms_account.username }}"
login_password: "{{ jms_account.secret }}"
login_host: "{{ jms_asset.address }}"
login_port: "{{ jms_asset.port }}"
login_port: "{{ asset_port }}"
login_secret_type: "{{ jms_account.secret_type }}"
login_private_key_path: "{{ jms_account.private_key_path }}"
become: "{{ custom_become | default(False) }}"
become_method: "{{ custom_become_method | default('su') }}"
become_user: "{{ custom_become_user | default('') }}"
become_password: "{{ custom_become_password | default('') }}"
become_private_key_path: "{{ custom_become_private_key_path | default(None) }}"
name: "{{ account.username }}"
password: "{{ account.secret }}"
commands: "{{ params.commands }}"
@@ -29,10 +42,17 @@
ignore_errors: true
when: ping_info is succeeded
register: change_info
delegate_to: localhost
- name: Verify password
- name: Verify password (paramiko)
ssh_ping:
login_user: "{{ account.username }}"
login_password: "{{ account.secret }}"
login_host: "{{ jms_asset.address }}"
login_port: "{{ jms_asset.port }}"
login_port: "{{ asset_port }}"
become: "{{ account.become.ansible_become | default(False) }}"
become_method: su
become_user: "{{ account.become.ansible_user | default('') }}"
become_password: "{{ account.become.ansible_password | default('') }}"
become_private_key_path: "{{ account.become.ansible_ssh_private_key_file | default(None) }}"
delegate_to: localhost

View File

@@ -1,7 +1,7 @@
- hosts: mongodb
gather_facts: no
vars:
ansible_python_interpreter: /usr/local/bin/python
ansible_python_interpreter: /opt/py3/bin/python
tasks:
- name: Test MongoDB connection
@@ -11,9 +11,9 @@
login_host: "{{ jms_asset.address }}"
login_port: "{{ jms_asset.port }}"
login_database: "{{ jms_asset.spec_info.db_name }}"
ssl: "{{ jms_asset.spec_info.use_ssl }}"
ssl_ca_certs: "{{ jms_asset.secret_info.ca_cert }}"
ssl_certfile: "{{ jms_asset.secret_info.client_key }}"
ssl: "{{ jms_asset.spec_info.use_ssl | default('') }}"
ssl_ca_certs: "{{ jms_asset.secret_info.ca_cert | default('') }}"
ssl_certfile: "{{ jms_asset.secret_info.client_key | default('') }}"
connection_options:
- tlsAllowInvalidHostnames: "{{ jms_asset.spec_info.allow_invalid_cert}}"
register: db_info
@@ -31,8 +31,8 @@
login_port: "{{ jms_asset.port }}"
login_database: "{{ jms_asset.spec_info.db_name }}"
ssl: "{{ jms_asset.spec_info.use_ssl }}"
ssl_ca_certs: "{{ jms_asset.secret_info.ca_cert }}"
ssl_certfile: "{{ jms_asset.secret_info.client_key }}"
ssl_ca_certs: "{{ jms_asset.secret_info.ca_cert | default('') }}"
ssl_certfile: "{{ jms_asset.secret_info.client_key | default('') }}"
connection_options:
- tlsAllowInvalidHostnames: "{{ jms_asset.spec_info.allow_invalid_cert}}"
db: "{{ jms_asset.spec_info.db_name }}"
@@ -49,7 +49,7 @@
login_port: "{{ jms_asset.port }}"
login_database: "{{ jms_asset.spec_info.db_name }}"
ssl: "{{ jms_asset.spec_info.use_ssl }}"
ssl_ca_certs: "{{ jms_asset.secret_info.ca_cert }}"
ssl_certfile: "{{ jms_asset.secret_info.client_key }}"
ssl_ca_certs: "{{ jms_asset.secret_info.ca_cert | default('') }}"
ssl_certfile: "{{ jms_asset.secret_info.client_key | default('') }}"
connection_options:
- tlsAllowInvalidHostnames: "{{ jms_asset.spec_info.allow_invalid_cert}}"

View File

@@ -1,8 +1,9 @@
- hosts: mysql
gather_facts: no
vars:
ansible_python_interpreter: /usr/local/bin/python
ansible_python_interpreter: /opt/py3/bin/python
db_name: "{{ jms_asset.spec_info.db_name }}"
check_ssl: "{{ jms_asset.spec_info.use_ssl and not jms_asset.spec_info.allow_invalid_cert }}"
tasks:
- name: Test MySQL connection
@@ -11,6 +12,10 @@
login_password: "{{ jms_account.secret }}"
login_host: "{{ jms_asset.address }}"
login_port: "{{ jms_asset.port }}"
check_hostname: "{{ check_ssl if check_ssl else omit }}"
ca_cert: "{{ jms_asset.secret_info.ca_cert | default(omit) if check_ssl else omit }}"
client_cert: "{{ jms_asset.secret_info.client_cert | default(omit) if check_ssl else omit }}"
client_key: "{{ jms_asset.secret_info.client_key | default(omit) if check_ssl else omit }}"
filter: version
register: db_info
@@ -24,6 +29,10 @@
login_password: "{{ jms_account.secret }}"
login_host: "{{ jms_asset.address }}"
login_port: "{{ jms_asset.port }}"
check_hostname: "{{ check_ssl if check_ssl else omit }}"
ca_cert: "{{ jms_asset.secret_info.ca_cert | default(omit) if check_ssl else omit }}"
client_cert: "{{ jms_asset.secret_info.client_cert | default(omit) if check_ssl else omit }}"
client_key: "{{ jms_asset.secret_info.client_key | default(omit) if check_ssl else omit }}"
name: "{{ account.username }}"
password: "{{ account.secret }}"
host: "%"
@@ -37,4 +46,8 @@
login_password: "{{ account.secret }}"
login_host: "{{ jms_asset.address }}"
login_port: "{{ jms_asset.port }}"
check_hostname: "{{ check_ssl if check_ssl else omit }}"
ca_cert: "{{ jms_asset.secret_info.ca_cert | default(omit) if check_ssl else omit }}"
client_cert: "{{ jms_asset.secret_info.client_cert | default(omit) if check_ssl else omit }}"
client_key: "{{ jms_asset.secret_info.client_key | default(omit) if check_ssl else omit }}"
filter: version

View File

@@ -1,7 +1,7 @@
- hosts: oracle
gather_facts: no
vars:
ansible_python_interpreter: /usr/local/bin/python
ansible_python_interpreter: /opt/py3/bin/python
tasks:
- name: Test Oracle connection

View File

@@ -1,7 +1,7 @@
- hosts: postgre
gather_facts: no
vars:
ansible_python_interpreter: /usr/local/bin/python
ansible_python_interpreter: /opt/py3/bin/python
tasks:
- name: Test PostgreSQL connection

View File

@@ -1,7 +1,7 @@
- hosts: sqlserver
gather_facts: no
vars:
ansible_python_interpreter: /usr/local/bin/python
ansible_python_interpreter: /opt/py3/bin/python
tasks:
- name: Test SQLServer connection
@@ -40,7 +40,7 @@
login_host: "{{ jms_asset.address }}"
login_port: "{{ jms_asset.port }}"
name: '{{ jms_asset.spec_info.db_name }}'
script: "ALTER LOGIN {{ account.username }} WITH PASSWORD = '{{ account.secret }}'; select @@version"
script: "ALTER LOGIN {{ account.username }} WITH PASSWORD = '{{ account.secret }}', DEFAULT_DATABASE = {{ jms_asset.spec_info.db_name }}; select @@version"
ignore_errors: true
when: user_exist.query_results[0] | length != 0
@@ -51,7 +51,7 @@
login_host: "{{ jms_asset.address }}"
login_port: "{{ jms_asset.port }}"
name: '{{ jms_asset.spec_info.db_name }}'
script: "CREATE LOGIN {{ account.username }} WITH PASSWORD = '{{ account.secret }}'; select @@version"
script: "CREATE LOGIN {{ account.username }} WITH PASSWORD = '{{ account.secret }}', DEFAULT_DATABASE = {{ jms_asset.spec_info.db_name }}; CREATE USER {{ account.username }} FOR LOGIN {{ account.username }}; select @@version"
ignore_errors: true
when: user_exist.query_results[0] | length == 0

View File

@@ -1,10 +1,41 @@
- hosts: demo
gather_facts: no
tasks:
- name: Test privileged account
- name: "Test privileged {{ jms_account.username }} account"
ansible.builtin.ping:
- name: Change password
- name: "Check if {{ account.username }} user exists"
getent:
database: passwd
key: "{{ account.username }}"
register: user_info
ignore_errors: yes # 忽略错误如果用户不存在时不会导致playbook失败
- name: "Add {{ account.username }} user"
ansible.builtin.user:
name: "{{ account.username }}"
shell: "{{ params.shell }}"
home: "{{ params.home | default('/home/' + account.username, true) }}"
groups: "{{ params.groups }}"
expires: -1
state: present
when: user_info.failed
- name: "Add {{ account.username }} group"
ansible.builtin.group:
name: "{{ account.username }}"
state: present
when: user_info.failed
- name: "Add {{ account.username }} user to group"
ansible.builtin.user:
name: "{{ account.username }}"
groups: "{{ params.groups }}"
when:
- user_info.failed
- params.groups
- name: "Change {{ account.username }} password"
ansible.builtin.user:
name: "{{ account.username }}"
password: "{{ account.secret | password_hash('des') }}"
@@ -12,44 +43,57 @@
ignore_errors: true
when: account.secret_type == "password"
- name: create user If it already exists, no operation will be performed
ansible.builtin.user:
name: "{{ account.username }}"
when: account.secret_type == "ssh_key"
- name: remove jumpserver ssh key
ansible.builtin.lineinfile:
dest: "{{ ssh_params.dest }}"
regexp: "{{ ssh_params.regexp }}"
state: absent
when:
- account.secret_type == "ssh_key"
- ssh_params.strategy == "set_jms"
- account.secret_type == "ssh_key"
- ssh_params.strategy == "set_jms"
- name: Change SSH key
- name: "Change {{ account.username }} SSH key"
ansible.builtin.authorized_key:
user: "{{ account.username }}"
key: "{{ account.secret }}"
exclusive: "{{ ssh_params.exclusive }}"
when: account.secret_type == "ssh_key"
- name: "Set {{ account.username }} sudo setting"
ansible.builtin.lineinfile:
dest: /etc/sudoers
state: present
regexp: "^{{ account.username }} ALL="
line: "{{ account.username + ' ALL=(ALL) NOPASSWD: ' + params.sudo }}"
validate: visudo -cf %s
when:
- user_info.failed
- params.sudo
- name: Refresh connection
ansible.builtin.meta: reset_connection
- name: Verify password
ansible.builtin.ping:
become: no
vars:
ansible_user: "{{ account.username }}"
ansible_password: "{{ account.secret }}"
ansible_become: no
- name: "Verify {{ account.username }} password (paramiko)"
ssh_ping:
login_user: "{{ account.username }}"
login_password: "{{ account.secret }}"
login_host: "{{ jms_asset.address }}"
login_port: "{{ jms_asset.port }}"
gateway_args: "{{ jms_asset.ansible_ssh_common_args | default('') }}"
become: "{{ account.become.ansible_become | default(False) }}"
become_method: su
become_user: "{{ account.become.ansible_user | default('') }}"
become_password: "{{ account.become.ansible_password | default('') }}"
become_private_key_path: "{{ account.become.ansible_ssh_private_key_file | default(None) }}"
when: account.secret_type == "password"
delegate_to: localhost
- name: Verify SSH key
ansible.builtin.ping:
become: no
vars:
ansible_user: "{{ account.username }}"
ansible_ssh_private_key_file: "{{ account.private_key_path }}"
ansible_become: no
- name: "Verify {{ account.username }} SSH KEY (paramiko)"
ssh_ping:
login_host: "{{ jms_asset.address }}"
login_port: "{{ jms_asset.port }}"
login_user: "{{ account.username }}"
login_private_key_path: "{{ account.private_key_path }}"
gateway_args: "{{ jms_asset.ansible_ssh_common_args | default('') }}"
when: account.secret_type == "ssh_key"
delegate_to: localhost

View File

@@ -1,10 +1,17 @@
- hosts: demo
gather_facts: no
tasks:
- name: Test privileged account
- name: "Test privileged {{ jms_account.username }} account"
ansible.builtin.ping:
- name: Check user
- name: "Check if {{ account.username }} user exists"
getent:
database: passwd
key: "{{ account.username }}"
register: user_info
ignore_errors: yes # 忽略错误如果用户不存在时不会导致playbook失败
- name: "Add {{ account.username }} user"
ansible.builtin.user:
name: "{{ account.username }}"
shell: "{{ params.shell }}"
@@ -12,19 +19,23 @@
groups: "{{ params.groups }}"
expires: -1
state: present
when: user_info.failed
- name: "Add {{ account.username }} group"
ansible.builtin.group:
name: "{{ account.username }}"
state: present
when: user_info.failed
- name: Add user groups
- name: "Add {{ account.username }} user to group"
ansible.builtin.user:
name: "{{ account.username }}"
groups: "{{ params.groups }}"
when: params.groups
when:
- user_info.failed
- params.groups
- name: Change password
- name: "Change {{ account.username }} password"
ansible.builtin.user:
name: "{{ account.username }}"
password: "{{ account.secret | password_hash('sha512') }}"
@@ -32,11 +43,6 @@
ignore_errors: true
when: account.secret_type == "password"
- name: create user If it already exists, no operation will be performed
ansible.builtin.user:
name: "{{ account.username }}"
when: account.secret_type == "ssh_key"
- name: remove jumpserver ssh key
ansible.builtin.lineinfile:
dest: "{{ ssh_params.dest }}"
@@ -46,14 +52,14 @@
- account.secret_type == "ssh_key"
- ssh_params.strategy == "set_jms"
- name: Change SSH key
- name: "Change {{ account.username }} SSH key"
ansible.builtin.authorized_key:
user: "{{ account.username }}"
key: "{{ account.secret }}"
exclusive: "{{ ssh_params.exclusive }}"
when: account.secret_type == "ssh_key"
- name: Set sudo setting
- name: "Set {{ account.username }} sudo setting"
ansible.builtin.lineinfile:
dest: /etc/sudoers
state: present
@@ -61,25 +67,33 @@
line: "{{ account.username + ' ALL=(ALL) NOPASSWD: ' + params.sudo }}"
validate: visudo -cf %s
when:
- user_info.failed
- params.sudo
- name: Refresh connection
ansible.builtin.meta: reset_connection
- name: Verify password
ansible.builtin.ping:
become: no
vars:
ansible_user: "{{ account.username }}"
ansible_password: "{{ account.secret }}"
ansible_become: no
- name: "Verify {{ account.username }} password (paramiko)"
ssh_ping:
login_user: "{{ account.username }}"
login_password: "{{ account.secret }}"
login_host: "{{ jms_asset.address }}"
login_port: "{{ jms_asset.port }}"
gateway_args: "{{ jms_asset.ansible_ssh_common_args | default('') }}"
become: "{{ account.become.ansible_become | default(False) }}"
become_method: su
become_user: "{{ account.become.ansible_user | default('') }}"
become_password: "{{ account.become.ansible_password | default('') }}"
become_private_key_path: "{{ account.become.ansible_ssh_private_key_file | default(None) }}"
when: account.secret_type == "password"
delegate_to: localhost
- name: Verify SSH key
ansible.builtin.ping:
become: no
vars:
ansible_user: "{{ account.username }}"
ansible_ssh_private_key_file: "{{ account.private_key_path }}"
ansible_become: no
- name: "Verify {{ account.username }} SSH KEY (paramiko)"
ssh_ping:
login_host: "{{ jms_asset.address }}"
login_port: "{{ jms_asset.port }}"
login_user: "{{ account.username }}"
login_private_key_path: "{{ account.private_key_path }}"
gateway_args: "{{ jms_asset.ansible_ssh_common_args | default('') }}"
when: account.secret_type == "ssh_key"
delegate_to: localhost

View File

@@ -0,0 +1,35 @@
- hosts: demo
gather_facts: no
tasks:
- name: Test privileged account
ansible.windows.win_ping:
# - name: Print variables
# debug:
# msg: "Username: {{ account.username }}, Password: {{ account.secret }}"
- name: Change password
ansible.windows.win_user:
fullname: "{{ account.username}}"
name: "{{ account.username }}"
password: "{{ account.secret }}"
password_never_expires: yes
groups: "{{ params.groups }}"
groups_action: add
update_password: always
ignore_errors: true
when: account.secret_type == "password"
- name: Refresh connection
ansible.builtin.meta: reset_connection
- name: Verify password (pyfreerdp)
rdp_ping:
login_host: "{{ jms_asset.address }}"
login_port: "{{ jms_asset.protocols | selectattr('name', 'equalto', 'rdp') | map(attribute='port') | first }}"
login_user: "{{ account.username }}"
login_password: "{{ account.secret }}"
login_secret_type: "{{ account.secret_type }}"
login_private_key_path: "{{ account.private_key_path }}"
when: account.secret_type == "password"
delegate_to: localhost

View File

@@ -0,0 +1,26 @@
id: change_secret_windows_rdp_verify
name: "{{ 'Windows account change secret rdp verify' | trans }}"
version: 1
method: change_secret
category: host
type:
- windows
params:
- name: groups
type: str
label: '用户组'
default: 'Users,Remote Desktop Users'
help_text: "{{ 'Params groups help text' | trans }}"
i18n:
Windows account change secret rdp verify:
zh: '使用 Ansible 模块 win_user 执行 Windows 账号改密 RDP 协议测试最后的可连接性'
ja: 'Ansibleモジュールwin_userはWindowsアカウントの改密RDPプロトコルテストの最後の接続性を実行する'
en: 'Using the Ansible module win_user performs Windows account encryption RDP protocol testing for final connectivity'
Params groups help text:
zh: '请输入用户组,多个用户组使用逗号分隔(需填写已存在的用户组)'
ja: 'グループを入力してください。複数のグループはコンマで区切ってください(既存のグループを入力してください)'
en: 'Please enter the group. Multiple groups are separated by commas (please enter the existing group)'

View File

@@ -1,6 +1,5 @@
import os
import time
from collections import defaultdict
from copy import deepcopy
from django.conf import settings
@@ -14,7 +13,7 @@ from accounts.serializers import ChangeSecretRecordBackUpSerializer
from assets.const import HostTypes
from common.utils import get_logger
from common.utils.file import encrypt_and_compress_zip_file
from common.utils.timezone import local_now_display
from common.utils.timezone import local_now_filename
from users.models import User
from ..base.manager import AccountBasePlaybookManager
from ...utils import SecretGenerator
@@ -27,7 +26,7 @@ class ChangeSecretManager(AccountBasePlaybookManager):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.method_hosts_mapper = defaultdict(list)
self.record_id = self.execution.snapshot.get('record_id')
self.secret_type = self.execution.snapshot.get('secret_type')
self.secret_strategy = self.execution.snapshot.get(
'secret_strategy', SecretStrategy.custom
@@ -50,7 +49,9 @@ class ChangeSecretManager(AccountBasePlaybookManager):
kwargs['exclusive'] = 'yes' if kwargs['strategy'] == SSHKeyStrategy.set else 'no'
if kwargs['strategy'] == SSHKeyStrategy.set_jms:
kwargs['dest'] = '/home/{}/.ssh/authorized_keys'.format(account.username)
username = account.username
path = f'/{username}' if username == "root" else f'/home/{username}'
kwargs['dest'] = f'{path}/.ssh/authorized_keys'
kwargs['regexp'] = '.*{}$'.format(secret.split()[2].strip())
return kwargs
@@ -96,17 +97,13 @@ class ChangeSecretManager(AccountBasePlaybookManager):
accounts = self.get_accounts(account)
if not accounts:
print('没有发现待改密账号: %s 用户ID: %s 类型: %s' % (
print('没有发现待处理的账号: %s 用户ID: %s 类型: %s' % (
asset.name, self.account_ids, self.secret_type
))
return []
method_attr = getattr(automation, self.method_type() + '_method')
method_hosts = self.method_hosts_mapper[method_attr]
method_hosts = [h for h in method_hosts if h != host['name']]
inventory_hosts = []
records = []
inventory_hosts = []
if asset.type == HostTypes.WINDOWS and self.secret_type == SecretType.SSH_KEY:
print(f'Windows {asset} does not support ssh key push')
return inventory_hosts
@@ -116,13 +113,20 @@ class ChangeSecretManager(AccountBasePlaybookManager):
h = deepcopy(host)
secret_type = account.secret_type
h['name'] += '(' + account.username + ')'
new_secret = self.get_secret(secret_type)
if self.secret_type is None:
new_secret = account.secret
else:
new_secret = self.get_secret(secret_type)
if self.record_id is None:
recorder = ChangeSecretRecord(
asset=asset, account=account, execution=self.execution,
old_secret=account.secret, new_secret=new_secret,
)
records.append(recorder)
else:
recorder = ChangeSecretRecord.objects.get(id=self.record_id)
recorder = ChangeSecretRecord(
asset=asset, account=account, execution=self.execution,
old_secret=account.secret, new_secret=new_secret,
)
records.append(recorder)
self.name_recorder_mapper[h['name']] = recorder
private_key_path = None
@@ -136,13 +140,12 @@ class ChangeSecretManager(AccountBasePlaybookManager):
'username': account.username,
'secret_type': secret_type,
'secret': new_secret,
'private_key_path': private_key_path
'private_key_path': private_key_path,
'become': account.get_ansible_become_auth(),
}
if asset.platform.type == 'oracle':
h['account']['mode'] = 'sysdba' if account.privileged else None
inventory_hosts.append(h)
method_hosts.append(h['name'])
self.method_hosts_mapper[method_attr] = method_hosts
ChangeSecretRecord.objects.bulk_create(records)
return inventory_hosts
@@ -170,7 +173,7 @@ class ChangeSecretManager(AccountBasePlaybookManager):
recorder.save()
def on_runner_failed(self, runner, e):
logger.error("Change secret error: ", e)
logger.error("Account error: ", e)
def check_secret(self):
if self.secret_strategy == SecretStrategy.custom \
@@ -180,9 +183,11 @@ class ChangeSecretManager(AccountBasePlaybookManager):
return True
def run(self, *args, **kwargs):
if not self.check_secret():
if self.secret_type and not self.check_secret():
return
super().run(*args, **kwargs)
if self.record_id:
return
recorders = self.name_recorder_mapper.values()
recorders = list(recorders)
self.send_recorder_mail(recorders)
@@ -196,7 +201,7 @@ class ChangeSecretManager(AccountBasePlaybookManager):
name = self.execution.snapshot['name']
path = os.path.join(os.path.dirname(settings.BASE_DIR), 'tmp')
filename = os.path.join(path, f'{name}-{local_now_display()}-{time.time()}.xlsx')
filename = os.path.join(path, f'{name}-{local_now_filename()}-{time.time()}.xlsx')
if not self.create_file(recorders, filename):
return
@@ -204,7 +209,7 @@ class ChangeSecretManager(AccountBasePlaybookManager):
attachments = []
if user.secret_key:
password = user.secret_key.encode('utf8')
attachment = os.path.join(path, f'{name}-{local_now_display()}-{time.time()}.zip')
attachment = os.path.join(path, f'{name}-{local_now_filename()}-{time.time()}.zip')
encrypt_and_compress_zip_file(attachment, password, [filename])
attachments = [attachment]
ChangeSecretExecutionTaskMsg(name, user).publish(attachments)

View File

@@ -1,7 +1,7 @@
- hosts: mongodb
gather_facts: no
vars:
ansible_python_interpreter: /usr/local/bin/python
ansible_python_interpreter: /opt/py3/bin/python
tasks:
- name: Get info
@@ -12,8 +12,8 @@
login_port: "{{ jms_asset.port }}"
login_database: "{{ jms_asset.spec_info.db_name }}"
ssl: "{{ jms_asset.spec_info.use_ssl }}"
ssl_ca_certs: "{{ jms_asset.secret_info.ca_cert }}"
ssl_certfile: "{{ jms_asset.secret_info.client_key }}"
ssl_ca_certs: "{{ jms_asset.secret_info.ca_cert | default('') }}"
ssl_certfile: "{{ jms_asset.secret_info.client_key | default('') }}"
connection_options:
- tlsAllowInvalidHostnames: "{{ jms_asset.spec_info.allow_invalid_cert}}"
filter: users

View File

@@ -1,7 +1,8 @@
- hosts: mysql
gather_facts: no
vars:
ansible_python_interpreter: /usr/local/bin/python
ansible_python_interpreter: /opt/py3/bin/python
check_ssl: "{{ jms_asset.spec_info.use_ssl and not jms_asset.spec_info.allow_invalid_cert }}"
tasks:
- name: Get info
@@ -10,6 +11,10 @@
login_password: "{{ jms_account.secret }}"
login_host: "{{ jms_asset.address }}"
login_port: "{{ jms_asset.port }}"
check_hostname: "{{ check_ssl if check_ssl else omit }}"
ca_cert: "{{ jms_asset.secret_info.ca_cert | default(omit) if check_ssl else omit }}"
client_cert: "{{ jms_asset.secret_info.client_cert | default(omit) if check_ssl else omit }}"
client_key: "{{ jms_asset.secret_info.client_key | default(omit) if check_ssl else omit }}"
filter: users
register: db_info

View File

@@ -1,7 +1,7 @@
- hosts: oralce
gather_facts: no
vars:
ansible_python_interpreter: /usr/local/bin/python
ansible_python_interpreter: /opt/py3/bin/python
tasks:
- name: Get info

View File

@@ -1,7 +1,7 @@
- hosts: postgresql
gather_facts: no
vars:
ansible_python_interpreter: /usr/local/bin/python
ansible_python_interpreter: /opt/py3/bin/python
tasks:
- name: Get info

View File

@@ -1,9 +1,14 @@
from collections import defaultdict
from accounts.const import AutomationTypes
from accounts.models import GatheredAccount
from assets.models import Asset
from common.utils import get_logger
from orgs.utils import tmp_to_org
from users.models import User
from .filter import GatherAccountsFilter
from ..base.manager import AccountBasePlaybookManager
from ...notifications import GatherAccountChangeMsg
logger = get_logger(__name__)
@@ -12,6 +17,9 @@ class GatherAccountsManager(AccountBasePlaybookManager):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.host_asset_mapper = {}
self.asset_account_info = {}
self.asset_username_mapper = defaultdict(set)
self.is_sync_account = self.execution.snapshot.get('is_sync_account')
@classmethod
@@ -26,10 +34,11 @@ class GatherAccountsManager(AccountBasePlaybookManager):
def filter_success_result(self, tp, result):
result = GatherAccountsFilter(tp).run(self.method_id_meta_mapper, result)
return result
@staticmethod
def generate_data(asset, result):
def generate_data(self, asset, result):
data = []
for username, info in result.items():
self.asset_username_mapper[str(asset.id)].add(username)
d = {'asset': asset, 'username': username, 'present': True}
if info.get('date'):
d['date_last_login'] = info['date']
@@ -38,26 +47,85 @@ class GatherAccountsManager(AccountBasePlaybookManager):
data.append(d)
return data
def update_or_create_accounts(self, asset, result):
def collect_asset_account_info(self, asset, result):
data = self.generate_data(asset, result)
with tmp_to_org(asset.org_id):
gathered_accounts = []
GatheredAccount.objects.filter(asset=asset, present=True).update(present=False)
for d in data:
username = d['username']
gathered_account, __ = GatheredAccount.objects.update_or_create(
defaults=d, asset=asset, username=username,
)
gathered_accounts.append(gathered_account)
if not self.is_sync_account:
return
GatheredAccount.sync_accounts(gathered_accounts)
self.asset_account_info[asset] = data
def on_host_success(self, host, result):
info = result.get('debug', {}).get('res', {}).get('info', {})
asset = self.host_asset_mapper.get(host)
if asset and info:
result = self.filter_success_result(asset.type, info)
self.update_or_create_accounts(asset, result)
self.collect_asset_account_info(asset, result)
else:
logger.error("Not found info".format(host))
logger.error(f'Not found {host} info')
def update_or_create_accounts(self):
for asset, data in self.asset_account_info.items():
with tmp_to_org(asset.org_id):
gathered_accounts = []
GatheredAccount.objects.filter(asset=asset, present=True).update(present=False)
for d in data:
username = d['username']
gathered_account, __ = GatheredAccount.objects.update_or_create(
defaults=d, asset=asset, username=username,
)
gathered_accounts.append(gathered_account)
if not self.is_sync_account:
return
GatheredAccount.sync_accounts(gathered_accounts)
def run(self, *args, **kwargs):
super().run(*args, **kwargs)
users, change_info = self.generate_send_users_and_change_info()
self.update_or_create_accounts()
self.send_email_if_need(users, change_info)
def generate_send_users_and_change_info(self):
recipients = self.execution.recipients
if not self.asset_username_mapper or not recipients:
return None, None
users = User.objects.filter(id__in=recipients)
if not users:
return users, None
asset_ids = self.asset_username_mapper.keys()
assets = Asset.objects.filter(id__in=asset_ids)
gather_accounts = GatheredAccount.objects.filter(asset_id__in=asset_ids, present=True)
asset_id_map = {str(asset.id): asset for asset in assets}
asset_id_username = list(assets.values_list('id', 'accounts__username'))
asset_id_username.extend(list(gather_accounts.values_list('asset_id', 'username')))
system_asset_username_mapper = defaultdict(set)
for asset_id, username in asset_id_username:
system_asset_username_mapper[str(asset_id)].add(username)
change_info = {}
for asset_id, usernames in self.asset_username_mapper.items():
system_usernames = system_asset_username_mapper.get(asset_id)
if not system_usernames:
continue
add_usernames = usernames - system_usernames
remove_usernames = system_usernames - usernames
k = f'{asset_id_map[asset_id]}[{asset_id}]'
if not add_usernames and not remove_usernames:
continue
change_info[k] = {
'add_usernames': ', '.join(add_usernames),
'remove_usernames': ', '.join(remove_usernames),
}
return users, change_info
@staticmethod
def send_email_if_need(users, change_info):
if not users or not change_info:
return
for user in users:
GatherAccountChangeMsg(user, change_info).publish_async()

View File

@@ -1,7 +1,7 @@
- hosts: mongodb
gather_facts: no
vars:
ansible_python_interpreter: /usr/local/bin/python
ansible_python_interpreter: /opt/py3/bin/python
tasks:
- name: Test MongoDB connection
@@ -12,8 +12,8 @@
login_port: "{{ jms_asset.port }}"
login_database: "{{ jms_asset.spec_info.db_name }}"
ssl: "{{ jms_asset.spec_info.use_ssl }}"
ssl_ca_certs: "{{ jms_asset.secret_info.ca_cert }}"
ssl_certfile: "{{ jms_asset.secret_info.client_key }}"
ssl_ca_certs: "{{ jms_asset.secret_info.ca_cert | default('') }}"
ssl_certfile: "{{ jms_asset.secret_info.client_key | default('') }}"
connection_options:
- tlsAllowInvalidHostnames: "{{ jms_asset.spec_info.allow_invalid_cert}}"
register: db_info
@@ -31,8 +31,8 @@
login_port: "{{ jms_asset.port }}"
login_database: "{{ jms_asset.spec_info.db_name }}"
ssl: "{{ jms_asset.spec_info.use_ssl }}"
ssl_ca_certs: "{{ jms_asset.secret_info.ca_cert }}"
ssl_certfile: "{{ jms_asset.secret_info.client_key }}"
ssl_ca_certs: "{{ jms_asset.secret_info.ca_cert | default('') }}"
ssl_certfile: "{{ jms_asset.secret_info.client_key | default('') }}"
connection_options:
- tlsAllowInvalidHostnames: "{{ jms_asset.spec_info.allow_invalid_cert}}"
db: "{{ jms_asset.spec_info.db_name }}"
@@ -49,7 +49,7 @@
login_port: "{{ jms_asset.port }}"
login_database: "{{ jms_asset.spec_info.db_name }}"
ssl: "{{ jms_asset.spec_info.use_ssl }}"
ssl_ca_certs: "{{ jms_asset.secret_info.ca_cert }}"
ssl_certfile: "{{ jms_asset.secret_info.client_key }}"
ssl_ca_certs: "{{ jms_asset.secret_info.ca_cert | default('') }}"
ssl_certfile: "{{ jms_asset.secret_info.client_key | default('') }}"
connection_options:
- tlsAllowInvalidHostnames: "{{ jms_asset.spec_info.allow_invalid_cert}}"

View File

@@ -1,8 +1,9 @@
- hosts: mysql
gather_facts: no
vars:
ansible_python_interpreter: /usr/local/bin/python
ansible_python_interpreter: /opt/py3/bin/python
db_name: "{{ jms_asset.spec_info.db_name }}"
check_ssl: "{{ jms_asset.spec_info.use_ssl and not jms_asset.spec_info.allow_invalid_cert }}"
tasks:
- name: Test MySQL connection
@@ -11,6 +12,10 @@
login_password: "{{ jms_account.secret }}"
login_host: "{{ jms_asset.address }}"
login_port: "{{ jms_asset.port }}"
check_hostname: "{{ check_ssl if check_ssl else omit }}"
ca_cert: "{{ jms_asset.secret_info.ca_cert | default(omit) if check_ssl else omit }}"
client_cert: "{{ jms_asset.secret_info.client_cert | default(omit) if check_ssl else omit }}"
client_key: "{{ jms_asset.secret_info.client_key | default(omit) if check_ssl else omit }}"
filter: version
register: db_info
@@ -24,6 +29,10 @@
login_password: "{{ jms_account.secret }}"
login_host: "{{ jms_asset.address }}"
login_port: "{{ jms_asset.port }}"
check_hostname: "{{ check_ssl if check_ssl else omit }}"
ca_cert: "{{ jms_asset.secret_info.ca_cert | default(omit) if check_ssl else omit }}"
client_cert: "{{ jms_asset.secret_info.client_cert | default(omit) if check_ssl else omit }}"
client_key: "{{ jms_asset.secret_info.client_key | default(omit) if check_ssl else omit }}"
name: "{{ account.username }}"
password: "{{ account.secret }}"
host: "%"
@@ -37,4 +46,8 @@
login_password: "{{ account.secret }}"
login_host: "{{ jms_asset.address }}"
login_port: "{{ jms_asset.port }}"
check_hostname: "{{ check_ssl if check_ssl else omit }}"
ca_cert: "{{ jms_asset.secret_info.ca_cert | default(omit) if check_ssl else omit }}"
client_cert: "{{ jms_asset.secret_info.client_cert | default(omit) if check_ssl else omit }}"
client_key: "{{ jms_asset.secret_info.client_key | default(omit) if check_ssl else omit }}"
filter: version

View File

@@ -1,7 +1,7 @@
- hosts: oracle
gather_facts: no
vars:
ansible_python_interpreter: /usr/local/bin/python
ansible_python_interpreter: /opt/py3/bin/python
tasks:
- name: Test Oracle connection

View File

@@ -1,7 +1,7 @@
- hosts: postgre
gather_facts: no
vars:
ansible_python_interpreter: /usr/local/bin/python
ansible_python_interpreter: /opt/py3/bin/python
tasks:
- name: Test PostgreSQL connection
@@ -31,6 +31,7 @@
role_attr_flags: LOGIN
ignore_errors: true
when: result is succeeded
register: change_info
- name: Verify password
community.postgresql.postgresql_ping:
@@ -42,3 +43,5 @@
when:
- result is succeeded
- change_info is succeeded
register: result
failed_when: not result.is_available

View File

@@ -1,7 +1,7 @@
- hosts: sqlserver
gather_facts: no
vars:
ansible_python_interpreter: /usr/local/bin/python
ansible_python_interpreter: /opt/py3/bin/python
tasks:
- name: Test SQLServer connection
@@ -40,7 +40,7 @@
login_host: "{{ jms_asset.address }}"
login_port: "{{ jms_asset.port }}"
name: '{{ jms_asset.spec_info.db_name }}'
script: "ALTER LOGIN {{ account.username }} WITH PASSWORD = '{{ account.secret }}'; select @@version"
script: "ALTER LOGIN {{ account.username }} WITH PASSWORD = '{{ account.secret }}', DEFAULT_DATABASE = {{ jms_asset.spec_info.db_name }}; select @@version"
ignore_errors: true
when: user_exist.query_results[0] | length != 0
register: change_info
@@ -52,7 +52,7 @@
login_host: "{{ jms_asset.address }}"
login_port: "{{ jms_asset.port }}"
name: '{{ jms_asset.spec_info.db_name }}'
script: "CREATE LOGIN {{ account.username }} WITH PASSWORD = '{{ account.secret }}'; select @@version"
script: "CREATE LOGIN [{{ account.username }}] WITH PASSWORD = '{{ account.secret }}'; CREATE USER [{{ account.username }}] FOR LOGIN [{{ account.username }}]; select @@version"
ignore_errors: true
when: user_exist.query_results[0] | length == 0
register: change_info

View File

@@ -1,10 +1,17 @@
- hosts: demo
gather_facts: no
tasks:
- name: Test privileged account
- name: "Test privileged {{ jms_account.username }} account"
ansible.builtin.ping:
- name: Push user
- name: "Check if {{ account.username }} user exists"
getent:
database: passwd
key: "{{ account.username }}"
register: user_info
ignore_errors: yes # 忽略错误如果用户不存在时不会导致playbook失败
- name: "Add {{ account.username }} user"
ansible.builtin.user:
name: "{{ account.username }}"
shell: "{{ params.shell }}"
@@ -12,22 +19,26 @@
groups: "{{ params.groups }}"
expires: -1
state: present
when: user_info.failed
- name: "Add {{ account.username }} group"
ansible.builtin.group:
name: "{{ account.username }}"
state: present
when: user_info.failed
- name: Add user groups
- name: "Add {{ account.username }} user to group"
ansible.builtin.user:
name: "{{ account.username }}"
groups: "{{ params.groups }}"
when: params.groups
when:
- user_info.failed
- params.groups
- name: Push user password
- name: "Change {{ account.username }} password"
ansible.builtin.user:
name: "{{ account.username }}"
password: "{{ account.secret | password_hash('sha512') }}"
password: "{{ account.secret | password_hash('des') }}"
update_password: always
ignore_errors: true
when: account.secret_type == "password"
@@ -41,14 +52,14 @@
- account.secret_type == "ssh_key"
- ssh_params.strategy == "set_jms"
- name: Push SSH key
- name: "Change {{ account.username }} SSH key"
ansible.builtin.authorized_key:
user: "{{ account.username }}"
key: "{{ account.secret }}"
exclusive: "{{ ssh_params.exclusive }}"
when: account.secret_type == "ssh_key"
- name: Set sudo setting
- name: "Set {{ account.username }} sudo setting"
ansible.builtin.lineinfile:
dest: /etc/sudoers
state: present
@@ -56,25 +67,34 @@
line: "{{ account.username + ' ALL=(ALL) NOPASSWD: ' + params.sudo }}"
validate: visudo -cf %s
when:
- user_info.failed
- params.sudo
- name: Refresh connection
ansible.builtin.meta: reset_connection
- name: Verify password
ansible.builtin.ping:
become: no
vars:
ansible_user: "{{ account.username }}"
ansible_password: "{{ account.secret }}"
ansible_become: no
- name: "Verify {{ account.username }} password (paramiko)"
ssh_ping:
login_user: "{{ account.username }}"
login_password: "{{ account.secret }}"
login_host: "{{ jms_asset.address }}"
login_port: "{{ jms_asset.port }}"
gateway_args: "{{ jms_asset.ansible_ssh_common_args | default('') }}"
become: "{{ account.become.ansible_become | default(False) }}"
become_method: su
become_user: "{{ account.become.ansible_user | default('') }}"
become_password: "{{ account.become.ansible_password | default('') }}"
become_private_key_path: "{{ account.become.ansible_ssh_private_key_file | default(None) }}"
when: account.secret_type == "password"
delegate_to: localhost
- name: Verify SSH key
ansible.builtin.ping:
become: no
vars:
ansible_user: "{{ account.username }}"
ansible_ssh_private_key_file: "{{ account.private_key_path }}"
ansible_become: no
- name: "Verify {{ account.username }} SSH KEY (paramiko)"
ssh_ping:
login_host: "{{ jms_asset.address }}"
login_port: "{{ jms_asset.port }}"
login_user: "{{ account.username }}"
login_private_key_path: "{{ account.private_key_path }}"
gateway_args: "{{ jms_asset.ansible_ssh_common_args | default('') }}"
when: account.secret_type == "ssh_key"
delegate_to: localhost

View File

@@ -1,10 +1,17 @@
- hosts: demo
gather_facts: no
tasks:
- name: Test privileged account
- name: "Test privileged {{ jms_account.username }} account"
ansible.builtin.ping:
- name: Push user
- name: "Check if {{ account.username }} user exists"
getent:
database: passwd
key: "{{ account.username }}"
register: user_info
ignore_errors: yes # 忽略错误如果用户不存在时不会导致playbook失败
- name: "Add {{ account.username }} user"
ansible.builtin.user:
name: "{{ account.username }}"
shell: "{{ params.shell }}"
@@ -12,19 +19,23 @@
groups: "{{ params.groups }}"
expires: -1
state: present
when: user_info.failed
- name: "Add {{ account.username }} group"
ansible.builtin.group:
name: "{{ account.username }}"
state: present
when: user_info.failed
- name: Add user groups
- name: "Add {{ account.username }} user to group"
ansible.builtin.user:
name: "{{ account.username }}"
groups: "{{ params.groups }}"
when: params.groups
when:
- user_info.failed
- params.groups
- name: Push user password
- name: "Change {{ account.username }} password"
ansible.builtin.user:
name: "{{ account.username }}"
password: "{{ account.secret | password_hash('sha512') }}"
@@ -41,14 +52,14 @@
- account.secret_type == "ssh_key"
- ssh_params.strategy == "set_jms"
- name: Push SSH key
- name: "Change {{ account.username }} SSH key"
ansible.builtin.authorized_key:
user: "{{ account.username }}"
key: "{{ account.secret }}"
exclusive: "{{ ssh_params.exclusive }}"
when: account.secret_type == "ssh_key"
- name: Set sudo setting
- name: "Set {{ account.username }} sudo setting"
ansible.builtin.lineinfile:
dest: /etc/sudoers
state: present
@@ -56,25 +67,34 @@
line: "{{ account.username + ' ALL=(ALL) NOPASSWD: ' + params.sudo }}"
validate: visudo -cf %s
when:
- user_info.failed
- params.sudo
- name: Refresh connection
ansible.builtin.meta: reset_connection
- name: Verify password
ansible.builtin.ping:
become: no
vars:
ansible_user: "{{ account.username }}"
ansible_password: "{{ account.secret }}"
ansible_become: no
- name: "Verify {{ account.username }} password (paramiko)"
ssh_ping:
login_user: "{{ account.username }}"
login_password: "{{ account.secret }}"
login_host: "{{ jms_asset.address }}"
login_port: "{{ jms_asset.port }}"
gateway_args: "{{ jms_asset.ansible_ssh_common_args | default('') }}"
become: "{{ account.become.ansible_become | default(False) }}"
become_method: su
become_user: "{{ account.become.ansible_user | default('') }}"
become_password: "{{ account.become.ansible_password | default('') }}"
become_private_key_path: "{{ account.become.ansible_ssh_private_key_file | default(None) }}"
when: account.secret_type == "password"
delegate_to: localhost
- name: Verify SSH key
ansible.builtin.ping:
become: no
vars:
ansible_user: "{{ account.username }}"
ansible_ssh_private_key_file: "{{ account.private_key_path }}"
ansible_become: no
- name: "Verify {{ account.username }} SSH KEY (paramiko)"
ssh_ping:
login_host: "{{ jms_asset.address }}"
login_port: "{{ jms_asset.port }}"
login_user: "{{ account.username }}"
login_private_key_path: "{{ account.private_key_path }}"
gateway_args: "{{ jms_asset.ansible_ssh_common_args | default('') }}"
when: account.secret_type == "ssh_key"
delegate_to: localhost

View File

@@ -0,0 +1,35 @@
- hosts: demo
gather_facts: no
tasks:
- name: Test privileged account
ansible.windows.win_ping:
# - name: Print variables
# debug:
# msg: "Username: {{ account.username }}, Password: {{ account.secret }}"
- name: Push user password
ansible.windows.win_user:
fullname: "{{ account.username}}"
name: "{{ account.username }}"
password: "{{ account.secret }}"
password_never_expires: yes
groups: "{{ params.groups }}"
groups_action: add
update_password: always
ignore_errors: true
when: account.secret_type == "password"
- name: Refresh connection
ansible.builtin.meta: reset_connection
- name: Verify password (pyfreerdp)
rdp_ping:
login_host: "{{ jms_asset.address }}"
login_port: "{{ jms_asset.protocols | selectattr('name', 'equalto', 'rdp') | map(attribute='port') | first }}"
login_user: "{{ account.username }}"
login_password: "{{ account.secret }}"
login_secret_type: "{{ account.secret_type }}"
login_private_key_path: "{{ account.private_key_path }}"
when: account.secret_type == "password"
delegate_to: localhost

View File

@@ -0,0 +1,19 @@
id: push_account_windows_rdp_verify
name: "{{ 'Windows account push rdp verify' | trans }}"
version: 1
method: push_account
category: host
type:
- windows
params:
- name: groups
type: str
label: '用户组'
default: 'Users,Remote Desktop Users'
help_text: '请输入用户组,多个用户组使用逗号分隔(需填写已存在的用户组)'
i18n:
Windows account push rdp verify:
zh: 使用 Ansible 模块 win_user 执行 Windows 账号推送 RDP 协议测试最后的可连接性
ja: Ansibleモジュールwin_userがWindowsアカウントプッシュRDPプロトコルテストを実行する最後の接続性
en: Using the Ansible module win_user performs Windows account push RDP protocol testing for final connectivity

View File

@@ -1,7 +1,4 @@
from copy import deepcopy
from accounts.const import AutomationTypes, SecretType, Connectivity
from assets.const import HostTypes
from accounts.const import AutomationTypes
from common.utils import get_logger
from ..base.manager import AccountBasePlaybookManager
from ..change_secret.manager import ChangeSecretManager
@@ -10,83 +7,11 @@ logger = get_logger(__name__)
class PushAccountManager(ChangeSecretManager, AccountBasePlaybookManager):
ansible_account_prefer = ''
@classmethod
def method_type(cls):
return AutomationTypes.push_account
def host_callback(self, host, asset=None, account=None, automation=None, path_dir=None, **kwargs):
host = super(ChangeSecretManager, self).host_callback(
host, asset=asset, account=account, automation=automation,
path_dir=path_dir, **kwargs
)
if host.get('error'):
return host
accounts = self.get_accounts(account)
inventory_hosts = []
if asset.type == HostTypes.WINDOWS and self.secret_type == SecretType.SSH_KEY:
msg = f'Windows {asset} does not support ssh key push'
print(msg)
return inventory_hosts
host['ssh_params'] = {}
for account in accounts:
h = deepcopy(host)
secret_type = account.secret_type
h['name'] += '(' + account.username + ')'
if self.secret_type is None:
new_secret = account.secret
else:
new_secret = self.get_secret(secret_type)
self.name_recorder_mapper[h['name']] = {
'account': account, 'new_secret': new_secret,
}
private_key_path = None
if secret_type == SecretType.SSH_KEY:
private_key_path = self.generate_private_key_path(new_secret, path_dir)
new_secret = self.generate_public_key(new_secret)
h['ssh_params'].update(self.get_ssh_params(account, new_secret, secret_type))
h['account'] = {
'name': account.name,
'username': account.username,
'secret_type': secret_type,
'secret': new_secret,
'private_key_path': private_key_path
}
if asset.platform.type == 'oracle':
h['account']['mode'] = 'sysdba' if account.privileged else None
inventory_hosts.append(h)
return inventory_hosts
def on_host_success(self, host, result):
account_info = self.name_recorder_mapper.get(host)
if not account_info:
return
account = account_info['account']
new_secret = account_info['new_secret']
if not account:
return
account.secret = new_secret
account.save(update_fields=['secret'])
account.set_connectivity(Connectivity.OK)
def on_host_error(self, host, error, result):
pass
def on_runner_failed(self, runner, e):
logger.error("Pust account error: ", e)
def run(self, *args, **kwargs):
if self.secret_type and not self.check_secret():
return
super(ChangeSecretManager, self).run(*args, **kwargs)
# @classmethod
# def trigger_by_asset_create(cls, asset):
# automations = PushAccountAutomation.objects.filter(

View File

@@ -8,7 +8,7 @@
- name: Verify account (pyfreerdp)
rdp_ping:
login_host: "{{ jms_asset.address }}"
login_port: "{{ jms_asset.port }}"
login_port: "{{ jms_asset.protocols | selectattr('name', 'equalto', 'rdp') | map(attribute='port') | first }}"
login_user: "{{ account.username }}"
login_password: "{{ account.secret }}"
login_secret_type: "{{ account.secret_type }}"

View File

@@ -2,13 +2,20 @@
gather_facts: no
vars:
ansible_connection: local
ansible_shell_type: sh
ansible_become: false
tasks:
- name: Verify account (paramiko)
ssh_ping:
login_host: "{{ jms_asset.address }}"
login_port: "{{ jms_asset.port }}"
login_port: "{{ jms_asset.protocols | selectattr('name', 'equalto', 'ssh') | map(attribute='port') | first }}"
login_user: "{{ account.username }}"
login_password: "{{ account.secret }}"
login_secret_type: "{{ account.secret_type }}"
login_private_key_path: "{{ account.private_key_path }}"
become: "{{ account.become.ansible_become | default(False) }}"
become_method: "{{ account.become.ansible_become_method | default('su') }}"
become_user: "{{ account.become.ansible_user | default('') }}"
become_password: "{{ account.become.ansible_password | default('') }}"
become_private_key_path: "{{ account.become.ansible_ssh_private_key_file | default(None) }}"

View File

@@ -1,7 +1,7 @@
- hosts: mongdb
gather_facts: no
vars:
ansible_python_interpreter: /usr/local/bin/python
ansible_python_interpreter: /opt/py3/bin/python
tasks:
- name: Verify account
@@ -12,7 +12,7 @@
login_port: "{{ jms_asset.port }}"
login_database: "{{ jms_asset.spec_info.db_name }}"
ssl: "{{ jms_asset.spec_info.use_ssl }}"
ssl_ca_certs: "{{ jms_asset.secret_info.ca_cert }}"
ssl_certfile: "{{ jms_asset.secret_info.client_key }}"
ssl_ca_certs: "{{ jms_asset.secret_info.ca_cert | default('') }}"
ssl_certfile: "{{ jms_asset.secret_info.client_key | default('') }}"
connection_options:
- tlsAllowInvalidHostnames: "{{ jms_asset.spec_info.allow_invalid_cert}}"
- tlsAllowInvalidHostnames: "{{ jms_asset.spec_info.allow_invalid_cert }}"

View File

@@ -1,7 +1,8 @@
- hosts: mysql
gather_facts: no
vars:
ansible_python_interpreter: /usr/local/bin/python
ansible_python_interpreter: /opt/py3/bin/python
check_ssl: "{{ jms_asset.spec_info.use_ssl and not jms_asset.spec_info.allow_invalid_cert }}"
tasks:
- name: Verify account
@@ -10,4 +11,8 @@
login_password: "{{ account.secret }}"
login_host: "{{ jms_asset.address }}"
login_port: "{{ jms_asset.port }}"
check_hostname: "{{ check_ssl if check_ssl else omit }}"
ca_cert: "{{ jms_asset.secret_info.ca_cert | default(omit) if check_ssl else omit }}"
client_cert: "{{ jms_asset.secret_info.client_cert | default(omit) if check_ssl else omit }}"
client_key: "{{ jms_asset.secret_info.client_key | default(omit) if check_ssl else omit }}"
filter: version

View File

@@ -1,7 +1,7 @@
- hosts: oracle
gather_facts: no
vars:
ansible_python_interpreter: /usr/local/bin/python
ansible_python_interpreter: /opt/py3/bin/python
tasks:
- name: Verify account

View File

@@ -1,8 +1,7 @@
- hosts: postgresql
gather_facts: no
vars:
ansible_python_interpreter: /usr/local/bin/python
ansible_python_interpreter: /opt/py3/bin/python
tasks:
- name: Verify account

View File

@@ -1,7 +1,7 @@
- hosts: sqlserver
gather_facts: no
vars:
ansible_python_interpreter: /usr/local/bin/python
ansible_python_interpreter: /opt/py3/bin/python
tasks:
- name: Verify account

View File

@@ -1,11 +1,23 @@
- hosts: demo
gather_facts: no
tasks:
- name: Verify account connectivity
become: no
- name: Verify account connectivity(Do not switch)
ansible.builtin.ping:
vars:
ansible_become: no
ansible_user: "{{ account.username }}"
ansible_password: "{{ account.secret }}"
ansible_ssh_private_key_file: "{{ account.private_key_path }}"
when: not account.become.ansible_become
- name: Verify account connectivity(Switch)
ansible.builtin.ping:
vars:
ansible_become: yes
ansible_user: "{{ account.become.ansible_user }}"
ansible_password: "{{ account.become.ansible_password }}"
ansible_ssh_private_key_file: "{{ account.become.ansible_ssh_private_key_file }}"
ansible_become_method: "{{ account.become.ansible_become_method }}"
ansible_become_user: "{{ account.become.ansible_become_user }}"
ansible_become_password: "{{ account.become.ansible_become_password }}"
when: account.become.ansible_become

View File

@@ -42,7 +42,6 @@ class VerifyAccountManager(AccountBasePlaybookManager):
if host.get('error'):
return host
# host['ssh_args'] = '-o ControlMaster=no -o ControlPersist=no'
accounts = asset.accounts.all()
accounts = self.get_accounts(account, accounts)
inventory_hosts = []
@@ -64,7 +63,8 @@ class VerifyAccountManager(AccountBasePlaybookManager):
'username': account.username,
'secret_type': account.secret_type,
'secret': secret,
'private_key_path': private_key_path
'private_key_path': private_key_path,
'become': account.get_ansible_become_auth(),
}
if account.platform.type == 'oracle':
h['account']['mode'] = 'sysdba' if account.privileged else None

View File

@@ -0,0 +1,41 @@
from importlib import import_module
from django.utils.functional import LazyObject
from common.utils import get_logger
from ..const import VaultTypeChoices
__all__ = ['vault_client', 'get_vault_client']
logger = get_logger(__file__)
def get_vault_client(raise_exception=False, **kwargs):
enabled = kwargs.get('VAULT_ENABLED')
tp = 'hcp' if enabled else 'local'
try:
module_path = f'apps.accounts.backends.{tp}.main'
client = import_module(module_path).Vault(**kwargs)
except Exception as e:
logger.error(f'Init vault client failed: {e}')
if raise_exception:
raise
tp = VaultTypeChoices.local
module_path = f'apps.accounts.backends.{tp}.main'
client = import_module(module_path).Vault(**kwargs)
return client
class VaultClient(LazyObject):
def _setup(self):
from jumpserver import settings as js_settings
from django.conf import settings
vault_config_names = [k for k in js_settings.__dict__.keys() if k.startswith('VAULT_')]
vault_configs = {name: getattr(settings, name, None) for name in vault_config_names}
self._wrapped = get_vault_client(**vault_configs)
""" 为了安全, 页面修改配置, 重启服务后才会重新初始化 vault_client """
vault_client = VaultClient()

View File

@@ -0,0 +1,74 @@
from abc import ABC, abstractmethod
from django.forms.models import model_to_dict
__all__ = ['BaseVault']
class BaseVault(ABC):
def __init__(self, *args, **kwargs):
self.enabled = kwargs.get('VAULT_ENABLED')
def get(self, instance):
""" 返回 secret 值 """
return self._get(instance)
def create(self, instance):
if not instance.secret_has_save_to_vault:
self._create(instance)
self._clean_db_secret(instance)
self.save_metadata(instance)
if instance.is_sync_metadata:
self.save_metadata(instance)
def update(self, instance):
if not instance.secret_has_save_to_vault:
self._update(instance)
self._clean_db_secret(instance)
self.save_metadata(instance)
if instance.is_sync_metadata:
self.save_metadata(instance)
def delete(self, instance):
self._delete(instance)
def save_metadata(self, instance):
metadata = model_to_dict(instance, fields=[
'name', 'username', 'secret_type',
'connectivity', 'su_from', 'privileged'
])
metadata = {k: str(v)[:500] for k, v in metadata.items() if v}
return self._save_metadata(instance, metadata)
# -------- abstractmethod -------- #
@abstractmethod
def _get(self, instance):
raise NotImplementedError
@abstractmethod
def _create(self, instance):
raise NotImplementedError
@abstractmethod
def _update(self, instance):
raise NotImplementedError
@abstractmethod
def _delete(self, instance):
raise NotImplementedError
@abstractmethod
def _clean_db_secret(self, instance):
raise NotImplementedError
@abstractmethod
def _save_metadata(self, instance, metadata):
raise NotImplementedError
@abstractmethod
def is_active(self, *args, **kwargs) -> (bool, str):
raise NotImplementedError

View File

@@ -0,0 +1 @@
from .main import *

View File

@@ -0,0 +1,84 @@
import sys
from abc import ABC
from common.db.utils import Encryptor
from common.utils import lazyproperty
current_module = sys.modules[__name__]
__all__ = ['build_entry']
class BaseEntry(ABC):
def __init__(self, instance):
self.instance = instance
@lazyproperty
def full_path(self):
path_base = self.path_base
path_spec = self.path_spec
path = f'{path_base}/{path_spec}'
return path
@property
def path_base(self):
path = f'orgs/{self.instance.org_id}'
return path
@property
def path_spec(self):
raise NotImplementedError
def to_internal_data(self):
secret = getattr(self.instance, '_secret', None)
if secret is not None:
secret = Encryptor(secret).encrypt()
data = {'secret': secret}
return data
@staticmethod
def to_external_data(data):
secret = data.pop('secret', None)
if secret is not None:
secret = Encryptor(secret).decrypt()
return secret
class AccountEntry(BaseEntry):
@property
def path_spec(self):
path = f'assets/{self.instance.asset_id}/accounts/{self.instance.id}'
return path
class AccountTemplateEntry(BaseEntry):
@property
def path_spec(self):
path = f'account-templates/{self.instance.id}'
return path
class HistoricalAccountEntry(BaseEntry):
@property
def path_base(self):
account = self.instance.instance
path = f'accounts/{account.id}/'
return path
@property
def path_spec(self):
path = f'histories/{self.instance.history_id}'
return path
def build_entry(instance) -> BaseEntry:
class_name = instance.__class__.__name__
entry_class_name = f'{class_name}Entry'
entry_class = getattr(current_module, entry_class_name, None)
if not entry_class:
raise Exception(f'Entry class {entry_class_name} is not found')
return entry_class(instance)

View File

@@ -0,0 +1,53 @@
from common.db.utils import get_logger
from .entries import build_entry
from .service import VaultKVClient
from ..base import BaseVault
__all__ = ['Vault']
logger = get_logger(__name__)
class Vault(BaseVault):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.client = VaultKVClient(
url=kwargs.get('VAULT_HCP_HOST'),
token=kwargs.get('VAULT_HCP_TOKEN'),
mount_point=kwargs.get('VAULT_HCP_MOUNT_POINT')
)
def is_active(self):
return self.client.is_active()
def _get(self, instance):
entry = build_entry(instance)
# TODO: get data 是不是层数太多了
data = self.client.get(path=entry.full_path).get('data', {})
data = entry.to_external_data(data)
return data
def _create(self, instance):
entry = build_entry(instance)
data = entry.to_internal_data()
self.client.create(path=entry.full_path, data=data)
def _update(self, instance):
entry = build_entry(instance)
data = entry.to_internal_data()
self.client.patch(path=entry.full_path, data=data)
def _delete(self, instance):
entry = build_entry(instance)
self.client.delete(path=entry.full_path)
def _clean_db_secret(self, instance):
instance.is_sync_metadata = False
instance.mark_secret_save_to_vault()
def _save_metadata(self, instance, metadata):
try:
entry = build_entry(instance)
self.client.update_metadata(path=entry.full_path, metadata=metadata)
except Exception as e:
logger.error(f'save metadata error: {e}')

View File

@@ -0,0 +1,102 @@
# -*- coding: utf-8 -*-
#
import hvac
from hvac import exceptions
from requests.exceptions import ConnectionError
from common.utils import get_logger
logger = get_logger(__name__)
__all__ = ['VaultKVClient']
class VaultKVClient(object):
max_versions = 20
def __init__(self, url, token, mount_point):
assert isinstance(self.max_versions, int) and self.max_versions >= 3, (
'max_versions must to be an integer that is greater than or equal to 3'
)
self.client = hvac.Client(url=url, token=token)
self.mount_point = mount_point
self.enable_secrets_engine_if_need()
def is_active(self):
try:
if not self.client.sys.is_initialized():
return False, 'Vault is not initialized'
if self.client.sys.is_sealed():
return False, 'Vault is sealed'
if not self.client.is_authenticated():
return False, 'Vault is not authenticated'
except ConnectionError as e:
logger.error(str(e))
return False, f'Vault is not reachable: {e}'
else:
return True, ''
def enable_secrets_engine_if_need(self):
secrets_engines = self.client.sys.list_mounted_secrets_engines()
mount_points = secrets_engines.keys()
if f'{self.mount_point}/' in mount_points:
return
self.client.sys.enable_secrets_engine(
backend_type='kv',
path=self.mount_point,
options={'version': 2} # TODO: version 是否从配置中读取?
)
self.client.secrets.kv.v2.configure(
max_versions=self.max_versions,
mount_point=self.mount_point
)
def get(self, path, version=None):
try:
response = self.client.secrets.kv.v2.read_secret_version(
path=path,
version=version,
mount_point=self.mount_point
)
except exceptions.InvalidPath as e:
return {}
data = response.get('data', {})
return data
def create(self, path, data: dict):
self._update_or_create(path=path, data=data)
def update(self, path, data: dict):
""" 未更新的数据会被删除 """
self._update_or_create(path=path, data=data)
def patch(self, path, data: dict):
""" 未更新的数据不会被删除 """
self.client.secrets.kv.v2.patch(
path=path,
secret=data,
mount_point=self.mount_point
)
def delete(self, path):
self.client.secrets.kv.v2.delete_metadata_and_all_versions(
path=path,
mount_point=self.mount_point,
)
def _update_or_create(self, path, data: dict):
self.client.secrets.kv.v2.create_or_update_secret(
path=path,
secret=data,
mount_point=self.mount_point
)
def update_metadata(self, path, metadata: dict):
try:
self.client.secrets.kv.v2.update_metadata(
path=path,
mount_point=self.mount_point,
custom_metadata=metadata
)
except exceptions.InvalidPath as e:
logger.error('Update metadata error: {}'.format(e))

View File

@@ -0,0 +1 @@
from .main import *

View File

@@ -0,0 +1,36 @@
from common.utils import get_logger
from ..base import BaseVault
logger = get_logger(__name__)
__all__ = ['Vault']
class Vault(BaseVault):
def is_active(self):
return True, ''
def _get(self, instance):
secret = getattr(instance, '_secret', None)
return secret
def _create(self, instance):
""" Ignore """
pass
def _update(self, instance):
""" Ignore """
pass
def _delete(self, instance):
""" Ignore """
pass
def _save_metadata(self, instance, metadata):
""" Ignore """
pass
def _clean_db_secret(self, instance):
""" Ignore *重要* 不能删除本地 secret """
pass

View File

@@ -1,2 +1,3 @@
from .account import *
from .automation import *
from .vault import *

View File

@@ -1,5 +1,5 @@
from django.db.models import TextChoices
from django.utils.translation import ugettext_lazy as _
from django.utils.translation import gettext_lazy as _
class SecretType(TextChoices):
@@ -16,6 +16,10 @@ class AliasAccount(TextChoices):
USER = '@USER', _('Dynamic user')
ANON = '@ANON', _('Anonymous account')
@classmethod
def virtual_choices(cls):
return [(k, v) for k, v in cls.choices if k not in (cls.ALL,)]
class Source(TextChoices):
LOCAL = 'local', _('Local')

View File

@@ -1,20 +1,22 @@
from django.db import models
from django.utils.translation import ugettext_lazy as _
from django.utils.translation import gettext_lazy as _
from assets.const import Connectivity
from common.db.fields import TreeChoices
string_punctuation = '!#$%&()*+,-.:;<=>?@[]^_~'
DEFAULT_PASSWORD_LENGTH = 30
DEFAULT_PASSWORD_RULES = {
'length': DEFAULT_PASSWORD_LENGTH,
'symbol_set': string_punctuation
'uppercase': True,
'lowercase': True,
'digit': True,
'symbol': True,
}
__all__ = [
'AutomationTypes', 'SecretStrategy', 'SSHKeyStrategy', 'Connectivity',
'DEFAULT_PASSWORD_LENGTH', 'DEFAULT_PASSWORD_RULES', 'TriggerChoice',
'PushAccountActionChoice',
'PushAccountActionChoice', 'AccountBackupType'
]
@@ -41,8 +43,8 @@ class AutomationTypes(models.TextChoices):
class SecretStrategy(models.TextChoices):
custom = 'specific', _('Specific password')
random = 'random', _('Random')
custom = 'specific', _('Specific secret')
random = 'random', _('Random generate')
class SSHKeyStrategy(models.TextChoices):
@@ -93,3 +95,10 @@ class TriggerChoice(models.TextChoices, TreeChoices):
class PushAccountActionChoice(models.TextChoices):
create_and_push = 'create_and_push', _('Create and push')
only_create = 'only_create', _('Only create')
class AccountBackupType(models.TextChoices):
"""Backup type"""
email = 'email', _('Email')
# 目前只支持sftp方式
object_storage = 'object_storage', _('SFTP')

View File

@@ -0,0 +1,9 @@
from django.db import models
from django.utils.translation import gettext_lazy as _
__all__ = ['VaultTypeChoices']
class VaultTypeChoices(models.TextChoices):
local = 'local', _('Database')
hcp = 'hcp', _('HCP Vault')

View File

@@ -13,7 +13,8 @@ class AccountFilterSet(BaseFilterSet):
hostname = drf_filters.CharFilter(field_name='name', lookup_expr='exact')
username = drf_filters.CharFilter(field_name="username", lookup_expr='exact')
address = drf_filters.CharFilter(field_name="asset__address", lookup_expr='exact')
asset = drf_filters.CharFilter(field_name="asset_id", lookup_expr='exact')
asset_id = drf_filters.CharFilter(field_name="asset", lookup_expr='exact')
asset = drf_filters.CharFilter(field_name='asset', lookup_expr='exact')
assets = drf_filters.CharFilter(field_name='asset_id', lookup_expr='exact')
nodes = drf_filters.CharFilter(method='filter_nodes')
node_id = drf_filters.CharFilter(method='filter_nodes')
@@ -45,7 +46,7 @@ class AccountFilterSet(BaseFilterSet):
class Meta:
model = Account
fields = ['id', 'asset_id', 'source_id', 'secret_type']
fields = ['id', 'asset', 'source_id', 'secret_type']
class GatheredAccountFilterSet(BaseFilterSet):

View File

@@ -38,7 +38,7 @@ class Migration(migrations.Migration):
'verbose_name': 'Automation execution',
'verbose_name_plural': 'Automation executions',
'permissions': [('view_changesecretexecution', 'Can view change secret execution'),
('add_changesecretexection', 'Can add change secret execution'),
('add_changesecretexecution', 'Can add change secret execution'),
('view_gatheraccountsexecution', 'Can view gather accounts execution'),
('add_gatheraccountsexecution', 'Can add gather accounts execution')],
'proxy': True,
@@ -113,10 +113,10 @@ class Migration(migrations.Migration):
('comment', models.TextField(blank=True, default='', verbose_name='Comment')),
('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)),
('old_secret', common.db.fields.EncryptTextField(blank=True, null=True, verbose_name='Old secret')),
('new_secret', common.db.fields.EncryptTextField(blank=True, null=True, verbose_name='Secret')),
('new_secret', common.db.fields.EncryptTextField(blank=True, null=True, verbose_name='New secret')),
('date_started', models.DateTimeField(blank=True, null=True, verbose_name='Date started')),
('date_finished', models.DateTimeField(blank=True, null=True, verbose_name='Date finished')),
('status', models.CharField(default='pending', max_length=16)),
('status', models.CharField(default='pending', max_length=16, verbose_name='Status')),
('error', models.TextField(blank=True, null=True, verbose_name='Error')),
('account',
models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='accounts.account')),
@@ -184,7 +184,7 @@ class Migration(migrations.Migration):
migrations.AlterModelOptions(
name='automationexecution',
options={'permissions': [('view_changesecretexecution', 'Can view change secret execution'),
('add_changesecretexection', 'Can add change secret execution'),
('add_changesecretexecution', 'Can add change secret execution'),
('view_gatheraccountsexecution', 'Can view gather accounts execution'),
('add_gatheraccountsexecution', 'Can add gather accounts execution'),
('view_pushaccountexecution', 'Can view push account execution'),

View File

@@ -13,11 +13,11 @@ class Migration(migrations.Migration):
migrations.AlterField(
model_name='changesecretautomation',
name='secret_strategy',
field=models.CharField(choices=[('specific', 'Specific password'), ('random', 'Random')], default='specific', max_length=16, verbose_name='Secret strategy'),
field=models.CharField(choices=[('specific', 'Specific secret'), ('random', 'Random generate')], default='specific', max_length=16, verbose_name='Secret strategy'),
),
migrations.AlterField(
model_name='pushaccountautomation',
name='secret_strategy',
field=models.CharField(choices=[('specific', 'Specific password'), ('random', 'Random')], default='specific', max_length=16, verbose_name='Secret strategy'),
field=models.CharField(choices=[('specific', 'Specific secret'), ('random', 'Random generate')], default='specific', max_length=16, verbose_name='Secret strategy'),
),
]

View File

@@ -0,0 +1,28 @@
# Generated by Django 3.2.19 on 2023-06-21 06:56
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('accounts', '0011_auto_20230506_1443'),
]
operations = [
migrations.RenameField(
model_name='account',
old_name='secret',
new_name='_secret',
),
migrations.RenameField(
model_name='accounttemplate',
old_name='secret',
new_name='_secret',
),
migrations.RenameField(
model_name='historicalaccount',
old_name='secret',
new_name='_secret',
),
]

View File

@@ -0,0 +1,77 @@
# Generated by Django 4.1.10 on 2023-08-03 08:28
from django.conf import settings
from django.db import migrations, models
import common.db.encoder
def migrate_recipients(apps, schema_editor):
account_backup_model = apps.get_model('accounts', 'AccountBackupAutomation')
execution_model = apps.get_model('accounts', 'AccountBackupExecution')
for account_backup in account_backup_model.objects.all():
recipients = list(account_backup.recipients.all())
if not recipients:
continue
account_backup.recipients_part_one.set(recipients)
objs = []
for execution in execution_model.objects.all():
snapshot = execution.snapshot
recipients = snapshot.pop('recipients', {})
snapshot.update({'recipients_part_one': recipients, 'recipients_part_two': {}})
objs.append(execution)
execution_model.objects.bulk_update(objs, ['snapshot'])
def migrate_snapshot(apps, schema_editor):
model = apps.get_model('accounts', 'AccountBackupExecution')
objs = []
for execution in model.objects.all():
execution.snapshot = execution.plan_snapshot
objs.append(execution)
model.objects.bulk_update(objs, ['snapshot'])
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('accounts', '0012_auto_20230621_1456'),
]
operations = [
migrations.AddField(
model_name='accountbackupautomation',
name='recipients_part_one',
field=models.ManyToManyField(
blank=True, related_name='recipient_part_one_plans',
to=settings.AUTH_USER_MODEL, verbose_name='Recipient part one'
),
),
migrations.AddField(
model_name='accountbackupautomation',
name='recipients_part_two',
field=models.ManyToManyField(
blank=True, related_name='recipient_part_two_plans',
to=settings.AUTH_USER_MODEL, verbose_name='Recipient part two'
),
),
migrations.AddField(
model_name='accountbackupexecution',
name='snapshot',
field=models.JSONField(
default=dict, encoder=common.db.encoder.ModelJSONFieldEncoder,
null=True, blank=True, verbose_name='Account backup snapshot'
),
),
migrations.RunPython(migrate_snapshot),
migrations.RunPython(migrate_recipients),
migrations.RemoveField(
model_name='accountbackupexecution',
name='plan_snapshot',
),
migrations.RemoveField(
model_name='accountbackupautomation',
name='recipients',
),
]

View File

@@ -0,0 +1,30 @@
# Generated by Django 4.1.10 on 2023-08-01 09:12
from django.db import migrations, models
import uuid
class Migration(migrations.Migration):
dependencies = [
('accounts', '0013_account_backup_recipients'),
]
operations = [
migrations.CreateModel(
name='VirtualAccount',
fields=[
('created_by', models.CharField(blank=True, max_length=128, null=True, verbose_name='Created by')),
('updated_by', models.CharField(blank=True, max_length=128, null=True, verbose_name='Updated by')),
('date_created', models.DateTimeField(auto_now_add=True, null=True, verbose_name='Date created')),
('date_updated', models.DateTimeField(auto_now=True, verbose_name='Date updated')),
('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)),
('org_id', models.CharField(blank=True, db_index=True, default='', max_length=36, verbose_name='Organization')),
('alias', models.CharField(choices=[('@INPUT', 'Manual input'), ('@USER', 'Dynamic user'), ('@ANON', 'Anonymous account')], max_length=128, verbose_name='Alias')),
('secret_from_login', models.BooleanField(default=None, null=True, verbose_name='Secret from login')),
],
options={
'unique_together': {('alias', 'org_id')},
},
),
]

View File

@@ -0,0 +1,34 @@
# Generated by Django 4.1.10 on 2023-08-25 03:19
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('assets', '0122_auto_20230803_1553'),
('accounts', '0014_virtualaccount'),
]
operations = [
migrations.AddField(
model_name='accounttemplate',
name='auto_push',
field=models.BooleanField(default=False, verbose_name='Auto push'),
),
migrations.AddField(
model_name='accounttemplate',
name='platforms',
field=models.ManyToManyField(related_name='account_templates', to='assets.platform', verbose_name='Platforms', blank=True),
),
migrations.AddField(
model_name='accounttemplate',
name='push_params',
field=models.JSONField(default=dict, verbose_name='Push params'),
),
migrations.AddField(
model_name='accounttemplate',
name='secret_strategy',
field=models.CharField(choices=[('specific', 'Specific secret'), ('random', 'Random generate')], default='specific', max_length=16, verbose_name='Secret strategy'),
),
]

View File

@@ -0,0 +1,18 @@
# Generated by Django 4.1.10 on 2023-09-18 08:09
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('accounts', '0015_auto_20230825_1120'),
]
operations = [
migrations.AddField(
model_name='accounttemplate',
name='password_rules',
field=models.JSONField(default=dict, verbose_name='Password rules'),
),
]

View File

@@ -0,0 +1,25 @@
# Generated by Django 4.1.10 on 2023-10-24 05:59
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('accounts', '0016_accounttemplate_password_rules'),
]
operations = [
migrations.AlterModelOptions(
name='automationexecution',
options={
'permissions': [
('view_changesecretexecution', 'Can view change secret execution'),
('add_changesecretexecution', 'Can add change secret execution'),
('view_gatheraccountsexecution', 'Can view gather accounts execution'),
('add_gatheraccountsexecution', 'Can add gather accounts execution'),
('view_pushaccountexecution', 'Can view push account execution'),
('add_pushaccountexecution', 'Can add push account execution')
],
'verbose_name': 'Automation execution', 'verbose_name_plural': 'Automation executions'},
),
]

View File

@@ -0,0 +1,45 @@
# Generated by Django 4.1.10 on 2023-11-03 07:10
import common.db.fields
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('terminal', '0067_alter_replaystorage_type'),
('accounts', '0017_alter_automationexecution_options'),
]
operations = [
migrations.AddField(
model_name='accountbackupautomation',
name='backup_type',
field=models.CharField(choices=[('email', 'Email'), ('object_storage', 'Object Storage')], default='email', max_length=128),
),
migrations.AddField(
model_name='accountbackupautomation',
name='is_password_divided_by_email',
field=models.BooleanField(default=True),
),
migrations.AddField(
model_name='accountbackupautomation',
name='is_password_divided_by_obj_storage',
field=models.BooleanField(default=True),
),
migrations.AddField(
model_name='accountbackupautomation',
name='obj_recipients_part_one',
field=models.ManyToManyField(blank=True, related_name='obj_recipient_part_one_plans', to='terminal.replaystorage', verbose_name='Object Storage Recipient part one'),
),
migrations.AddField(
model_name='accountbackupautomation',
name='obj_recipients_part_two',
field=models.ManyToManyField(blank=True, related_name='obj_recipient_part_two_plans', to='terminal.replaystorage', verbose_name='Object Storage Recipient part two'),
),
migrations.AddField(
model_name='accountbackupautomation',
name='zip_encrypt_password',
field=common.db.fields.EncryptCharField(blank=True, max_length=4096, null=True, verbose_name='Zip Encrypt Password'),
),
]

View File

@@ -0,0 +1,20 @@
# Generated by Django 4.1.10 on 2023-10-31 06:12
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('accounts', '0018_accountbackupautomation_backup_type_and_more'),
]
operations = [
migrations.AddField(
model_name='gatheraccountsautomation',
name='recipients',
field=models.ManyToManyField(blank=True, to=settings.AUTH_USER_MODEL, verbose_name='Recipient'),
),
]

View File

@@ -0,0 +1,28 @@
# Generated by Django 4.1.10 on 2023-11-16 02:13
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('accounts', '0019_gatheraccountsautomation_recipients'),
]
operations = [
migrations.AlterField(
model_name='accountbackupautomation',
name='backup_type',
field=models.CharField(choices=[('email', 'Email'), ('object_storage', 'SFTP')], default='email', max_length=128, verbose_name='Backup Type'),
),
migrations.AlterField(
model_name='accountbackupautomation',
name='is_password_divided_by_email',
field=models.BooleanField(default=True, verbose_name='Is Password Divided'),
),
migrations.AlterField(
model_name='accountbackupautomation',
name='is_password_divided_by_obj_storage',
field=models.BooleanField(default=True, verbose_name='Is Password Divided'),
),
]

75
apps/accounts/mixins.py Normal file
View File

@@ -0,0 +1,75 @@
from rest_framework.response import Response
from rest_framework import status
from django.utils import translation
from django.utils.translation import gettext_noop
from audits.const import ActionChoices
from common.views.mixins import RecordViewLogMixin
from common.utils import i18n_fmt
class AccountRecordViewLogMixin(RecordViewLogMixin):
get_object: callable
get_queryset: callable
@staticmethod
def _filter_params(params):
new_params = {}
need_pop_params = ('format', 'order')
for key, value in params.items():
if key in need_pop_params:
continue
if isinstance(value, list):
value = list(filter(None, value))
if value:
new_params[key] = value
return new_params
def get_resource_display(self, request):
query_params = dict(request.query_params)
params = self._filter_params(query_params)
spm_filter = params.pop("spm", None)
if not params and not spm_filter:
display_message = gettext_noop("Export all")
elif spm_filter:
display_message = gettext_noop("Export only selected items")
else:
query = ",".join(
["%s=%s" % (key, value) for key, value in params.items()]
)
display_message = i18n_fmt(gettext_noop("Export filtered: %s"), query)
return display_message
@property
def detail_msg(self):
return i18n_fmt(
gettext_noop('User %s view/export secret'), self.request.user
)
def list(self, request, *args, **kwargs):
list_func = getattr(super(), 'list')
if not callable(list_func):
return Response(status=status.HTTP_405_METHOD_NOT_ALLOWED)
response = list_func(request, *args, **kwargs)
with translation.override('en'):
resource_display = self.get_resource_display(request)
ids = [q.id for q in self.get_queryset()]
self.record_logs(
ids, ActionChoices.view, self.detail_msg, resource_display=resource_display
)
return response
def retrieve(self, request, *args, **kwargs):
retrieve_func = getattr(super(), 'retrieve')
if not callable(retrieve_func):
return Response(status=status.HTTP_405_METHOD_NOT_ALLOWED)
response = retrieve_func(request, *args, **kwargs)
with translation.override('en'):
resource = self.get_object()
self.record_logs(
[resource.id], ActionChoices.view, self.detail_msg, resource=resource
)
return response

View File

@@ -1,3 +1,5 @@
from .account import *
from .automations import *
from .base import *
from .account import * # noqa
from .base import * # noqa
from .automations import * # noqa
from .template import * # noqa
from .virtual import * # noqa

View File

@@ -1,15 +1,14 @@
from django.db import models
from django.db.models import Count, Q
from django.utils import timezone
from django.utils.translation import gettext_lazy as _
from simple_history.models import HistoricalRecords
from assets.models.base import AbsConnectivity
from common.utils import lazyproperty
from .base import BaseAccount
from ..const import AliasAccount, Source
from .mixins import VaultModelMixin
from ..const import Source
__all__ = ['Account', 'AccountTemplate']
__all__ = ['Account', 'AccountHistoricalRecords']
class AccountHistoricalRecords(HistoricalRecords):
@@ -32,7 +31,7 @@ class AccountHistoricalRecords(HistoricalRecords):
diff = attrs - history_attrs
if not diff:
return
super().post_save(instance, created, using=using, **kwargs)
return super().post_save(instance, created, using=using, **kwargs)
def create_history_model(self, model, inherited):
if self.included_fields and not self.excluded_fields:
@@ -53,7 +52,7 @@ class Account(AbsConnectivity, BaseAccount):
on_delete=models.SET_NULL, verbose_name=_("Su from")
)
version = models.IntegerField(default=0, verbose_name=_('Version'))
history = AccountHistoricalRecords(included_fields=['id', 'secret', 'secret_type', 'version'])
history = AccountHistoricalRecords(included_fields=['id', '_secret', 'secret_type', 'version'])
source = models.CharField(max_length=30, default=Source.LOCAL, verbose_name=_('Source'))
source_id = models.CharField(max_length=128, null=True, blank=True, verbose_name=_('Source ID'))
@@ -88,29 +87,6 @@ class Account(AbsConnectivity, BaseAccount):
def has_secret(self):
return bool(self.secret)
@classmethod
def get_special_account(cls, name):
if name == AliasAccount.INPUT.value:
return cls.get_manual_account()
elif name == AliasAccount.ANON.value:
return cls.get_anonymous_account()
else:
return cls(name=name, username=name, secret=None)
@classmethod
def get_manual_account(cls):
""" @INPUT 手动登录的账号(any) """
return cls(name=AliasAccount.INPUT.label, username=AliasAccount.INPUT.value, secret=None)
@classmethod
def get_anonymous_account(cls):
return cls(name=AliasAccount.ANON.label, username=AliasAccount.ANON.value, secret=None)
@classmethod
def get_user_account(cls):
""" @USER 动态用户的账号(self) """
return cls(name=AliasAccount.USER.label, username=AliasAccount.USER.value, secret=None)
@lazyproperty
def versions(self):
return self.history.count()
@@ -119,82 +95,47 @@ class Account(AbsConnectivity, BaseAccount):
""" 排除自己和以自己为 su-from 的账号 """
return self.asset.accounts.exclude(id=self.id).exclude(su_from=self)
class AccountTemplate(BaseAccount):
su_from = models.ForeignKey(
'self', related_name='su_to', null=True,
on_delete=models.SET_NULL, verbose_name=_("Su from")
)
class Meta:
verbose_name = _('Account template')
unique_together = (
('name', 'org_id'),
)
permissions = [
('view_accounttemplatesecret', _('Can view asset account template secret')),
('change_accounttemplatesecret', _('Can change asset account template secret')),
]
@classmethod
def get_su_from_account_templates(cls, pk=None):
if pk is None:
return cls.objects.all()
return cls.objects.exclude(Q(id=pk) | Q(su_from_id=pk))
def __str__(self):
return f'{self.name}({self.username})'
def get_su_from_account(self, asset):
su_from = self.su_from
if su_from and asset.platform.su_enabled:
account = asset.accounts.filter(
username=su_from.username,
secret_type=su_from.secret_type
).first()
return account
def __str__(self):
return self.username
@staticmethod
def bulk_update_accounts(accounts, data):
history_model = Account.history.model
account_ids = accounts.values_list('id', flat=True)
history_accounts = history_model.objects.filter(id__in=account_ids)
account_id_count_map = {
str(i['id']): i['count']
for i in history_accounts.values('id').order_by('id')
.annotate(count=Count(1)).values('id', 'count')
def make_account_ansible_vars(su_from):
var = {
'ansible_user': su_from.username,
}
if not su_from.secret:
return var
var['ansible_password'] = su_from.secret
var['ansible_ssh_private_key_file'] = su_from.private_key_path
return var
for account in accounts:
account_id = str(account.id)
account.version = account_id_count_map.get(account_id) + 1
for k, v in data.items():
setattr(account, k, v)
Account.objects.bulk_update(accounts, ['version', 'secret'])
def get_ansible_become_auth(self):
su_from = self.su_from
platform = self.platform
auth = {'ansible_become': False}
if not (platform.su_enabled and su_from):
return auth
@staticmethod
def bulk_create_history_accounts(accounts, user_id):
history_model = Account.history.model
history_account_objs = []
for account in accounts:
history_account_objs.append(
history_model(
id=account.id,
version=account.version,
secret=account.secret,
secret_type=account.secret_type,
history_user_id=user_id,
history_date=timezone.now()
)
)
history_model.objects.bulk_create(history_account_objs)
auth.update(self.make_account_ansible_vars(su_from))
become_method = platform.su_method if platform.su_method else 'sudo'
password = su_from.secret if become_method == 'sudo' else self.secret
auth['ansible_become'] = True
auth['ansible_become_method'] = become_method
auth['ansible_become_user'] = self.username
auth['ansible_become_password'] = password
return auth
def bulk_sync_account_secret(self, accounts, user_id):
""" 批量同步账号密码 """
if not accounts:
return
self.bulk_update_accounts(accounts, {'secret': self.secret})
self.bulk_create_history_accounts(accounts, user_id)
def replace_history_model_with_mixin():
"""
替换历史模型中的父类为指定的Mixin类。
Parameters:
model (class): 历史模型类,例如 Account.history.model
mixin_class (class): 要替换为的Mixin类
Returns:
None
"""
model = Account.history.model
model.__bases__ = (VaultModelMixin,) + model.__bases__
replace_history_model_with_mixin()

View File

@@ -6,12 +6,13 @@ import uuid
from celery import current_task
from django.db import models
from django.db.models import F
from django.utils.translation import ugettext_lazy as _
from django.utils.translation import gettext_lazy as _
from accounts.const.automation import AccountBackupType
from common.const.choices import Trigger
from common.db import fields
from common.db.encoder import ModelJSONFieldEncoder
from common.utils import get_logger
from common.utils import lazyproperty
from common.utils import get_logger, lazyproperty
from ops.mixin import PeriodTaskModelMixin
from orgs.mixins.models import OrgModelMixin, JMSOrgBaseModel
@@ -22,10 +23,28 @@ logger = get_logger(__file__)
class AccountBackupAutomation(PeriodTaskModelMixin, JMSOrgBaseModel):
types = models.JSONField(default=list)
recipients = models.ManyToManyField(
'users.User', related_name='recipient_escape_route_plans', blank=True,
verbose_name=_("Recipient")
backup_type = models.CharField(max_length=128, choices=AccountBackupType.choices,
default=AccountBackupType.email.value, verbose_name=_('Backup Type'))
is_password_divided_by_email = models.BooleanField(default=True, verbose_name=_('Is Password Divided'))
is_password_divided_by_obj_storage = models.BooleanField(default=True, verbose_name=_('Is Password Divided'))
recipients_part_one = models.ManyToManyField(
'users.User', related_name='recipient_part_one_plans', blank=True,
verbose_name=_("Recipient part one")
)
recipients_part_two = models.ManyToManyField(
'users.User', related_name='recipient_part_two_plans', blank=True,
verbose_name=_("Recipient part two")
)
obj_recipients_part_one = models.ManyToManyField(
'terminal.ReplayStorage', related_name='obj_recipient_part_one_plans', blank=True,
verbose_name=_("Object Storage Recipient part one")
)
obj_recipients_part_two = models.ManyToManyField(
'terminal.ReplayStorage', related_name='obj_recipient_part_two_plans', blank=True,
verbose_name=_("Object Storage Recipient part two")
)
zip_encrypt_password = fields.EncryptCharField(max_length=4096, blank=True, null=True,
verbose_name=_('Zip Encrypt Password'))
def __str__(self):
return f'{self.name}({self.org_id})'
@@ -45,6 +64,7 @@ class AccountBackupAutomation(PeriodTaskModelMixin, JMSOrgBaseModel):
def to_attr_json(self):
return {
'id': self.id,
'name': self.name,
'is_periodic': self.is_periodic,
'interval': self.interval,
@@ -52,10 +72,26 @@ class AccountBackupAutomation(PeriodTaskModelMixin, JMSOrgBaseModel):
'org_id': self.org_id,
'created_by': self.created_by,
'types': self.types,
'recipients': {
str(recipient.id): (str(recipient), bool(recipient.secret_key))
for recipient in self.recipients.all()
}
'backup_type': self.backup_type,
'is_password_divided_by_email': self.is_password_divided_by_email,
'is_password_divided_by_obj_storage': self.is_password_divided_by_obj_storage,
'zip_encrypt_password': self.zip_encrypt_password,
'recipients_part_one': {
str(user.id): (str(user), bool(user.secret_key))
for user in self.recipients_part_one.all()
},
'recipients_part_two': {
str(user.id): (str(user), bool(user.secret_key))
for user in self.recipients_part_two.all()
},
'obj_recipients_part_one': {
str(obj_storage.id): (str(obj_storage.name), str(obj_storage.type))
for obj_storage in self.obj_recipients_part_one.all()
},
'obj_recipients_part_two': {
str(obj_storage.id): (str(obj_storage.name), str(obj_storage.type))
for obj_storage in self.obj_recipients_part_two.all()
},
}
@property
@@ -68,7 +104,7 @@ class AccountBackupAutomation(PeriodTaskModelMixin, JMSOrgBaseModel):
except AttributeError:
hid = str(uuid.uuid4())
execution = AccountBackupExecution.objects.create(
id=hid, plan=self, plan_snapshot=self.to_attr_json(), trigger=trigger
id=hid, plan=self, snapshot=self.to_attr_json(), trigger=trigger
)
return execution.start()
@@ -85,7 +121,7 @@ class AccountBackupExecution(OrgModelMixin):
timedelta = models.FloatField(
default=0.0, verbose_name=_('Time'), null=True
)
plan_snapshot = models.JSONField(
snapshot = models.JSONField(
encoder=ModelJSONFieldEncoder, default=dict,
blank=True, null=True, verbose_name=_('Account backup snapshot')
)
@@ -108,16 +144,9 @@ class AccountBackupExecution(OrgModelMixin):
@property
def types(self):
types = self.plan_snapshot.get('types')
types = self.snapshot.get('types')
return types
@property
def recipients(self):
recipients = self.plan_snapshot.get('recipients')
if not recipients:
return []
return recipients.values()
@lazyproperty
def backup_accounts(self):
from accounts.models import Account

View File

@@ -1,12 +1,15 @@
from django.db import models
from django.utils.translation import gettext_lazy as _
from accounts.const import SSHKeyStrategy
from accounts.models import Account, SecretWithRandomMixin
from accounts.tasks import execute_account_automation_task
from assets.models.automations import (
BaseAutomation as AssetBaseAutomation,
AutomationExecution as AssetAutomationExecution
)
__all__ = ['AccountBaseAutomation', 'AutomationExecution']
__all__ = ['AccountBaseAutomation', 'AutomationExecution', 'ChangeSecretMixin']
class AccountBaseAutomation(AssetBaseAutomation):
@@ -30,7 +33,7 @@ class AutomationExecution(AssetAutomationExecution):
verbose_name_plural = _("Automation executions")
permissions = [
('view_changesecretexecution', _('Can view change secret execution')),
('add_changesecretexection', _('Can add change secret execution')),
('add_changesecretexecution', _('Can add change secret execution')),
('view_gatheraccountsexecution', _('Can view gather accounts execution')),
('add_gatheraccountsexecution', _('Can add gather accounts execution')),
@@ -43,3 +46,40 @@ class AutomationExecution(AssetAutomationExecution):
from accounts.automations.endpoint import ExecutionManager
manager = ExecutionManager(execution=self)
return manager.run()
class ChangeSecretMixin(SecretWithRandomMixin):
ssh_key_change_strategy = models.CharField(
choices=SSHKeyStrategy.choices, max_length=16,
default=SSHKeyStrategy.add, verbose_name=_('SSH key change strategy')
)
get_all_assets: callable # get all assets
class Meta:
abstract = True
def create_nonlocal_accounts(self, usernames, asset):
pass
def get_account_ids(self):
usernames = self.accounts
accounts = Account.objects.none()
for asset in self.get_all_assets():
self.create_nonlocal_accounts(usernames, asset)
accounts = accounts | asset.accounts.all()
account_ids = accounts.filter(
username__in=usernames, secret_type=self.secret_type
).values_list('id', flat=True)
return [str(_id) for _id in account_ids]
def to_attr_json(self):
attr_json = super().to_attr_json()
attr_json.update({
'secret': self.secret,
'secret_type': self.secret_type,
'accounts': self.get_account_ids(),
'password_rules': self.password_rules,
'secret_strategy': self.secret_strategy,
'ssh_key_change_strategy': self.ssh_key_change_strategy,
})
return attr_json

View File

@@ -1,63 +1,14 @@
from django.db import models
from django.utils.translation import ugettext_lazy as _
from django.utils.translation import gettext_lazy as _
from accounts.const import (
AutomationTypes, SecretType, SecretStrategy, SSHKeyStrategy
AutomationTypes
)
from accounts.models import Account
from common.db import fields
from common.db.models import JMSBaseModel
from .base import AccountBaseAutomation
from .base import AccountBaseAutomation, ChangeSecretMixin
__all__ = ['ChangeSecretAutomation', 'ChangeSecretRecord', 'ChangeSecretMixin']
class ChangeSecretMixin(models.Model):
secret_type = models.CharField(
choices=SecretType.choices, max_length=16,
default=SecretType.PASSWORD, verbose_name=_('Secret type')
)
secret = fields.EncryptTextField(blank=True, null=True, verbose_name=_('Secret'))
secret_strategy = models.CharField(
choices=SecretStrategy.choices, max_length=16,
default=SecretStrategy.custom, verbose_name=_('Secret strategy')
)
password_rules = models.JSONField(default=dict, verbose_name=_('Password rules'))
ssh_key_change_strategy = models.CharField(
choices=SSHKeyStrategy.choices, max_length=16,
default=SSHKeyStrategy.add, verbose_name=_('SSH key change strategy')
)
get_all_assets: callable # get all assets
class Meta:
abstract = True
def create_nonlocal_accounts(self, usernames, asset):
pass
def get_account_ids(self):
usernames = self.accounts
accounts = Account.objects.none()
for asset in self.get_all_assets():
self.create_nonlocal_accounts(usernames, asset)
accounts = accounts | asset.accounts.all()
account_ids = accounts.filter(
username__in=usernames, secret_type=self.secret_type
).values_list('id', flat=True)
return [str(_id) for _id in account_ids]
def to_attr_json(self):
attr_json = super().to_attr_json()
attr_json.update({
'secret': self.secret,
'secret_type': self.secret_type,
'accounts': self.get_account_ids(),
'password_rules': self.password_rules,
'secret_strategy': self.secret_strategy,
'ssh_key_change_strategy': self.ssh_key_change_strategy,
})
return attr_json
__all__ = ['ChangeSecretAutomation', 'ChangeSecretRecord', ]
class ChangeSecretAutomation(ChangeSecretMixin, AccountBaseAutomation):
@@ -86,10 +37,10 @@ class ChangeSecretRecord(JMSBaseModel):
asset = models.ForeignKey('assets.Asset', on_delete=models.CASCADE, null=True)
account = models.ForeignKey('accounts.Account', on_delete=models.CASCADE, null=True)
old_secret = fields.EncryptTextField(blank=True, null=True, verbose_name=_('Old secret'))
new_secret = fields.EncryptTextField(blank=True, null=True, verbose_name=_('Secret'))
new_secret = fields.EncryptTextField(blank=True, null=True, verbose_name=_('New secret'))
date_started = models.DateTimeField(blank=True, null=True, verbose_name=_('Date started'))
date_finished = models.DateTimeField(blank=True, null=True, verbose_name=_('Date finished'))
status = models.CharField(max_length=16, default='pending')
status = models.CharField(max_length=16, default='pending', verbose_name=_('Status'))
error = models.TextField(blank=True, null=True, verbose_name=_('Error'))
class Meta:
@@ -98,9 +49,3 @@ class ChangeSecretRecord(JMSBaseModel):
def __str__(self):
return self.account.__str__()
@property
def timedelta(self):
if self.date_started and self.date_finished:
return self.date_finished - self.date_started
return None

View File

@@ -1,6 +1,6 @@
from django.db import models
from django.db.models import Q
from django.utils.translation import ugettext_lazy as _
from django.utils.translation import gettext_lazy as _
from accounts.const import AutomationTypes, Source
from accounts.models import Account
@@ -55,11 +55,15 @@ class GatherAccountsAutomation(AccountBaseAutomation):
is_sync_account = models.BooleanField(
default=False, blank=True, verbose_name=_("Is sync account")
)
recipients = models.ManyToManyField('users.User', verbose_name=_("Recipient"), blank=True)
def to_attr_json(self):
attr_json = super().to_attr_json()
attr_json.update({
'is_sync_account': self.is_sync_account,
'recipients': [
str(recipient.id) for recipient in self.recipients.all()
]
})
return attr_json

View File

@@ -1,9 +1,9 @@
from django.conf import settings
from django.db import models
from django.utils.translation import ugettext_lazy as _
from django.utils.translation import gettext_lazy as _
from accounts.const import AutomationTypes
from accounts.models import Account
from jumpserver.utils import has_valid_xpack_license
from .base import AccountBaseAutomation
from .change_secret import ChangeSecretMixin
@@ -17,9 +17,9 @@ class PushAccountAutomation(ChangeSecretMixin, AccountBaseAutomation):
def create_nonlocal_accounts(self, usernames, asset):
secret_type = self.secret_type
account_usernames = asset.accounts.filter(secret_type=self.secret_type).values_list(
'username', flat=True
)
account_usernames = asset.accounts \
.filter(secret_type=self.secret_type) \
.values_list('username', flat=True)
create_usernames = set(usernames) - set(account_usernames)
create_account_objs = [
Account(
@@ -30,9 +30,6 @@ class PushAccountAutomation(ChangeSecretMixin, AccountBaseAutomation):
]
Account.objects.bulk_create(create_account_objs)
def set_period_schedule(self):
pass
@property
def dynamic_username(self):
return self.username == '@USER'
@@ -44,7 +41,7 @@ class PushAccountAutomation(ChangeSecretMixin, AccountBaseAutomation):
def save(self, *args, **kwargs):
self.type = AutomationTypes.push_account
if not has_valid_xpack_license():
if not settings.XPACK_LICENSE_IS_VALID:
self.is_periodic = False
super().save(*args, **kwargs)

View File

@@ -1,4 +1,4 @@
from django.utils.translation import ugettext_lazy as _
from django.utils.translation import gettext_lazy as _
from accounts.const import AutomationTypes
from .base import AccountBaseAutomation

View File

@@ -6,9 +6,11 @@ from hashlib import md5
import sshpubkeys
from django.conf import settings
from django.db import models
from django.utils.translation import ugettext_lazy as _
from django.utils.translation import gettext_lazy as _
from accounts.const import SecretType
from accounts.const import SecretType, SecretStrategy
from accounts.models.mixins import VaultModelMixin, VaultManagerMixin, VaultQuerySetMixin
from accounts.utils import SecretGenerator
from common.db import fields
from common.utils import (
ssh_key_string_to_obj, ssh_key_gen, get_logger,
@@ -19,23 +21,51 @@ from orgs.mixins.models import JMSOrgBaseModel, OrgManager
logger = get_logger(__file__)
class BaseAccountQuerySet(models.QuerySet):
class BaseAccountQuerySet(VaultQuerySetMixin, models.QuerySet):
def active(self):
return self.filter(is_active=True)
class BaseAccountManager(OrgManager):
class BaseAccountManager(VaultManagerMixin, OrgManager):
def active(self):
return self.get_queryset().active()
class BaseAccount(JMSOrgBaseModel):
class SecretWithRandomMixin(models.Model):
secret_type = models.CharField(
choices=SecretType.choices, max_length=16,
default=SecretType.PASSWORD, verbose_name=_('Secret type')
)
secret = fields.EncryptTextField(blank=True, null=True, verbose_name=_('Secret'))
secret_strategy = models.CharField(
choices=SecretStrategy.choices, max_length=16,
default=SecretStrategy.custom, verbose_name=_('Secret strategy')
)
password_rules = models.JSONField(default=dict, verbose_name=_('Password rules'))
class Meta:
abstract = True
@lazyproperty
def secret_generator(self):
return SecretGenerator(
self.secret_strategy, self.secret_type,
self.password_rules,
)
def get_secret(self):
if self.secret_strategy == 'random':
return self.secret_generator.get_secret()
else:
return self.secret
class BaseAccount(VaultModelMixin, JMSOrgBaseModel):
name = models.CharField(max_length=128, verbose_name=_("Name"))
username = models.CharField(max_length=128, blank=True, verbose_name=_('Username'), db_index=True)
secret_type = models.CharField(
max_length=16, choices=SecretType.choices, default=SecretType.PASSWORD, verbose_name=_('Secret type')
)
secret = fields.EncryptTextField(blank=True, null=True, verbose_name=_('Secret'))
privileged = models.BooleanField(verbose_name=_("Privileged"), default=False)
is_active = models.BooleanField(default=True, verbose_name=_("Is active"))

View File

@@ -0,0 +1 @@
from .vault import *

View File

@@ -0,0 +1,95 @@
from django.db import models
from django.db.models.signals import post_save
from django.utils.translation import gettext_lazy as _
from common.db import fields
__all__ = ['VaultQuerySetMixin', 'VaultManagerMixin', 'VaultModelMixin']
class VaultQuerySetMixin(models.QuerySet):
def update(self, **kwargs):
"""
1. 替换 secret 为 _secret
2. 触发 post_save 信号
"""
if 'secret' in kwargs:
kwargs.update({
'_secret': kwargs.pop('secret')
})
rows = super().update(**kwargs)
# 为了获取更新后的对象所以单独查询一次
ids = self.values_list('id', flat=True)
objs = self.model.objects.filter(id__in=ids)
for obj in objs:
post_save.send(obj.__class__, instance=obj, created=False)
return rows
class VaultManagerMixin(models.Manager):
""" 触发 bulk_create 和 bulk_update 操作下的 post_save 信号 """
def bulk_create(self, objs, batch_size=None, ignore_conflicts=False):
objs = super().bulk_create(objs, batch_size=batch_size, ignore_conflicts=ignore_conflicts)
for obj in objs:
post_save.send(obj.__class__, instance=obj, created=True)
return objs
def bulk_update(self, objs, fields, batch_size=None):
fields = ["_secret" if field == "secret" else field for field in fields]
super().bulk_update(objs, fields, batch_size=batch_size)
for obj in objs:
post_save.send(obj.__class__, instance=obj, created=False)
return objs
class VaultModelMixin(models.Model):
_secret = fields.EncryptTextField(blank=True, null=True, verbose_name=_('Secret'))
is_sync_metadata = True
class Meta:
abstract = True
# 缓存 secret 值, lazy-property 不能用
__secret = None
@property
def secret(self):
if self.__secret:
return self.__secret
from accounts.backends import vault_client
secret = vault_client.get(self)
if not secret and not self.secret_has_save_to_vault:
# vault_client 获取不到, 并且 secret 没有保存到 vault, 就从 self._secret 获取
secret = self._secret
self.__secret = secret
return self.__secret
@secret.setter
def secret(self, value):
"""
保存的时候通过 post_save 信号监听进行处理,
先保存到 db, 再保存到 vault 同时删除本地 db _secret 值
"""
self._secret = value
self.__secret = value
_secret_save_to_vault_mark = '# Secret-has-been-saved-to-vault #'
def mark_secret_save_to_vault(self):
self._secret = self._secret_save_to_vault_mark
self.save()
@property
def secret_has_save_to_vault(self):
return self._secret == self._secret_save_to_vault_mark
def save(self, *args, **kwargs):
""" 通过 post_save signal 处理 _secret 数据 """
update_fields = kwargs.get('update_fields')
if update_fields and 'secret' in update_fields:
update_fields.remove('secret')
update_fields.append('_secret')
return super().save(*args, **kwargs)

View File

@@ -0,0 +1,88 @@
from django.db import models
from django.db.models import Count, Q
from django.utils import timezone
from django.utils.translation import gettext_lazy as _
from .account import Account
from .base import BaseAccount, SecretWithRandomMixin
__all__ = ['AccountTemplate', ]
class AccountTemplate(BaseAccount, SecretWithRandomMixin):
su_from = models.ForeignKey(
'self', related_name='su_to', null=True,
on_delete=models.SET_NULL, verbose_name=_("Su from")
)
auto_push = models.BooleanField(default=False, verbose_name=_('Auto push'))
platforms = models.ManyToManyField(
'assets.Platform', related_name='account_templates',
verbose_name=_('Platforms'), blank=True,
)
push_params = models.JSONField(default=dict, verbose_name=_('Push params'))
class Meta:
verbose_name = _('Account template')
unique_together = (
('name', 'org_id'),
)
permissions = [
('view_accounttemplatesecret', _('Can view asset account template secret')),
('change_accounttemplatesecret', _('Can change asset account template secret')),
]
def __str__(self):
return f'{self.name}({self.username})'
@classmethod
def get_su_from_account_templates(cls, pk=None):
if pk is None:
return cls.objects.all()
return cls.objects.exclude(Q(id=pk) | Q(su_from_id=pk))
def get_su_from_account(self, asset):
su_from = self.su_from
if su_from and asset.platform.su_enabled:
account = asset.accounts.filter(
username=su_from.username,
secret_type=su_from.secret_type
).first()
return account
def bulk_update_accounts(self, accounts):
history_model = Account.history.model
account_ids = accounts.values_list('id', flat=True)
history_accounts = history_model.objects.filter(id__in=account_ids)
account_id_count_map = {
str(i['id']): i['count']
for i in history_accounts.values('id').order_by('id')
.annotate(count=Count(1)).values('id', 'count')
}
for account in accounts:
account_id = str(account.id)
account.version = account_id_count_map.get(account_id) + 1
account.secret = self.get_secret()
Account.objects.bulk_update(accounts, ['version', 'secret'])
@staticmethod
def bulk_create_history_accounts(accounts, user_id):
history_model = Account.history.model
history_account_objs = []
for account in accounts:
history_account_objs.append(
history_model(
id=account.id,
version=account.version,
secret=account.secret,
secret_type=account.secret_type,
history_user_id=user_id,
history_date=timezone.now()
)
)
history_model.objects.bulk_create(history_account_objs)
def bulk_sync_account_secret(self, accounts, user_id):
""" 批量同步账号密码 """
self.bulk_update_accounts(accounts)
self.bulk_create_history_accounts(accounts, user_id)

View File

@@ -0,0 +1,103 @@
from django.db import models
from django.utils.translation import gettext_lazy as _
from accounts.const import AliasAccount
from orgs.mixins.models import JMSOrgBaseModel
__all__ = ['VirtualAccount']
from orgs.utils import tmp_to_org
class VirtualAccount(JMSOrgBaseModel):
alias = models.CharField(max_length=128, choices=AliasAccount.virtual_choices(), verbose_name=_('Alias'), )
secret_from_login = models.BooleanField(default=None, null=True, verbose_name=_("Secret from login"), )
class Meta:
unique_together = [('alias', 'org_id')]
@property
def name(self):
return self.get_alias_display()
@property
def username(self):
usernames_map = {
AliasAccount.INPUT: _("Manual input"),
AliasAccount.USER: _("Same with user"),
AliasAccount.ANON: ''
}
usernames_map = {str(k): v for k, v in usernames_map.items()}
return usernames_map.get(self.alias, '')
@property
def comment(self):
comments_map = {
AliasAccount.INPUT: _('Non-asset account, Input username/password on connect'),
AliasAccount.USER: _('The account username name same with user on connect'),
AliasAccount.ANON: _('Connect asset without using a username and password, '
'and it only supports web-based and custom-type assets'),
}
comments_map = {str(k): v for k, v in comments_map.items()}
return comments_map.get(self.alias, '')
@classmethod
def get_or_init_queryset(cls):
aliases = [i[0] for i in AliasAccount.virtual_choices()]
alias_created = cls.objects.all().values_list('alias', flat=True)
need_created = set(aliases) - set(alias_created)
if need_created:
accounts = [cls(alias=alias) for alias in need_created]
cls.objects.bulk_create(accounts, ignore_conflicts=True)
return cls.objects.all()
@classmethod
def get_special_account(cls, alias, user, asset, input_username='', input_secret='', from_permed=True):
if alias == AliasAccount.INPUT.value:
account = cls.get_manual_account(input_username, input_secret, from_permed)
elif alias == AliasAccount.ANON.value:
account = cls.get_anonymous_account()
elif alias == AliasAccount.USER.value:
account = cls.get_same_account(user, asset, input_secret=input_secret, from_permed=from_permed)
else:
account = cls(name=alias, username=alias, secret=None)
account.alias = alias
if asset:
account.asset = asset
account.org_id = asset.org_id
return account
@classmethod
def get_manual_account(cls, input_username='', input_secret='', from_permed=True):
""" @INPUT 手动登录的账号(any) """
from .account import Account
if from_permed:
username = AliasAccount.INPUT.value
secret = ''
else:
username = input_username
secret = input_secret
return Account(name=AliasAccount.INPUT.label, username=username, secret=secret)
@classmethod
def get_anonymous_account(cls):
from .account import Account
return Account(name=AliasAccount.ANON.label, username=AliasAccount.ANON.value, secret=None)
@classmethod
def get_same_account(cls, user, asset, input_secret='', from_permed=True):
""" @USER 动态用户的账号(self) """
from .account import Account
username = user.username
with tmp_to_org(asset.org):
same_account = cls.objects.filter(alias='@USER').first()
secret = ''
if same_account and same_account.secret_from_login:
secret = user.get_cached_password_if_has()
if not secret and not from_permed:
secret = input_secret
return Account(name=AliasAccount.USER.label, username=username, secret=secret)

View File

@@ -1,7 +1,10 @@
from django.utils.translation import ugettext_lazy as _
from django.template.loader import render_to_string
from django.utils.translation import gettext_lazy as _
from common.tasks import send_mail_attachment_async
from common.tasks import send_mail_attachment, upload_backup_to_obj_storage
from notifications.notifications import UserMessage
from users.models import User
from terminal.models.component.storage import ReplayStorage
class AccountBackupExecutionTaskMsg(object):
@@ -24,11 +27,30 @@ class AccountBackupExecutionTaskMsg(object):
"to set the encryption password").format(name)
def publish(self, attachment_list=None):
send_mail_attachment_async(
send_mail_attachment(
self.subject, self.message, [self.user.email], attachment_list
)
class AccountBackupByObjStorageExecutionTaskMsg(object):
subject = _('Notification of account backup route task results')
def __init__(self, name: str, obj_storage: ReplayStorage):
self.name = name
self.obj_storage = obj_storage
@property
def message(self):
name = self.name
return _('{} - The account backup passage task has been completed.'
' See the attachment for details').format(name)
def publish(self, attachment_list=None):
upload_backup_to_obj_storage(
self.obj_storage, attachment_list
)
class ChangeSecretExecutionTaskMsg(object):
subject = _('Notification of implementation result of encryption change plan')
@@ -48,6 +70,28 @@ class ChangeSecretExecutionTaskMsg(object):
"file encryption password to set the encryption password").format(name)
def publish(self, attachments=None):
send_mail_attachment_async(
send_mail_attachment(
self.subject, self.message, [self.user.email], attachments
)
class GatherAccountChangeMsg(UserMessage):
subject = _('Gather account change information')
def __init__(self, user, change_info: dict):
self.change_info = change_info
super().__init__(user)
def get_html_msg(self) -> dict:
context = {'change_info': self.change_info}
message = render_to_string('accounts/asset_account_change_info.html', context)
return {
'subject': str(self.subject),
'message': message
}
@classmethod
def gen_test_msg(cls):
user = User.objects.first()
return cls(user, {})

View File

@@ -1,5 +1,6 @@
from .account import *
from .backup import *
from .base import *
from .template import *
from .gathered_account import *
from .template import *
from .virtual import *

View File

@@ -2,8 +2,9 @@ import uuid
from copy import deepcopy
from django.db import IntegrityError
from django.db import transaction
from django.db.models import Q
from django.utils.translation import ugettext_lazy as _
from django.utils.translation import gettext_lazy as _
from rest_framework import serializers
from rest_framework.generics import get_object_or_404
from rest_framework.validators import UniqueTogetherValidator
@@ -73,6 +74,23 @@ class AccountCreateUpdateSerializerMixin(serializers.Serializer):
name = name + '_' + uuid.uuid4().hex[:4]
initial_data['name'] = name
@staticmethod
def get_template_attr_for_account(template):
# Set initial data from template
field_names = [
'name', 'username', 'secret',
'secret_type', 'privileged', 'is_active'
]
attrs = {}
for name in field_names:
value = getattr(template, name, None)
if value is None:
continue
attrs[name] = value
attrs['secret'] = template.get_secret()
return attrs
def from_template_if_need(self, initial_data):
if isinstance(initial_data, str):
return
@@ -89,18 +107,7 @@ class AccountCreateUpdateSerializerMixin(serializers.Serializer):
raise serializers.ValidationError({'template': 'Template not found'})
self._template = template
# Set initial data from template
ignore_fields = ['id', 'date_created', 'date_updated', 'su_from', 'org_id']
field_names = [
field.name for field in template._meta.fields
if field.name not in ignore_fields
]
attrs = {}
for name in field_names:
value = getattr(template, name, None)
if value is None:
continue
attrs[name] = value
attrs = self.get_template_attr_for_account(template)
initial_data.update(attrs)
initial_data.update({
'source': Source.TEMPLATE,
@@ -112,10 +119,13 @@ class AccountCreateUpdateSerializerMixin(serializers.Serializer):
asset = get_object_or_404(Asset, pk=asset_id)
initial_data['su_from'] = template.get_su_from_account(asset)
@staticmethod
def push_account_if_need(instance, push_now, params, stat):
def push_account_if_need(self, instance, push_now, params, stat):
if not push_now or stat not in ['created', 'updated']:
return
transaction.on_commit(lambda: self.start_push(instance, params))
@staticmethod
def start_push(instance, params):
push_accounts_to_assets_task.delay([str(instance.id)], params)
def get_validators(self):
@@ -198,7 +208,6 @@ class AccountAssetSerializer(serializers.ModelSerializer):
class AccountSerializer(AccountCreateUpdateSerializerMixin, BaseAccountSerializer):
asset = AccountAssetSerializer(label=_('Asset'))
has_secret = serializers.BooleanField(label=_("Has secret"), read_only=True)
source = LabeledChoiceField(
choices=Source.choices, label=_("Source"), required=False,
allow_null=True, default=Source.LOCAL
@@ -233,6 +242,15 @@ class AccountSerializer(AccountCreateUpdateSerializerMixin, BaseAccountSerialize
return queryset
class AccountDetailSerializer(AccountSerializer):
has_secret = serializers.BooleanField(label=_("Has secret"), read_only=True)
class Meta(AccountSerializer.Meta):
model = Account
fields = AccountSerializer.Meta.fields + ['has_secret']
read_only_fields = AccountSerializer.Meta.read_only_fields + ['has_secret']
class AssetAccountBulkSerializerResultSerializer(serializers.Serializer):
asset = serializers.CharField(read_only=True, label=_('Asset'))
state = serializers.CharField(read_only=True, label=_('State'))

View File

@@ -1,11 +1,11 @@
# -*- coding: utf-8 -*-
#
from django.utils.translation import ugettext_lazy as _
from django.utils.translation import gettext_lazy as _
from rest_framework import serializers
from accounts.models import AccountBackupAutomation, AccountBackupExecution
from common.const.choices import Trigger
from common.serializers.fields import LabeledChoiceField
from common.serializers.fields import LabeledChoiceField, EncryptedField
from common.utils import get_logger
from ops.mixin import PeriodTaskSerializerMixin
from orgs.mixins.serializers import BulkOrgResourceModelSerializer
@@ -16,6 +16,11 @@ __all__ = ['AccountBackupSerializer', 'AccountBackupPlanExecutionSerializer']
class AccountBackupSerializer(PeriodTaskSerializerMixin, BulkOrgResourceModelSerializer):
zip_encrypt_password = EncryptedField(
label=_('Zip Encrypt Password'), required=False, max_length=40960, allow_blank=True,
allow_null=True, write_only=True,
)
class Meta:
model = AccountBackupAutomation
read_only_fields = [
@@ -24,7 +29,9 @@ class AccountBackupSerializer(PeriodTaskSerializerMixin, BulkOrgResourceModelSer
]
fields = read_only_fields + [
'id', 'name', 'is_periodic', 'interval', 'crontab',
'comment', 'recipients', 'types'
'comment', 'types', 'recipients_part_one', 'recipients_part_two', 'backup_type',
'is_password_divided_by_email', 'is_password_divided_by_obj_storage', 'obj_recipients_part_one',
'obj_recipients_part_two', 'zip_encrypt_password'
]
extra_kwargs = {
'name': {'required': True},
@@ -44,7 +51,7 @@ class AccountBackupPlanExecutionSerializer(serializers.ModelSerializer):
class Meta:
model = AccountBackupExecution
read_only_fields = [
'id', 'date_start', 'timedelta', 'plan_snapshot',
'trigger', 'reason', 'is_success', 'org_id', 'recipients'
'id', 'date_start', 'timedelta', 'snapshot',
'trigger', 'reason', 'is_success', 'org_id'
]
fields = read_only_fields + ['plan']

View File

@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
from django.utils.translation import ugettext_lazy as _
from django.utils.translation import gettext_lazy as _
from rest_framework import serializers
from accounts.const import SecretType
@@ -61,20 +61,18 @@ class AuthValidateMixin(serializers.Serializer):
class BaseAccountSerializer(AuthValidateMixin, BulkOrgResourceModelSerializer):
has_secret = serializers.BooleanField(label=_("Has secret"), read_only=True)
class Meta:
model = BaseAccount
fields_mini = ['id', 'name', 'username']
fields_small = fields_mini + [
'secret_type', 'secret', 'has_secret', 'passphrase',
'secret_type', 'secret', 'passphrase',
'privileged', 'is_active', 'spec_info',
]
fields_other = ['created_by', 'date_created', 'date_updated', 'comment']
fields = fields_small + fields_other
read_only_fields = [
'has_secret', 'spec_info',
'date_verified', 'created_by', 'date_created',
'spec_info', 'date_verified', 'created_by', 'date_created',
]
extra_kwargs = {
'spec_info': {'label': _('Spec info')},

View File

@@ -1,4 +1,4 @@
from django.utils.translation import ugettext_lazy as _
from django.utils.translation import gettext_lazy as _
from accounts.models import GatheredAccount
from orgs.mixins.serializers import BulkOrgResourceModelSerializer

View File

@@ -1,16 +1,24 @@
from django.utils.translation import ugettext_lazy as _
from django.utils.translation import gettext_lazy as _
from rest_framework import serializers
from accounts.models import AccountTemplate, Account
from accounts.const import SecretStrategy, SecretType
from accounts.models import AccountTemplate
from accounts.utils import SecretGenerator
from common.serializers import SecretReadableMixin
from common.serializers.fields import ObjectRelatedField
from .base import BaseAccountSerializer
class AccountTemplateSerializer(BaseAccountSerializer):
is_sync_account = serializers.BooleanField(default=False, write_only=True)
_is_sync_account = False
class PasswordRulesSerializer(serializers.Serializer):
length = serializers.IntegerField(min_value=8, max_value=30, default=16, label=_('Password length'))
lowercase = serializers.BooleanField(default=True, label=_('Lowercase'))
uppercase = serializers.BooleanField(default=True, label=_('Uppercase'))
digit = serializers.BooleanField(default=True, label=_('Digit'))
symbol = serializers.BooleanField(default=True, label=_('Special symbol'))
class AccountTemplateSerializer(BaseAccountSerializer):
password_rules = PasswordRulesSerializer(required=False, label=_('Password rules'))
su_from = ObjectRelatedField(
required=False, queryset=AccountTemplate.objects, allow_null=True,
allow_empty=True, label=_('Su from'), attrs=('id', 'name', 'username')
@@ -18,36 +26,38 @@ class AccountTemplateSerializer(BaseAccountSerializer):
class Meta(BaseAccountSerializer.Meta):
model = AccountTemplate
fields = BaseAccountSerializer.Meta.fields + ['is_sync_account', 'su_from']
def sync_accounts_secret(self, instance, diff):
if not self._is_sync_account or 'secret' not in diff:
return
query_data = {
'source_id': instance.id,
'username': instance.username,
'secret_type': instance.secret_type
fields = BaseAccountSerializer.Meta.fields + [
'secret_strategy', 'password_rules',
'auto_push', 'push_params', 'platforms',
'su_from'
]
extra_kwargs = {
'secret_strategy': {'help_text': _('Secret generation strategy for account creation')},
'auto_push': {'help_text': _('Whether to automatically push the account to the asset')},
'platforms': {
'help_text': _(
'Associated platform, you can configure push parameters. '
'If not associated, default parameters will be used'
),
'required': False
},
}
accounts = Account.objects.filter(**query_data)
instance.bulk_sync_account_secret(accounts, self.context['request'].user.id)
@staticmethod
def generate_secret(attrs):
secret_type = attrs.get('secret_type', SecretType.PASSWORD)
secret_strategy = attrs.get('secret_strategy', SecretStrategy.custom)
password_rules = attrs.get('password_rules')
if secret_strategy != SecretStrategy.random:
return
generator = SecretGenerator(secret_strategy, secret_type, password_rules)
attrs['secret'] = generator.get_secret()
def validate(self, attrs):
self._is_sync_account = attrs.pop('is_sync_account', None)
attrs = super().validate(attrs)
self.generate_secret(attrs)
return attrs
def update(self, instance, validated_data):
diff = {
k: v for k, v in validated_data.items()
if getattr(instance, k, None) != v
}
instance = super().update(instance, validated_data)
if {'username', 'secret_type'} & set(diff.keys()):
Account.objects.filter(source_id=instance.id).update(source_id=None)
else:
self.sync_accounts_secret(instance, diff)
return instance
class AccountTemplateSecretSerializer(SecretReadableMixin, AccountTemplateSerializer):
class Meta(AccountTemplateSerializer.Meta):

View File

@@ -0,0 +1,30 @@
from django.utils.translation import gettext_lazy as _
from rest_framework import serializers
from accounts.models import VirtualAccount
__all__ = ['VirtualAccountSerializer']
class VirtualAccountSerializer(serializers.ModelSerializer):
class Meta:
model = VirtualAccount
field_mini = ['id', 'alias', 'username', 'name']
common_fields = ['date_created', 'date_updated', 'comment']
fields = field_mini + [
'secret_from_login',
] + common_fields
read_only_fields = common_fields + common_fields
extra_kwargs = {
'comment': {'label': _('Comment')},
'name': {'label': _('Name')},
'username': {'label': _('Username')},
'secret_from_login': {
'help_text': _(
'Current only support login from AD/LDAP. Secret priority: '
'Same account in asset secret > Login secret > Manual input. <br/ >'
'For security, please set config CACHE_LOGIN_PASSWORD_ENABLED to true'
)
},
'alias': {'required': False},
}

Some files were not shown because too many files have changed in this diff Show More