Compare commits

...

386 Commits

Author SHA1 Message Date
fit2bot
68135f3b79 feat: Update v3.10.4 2024-02-29 16:41:27 +08:00
Bryan
eedc2f1b41 Merge pull request #12734 from jumpserver/master
v3.10.4 (branch-v3.10)
2024-02-29 16:39:55 +08:00
Bryan
7ba24293d1 Merge pull request #12736 from jumpserver/pr@dev@master_fix
fix: 解决冲突
2024-02-29 16:38:43 +08:00
Bai
f10114c9ed fix: 解决冲突 2024-02-29 16:37:10 +08:00
Bryan
cf31cbfb07 Merge pull request #12729 from jumpserver/dev
v3.10.4
2024-02-29 16:19:59 +08:00
feng
f0ffa2408d fix: 哨兵redis 密码里有@ 无法连接 2024-02-29 16:19:33 +08:00
jiangweidong
b557e264bc fix: 账号备份选择SFTP有多个时,重复编码password会导致任务异常 2024-02-29 16:10:21 +08:00
wangruidong
457d2b2359 fix: 作业管理资产根据标签过滤获取不对 2024-02-28 15:50:46 +05:00
Bai
8ebc99339b perf: 更新 poetry.lock 文件 2024-02-28 07:22:16 +05:00
wangruidong
e71e335f5c fix: 终断任务时接口报错 2024-02-27 16:22:06 +05:00
masix
7517e77af9 指定lxml版本为4.9.3
修复SAML2认证回调/core/auth/saml2/callback/时偶发出现http 502错误
2024-02-27 08:16:10 +05:00
wangruidong
889cdca3b0 fix: 操作日志资源类型搜索无效 2024-02-26 16:36:05 +05:00
wangruidong
4cfd1bc047 fix: 远程应用列表接口报错 2024-02-26 16:03:34 +05:00
Eric
fc0891ceee perf: 会话生命周期日志翻译 2024-02-26 15:33:11 +05:00
feng
cea16fc41f perf: 命令上传 取消input长度限制 2024-02-26 14:29:13 +05:00
fit2bot
4b7c0b8437 perf: 用户列表翻译超级管理员,组织管理员 2024-02-26 14:27:19 +05:00
fit2bot
09432b01a7 fix: 自动化任务密钥为 None 报错 (#12709)
Co-authored-by: feng <1304903146@qq.com>
2024-02-26 14:47:19 +08:00
wangruidong
d7f8ba58ad perf: 作业日志添加任务类型 2024-02-26 13:43:49 +08:00
吴小白
f660c38d80 fix: 添加 psycopg2 缺失依赖 2024-02-22 19:08:18 +08:00
wangruidong
edf0630cef fix: 用户列表导出优化 2024-02-22 17:44:56 +08:00
wangruidong
c4342567ba fix: 远程应用README国际化 2024-02-22 16:11:43 +08:00
fit2bot
d4e53be7ce perf: 修改core celery 组件状态 (#12684)
Co-authored-by: feng <1304903146@qq.com>
2024-02-22 14:47:26 +08:00
wangruidong
d4721e90d5 fix: LDAP用户导入会超时 2024-02-22 11:37:30 +08:00
jiangweidong
bb6c6c8f6a perf: jms-storage==0.0.56 2024-02-22 11:36:33 +08:00
fit2bot
753ab77c46 perf: 关闭页面等待ws的最大重连时间改为6秒 (#12677)
Co-authored-by: feng <1304903146@qq.com>
2024-02-21 17:51:06 +08:00
jiangweidong
ba127c506d feat: 支持工单链接直接免密审批 2024-02-21 11:39:01 +08:00
fit2bot
c21ca70158 perf: 账号收集添加资产名称模糊搜索 (#12673)
Co-authored-by: feng <1304903146@qq.com>
2024-02-20 18:42:11 +08:00
wangruidong
135fb7c6f9 perf: 终断批量快捷命令执行的任务 2024-02-20 15:09:39 +08:00
feng
f592f19b08 perf: 自动化任务按优先级默认排序 2024-02-19 18:19:14 +08:00
fit2bot
dce68cd011 perf: 授权用户不显示组件用户 (#12664)
Co-authored-by: feng <1304903146@qq.com>
2024-02-19 14:48:31 +08:00
fit2bot
d7b1903fb7 perf: 修改登录页面定期 check 的时间 (#12660)
Co-authored-by: feng <1304903146@qq.com>
2024-02-19 10:57:40 +08:00
feng
6e506e3146 fix: 【登录超时】修复登录页面提示 <登录超时,请重新登录> 问题 2024-02-19 10:21:13 +08:00
fit2bot
58d30e7f85 perf: 记录会话活动日志 (#12523)
* perf: 更新会话生命周期日志

* perf: 优化错误原因

* perf: 增加错误类型

---------

Co-authored-by: Eric <xplzv@126.com>
2024-02-06 18:28:31 +08:00
wangruidong
2062778ab8 fix: 资产登录未发送提醒 2024-02-06 15:24:41 +08:00
wangruidong
eaca296bd0 perf: 支持改密日志记录保留天数 2024-02-05 18:09:45 +08:00
Bai
1051c6af04 fix: 修复用户登录后仪表盘显示403的问题(用户在非Default组织下是组织管理员权限) 2024-02-05 16:52:09 +08:00
wangruidong
aa69353474 perf: 支持远程应用描述文案的国际化 2024-02-05 10:46:47 +08:00
jiangweidong
d1f31f078b perf: 账号支持批量更新 2024-02-04 17:28:31 +08:00
ibuler
be80663436 perf: 优化日志显示避免太长 2024-02-04 17:23:37 +08:00
wangruidong
1ae363d6bd perf: MFA认证App支持自定义下载二维码 2024-02-04 17:19:04 +08:00
fit2bot
31b0d345ad perf: 使用新的钉钉登录接口 (#12635)
* perf: 暂存

* perf: 使用新的钉钉登录接口

---------

Co-authored-by: halo <wuyihuangw@gmail.com>
2024-02-04 17:05:11 +08:00
Bai
cabda0a32f perf: 修改依赖 2024-02-04 17:02:07 +08:00
wangruidong
f606dd8920 perf: 增加国际电话区号选择 2024-02-04 14:52:15 +08:00
wangruidong
973df0360c fix: 控制台-仪表盘会话用户,资产排名不对 2024-02-04 11:42:58 +08:00
wangruidong
f9f1d96674 fix: 资产过期消息提示发送失败 2024-02-04 11:42:15 +08:00
wangruidong
0edad24d5d fix: 资产过期消息提示发送失败 2024-02-04 11:41:48 +08:00
feng
8cb74976e1 perf: 优化用户session 会话过期 2024-02-02 17:52:50 +08:00
wangruidong
279109c9a6 perf: 使用winrm协议批量上传文件 2024-01-30 11:12:24 +08:00
jiangweidong
8c7ba4a497 perf: 优化工单审批时间不准确问题 2024-01-29 16:40:03 +08:00
feng
9cc048267b feat: 批量测试账号可连接性 2024-01-29 16:39:24 +08:00
wangruidong
78d0e3f485 perf: 使用winrm协议执行快捷命令 2024-01-29 11:21:30 +08:00
wangruidong
8aefacd7ed perf: 安全模式返回授权的资产 2024-01-25 17:07:37 +08:00
ibuler
ef8db68db1 perf: 优化组织刷新资源 2024-01-25 14:48:15 +08:00
fit2bot
00256f86df perf: OAuth2协议获取token支持配置json或者data (#12602)
* perf: OAuth2协议获取token支持配置json或者data

* perf: 优化注释

---------

Co-authored-by: jiangweidong <weidong.jiang@fit2cloud.com>
2024-01-25 14:00:13 +08:00
ibuler
77569c554b perf: 去掉资产查询的默认排序 2024-01-25 13:49:49 +08:00
jiangweidong
7897462e32 perf: jms_storage==0.0.55 2024-01-25 13:48:34 +08:00
Bai
aee11827c4 feat: 修改 jms-storage 0.0.55 2024-01-25 11:48:16 +08:00
fit2bot
a6bf592046 perf: 翻译 (#12600)
Co-authored-by: feng <1304903146@qq.com>
2024-01-24 19:50:05 +08:00
Bryan
1dea424104 Revert "fix: 修复 OAuth2 认证时 POST 方式获取 access_token API 使用 json 传递数据" 2024-01-24 18:23:29 +08:00
Bai
1f5554d945 fix: 修复 OAuth2 认证时 POST 方式获取 access_token API 使用 json 传递数据 2024-01-24 16:53:11 +08:00
ibuler
0303408be8 perf: 优化授权树的刷新,同步解决同步异步的问题 2024-01-24 16:44:19 +08:00
fit2bot
f5802ace02 fix: oracle 用户是sysdba类型的 改密推送 验证账号可连接性失败 (#12596)
Co-authored-by: feng <1304903146@qq.com>
2024-01-24 16:39:43 +08:00
fit2bot
8bde45d9dc perf: 改密添加最后汇总信息 (#12595)
Co-authored-by: feng <1304903146@qq.com>
2024-01-24 16:20:44 +08:00
ibuler
e8bbc44647 perf: 优化授权的资产,速度快 10 倍 2024-01-24 16:00:18 +08:00
ibuler
34aa48d18c fix: 修复定时检测用户是否活跃任务无法执行的问题 2024-01-23 09:29:00 +00:00
ibuler
1f1c1a9157 fix: 修复定时检测用户是否活跃任务无法执行的问题 2024-01-23 09:28:38 +00:00
jiangweidong
7aa6613e69 perf: 更新jms-storage版本 2024-01-23 03:42:59 +00:00
fit2bot
503034299e fix: windows 收集账号 收集失败 (#12583)
Co-authored-by: feng <1304903146@qq.com>
2024-01-23 11:07:56 +08:00
feng
6c9d271ae1 fix: redis 密码有特殊字符celery beat启动失败 2024-01-22 06:18:34 +00:00
Bai
6ff852e225 perf: 修复 Count 时没有去重的问题 2024-01-22 06:16:25 +00:00
fit2bot
0c74e92bfb perf: 优化 labels 在 json field 中的筛选 (#12577)
* perf: 优化 labels 在 json field 中的筛选

* perf: 修改 labels 搜索

---------

Co-authored-by: ibuler <ibuler@qq.com>
2024-01-21 23:36:18 -04:00
wangruidong
3853d0bcc6 fix:绑定的端点Default下载RDP文件中的地址是空的 2024-01-19 10:38:52 +00:00
feng
cd0348cca1 perf: 优化任务列表执行时间 性能快了10倍 2024-01-19 10:33:41 +00:00
Eric
ce94348d45 perf: ignore some err 2024-01-19 12:56:46 +08:00
Eric
f74f8b7d8c perf: 优化 delay_run 执行 2024-01-19 12:56:46 +08:00
Bai
dc79346bdc perf: 修复 Count 时没有去重的问题 2024-01-19 12:54:14 +08:00
wangruidong
37a0d831da perf:ldap sync add no user msg 2024-01-18 10:31:53 +00:00
feng
e509568fe5 fix: redis 密码有特殊字符celery beat启动失败 2024-01-18 10:30:00 +00:00
Bryan
baa75dc735 Merge pull request #12566 from jumpserver/master
v3.10.2
2024-01-17 07:34:28 -04:00
Bryan
8a9f0436b8 Merge pull request #12565 from jumpserver/dev
v3.10.2
2024-01-17 07:23:30 -04:00
fit2bot
2c2c3eb21a perf: 翻译 (#12564)
Co-authored-by: feng <1304903146@qq.com>
2024-01-17 19:18:00 +08:00
fit2bot
18681d1f50 perf: 连接k8s 添加错误处理 (#12563)
Co-authored-by: feng <1304903146@qq.com>
2024-01-17 18:41:29 +08:00
feng
86ef984c02 perf: 查看授权用户 不展示组件用户 2024-01-17 03:13:39 -07:00
feng
e4d8ce097a fix: 创建资产失败 2024-01-17 18:05:30 +08:00
Eric
ae68241812 perf: 修复录像在线播放问题 2024-01-17 00:02:00 -07:00
feng
13d4177531 fix: 工单批量更新没有权限 2024-01-16 20:01:36 -07:00
feng
641e75a905 fix: 用户组列表 用户数量不准确 2024-01-16 02:19:36 -07:00
ibuler
a2d6e41816 perf: labels getter and setter for inherite model 2024-01-16 15:00:46 +08:00
wangruidong
6cd3672604 fix: sync LDAP notification error 2024-01-15 23:08:55 -07:00
Bai
3c3c1499b7 perf: Add requirement for exchangelib==5.1.0 2024-01-15 22:55:58 -07:00
fit2bot
e29e51121d perf: 优化账号版本计算策略 (#12547)
Co-authored-by: feng <1304903146@qq.com>
2024-01-16 11:33:21 +08:00
ibuler
fabee37e9e fix: user permed type tree recurse root node 2024-01-15 19:50:14 +08:00
ibuler
2994ea6f68 perf: revert asset labels api 2024-01-15 19:49:55 +08:00
halo
644eada8a1 fix: 解决openssh低版本时测试可连接失败问题 2024-01-15 10:39:34 +08:00
wangruidong
000a3038e1 fix: 终端输入错误的MFA无日志记录 2024-01-14 18:32:33 -08:00
ibuler
9c8635b230 perf: 优化授权资产 api,很多资产也不怕 2024-01-14 18:30:57 -08:00
wangruidong
e428eb351b feat: 同步ldap用户消息通知 2024-01-12 11:17:23 +05:00
fit2bot
1275087f19 perf: 添加LC_ALL环境变量C.UTF-8 解决ansible无法初始化首选语言环境问题 (#12530)
Co-authored-by: feng <1304903146@qq.com>
2024-01-12 11:21:27 +08:00
feng
311c01242b fix: 分页后排序失效 2024-01-12 10:18:05 +08:00
ibuler
bab5b67c52 fix: 修复自定义 applet 导入的 bug 2024-01-11 15:41:10 +05:00
fit2bot
3eb0b768a6 fix: 改密账号更新日期没有更新 (#12524)
Co-authored-by: feng <1304903146@qq.com>
2024-01-11 16:57:23 +07:00
fit2bot
6dcc74a388 fix: 账号备份只导出一条记录 (#12517)
Co-authored-by: wangruidong <940853815@qq.com>
2024-01-10 18:52:45 +08:00
ibuler
2b15fc5e8b perf: 兼用处理一下 tree 2024-01-10 11:23:16 +05:00
wangruidong
df655f304a fix: 登录日志不显示 2024-01-10 11:21:29 +05:00
Bai
25223719cb perf: 支持配置 RADIUS_ATTRIBUTES 属性 2024-01-09 18:36:16 +08:00
jiangweidong
814dbeb749 fix: 解决手机号加密导致忘记密码判断总是失败问题 2024-01-08 16:24:28 +05:00
jiangweidong
630bb56601 fix: 解决手机号加密导致忘记密码判断总是失败问题 2024-01-08 16:24:28 +05:00
ibuler
496b72aaee perf: 优化导入错误 2024-01-08 17:44:49 +08:00
吴小白
b57e943990 build(deps): 更新依赖版本 2024-01-08 17:03:08 +08:00
jiangweidong
b4c1dd2944 perf: slack消息解析优化-mistune升级 2024-01-08 12:35:26 +05:00
jiangweidong
9ede3670a7 perf: 邮箱支持exchange协议 2024-01-08 12:35:01 +05:00
Eric
2a29cd0e70 perf: 使用 nginx 处理静态资源 2024-01-03 17:20:14 +08:00
ibuler
15ac81a422 perf: 优化标签绑定,仅绑定到资产上 2024-01-03 17:08:15 +08:00
fit2bot
eb5a53b91b perf: 翻译 (#12487)
Co-authored-by: feng <1304903146@qq.com>
2024-01-03 11:25:45 +08:00
feng
4dd72b109f feat: 历史账号定期删除 可设置保留数量 2024-01-03 07:52:24 +05:00
fit2bot
2fcbfe9f21 perf: 优化 tree nodes 避免太慢 (#12472)
* perf: 优化 tree nodes 避免太慢

perf: 优化大量资产上的资产数生成比较慢

perf: 优化节点树

perf: 修改 tree nooooooooodes

perf: 优化一些 api 比较大的问题

perf: 优化平台 api

perf: 分页返回同步树

perf: 优化节点树

perf: 深度优化节点树

* perf: remove unused config

---------

Co-authored-by: ibuler <ibuler@qq.com>
2024-01-02 16:11:56 +08:00
wangruidong
e80a0e41ba fix: 同步LDAP用户时,用户组只移除LDAP同步过来的 2024-01-02 12:04:25 +05:00
吴小白
7cdba3ef38 build(deps): bump pyfreerdp from 0.0.1 to 0.0.2 2024-01-02 08:13:28 +05:00
feng
2d6e815b3d fix: 如 Redis密码信息包含特殊字符时,服务启动失败 2024-01-02 08:11:12 +05:00
Bryan
38642024be Update README.md (#12463) 2023-12-29 16:23:02 +08:00
faming.zhou
257ee205ac fix: UNION 的类型 character varying 和 uuid 不匹配 2023-12-29 12:14:31 +05:00
Bryan
a9620a3cbe Merge pull request #12461 from jumpserver/master
v3.10.1
2023-12-29 11:33:05 +05:00
Bryan
769e7dc8a0 Merge pull request #12460 from jumpserver/dev
v3.10.1
2023-12-29 11:20:36 +05:00
feng
4b961a626b perf: 用户组列表中的用户添加 is_service_account 属性 2023-12-29 11:11:19 +05:00
Bryan
2a70449411 Merge pull request #12458 from jumpserver/dev
v3.10.1
2023-12-29 11:01:13 +05:00
wangruidong
653a6752b6 fix: 用户组删除用户权限问题 2023-12-29 13:36:39 +08:00
wangruidong
32255c6077 fix: 更新用户组权限问题 2023-12-29 07:42:10 +05:00
feng626
7a708156ee Revert "fix: 特定key paramiko 测试可连接性失败"
This reverts commit a4d0e3fd17.
2023-12-28 14:34:42 +05:00
Bai
b72a446bbd fix: 修复label关联用户时不显示服务账号 2023-12-28 14:21:38 +05:00
feng
219fad9b62 fix: 账号备份密码如 t08\x08fIE 备份失败 2023-12-28 13:53:57 +05:00
Bai
6c1c8b241e perf: 优化资产管理中的标签权限位不显示 2023-12-28 15:27:20 +08:00
feng
a4d0e3fd17 fix: 特定key paramiko 测试可连接性失败 2023-12-27 13:13:03 +05:00
fit2bot
af44ffab0a fix: 资产账号不存在时 同步删除资产账号任务报错 (#12437)
Co-authored-by: feng <1304903146@qq.com>
2023-12-27 14:39:11 +08:00
fit2bot
a09b7b29e2 fix: 【账号收集】账号收集任务,关闭同步到资产时,只收集了一个资产的账号,其他资产的账号未收集 (#12428)
Co-authored-by: feng <1304903146@qq.com>
2023-12-26 16:05:28 +08:00
wangruidong
8f67922c80 perf: 资产登录提醒和用户登录提醒能显示用户名称 2023-12-26 12:54:28 +05:00
feng
f1db5d6f44 perf: 重写GenericForeignKey 2023-12-26 12:51:09 +05:00
fit2bot
33ea5eb41f perf: 资产可以通过address 排序 (#12427)
Co-authored-by: feng <1304903146@qq.com>
2023-12-26 14:23:04 +08:00
fit2bot
48bcbc6c53 perf: 翻译 (#12426)
Co-authored-by: feng <1304903146@qq.com>
2023-12-26 14:16:11 +08:00
Bai
3e090eb701 fix: 修复 连接远程应用时标签匹配失败的问题 2023-12-25 16:20:38 +05:00
Bai
6ac956c626 fix: 修复 api/docs 报错问题 2023-12-25 19:04:44 +08:00
wangruidong
edb2d1bd7b fix: 我的资产列表标签信息没有显示 2023-12-25 16:02:16 +05:00
feng
81b4909016 fix: 【用户登录会话失效问题】SESSION_COOKIE_AGE 配置不生效的问题 2023-12-25 13:10:58 +05:00
wangruidong
f6f1be423c perf: 统计任务执行结果 2023-12-22 14:18:25 +05:00
Bryan
fae5392a03 Update README.md 2023-12-22 12:30:11 +05:00
Bryan
d5224968bc Update README.md 2023-12-22 12:30:11 +05:00
feng
6565f8c0a8 perf: 在 ansible 中切换用户时 添加超时操作 2023-12-22 12:23:19 +05:00
Bryan
8df720f19e Merge pull request #12401 from jumpserver/dev
v3.10
2023-12-21 15:14:19 +05:00
ibuler
bc5494bbb0 perf: 优化 label choice 2023-12-21 16:51:36 +08:00
fit2bot
febf08629a fix: 翻译 (#12400)
Co-authored-by: feng <1304903146@qq.com>
2023-12-21 16:15:20 +08:00
fit2bot
b6774aa749 perf: 更新全局组织名字 添加唯一性校验 (#12399)
Co-authored-by: feng <1304903146@qq.com>
2023-12-21 16:07:11 +08:00
fit2bot
bc668f3e9f fix: applet 压缩包名字(1).zip时 上传失败} (#12397)
Co-authored-by: feng <1304903146@qq.com>
2023-12-21 15:42:09 +08:00
fit2bot
dc56b019b1 perf: 权限树翻译 (#12396)
Co-authored-by: feng <1304903146@qq.com>
2023-12-21 15:00:46 +08:00
ibuler
a38624d198 perf: 修改同名账号登录报错 2023-12-21 14:04:09 +08:00
ibuler
ca026040fe perf: 优化导入账号报错 2023-12-21 12:40:59 +08:00
ibuler
88b9a4d693 perf: 修改搜索 2023-12-20 17:50:40 +05:00
ibuler
4d15e46ceb perf: 修改搜索 2023-12-20 17:50:40 +05:00
fit2bot
55575e9f7f perf: 用户账号导出去除is_service_account (#12388)
Co-authored-by: feng <1304903146@qq.com>
2023-12-20 19:24:09 +08:00
wangruidong
98c9cddcbf fix: es命令记录可以看到其他资产执行的命令 2023-12-20 16:00:35 +05:00
fit2bot
9f67ba573c perf: dockerfile 添加 freerdp2-dev 依赖 (#12386)
Co-authored-by: feng <1304903146@qq.com>
2023-12-20 18:48:37 +08:00
fit2bot
533f13c634 perf: 优化创建账号密码校验逻辑 (#12383)
Co-authored-by: feng <1304903146@qq.com>
2023-12-20 16:35:36 +08:00
fit2bot
c66b1db784 fix: 自动化任务网关连接数 自定义ansible rdp 测试可连接性端口错误 (#12373)
Co-authored-by: feng <1304903146@qq.com>
2023-12-20 16:02:13 +08:00
Eric
d03ba7c391 perf: 页面配置是否启用 Vitual App 2023-12-20 13:01:50 +05:00
fit2bot
6544f8ade8 perf: 修改 labels 搜索 (#12379)
* perf: 修改标签的搜索

* perf: 修改 labels 搜索

---------

Co-authored-by: ibuler <ibuler@qq.com>
2023-12-20 14:31:03 +08:00
ibuler
ac5991fc43 perf: 修改标签的搜索 2023-12-20 10:57:58 +05:00
wangruidong
9b2b71dddc fix: 工单列表类型没有翻译 2023-12-20 10:57:29 +05:00
fit2bot
e18e019460 fix: 账号列表,添加账号模版 500 (#12375)
Co-authored-by: feng <1304903146@qq.com>
2023-12-20 11:05:42 +08:00
fit2bot
ef1875d9b5 perf: 优化工单显示 (#12368)
Co-authored-by: wangruidong <940853815@qq.com>
2023-12-19 19:06:39 +08:00
ibuler
0b7552a6ee perf: 修改 labels 绑定引起的问题 2023-12-19 16:06:05 +05:00
fit2bot
45425b11d2 perf: 优化 labels 支持多个搜索 (#12367)
Co-authored-by: ibuler <ibuler@qq.com>
2023-12-19 18:46:02 +08:00
jiangweidong
fda3e6ec9b perf: model_to_dict无法转换不可编辑字段,导致消息中有的值为None 2023-12-19 14:30:13 +05:00
huailei
2b41486f2a Merge pull request #12369 from jumpserver/pr@dev@chat_ai_test
fix: chatAI代理配置错误,服务器报错500
2023-12-19 15:42:06 +08:00
feng
59d9a3d4ec fix: chatAI代理配置错误,服务器报错500 2023-12-19 15:39:22 +08:00
wangruidong
3c7ba029dd perf: 工单显示优化 2023-12-19 12:10:34 +05:00
huailei
1335556272 Merge pull request #12366 from jumpserver/pr@dev@command
fix: 命令组模糊搜索,500
2023-12-19 15:07:50 +08:00
feng
8eab87f40d fix: 命令组模糊搜索,500 2023-12-19 15:05:35 +08:00
huailei
c441e5bb92 Merge pull request #12365 from jumpserver/pr@dev@ansible
fix: 修复ansible 任务 {{123}} 这样的密码失败问题
2023-12-19 14:59:23 +08:00
feng
da8d78f384 fix: 修复ansible 任务 {{123}} 这样的密码失败问题 2023-12-19 14:57:51 +08:00
jiangweidong
83b91cb739 perf: 优化命令禁止发送消息时,slack消息会包含html标签内容 2023-12-19 11:40:18 +05:00
fit2bot
1afad40dd3 perf: 优化 labels 绑定资源 (#12361)
* perf: 优化 labels 绑定资源

* perf: 优化 labels list 显示

* perf: add migrations

---------

Co-authored-by: ibuler <ibuler@qq.com>
2023-12-19 10:20:11 +08:00
ibuler
1358cf532f perf: 修改 labels 和 role 搜索 2023-12-18 18:23:06 +05:00
huailei
1e7f268f0c Merge pull request #12360 from jumpserver/pr@dev@translate
perf: 翻译
2023-12-18 18:38:21 +08:00
feng
d6b5590505 perf: 翻译 2023-12-18 18:36:44 +08:00
huailei
79b3b31492 Merge pull request #12358 from jumpserver/pr@dev@makemigrations
perf: 迁移文件
2023-12-18 17:46:23 +08:00
feng
4f2b3fbb43 perf: 迁移文件 2023-12-18 17:44:59 +08:00
fit2bot
1f2db65dba fix: ansible 密码支持 {{ }} {% %} (#12354)
Co-authored-by: feng <1304903146@qq.com>
2023-12-18 17:31:35 +08:00
halo
006faac326 perf: 配置xpack后logo没有修改 2023-12-18 14:51:28 +08:00
ibuler
f7fee0f430 perf: 修复标签搜索 2023-12-15 18:34:44 +08:00
fit2bot
714c44fbf4 perf: 授权创建时 通过模版创建账号 给账号添加来源 (#12345)
Co-authored-by: feng <1304903146@qq.com>
2023-12-15 18:28:48 +08:00
fit2bot
84b316e2c1 fix: 修复自动禁用用户默认排除 admin 用户 (#12346)
Co-authored-by: Bai <baijiangjie@gmail.com>
2023-12-15 18:28:24 +08:00
wangruidong
6955a3db11 perf: ldap测试登录提示优化&上产文件名长度限制 2023-12-15 18:06:47 +08:00
huailei
d92736e624 Merge pull request #12343 from jumpserver/pr@dev@perf_password_rules
perf: 优化校验密码规则 特殊字符校验
2023-12-15 17:16:04 +08:00
feng
9d0da64ea1 perf: 优化校验密码规则 特殊字符校验 2023-12-15 17:14:34 +08:00
wangruidong
b9e1d6093e perf: 翻译 2023-12-15 15:21:52 +08:00
Bai
c3820b30b8 fix: 修复远程应用连接 labels 过滤问题 2023-12-15 13:26:01 +08:00
huailei
6955fc1734 Merge pull request #12337 from jumpserver/pr@dev@prompt
perf: 修改默认prompt顺序
2023-12-15 10:57:18 +08:00
feng
32178b2344 perf: 修改默认prompt顺序 2023-12-15 10:48:04 +08:00
fit2bot
e3c0518cfb perf: 上传目标目录指定在/tmp下 (#12334)
Co-authored-by: wangruidong <940853815@qq.com>
2023-12-14 19:44:53 +08:00
jiangweidong
438e9dee2a fix: 解决第三方登录一个不存在的本地用户时,改密日志会增加的问题 2023-12-14 19:36:06 +08:00
Bai
3c9239eb09 fix: 修复 Release 应用账号的逻辑,解决首次连接远程应用可能出现没有可用账号的问题 2023-12-14 19:35:05 +08:00
Eric
81fb080c67 perf: 调整搜索字段 2023-12-14 18:28:55 +08:00
feng
6cf05435bf feat: chat prompt 2023-12-14 17:39:15 +08:00
wangruidong
65718c5a84 perf: 接口返回上传文件大小限制 2023-12-14 11:26:44 +08:00
wangruidong
27daebbe1b perf: 上传文件大小限制 2023-12-14 10:34:58 +08:00
huailei
dce1079fdc Merge pull request #12324 from jumpserver/pr@dev@perm_label
perf: 修改下线用户会话的权限位label
2023-12-13 17:39:15 +08:00
feng
d07db68426 perf: 修改下线用户会话的权限位label 2023-12-13 17:28:57 +08:00
huailei
6d37300a30 Merge pull request #12323 from jumpserver/pr@dev@gather_account
fix: 收集账号过滤asset_id 失败
2023-12-13 16:35:17 +08:00
feng
0c96af32c2 fix: 手机账号过滤asset_id 失败 2023-12-13 16:33:50 +08:00
huailei
1c6b1b0625 Merge pull request #12321 from jumpserver/pr@dev@translate
perf: 翻译
2023-12-13 16:16:50 +08:00
feng
4f7b4842f6 perf: 翻译 2023-12-13 16:15:34 +08:00
fit2bot
c4fef5899c perf: 连接 RDP 协议会话时,高级选项支持 session bpp:i 参数配置;默认 32; (#12319)
Co-authored-by: feng <1304903146@qq.com>
2023-12-13 15:51:22 +08:00
wangruidong
5b51a8231c fix: 点击备案号未跳转到指定链接 2023-12-13 11:22:41 +08:00
huailei
54417dd6d3 Merge pull request #12312 from jumpserver/pr@dev@chat_setting
fix: chat ai测试可连接性时失败
2023-12-12 19:15:26 +08:00
feng
2c7ad90524 fix: chat ai测试可连接性时失败 2023-12-12 19:14:13 +08:00
ibuler
01fcdad489 perf: 优化用户不活跃检测 2023-12-12 17:19:19 +08:00
feng
8801003461 perf: 支持 西班牙 Keyboard Layout 2023-12-12 17:16:55 +08:00
huailei
696397fdb0 Merge pull request #12306 from jumpserver/pr@dev@translate
perf: 翻译
2023-12-12 15:37:37 +08:00
feng
87a24991f1 perf: 翻译 2023-12-12 15:34:13 +08:00
Eric
3ec93b8f04 perf: 添加录像不支持 2023-12-12 14:56:45 +08:00
ibuler
4f1826d3ed perf: get request ip, only using x-forwarded-for 2023-12-12 14:44:57 +08:00
ibuler
9260f26c99 perf: 优化 db constrains 2023-12-12 14:44:11 +08:00
fit2bot
93da3e58f2 perf: 【优化系统任务】支持显示 执行周期、下次开始时间 字段 (#12298)
Co-authored-by: feng <1304903146@qq.com>
2023-12-12 14:18:26 +08:00
wangruidong
1eff33f3f7 perf: 优化获取同名文件列表 2023-12-12 10:39:23 +08:00
wangruidong
8e89d42343 perf: 同名文件处理 2023-12-12 10:39:23 +08:00
wangruidong
d0b0c87d3c feat: 支持批量发送文件 2023-12-12 10:39:23 +08:00
ibuler
e3ac26e377 perf: 修改 rbac labels node 2023-12-11 14:43:15 +08:00
Eric
4ea20a9103 perf: 优化迁移文件 verbose_name 2023-12-11 14:40:21 +08:00
fit2bot
dd57b14562 feat: 增加 sqlserver 支持 (#12288)
* feat: 增加 sqlserver 支持

* feat: 删除一些  migrations

---------

Co-authored-by: Aaron3S <chenyang@fit2cloud.com>
2023-12-11 13:57:35 +08:00
wangruidong
c312cdb625 perf: 优化资产授权过期提示信息 2023-12-11 11:41:52 +08:00
huailei
85fedf0704 Merge pull request #12287 from jumpserver/pr@dev@public_api
perf: PublicSetting API 添加GPT参数
2023-12-08 17:54:06 +08:00
feng
8b05260a6c perf: PublicSetting API 添加GPT参数 2023-12-08 17:46:40 +08:00
Bai
47cb6b1ec0 perf: 优化资产列表支持通过 创建日期 进行排序 2023-12-08 16:39:13 +08:00
huailei
79b5dff210 Merge pull request #12286 from jumpserver/pr@dev@password_rule
fix: 改密计划创建更新失败
2023-12-08 16:36:04 +08:00
feng
b08e1f6a47 fix: 改密计划创建更新失败 2023-12-08 16:34:46 +08:00
Bai
2e3184cbd6 fix: 修复 Endpoint 获取错误问题 2023-12-08 16:33:27 +08:00
huailei
fb903e53a4 Merge pull request #12284 from jumpserver/pr@dev@translate
perf: 翻译
2023-12-08 16:18:36 +08:00
feng
cc7220a4ad perf: 翻译 2023-12-08 16:17:20 +08:00
fit2bot
81de527e32 perf: 解决Slack解绑用户404问题 (#12283)
Co-authored-by: jiangweidong <weidong.jiang@fit2cloud.com>
2023-12-08 15:11:49 +08:00
huailei
7ad2abe104 Merge pull request #12280 from jumpserver/pr@dev@migrate
perf: 修改迁移文件
2023-12-08 14:22:55 +08:00
feng
9a2da98bd4 perf: 修改迁移文件 2023-12-08 14:21:10 +08:00
feng
eca50874f0 feat: 同步删除远程机器账号 2023-12-08 14:13:55 +08:00
fit2bot
8f82ca9856 perf: 优化操作日志 (#12249)
* perf: 优化操作日志

* perf: 修改migrations中关于Nodes的verbose_name

* perf: 优化代码逻辑

* perf: 优化日志详情展示逻辑

* perf: 代码优雅一下

---------

Co-authored-by: jiangweidong <weidong.jiang@fit2cloud.com>
2023-12-05 17:26:47 +08:00
Eric
e193d7a942 perf: 完善 yaml 加载 2023-12-05 17:25:25 +08:00
fit2bot
d2429f7883 feat: 支持 virtual app (#12199)
* feat: 支持 virtual app

* perf: 增加 virtual host

* perf: 新增 virtual app 上传接口

* perf: 更名为 app provider

* perf: 优化代码

---------

Co-authored-by: Eric <xplzv@126.com>
2023-12-05 16:52:11 +08:00
ibuler
a43bb25b5a perf: 优化 applet 账号选择 2023-12-05 16:19:56 +08:00
ibuler
ffe3e8a70c perf: 优化 for tidb 2023-12-05 16:13:16 +08:00
ibuler
0e7e499a1e perf: 修改 labels 创建 2023-12-05 15:22:47 +08:00
ibuler
e812e3ff89 fix: 优化 endpoint 的 ipv6 支持 2023-12-05 14:56:05 +08:00
halo
d2eacad97b perf: 更新客户端 v2.1.0 2023-12-05 14:02:33 +08:00
fit2bot
8291a81efd perf: 支持全局的 labels (#12043)
* perf: 支持全局的 labels

* perf: stash

* stash

* stash

* stash

* stash

* perf: 优化 labels

* stash

* perf: add debug sql

* perf: 修改 labels

* perf: 优化提交

* perf: 优化提交 labels

* perf: 基本完成

* perf: 完成 labels 搜索

* perf: 优化 labels

* perf: 去掉不用 debug

---------

Co-authored-by: ibuler <ibuler@qq.com>
2023-12-05 11:16:34 +08:00
fit2bot
a91cb1afd5 feat: 系统设置可配置gpt (#12207)
* feat: 系统设置可配置gpt

* perf: 添加gpt的terminal config

---------

Co-authored-by: feng <1304903146@qq.com>
2023-12-05 10:58:19 +08:00
wangruidong
2cad97065f feat: 资产详情页面添加历史执行命令列表页面 2023-12-04 16:38:28 +08:00
fit2bot
cf18300360 fix: 添加Slack认证发送消息格式包 (#12229)
Co-authored-by: jiangweidong <weidong.jiang@fit2cloud.com>
2023-11-30 14:38:43 +08:00
wangruidong
3cd22f05d2 perf: 优化工单处理提示消息页面 2023-11-30 10:15:26 +08:00
吴小白
eee41008cc perf: 优化 celery health 判断 2023-11-30 10:09:42 +08:00
fit2bot
0fdae00722 perf: 支持slack通知和认证 (#12193)
* perf: 支持slack通知和认证

* perf: 生成迁移文件

* perf: 优化获取access_token逻辑

---------

Co-authored-by: jiangweidong <weidong.jiang@fit2cloud.com>
2023-11-29 17:45:44 +08:00
吴小白
575562c416 Merge pull request #12216 from jumpserver/pr@dev@patch_greenlet
build(deps): bump github.com/python-greenlet/greenlet from 2.0.2 to 3.0.1
2023-11-28 16:26:25 +08:00
吴小白
e2b7f67fdc build(deps): bump github.com/python-greenlet/greenlet from 2.0.2 to 3.0.1
Signed-off-by: 吴小白 <296015668@qq.com>
2023-11-28 16:14:48 +08:00
fit2bot
d2498c0d53 fix: sftp不能设置为默认存储 (#12213)
Co-authored-by: wangruidong <940853815@qq.com>
2023-11-28 15:21:40 +08:00
huailei
01e40fd238 Merge pull request #12211 from jumpserver/pr@dev@random
perf: 随机密码生成规则添加可排除字符选项
2023-11-28 14:49:35 +08:00
feng
370ef11486 perf: 随机密码生成规则添加可排除字符选项 2023-11-28 14:46:51 +08:00
ibuler
089cadeae3 perf: 优化 queryset count 2023-11-28 12:54:04 +08:00
wangruidong
6b748e5ac5 feat: 用户详情展示所有会话 2023-11-28 12:52:11 +08:00
fit2bot
6d611bbbbd feat: 作业中心数据库支持网域命令执行 (#12117)
Co-authored-by: jiangweidong <weidong.jiang@fit2cloud.com>
2023-11-27 11:22:34 +08:00
wangruidong
18670d493e perf: 优化工单处理提示消息页面 2023-11-27 11:14:27 +08:00
ibuler
ba38852354 perf: 优化跳转页 2023-11-24 17:00:39 +08:00
wangruidong
64f3509c8c feat: 支持备案配置 2023-11-24 13:55:47 +08:00
huailei
805c78c0de Merge pull request #12188 from jumpserver/pr@dev@perf_ldap_user_websocket
perf: ldap接口请求换成websocket连接
2023-11-23 15:09:15 +08:00
huailei
11accf8854 Merge pull request #12189 from jumpserver/pr@dev@translate
perf: 国际化翻译
2023-11-22 18:33:06 +08:00
feng
18f6ffe0ce perf: 国际化翻译 2023-11-22 18:30:56 +08:00
wangruidong
6b7119ea74 perf: ldap接口请求换成websocket连接 2023-11-22 16:56:31 +08:00
huailei
efc7ca1164 Merge pull request #12182 from jumpserver/pr@dev@translate
perf: 修改翻译
2023-11-22 11:12:45 +08:00
feng
a6de9bdde6 perf: 修改翻译 2023-11-22 11:09:16 +08:00
feng
6e7074ba40 fix: mysql 开始ssl后 再关闭测试失败 2023-11-20 15:38:53 +08:00
feng
2edcb2f2d3 fix: mysql 开启ssl 再关闭 测试可连接性失败 2023-11-20 10:52:23 +08:00
ibuler
07e1918fa1 perf: 优化延迟运行
fix: 延迟执行设置超时

perf: 修改 delay run

perf: 优化 delay_run 执行

perf: 修改 delay run
2023-11-20 10:29:51 +08:00
feng
452b383278 fix: redis 开启 ssl websocket连接失败 2023-11-20 10:23:21 +08:00
ibuler
ed92f10208 fix: 修复自动禁用非活跃用户任务 2023-11-17 15:41:16 +08:00
Chenyang Shen
e8331ca708 Merge pull request #12148 from jumpserver/pr@dev@fix_delete_debug_info
fix: 删除debug信息
2023-11-17 11:33:52 +08:00
Aaron3S
814130204a fix: 删除debug信息 2023-11-17 10:45:44 +08:00
吴小白
e7dc9a2f6f perf: 优化 Dockerfile 2023-11-17 10:39:17 +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
吴小白
6f4082f800 fix: 修正 actions 测试构建任务 2023-10-16 14:00:40 +08:00
吴小白
edd65f965b perf: 按照需求添加 core-ce 镜像 2023-10-16 13:30:51 +08:00
460 changed files with 13042 additions and 5415 deletions

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 python:3.11-slim-bullseye as stage-build
FROM python:3.11-slim-bullseye as stage-1
ARG TARGETARCH
ARG VERSION
@@ -6,9 +6,10 @@ 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 python:3.11-slim-bullseye
FROM python:3.11-slim-bullseye as stage-2
ARG TARGETARCH
ARG BUILD_DEPENDENCIES=" \
@@ -18,11 +19,11 @@ ARG BUILD_DEPENDENCIES=" \
ARG DEPENDENCIES=" \
freetds-dev \
libpq-dev \
libffi-dev \
libjpeg-dev \
libkrb5-dev \
libldap2-dev \
libpq-dev \
libsasl2-dev \
libssl-dev \
libxml2-dev \
@@ -31,6 +32,54 @@ ARG DEPENDENCIES=" \
freerdp2-dev \
libaio-dev"
ARG TOOLS=" \
ca-certificates \
curl \
default-libmysqlclient-dev \
default-mysql-client \
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-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 ${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 \
libpq-dev \
libx11-dev \
freerdp2-dev \
libxmlsec1-openssl"
ARG TOOLS=" \
ca-certificates \
curl \
@@ -47,39 +96,30 @@ ARG TOOLS=" \
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 \
&& apt-get update \
&& 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} \
&& 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
COPY --from=stage-2 /opt/py3 /opt/py3
COPY --from=stage-1 /opt/jumpserver/release/jumpserver /opt/jumpserver
COPY --from=stage-build /opt/jumpserver/release/jumpserver /opt/jumpserver
WORKDIR /opt/jumpserver
ARG PIP_MIRROR=https://pypi.tuna.tsinghua.edu.cn/simple
RUN --mount=type=cache,target=/root/.cache \
set -ex \
&& echo > /opt/jumpserver/config.yml \
&& pip install poetry -i ${PIP_MIRROR} \
&& poetry config virtualenvs.create false \
&& poetry install --only=main
ARG VERSION
ENV VERSION=$VERSION
VOLUME /opt/jumpserver/data
VOLUME /opt/jumpserver/logs
ENV LANG=zh_CN.UTF-8
EXPOSE 8080

View File

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

1
GITSHA Normal file
View File

@@ -0,0 +1 @@
eedc2f1b4195b038a477fd18d7c81fec1543b17f

View File

@@ -61,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)
@@ -93,11 +94,12 @@ JumpServer 堡垒机帮助企业以更安全的方式管控和登录各种类型
| [KoKo](https://github.com/jumpserver/koko) | <a href="https://github.com/jumpserver/koko/releases"><img alt="Koko release" src="https://img.shields.io/github/release/jumpserver/koko.svg" /></a> | JumpServer 字符协议 Connector 项目 |
| [Lion](https://github.com/jumpserver/lion-release) | <a href="https://github.com/jumpserver/lion-release/releases"><img alt="Lion release" src="https://img.shields.io/github/release/jumpserver/lion-release.svg" /></a> | JumpServer 图形协议 Connector 项目,依赖 [Apache Guacamole](https://guacamole.apache.org/) |
| [Razor](https://github.com/jumpserver/razor) | <img alt="Chen" src="https://img.shields.io/badge/release-私有发布-red" /> | JumpServer RDP 代理 Connector 项目 |
| [Tinker](https://github.com/jumpserver/tinker) | <img alt="Tinker" src="https://img.shields.io/badge/release-私有发布-red" /> | JumpServer 远程应用 Connector 项目 |
| [Tinker](https://github.com/jumpserver/tinker) | <img alt="Tinker" src="https://img.shields.io/badge/release-私有发布-red" /> | JumpServer 远程应用 Connector 项目 (Windows) |
| [Panda](https://github.com/jumpserver/Panda) | <img alt="Panda" src="https://img.shields.io/badge/release-私有发布-red" /> | JumpServer 远程应用 Connector 项目 (Linux) |
| [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 安装包 项目 |
@@ -111,7 +113,7 @@ JumpServer是一款安全产品请参考 [基本安全建议](https://docs.ju
## License & Copyright
Copyright (c) 2014-2023 飞致云 FIT2CLOUD, All rights reserved.
Copyright (c) 2014-2024 飞致云 FIT2CLOUD, All rights reserved.
Licensed under The GNU General Public License version 3 (GPLv3) (the "License"); you may not use this file except in
compliance with the License. You may obtain a copy of the License at

View File

@@ -1,9 +1,13 @@
from django.db.models import Q
from rest_framework.generics import CreateAPIView
from rest_framework.response import Response
from accounts import serializers
from accounts.tasks import verify_accounts_connectivity_task, push_accounts_to_assets_task
from assets.exceptions import NotSupportedTemporarilyError
from accounts.models import Account
from accounts.permissions import AccountTaskActionPermission
from accounts.tasks import (
remove_accounts_task, verify_accounts_connectivity_task, push_accounts_to_assets_task
)
from authentication.permissions import UserConfirmation, ConfirmType
__all__ = [
'AccountsTaskCreateAPI',
@@ -12,40 +16,48 @@ __all__ = [
class AccountsTaskCreateAPI(CreateAPIView):
serializer_class = serializers.AccountTaskSerializer
permission_classes = (AccountTaskActionPermission,)
def check_permissions(self, request):
act = request.data.get('action')
if act == 'push':
code = 'accounts.push_account'
else:
code = 'accounts.verify_account'
has = request.user.has_perm(code)
if not has:
self.permission_denied(request)
def get_permissions(self):
act = self.request.data.get('action')
if act == 'remove':
self.permission_classes = [
AccountTaskActionPermission,
UserConfirmation.require(ConfirmType.PASSWORD)
]
return super().get_permissions()
@staticmethod
def get_account_ids(data, action):
account_type = 'gather_accounts' if action == 'remove' else 'accounts'
accounts = data.get(account_type, [])
account_ids = [str(a.id) for a in accounts]
if action == 'remove':
return account_ids
assets = data.get('assets', [])
asset_ids = [str(a.id) for a in assets]
ids = Account.objects.filter(
Q(id__in=account_ids) | Q(asset_id__in=asset_ids)
).distinct().values_list('id', flat=True)
return [str(_id) for _id in ids]
def perform_create(self, serializer):
data = serializer.validated_data
accounts = data.get('accounts', [])
params = data.get('params')
account_ids = [str(a.id) for a in accounts]
action = data['action']
ids = self.get_account_ids(data, action)
if data['action'] == 'push':
task = push_accounts_to_assets_task.delay(account_ids, params)
if action == 'push':
task = push_accounts_to_assets_task.delay(ids, data.get('params'))
elif action == 'remove':
task = remove_accounts_task.delay(ids)
elif action == 'verify':
task = verify_accounts_connectivity_task.delay(ids)
else:
account = accounts[0]
asset = account.asset
if not asset.auto_config['ansible_enabled'] or \
not asset.auto_config['ping_enabled']:
raise NotSupportedTemporarilyError()
task = verify_accounts_connectivity_task.delay(account_ids)
raise ValueError(f"Invalid action: {action}")
data = getattr(serializer, '_data', {})
data["task"] = task.id
setattr(serializer, '_data', data)
return task
def get_exception_handler(self):
def handler(e, context):
return Response({"error": str(e)}, status=401)
return handler

View File

@@ -1,11 +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
from accounts.tasks import execute_automation_record_task
from orgs.mixins.api import OrgBulkModelViewSet, OrgGenericViewSet
from .base import (
AutomationAssetsListApi, AutomationRemoveAssetApi, AutomationAddAssetApi,
@@ -29,18 +31,27 @@ class ChangeSecretAutomationViewSet(OrgBulkModelViewSet):
class ChangeSecretRecordViewSet(mixins.ListModelMixin, OrgGenericViewSet):
serializer_class = serializers.ChangeSecretRecordSerializer
filter_fields = ('asset', 'execution_id')
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')
return queryset.filter(execution_id=eid)
@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

@@ -3,19 +3,26 @@ import time
from collections import defaultdict, OrderedDict
from django.conf import settings
from openpyxl import Workbook
from rest_framework import serializers
from xlsxwriter import Workbook
from accounts.notifications import AccountBackupExecutionTaskMsg
from accounts.const.automation import AccountBackupType
from accounts.models.automations.backup_account import AccountBackupAutomation
from accounts.notifications import AccountBackupExecutionTaskMsg, AccountBackupByObjStorageExecutionTaskMsg
from accounts.serializers import AccountSecretSerializer
from assets.const import AllTypes
from common.utils.file import encrypt_and_compress_zip_file
from common.utils.timezone import local_now_display
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
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):
@@ -67,7 +74,7 @@ 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
@@ -137,13 +144,14 @@ class AccountBackupHandler:
wb = Workbook(filename)
for sheet, data in data_map.items():
ws = wb.create_sheet(str(sheet))
for row in data:
ws.append(row)
wb.save(filename)
ws = wb.add_worksheet(str(sheet))
for row_index, row_data in enumerate(data):
for col_index, col_data in enumerate(row_data):
ws.write_string(row_index, col_index, col_data)
wb.close()
files.append(filename)
timedelta = round((time.time() - time_start), 2)
print('步骤完成: 用时 {}s'.format(timedelta))
print('创建备份文件完成: 用时 {}s'.format(timedelta))
return files
def send_backup_mail(self, files, recipients):
@@ -152,7 +160,7 @@ class AccountBackupHandler:
recipients = User.objects.filter(id__in=list(recipients))
print(
'\n'
'\033[32m>>> 发送备份邮件\033[0m'
'\033[32m>>> 开始发送备份邮件\033[0m'
''
)
plan_name = self.plan_name
@@ -160,20 +168,42 @@ class AccountBackupHandler:
if not user.secret_key:
attachment_list = []
else:
password = user.secret_key.encode('utf8')
attachment = os.path.join(PATH, f'{plan_name}-{local_now_display()}-{time.time()}.zip')
encrypt_and_compress_zip_file(attachment, password, files)
attachment = os.path.join(PATH, f'{plan_name}-{local_now_filename()}-{time.time()}.zip')
encrypt_and_compress_zip_file(attachment, user.secret_key, files)
attachment_list = [attachment, ]
AccountBackupExecutionTaskMsg(plan_name, user).publish(attachment_list)
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')
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)
def step_perform_task_update(self, is_success, reason):
self.execution.reason = reason[:1024]
self.execution.is_success = is_success
self.execution.save()
print('已完成对任务状态的更新')
print('\n已完成对任务状态的更新\n')
@staticmethod
def step_finished(is_success):
@@ -186,24 +216,11 @@ class AccountBackupHandler:
is_success = False
error = '-'
try:
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[32m>>> 该备份任务未分配收件人\033[0m'
''
)
if recipients_part_one and recipients_part_two:
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)
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
print('任务执行被异常中断')
@@ -217,6 +234,52 @@ 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):
print('任务开始: {}'.format(local_now_display()))
time_start = time.time()
@@ -229,4 +292,4 @@ class AccountBackupHandler:
finally:
print('\n任务结束: {}'.format(local_now_display()))
timedelta = round((time.time() - time_start), 2)
print('用时: {}'.format(timedelta))
print('用时: {}s'.format(timedelta))

View File

@@ -19,6 +19,7 @@
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 (paramiko)
custom_command:
@@ -40,6 +41,7 @@
ignore_errors: true
when: ping_info is succeeded
register: change_info
delegate_to: localhost
- name: Verify password (paramiko)
ssh_ping:
@@ -52,3 +54,4 @@
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

@@ -6,15 +6,27 @@ category:
type:
- all
method: change_secret
protocol: ssh
priority: 50
params:
- name: commands
type: list
label: '自定义命令'
label: "{{ 'Params commands label' | trans }}"
default: [ '' ]
help_text: '自定义命令中如需包含账号的 账号、密码、SSH 连接的用户密码 字段,<br />请使用 &#123;username&#125;、&#123;password&#125;、&#123;login_password&#125;格式,执行任务时会进行替换 。<br />比如针对 Cisco 主机进行改密,一般需要配置五条命令:<br />1. enable<br />2. &#123;login_password&#125;<br />3. configure terminal<br />4. username &#123;username&#125; privilege 0 password &#123;password&#125; <br />5. end'
help_text: "{{ 'Params commands help text' | trans }}"
i18n:
SSH account change secret:
zh: 使用 SSH 命令行自定义改密
ja: SSH コマンドライン方式でカスタムパスワード変更
en: Custom password change by SSH command line
zh: '使用 SSH 命令行自定义改密'
ja: 'SSH コマンドライン方式でカスタムパスワード変更'
en: 'Custom password change by SSH command line'
Params commands help text:
zh: '自定义命令中如需包含账号的 账号、密码、SSH 连接的用户密码 字段,<br />请使用 &#123;username&#125;、&#123;password&#125;、&#123;login_password&#125;格式,执行任务时会进行替换 。<br />比如针对 Cisco 主机进行改密,一般需要配置五条命令:<br />1. enable<br />2. &#123;login_password&#125;<br />3. configure terminal<br />4. username &#123;username&#125; privilege 0 password &#123;password&#125; <br />5. end'
ja: 'カスタム コマンドに SSH 接続用のアカウント番号、パスワード、ユーザー パスワード フィールドを含める必要がある場合は、<br />&#123;ユーザー名&#125;、&#123;パスワード&#125;、&#123;login_password& を使用してください。 # 125; 形式。タスクの実行時に置き換えられます。 <br />たとえば、Cisco ホストのパスワードを変更するには、通常、次の 5 つのコマンドを設定する必要があります:<br />1.enable<br />2.&#123;login_password&#125;<br />3 .ターミナルの設定<br / >4. ユーザー名 &#123;ユーザー名&#125; 権限 0 パスワード &#123;パスワード&#125; <br />5. 終了'
en: 'If the custom command needs to include the account number, password, and user password field for SSH connection,<br />Please use &#123;username&#125;, &#123;password&#125;, &#123;login_password&# 125; format, which will be replaced when executing the task. <br />For example, to change the password of a Cisco host, you generally need to configure five commands:<br />1. enable<br />2. &#123;login_password&#125;<br />3. configure terminal<br / >4. username &#123;username&#125; privilege 0 password &#123;password&#125; <br />5. end'
Params commands label:
zh: '自定义命令'
ja: 'カスタムコマンド'
en: 'Custom command'

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

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,10 +12,10 @@
login_password: "{{ jms_account.secret }}"
login_host: "{{ jms_asset.address }}"
login_port: "{{ jms_asset.port }}"
check_hostname: "{{ jms_asset.spec_info.use_ssl and not jms_asset.spec_info.allow_invalid_cert }}"
ca_cert: "{{ jms_asset.secret_info.ca_cert | default(omit) }}"
client_cert: "{{ jms_asset.secret_info.client_cert | default(omit) }}"
client_key: "{{ jms_asset.secret_info.client_key | default(omit) }}"
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
@@ -28,10 +29,10 @@
login_password: "{{ jms_account.secret }}"
login_host: "{{ jms_asset.address }}"
login_port: "{{ jms_asset.port }}"
check_hostname: "{{ jms_asset.spec_info.use_ssl and not jms_asset.spec_info.allow_invalid_cert }}"
ca_cert: "{{ jms_asset.secret_info.ca_cert | default(omit) }}"
client_cert: "{{ jms_asset.secret_info.client_cert | default(omit) }}"
client_key: "{{ jms_asset.secret_info.client_key | default(omit) }}"
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: "%"
@@ -45,8 +46,8 @@
login_password: "{{ account.secret }}"
login_host: "{{ jms_asset.address }}"
login_port: "{{ jms_asset.port }}"
check_hostname: "{{ jms_asset.spec_info.use_ssl and not jms_asset.spec_info.allow_invalid_cert }}"
ca_cert: "{{ jms_asset.secret_info.ca_cert | default(omit) }}"
client_cert: "{{ jms_asset.secret_info.client_cert | default(omit) }}"
client_key: "{{ jms_asset.secret_info.client_key | default(omit) }}"
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
@@ -39,3 +39,4 @@
login_host: "{{ jms_asset.address }}"
login_port: "{{ jms_asset.port }}"
login_database: "{{ jms_asset.spec_info.db_name }}"
mode: "{{ account.mode }}"

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

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,27 @@
id: change_secret_windows_rdp_verify
name: "{{ 'Windows account change secret rdp verify' | trans }}"
version: 1
method: change_secret
category: host
type:
- windows
priority: 49
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,11 +1,11 @@
import os
import time
from collections import defaultdict
from copy import deepcopy
from django.conf import settings
from django.utils import timezone
from openpyxl import Workbook
from django.utils.translation import gettext_lazy as _
from xlsxwriter import Workbook
from accounts.const import AutomationTypes, SecretType, SSHKeyStrategy, SecretStrategy
from accounts.models import ChangeSecretRecord
@@ -14,7 +14,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 +27,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 +50,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 +98,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 +114,24 @@ 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 new_secret is None:
print(f'new_secret is None, account: {account}')
continue
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
@@ -135,14 +144,13 @@ class ChangeSecretManager(AccountBasePlaybookManager):
'name': account.name,
'username': account.username,
'secret_type': secret_type,
'secret': new_secret,
'private_key_path': private_key_path
'secret': account.escape_jinja2_syntax(new_secret),
'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
@@ -158,7 +166,8 @@ class ChangeSecretManager(AccountBasePlaybookManager):
print("Account not found, deleted ?")
return
account.secret = recorder.new_secret
account.save(update_fields=['secret'])
account.date_updated = timezone.now()
account.save(update_fields=['secret', 'date_updated'])
def on_host_error(self, host, error, result):
recorder = self.name_recorder_mapper.get(host)
@@ -170,7 +179,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 \
@@ -179,15 +188,33 @@ class ChangeSecretManager(AccountBasePlaybookManager):
return False
return True
@staticmethod
def get_summary(recorders):
total, succeed, failed = 0, 0, 0
for recorder in recorders:
if recorder.status == 'success':
succeed += 1
else:
failed += 1
total += 1
summary = _('Success: %s, Failed: %s, Total: %s') % (succeed, failed, total)
return summary
def run(self, *args, **kwargs):
if not self.check_secret():
if self.secret_type and not self.check_secret():
return
super().run(*args, **kwargs)
recorders = self.name_recorder_mapper.values()
recorders = list(recorders)
self.send_recorder_mail(recorders)
recorders = list(self.name_recorder_mapper.values())
summary = self.get_summary(recorders)
print(summary, end='')
def send_recorder_mail(self, recorders):
if self.record_id:
return
self.send_recorder_mail(recorders, summary)
def send_recorder_mail(self, recorders, summary):
recipients = self.execution.recipients
if not recorders or not recipients:
return
@@ -196,18 +223,17 @@ 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
for user in recipients:
attachments = []
if user.secret_key:
password = user.secret_key.encode('utf8')
attachment = os.path.join(path, f'{name}-{local_now_display()}-{time.time()}.zip')
encrypt_and_compress_zip_file(attachment, password, [filename])
attachment = os.path.join(path, f'{name}-{local_now_filename()}-{time.time()}.zip')
encrypt_and_compress_zip_file(attachment, user.secret_key, [filename])
attachments = [attachment]
ChangeSecretExecutionTaskMsg(name, user).publish(attachments)
ChangeSecretExecutionTaskMsg(name, user, summary).publish(attachments)
os.remove(filename)
@staticmethod
@@ -222,8 +248,9 @@ class ChangeSecretManager(AccountBasePlaybookManager):
rows.insert(0, header)
wb = Workbook(filename)
ws = wb.create_sheet('Sheet1')
for row in rows:
ws.append(row)
wb.save(filename)
ws = wb.add_worksheet('Sheet1')
for row_index, row_data in enumerate(rows):
for col_index, col_data in enumerate(row_data):
ws.write_string(row_index, col_index, col_data)
wb.close()
return True

View File

@@ -1,8 +1,9 @@
from .push_account.manager import PushAccountManager
from .change_secret.manager import ChangeSecretManager
from .verify_account.manager import VerifyAccountManager
from .backup_account.manager import AccountBackupManager
from .change_secret.manager import ChangeSecretManager
from .gather_accounts.manager import GatherAccountsManager
from .push_account.manager import PushAccountManager
from .remove_account.manager import RemoveAccountManager
from .verify_account.manager import VerifyAccountManager
from .verify_gateway_account.manager import VerifyGatewayAccountManager
from ..const import AutomationTypes
@@ -12,6 +13,7 @@ class ExecutionManager:
AutomationTypes.push_account: PushAccountManager,
AutomationTypes.change_secret: ChangeSecretManager,
AutomationTypes.verify_account: VerifyAccountManager,
AutomationTypes.remove_account: RemoveAccountManager,
AutomationTypes.gather_accounts: GatherAccountsManager,
AutomationTypes.verify_gateway_account: VerifyGatewayAccountManager,
# TODO 后期迁移到自动化策略中

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

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,10 +11,10 @@
login_password: "{{ jms_account.secret }}"
login_host: "{{ jms_asset.address }}"
login_port: "{{ jms_asset.port }}"
check_hostname: "{{ jms_asset.spec_info.use_ssl and not jms_asset.spec_info.allow_invalid_cert }}"
ca_cert: "{{ jms_asset.secret_info.ca_cert | default(omit) }}"
client_cert: "{{ jms_asset.secret_info.client_cert | default(omit) }}"
client_key: "{{ jms_asset.secret_info.client_key | default(omit) }}"
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,10 @@
- hosts: demo
gather_facts: no
tasks:
- name: Gather posix account
- name: Gather windows account
ansible.builtin.win_shell: net user
register: result
ignore_errors: true
- name: Define info by set_fact
ansible.builtin.set_fact:

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:
continue
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

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,10 +12,10 @@
login_password: "{{ jms_account.secret }}"
login_host: "{{ jms_asset.address }}"
login_port: "{{ jms_asset.port }}"
check_hostname: "{{ jms_asset.spec_info.use_ssl and not jms_asset.spec_info.allow_invalid_cert }}"
ca_cert: "{{ jms_asset.secret_info.ca_cert | default(omit) }}"
client_cert: "{{ jms_asset.secret_info.client_cert | default(omit) }}"
client_key: "{{ jms_asset.secret_info.client_key | default(omit) }}"
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
@@ -28,10 +29,10 @@
login_password: "{{ jms_account.secret }}"
login_host: "{{ jms_asset.address }}"
login_port: "{{ jms_asset.port }}"
check_hostname: "{{ jms_asset.spec_info.use_ssl and not jms_asset.spec_info.allow_invalid_cert }}"
ca_cert: "{{ jms_asset.secret_info.ca_cert | default(omit) }}"
client_cert: "{{ jms_asset.secret_info.client_cert | default(omit) }}"
client_key: "{{ jms_asset.secret_info.client_key | default(omit) }}"
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: "%"
@@ -45,8 +46,8 @@
login_password: "{{ account.secret }}"
login_host: "{{ jms_asset.address }}"
login_port: "{{ jms_asset.port }}"
check_hostname: "{{ jms_asset.spec_info.use_ssl and not jms_asset.spec_info.allow_invalid_cert }}"
ca_cert: "{{ jms_asset.secret_info.ca_cert | default(omit) }}"
client_cert: "{{ jms_asset.secret_info.client_cert | default(omit) }}"
client_key: "{{ jms_asset.secret_info.client_key | default(omit) }}"
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
@@ -39,3 +39,4 @@
login_host: "{{ jms_asset.address }}"
login_port: "{{ jms_asset.port }}"
login_database: "{{ jms_asset.spec_info.db_name }}"
mode: "{{ account.mode }}"

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

View File

@@ -9,7 +9,7 @@ params:
type: str
label: 'Sudo'
default: '/bin/whoami'
help_text: '使用逗号分隔多个命令,如: /bin/whoami,/sbin/ifconfig'
help_text: "{{ 'Params sudo help text' | trans }}"
- name: shell
type: str
@@ -18,19 +18,44 @@ params:
- name: home
type: str
label: '家目录'
label: "{{ 'Params home label' | trans }}"
default: ''
help_text: '默认家目录 /home/系统用户名: /home/username'
help_text: "{{ 'Params home help text' | trans }}"
- name: groups
type: str
label: '用户组'
label: "{{ 'Params groups label' | trans }}"
default: ''
help_text: '请输入用户组,多个用户组使用逗号分隔(需填写已存在的用户组)'
help_text: "{{ 'Params groups help text' | trans }}"
i18n:
Aix account push:
zh: 使用 Ansible 模块 user 执行 Aix 账号推送 (DES)
ja: Ansible user モジュールを使用して Aix アカウントをプッシュする (DES)
en: Using Ansible module user to push account (DES)
zh: '使用 Ansible 模块 user 执行 Aix 账号推送 (DES)'
ja: 'Ansible user モジュールを使用して Aix アカウントをプッシュする (DES)'
en: 'Using Ansible module user to push account (DES)'
Params sudo help text:
zh: '使用逗号分隔多个命令,如: /bin/whoami,/sbin/ifconfig'
ja: 'コンマで区切って複数のコマンドを入力してください。例: /bin/whoami,/sbin/ifconfig'
en: 'Use commas to separate multiple commands, such as: /bin/whoami,/sbin/ifconfig'
Params home help text:
zh: '默认家目录 /home/{账号用户名}'
ja: 'デフォルトのホームディレクトリ /home/{アカウントユーザ名}'
en: 'Default home directory /home/{account username}'
Params groups help text:
zh: '请输入用户组,多个用户组使用逗号分隔(需填写已存在的用户组)'
ja: 'グループを入力してください。複数のグループはコンマで区切ってください(既存のグループを入力してください)'
en: 'Please enter the group. Multiple groups are separated by commas (please enter the existing group)'
Params home label:
zh: '家目录'
ja: 'ホームディレクトリ'
en: 'Home'
Params groups label:
zh: '用户组'
ja: 'グループ'
en: 'Groups'

View File

@@ -10,7 +10,7 @@ params:
type: str
label: 'Sudo'
default: '/bin/whoami'
help_text: '使用逗号分隔多个命令,如: /bin/whoami,/sbin/ifconfig'
help_text: "{{ 'Params sudo help text' | trans }}"
- name: shell
type: str
@@ -20,18 +20,43 @@ params:
- name: home
type: str
label: '家目录'
label: "{{ 'Params home label' | trans }}"
default: ''
help_text: '默认家目录 /home/系统用户名: /home/username'
help_text: "{{ 'Params home help text' | trans }}"
- name: groups
type: str
label: '用户组'
label: "{{ 'Params groups label' | trans }}"
default: ''
help_text: '请输入用户组,多个用户组使用逗号分隔(需填写已存在的用户组)'
help_text: "{{ 'Params groups help text' | trans }}"
i18n:
Posix account push:
zh: 使用 Ansible 模块 user 执行账号推送 (sha512)
ja: Ansible user モジュールを使用してアカウントをプッシュする (sha512)
en: Using Ansible module user to push account (sha512)
zh: '使用 Ansible 模块 user 执行账号推送 (sha512)'
ja: 'Ansible user モジュールを使用してアカウントをプッシュする (sha512)'
en: 'Using Ansible module user to push account (sha512)'
Params sudo help text:
zh: '使用逗号分隔多个命令,如: /bin/whoami,/sbin/ifconfig'
ja: 'コンマで区切って複数のコマンドを入力してください。例: /bin/whoami,/sbin/ifconfig'
en: 'Use commas to separate multiple commands, such as: /bin/whoami,/sbin/ifconfig'
Params home help text:
zh: '默认家目录 /home/{账号用户名}'
ja: 'デフォルトのホームディレクトリ /home/{アカウントユーザ名}'
en: 'Default home directory /home/{account username}'
Params groups help text:
zh: '请输入用户组,多个用户组使用逗号分隔(需填写已存在的用户组)'
ja: 'グループを入力してください。複数のグループはコンマで区切ってください(既存のグループを入力してください)'
en: 'Please enter the group. Multiple groups are separated by commas (please enter the existing group)'
Params home label:
zh: '家目录'
ja: 'ホームディレクトリ'
en: 'Home'
Params groups label:
zh: '用户组'
ja: 'グループ'
en: 'Groups'

View File

@@ -10,10 +10,15 @@ params:
type: str
label: '用户组'
default: 'Users,Remote Desktop Users'
help_text: '请输入用户组,多个用户组使用逗号分隔(需填写已存在的用户组)'
help_text: "{{ 'Params groups help text' | trans }}"
i18n:
Windows account push:
zh: 使用 Ansible 模块 win_user 执行 Windows 账号推送
ja: Ansible win_user モジュールを使用して Windows アカウントをプッシュする
en: Using Ansible module win_user to push account
zh: '使用 Ansible 模块 win_user 执行 Windows 账号推送'
ja: 'Ansible win_user モジュールを使用して Windows アカウントをプッシュする'
en: 'Using Ansible module win_user to push account'
Params groups help text:
zh: '请输入用户组,多个用户组使用逗号分隔(需填写已存在的用户组)'
ja: 'グループを入力してください。複数のグループはコンマで区切ってください(既存のグループを入力してください)'
en: 'Please enter the group. Multiple groups are separated by commas (please enter the existing group)'

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,25 @@
id: push_account_windows_rdp_verify
name: "{{ 'Windows account push rdp verify' | trans }}"
version: 1
method: push_account
category: host
type:
- windows
priority: 49
params:
- name: groups
type: str
label: '用户组'
default: 'Users,Remote Desktop Users'
help_text: "{{ 'Params groups help text' | trans }}"
i18n:
Windows account push rdp verify:
zh: '使用 Ansible 模块 win_user 执行 Windows 账号推送(最后使用 Python 模块 pyfreerdp 验证账号的可连接性)'
ja: 'Ansible モジュール win_user を使用して Windows アカウントのプッシュを実行します (最後に Python モジュール pyfreerdp を使用してアカウントの接続性を確認します)'
en: 'Use the Ansible module win_user to perform Windows account push (finally use the Python module pyfreerdp to verify the connectability of the account)'
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,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: {}".format(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

@@ -0,0 +1,21 @@
- hosts: mongodb
gather_facts: no
vars:
ansible_python_interpreter: /opt/py3/bin/python
tasks:
- name: "Remove account"
mongodb_user:
login_user: "{{ jms_account.username }}"
login_password: "{{ jms_account.secret }}"
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 | 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 }}"
name: "{{ account.username }}"
state: absent

View File

@@ -0,0 +1,12 @@
id: remove_account_mongodb
name: "{{ 'MongoDB account remove' | trans }}"
category: database
type:
- mongodb
method: remove_account
i18n:
MongoDB account remove:
zh: 使用 Ansible 模块 mongodb 删除账号
ja: Ansible モジュール mongodb を使用してアカウントを削除する
en: Delete account using Ansible module mongodb

View File

@@ -0,0 +1,18 @@
- hosts: mysql
gather_facts: no
vars:
ansible_python_interpreter: /opt/py3/bin/python
tasks:
- name: "Remove account"
community.mysql.mysql_user:
login_user: "{{ jms_account.username }}"
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 }}"
state: absent

View File

@@ -0,0 +1,14 @@
id: remove_account_mysql
name: "{{ 'MySQL account remove' | trans }}"
category: database
type:
- mysql
- mariadb
method: remove_account
i18n:
MySQL account remove:
zh: 使用 Ansible 模块 mysql_user 删除账号
ja: Ansible モジュール mysql_user を使用してアカウントを削除します
en: Use the Ansible module mysql_user to delete the account

View File

@@ -0,0 +1,16 @@
- hosts: oracle
gather_facts: no
vars:
ansible_python_interpreter: /opt/py3/bin/python
tasks:
- name: "Remove account"
oracle_user:
login_user: "{{ jms_account.username }}"
login_password: "{{ jms_account.secret }}"
login_host: "{{ jms_asset.address }}"
login_port: "{{ jms_asset.port }}"
login_database: "{{ jms_asset.spec_info.db_name }}"
mode: "{{ jms_account.mode }}"
name: "{{ account.username }}"
state: absent

View File

@@ -0,0 +1,12 @@
id: remove_account_oracle
name: "{{ 'Oracle account remove' | trans }}"
category: database
type:
- oracle
method: remove_account
i18n:
Oracle account remove:
zh: 使用 Python 模块 oracledb 删除账号
ja: Python モジュール oracledb を使用してアカウントを検証する
en: Using Python module oracledb to verify account

View File

@@ -0,0 +1,15 @@
- hosts: postgresql
gather_facts: no
vars:
ansible_python_interpreter: /opt/py3/bin/python
tasks:
- name: "Remove account"
community.postgresql.postgresql_user:
login_user: "{{ jms_account.username }}"
login_password: "{{ jms_account.secret }}"
login_host: "{{ jms_asset.address }}"
login_port: "{{ jms_asset.port }}"
db: "{{ jms_asset.spec_info.db_name }}"
name: "{{ account.username }}"
state: absent

View File

@@ -0,0 +1,12 @@
id: remove_account_postgresql
name: "{{ 'PostgreSQL account remove' | trans }}"
category: database
type:
- postgresql
method: remove_account
i18n:
PostgreSQL account remove:
zh: 使用 Ansible 模块 postgresql_user 删除账号
ja: Ansible モジュール postgresql_user を使用してアカウントを削除します
en: Use the Ansible module postgresql_user to delete the account

View File

@@ -0,0 +1,14 @@
- hosts: sqlserver
gather_facts: no
vars:
ansible_python_interpreter: /opt/py3/bin/python
tasks:
- name: "Remove account"
community.general.mssql_script:
login_user: "{{ jms_account.username }}"
login_password: "{{ jms_account.secret }}"
login_host: "{{ jms_asset.address }}"
login_port: "{{ jms_asset.port }}"
name: "{{ jms_asset.spec_info.db_name }}"
script: "DROP USER {{ account.username }}"

View File

@@ -0,0 +1,12 @@
id: remove_account_sqlserver
name: "{{ 'SQLServer account remove' | trans }}"
category: database
type:
- sqlserver
method: remove_account
i18n:
SQLServer account remove:
zh: 使用 Ansible 模块 mssql 删除账号
ja: Ansible モジュール mssql を使用してアカウントを削除する
en: Use Ansible module mssql to delete account

View File

@@ -0,0 +1,26 @@
- hosts: demo
gather_facts: no
tasks:
- name: "Get user home directory path"
ansible.builtin.shell:
cmd: "getent passwd {{ account.username }} | cut -d: -f6"
register: user_home_dir
ignore_errors: yes
- name: "Check if user home directory exists"
ansible.builtin.stat:
path: "{{ user_home_dir.stdout }}"
register: home_dir
when: user_home_dir.stdout != ""
- name: "Rename user home directory if it exists"
ansible.builtin.command:
cmd: "mv {{ user_home_dir.stdout }} {{ user_home_dir.stdout }}.bak"
when: home_dir.stat | default(false) and user_home_dir.stdout != ""
- name: "Remove account"
ansible.builtin.user:
name: "{{ account.username }}"
state: absent
remove: "{{ home_dir.stat.exists }}"
when: home_dir.stat | default(false)

View File

@@ -0,0 +1,13 @@
id: remove_account_posix
name: "{{ 'Posix account remove' | trans }}"
category: host
type:
- linux
- unix
method: remove_account
i18n:
Posix account remove:
zh: 使用 Ansible 模块 user 删除账号
ja: Ansible モジュール ユーザーを使用してアカウントを削除します
en: Use the Ansible module user to delete the account

View File

@@ -0,0 +1,9 @@
- hosts: windows
gather_facts: no
tasks:
- name: "Remove account"
ansible.windows.win_user:
name: "{{ account.username }}"
state: absent
purge: yes
force: yes

View File

@@ -0,0 +1,13 @@
id: remove_account_windows
name: "{{ 'Windows account remove' | trans }}"
version: 1
method: remove_account
category: host
type:
- windows
i18n:
Windows account remove:
zh: 使用 Ansible 模块 win_user 删除账号
ja: Ansible モジュール win_user を使用してアカウントを削除する
en: Use the Ansible module win_user to delete an account

View File

@@ -0,0 +1,67 @@
import os
from copy import deepcopy
from django.db.models import QuerySet
from accounts.const import AutomationTypes
from accounts.models import Account
from common.utils import get_logger
from ..base.manager import AccountBasePlaybookManager
logger = get_logger(__name__)
class RemoveAccountManager(AccountBasePlaybookManager):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.host_account_mapper = {}
def prepare_runtime_dir(self):
path = super().prepare_runtime_dir()
ansible_config_path = os.path.join(path, 'ansible.cfg')
with open(ansible_config_path, 'w') as f:
f.write('[ssh_connection]\n')
f.write('ssh_args = -o ControlMaster=no -o ControlPersist=no\n')
return path
@classmethod
def method_type(cls):
return AutomationTypes.remove_account
def get_gather_accounts(self, privilege_account, gather_accounts: QuerySet):
gather_account_ids = self.execution.snapshot['gather_accounts']
gather_accounts = gather_accounts.filter(id__in=gather_account_ids)
gather_accounts = gather_accounts.exclude(
username__in=[privilege_account.username, 'root', 'Administrator']
)
return gather_accounts
def host_callback(self, host, asset=None, account=None, automation=None, path_dir=None, **kwargs):
if host.get('error'):
return host
gather_accounts = asset.gatheredaccount_set.all()
gather_accounts = self.get_gather_accounts(account, gather_accounts)
inventory_hosts = []
for gather_account in gather_accounts:
h = deepcopy(host)
h['name'] += '(' + gather_account.username + ')'
self.host_account_mapper[h['name']] = (asset, gather_account)
h['account'] = {'username': gather_account.username}
inventory_hosts.append(h)
return inventory_hosts
def on_host_success(self, host, result):
tuple_asset_gather_account = self.host_account_mapper.get(host)
if not tuple_asset_gather_account:
return
asset, gather_account = tuple_asset_gather_account
Account.objects.filter(
asset_id=asset.id,
username=gather_account.username
).delete()
gather_account.delete()

View File

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

View File

@@ -5,9 +5,11 @@ category:
type:
- windows
method: verify_account
protocol: rdp
priority: 1
i18n:
Windows rdp account verify:
zh: 使用 Python 模块 pyfreerdp 验证账号
ja: Python モジュール pyfreerdp を使用してアカウントを検証する
en: Using Python module pyfreerdp to verify account
zh: '使用 Python 模块 pyfreerdp 验证账号'
ja: 'Python モジュール pyfreerdp を使用してアカウントを検証する'
en: 'Using Python module pyfreerdp to verify account'

View File

@@ -2,6 +2,7 @@
gather_facts: no
vars:
ansible_connection: local
ansible_shell_type: sh
ansible_become: false
tasks:

View File

@@ -6,9 +6,11 @@ category:
type:
- all
method: verify_account
protocol: ssh
priority: 50
i18n:
SSH account verify:
zh: 使用 Python 模块 paramiko 验证账号
ja: Python モジュール paramiko を使用してアカウントを検証する
en: Using Python module paramiko to verify account
zh: '使用 Python 模块 paramiko 验证账号'
ja: 'Python モジュール paramiko を使用してアカウントを検証する'
en: 'Using Python module paramiko to verify account'

View File

@@ -1,7 +1,7 @@
- hosts: mongdb
- hosts: mongodb
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,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,8 +11,8 @@
login_password: "{{ account.secret }}"
login_host: "{{ jms_asset.address }}"
login_port: "{{ jms_asset.port }}"
check_hostname: "{{ jms_asset.spec_info.use_ssl and not jms_asset.spec_info.allow_invalid_cert }}"
ca_cert: "{{ jms_asset.secret_info.ca_cert | default(omit) }}"
client_cert: "{{ jms_asset.secret_info.client_cert | default(omit) }}"
client_key: "{{ jms_asset.secret_info.client_key | default(omit) }}"
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,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: 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

@@ -51,6 +51,9 @@ class VerifyAccountManager(AccountBasePlaybookManager):
h['name'] += '(' + account.username + ')'
self.host_account_mapper[h['name']] = account
secret = account.secret
if secret is None:
print(f'account {account.name} secret is None')
continue
private_key_path = None
if account.secret_type == SecretType.SSH_KEY:
@@ -62,7 +65,7 @@ class VerifyAccountManager(AccountBasePlaybookManager):
'name': account.name,
'username': account.username,
'secret_type': account.secret_type,
'secret': secret,
'secret': account.escape_jinja2_syntax(secret),
'private_key_path': private_key_path,
'become': account.get_ansible_become_auth(),
}

View File

@@ -16,7 +16,7 @@ DEFAULT_PASSWORD_RULES = {
__all__ = [
'AutomationTypes', 'SecretStrategy', 'SSHKeyStrategy', 'Connectivity',
'DEFAULT_PASSWORD_LENGTH', 'DEFAULT_PASSWORD_RULES', 'TriggerChoice',
'PushAccountActionChoice',
'PushAccountActionChoice', 'AccountBackupType'
]
@@ -24,6 +24,7 @@ class AutomationTypes(models.TextChoices):
push_account = 'push_account', _('Push account')
change_secret = 'change_secret', _('Change secret')
verify_account = 'verify_account', _('Verify account')
remove_account = 'remove_account', _('Remove account')
gather_accounts = 'gather_accounts', _('Gather accounts')
verify_gateway_account = 'verify_gateway_account', _('Verify gateway account')
@@ -95,3 +96,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

@@ -51,6 +51,8 @@ class AccountFilterSet(BaseFilterSet):
class GatheredAccountFilterSet(BaseFilterSet):
node_id = drf_filters.CharFilter(method='filter_nodes')
asset_id = drf_filters.CharFilter(field_name='asset_id', lookup_expr='exact')
asset_name = drf_filters.CharFilter(field_name='asset__name', lookup_expr='icontains')
@staticmethod
def filter_nodes(queryset, name, value):
@@ -58,4 +60,4 @@ class GatheredAccountFilterSet(BaseFilterSet):
class Meta:
model = GatheredAccount
fields = ['id', 'asset_id', 'username']
fields = ['id', 'username']

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,
@@ -116,7 +116,7 @@ class Migration(migrations.Migration):
('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

@@ -4,7 +4,6 @@ from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('accounts', '0006_gatheredaccount'),
]
@@ -12,6 +11,13 @@ class Migration(migrations.Migration):
operations = [
migrations.AlterModelOptions(
name='account',
options={'permissions': [('view_accountsecret', 'Can view asset account secret'), ('view_historyaccount', 'Can view asset history account'), ('view_historyaccountsecret', 'Can view asset history account secret'), ('verify_account', 'Can verify account'), ('push_account', 'Can push account')], 'verbose_name': 'Account'},
options={'permissions': [
('view_accountsecret', 'Can view asset account secret'),
('view_historyaccount', 'Can view asset history account'),
('view_historyaccountsecret', 'Can view asset history account secret'),
('verify_account', 'Can verify account'),
('push_account', 'Can push account'),
('remove_account', 'Can remove account'),
], 'verbose_name': 'Account'},
),
]

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'),
),
]

View File

@@ -4,6 +4,7 @@ from simple_history.models import HistoricalRecords
from assets.models.base import AbsConnectivity
from common.utils import lazyproperty
from labels.mixins import LabeledMixin
from .base import BaseAccount
from .mixins import VaultModelMixin
from ..const import Source
@@ -42,7 +43,7 @@ class AccountHistoricalRecords(HistoricalRecords):
return super().create_history_model(model, inherited)
class Account(AbsConnectivity, BaseAccount):
class Account(AbsConnectivity, LabeledMixin, BaseAccount):
asset = models.ForeignKey(
'assets.Asset', related_name='accounts',
on_delete=models.CASCADE, verbose_name=_('Asset')
@@ -68,10 +69,15 @@ class Account(AbsConnectivity, BaseAccount):
('view_historyaccountsecret', _('Can view asset history account secret')),
('verify_account', _('Can verify account')),
('push_account', _('Can push account')),
('remove_account', _('Can remove account')),
]
def __str__(self):
return '{}'.format(self.username)
if self.asset_id:
host = self.asset.name
else:
host = 'Dynamic'
return '{}({})'.format(self.name, host)
@lazyproperty
def platform(self):
@@ -95,14 +101,13 @@ class Account(AbsConnectivity, BaseAccount):
""" 排除自己和以自己为 su-from 的账号 """
return self.asset.accounts.exclude(id=self.id).exclude(su_from=self)
@staticmethod
def make_account_ansible_vars(su_from):
def make_account_ansible_vars(self, su_from):
var = {
'ansible_user': su_from.username,
}
if not su_from.secret:
return var
var['ansible_password'] = su_from.secret
var['ansible_password'] = self.escape_jinja2_syntax(su_from.secret)
var['ansible_ssh_private_key_file'] = su_from.private_key_path
return var
@@ -114,14 +119,30 @@ class Account(AbsConnectivity, BaseAccount):
return auth
auth.update(self.make_account_ansible_vars(su_from))
become_method = 'sudo' if platform.su_method != 'su' else 'su'
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
auth['ansible_become_password'] = self.escape_jinja2_syntax(password)
return auth
@staticmethod
def escape_jinja2_syntax(value):
if not isinstance(value, str):
return value
def escape(v):
v = v.replace('{{', '__TEMP_OPEN_BRACES__') \
.replace('}}', '__TEMP_CLOSE_BRACES__')
v = v.replace('__TEMP_OPEN_BRACES__', '{{ "{{" }}') \
.replace('__TEMP_CLOSE_BRACES__', '{{ "}}" }}')
return v.replace('{%', '{{ "{%" }}').replace('%}', '{{ "%}" }}')
return escape(value)
def replace_history_model_with_mixin():
"""

View File

@@ -8,10 +8,11 @@ from django.db import models
from django.db.models import F
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,6 +23,10 @@ logger = get_logger(__file__)
class AccountBackupAutomation(PeriodTaskModelMixin, JMSOrgBaseModel):
types = models.JSONField(default=list)
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")
@@ -30,6 +35,16 @@ class AccountBackupAutomation(PeriodTaskModelMixin, JMSOrgBaseModel):
'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})'
@@ -49,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,
@@ -56,6 +72,10 @@ class AccountBackupAutomation(PeriodTaskModelMixin, JMSOrgBaseModel):
'org_id': self.org_id,
'created_by': self.created_by,
'types': self.types,
'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()
@@ -63,7 +83,15 @@ class AccountBackupAutomation(PeriodTaskModelMixin, JMSOrgBaseModel):
'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

View File

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

View File

@@ -40,7 +40,7 @@ class ChangeSecretRecord(JMSBaseModel):
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:
@@ -49,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

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

@@ -3,13 +3,14 @@ from django.db.models import Count, Q
from django.utils import timezone
from django.utils.translation import gettext_lazy as _
from labels.mixins import LabeledMixin
from .account import Account
from .base import BaseAccount, SecretWithRandomMixin
__all__ = ['AccountTemplate', ]
class AccountTemplate(BaseAccount, SecretWithRandomMixin):
class AccountTemplate(LabeledMixin, BaseAccount, SecretWithRandomMixin):
su_from = models.ForeignKey(
'self', related_name='su_to', null=True,
on_delete=models.SET_NULL, verbose_name=_("Su from")

View File

@@ -1,6 +1,9 @@
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_async, upload_backup_to_obj_storage
from notifications.notifications import UserMessage
from terminal.models.component.storage import ReplayStorage
from users.models import User
@@ -20,8 +23,8 @@ class AccountBackupExecutionTaskMsg(object):
else:
return _("{} - The account backup passage task has been completed: "
"the encryption password has not been set - "
"please go to personal information -> file encryption password "
"to set the encryption password").format(name)
"please go to personal information -> Basic file encryption password for preference settings"
).format(name)
def publish(self, attachment_list=None):
send_mail_attachment_async(
@@ -29,25 +32,69 @@ class AccountBackupExecutionTaskMsg(object):
)
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')
def __init__(self, name: str, user: User):
def __init__(self, name: str, user: User, summary):
self.name = name
self.user = user
self.summary = summary
@property
def message(self):
name = self.name
if self.user.secret_key:
return _('{} - The encryption change task has been completed. '
'See the attachment for details').format(name)
default_message = _('{} - The encryption change task has been completed. '
'See the attachment for details').format(name)
else:
return _("{} - The encryption change task has been completed: the encryption "
"password has not been set - please go to personal information -> "
"file encryption password to set the encryption password").format(name)
default_message = _("{} - The encryption change task has been completed: the encryption "
"password has not been set - please go to personal information -> "
"set encryption password in preferences").format(name)
return self.summary + '\n' + default_message
def publish(self, attachments=None):
send_mail_attachment_async(
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

@@ -0,0 +1,19 @@
from rest_framework import permissions
def check_permissions(request):
act = request.data.get('action')
if act == 'push':
code = 'accounts.push_account'
elif act == 'remove':
code = 'accounts.remove_account'
else:
code = 'accounts.verify_account'
return request.user.has_perm(code)
class AccountTaskActionPermission(permissions.IsAuthenticated):
def has_permission(self, request, view):
return super().has_permission(request, view) \
and check_permissions(request)

View File

@@ -10,7 +10,7 @@ from rest_framework.generics import get_object_or_404
from rest_framework.validators import UniqueTogetherValidator
from accounts.const import SecretType, Source, AccountInvalidPolicy
from accounts.models import Account, AccountTemplate
from accounts.models import Account, AccountTemplate, GatheredAccount
from accounts.tasks import push_accounts_to_assets_task
from assets.const import Category, AllTypes
from assets.models import Asset
@@ -58,7 +58,7 @@ class AccountCreateUpdateSerializerMixin(serializers.Serializer):
for data in initial_data:
if not data.get('asset') and not self.instance:
raise serializers.ValidationError({'asset': UniqueTogetherValidator.missing_message})
asset = data.get('asset') or self.instance.asset
asset = data.get('asset') or getattr(self.instance, 'asset', None)
self.from_template_if_need(data)
self.set_uniq_name_if_need(data, asset)
@@ -66,6 +66,9 @@ class AccountCreateUpdateSerializerMixin(serializers.Serializer):
name = initial_data.get('name')
if name is not None:
return
request = self.context.get('request')
if request and request.method == 'PATCH':
return
if not name:
name = initial_data.get('username')
if self.instance and self.instance.name == name:
@@ -238,7 +241,7 @@ class AccountSerializer(AccountCreateUpdateSerializerMixin, BaseAccountSerialize
queryset = queryset.prefetch_related(
'asset', 'asset__platform',
'asset__platform__automation'
)
).prefetch_related('labels', 'labels__label')
return queryset
@@ -452,14 +455,20 @@ class AccountHistorySerializer(serializers.ModelSerializer):
class AccountTaskSerializer(serializers.Serializer):
ACTION_CHOICES = (
('test', 'test'),
('verify', 'verify'),
('push', 'push'),
('remove', 'remove'),
)
action = serializers.ChoiceField(choices=ACTION_CHOICES, write_only=True)
assets = serializers.PrimaryKeyRelatedField(
queryset=Asset.objects, required=False, allow_empty=True, many=True
)
accounts = serializers.PrimaryKeyRelatedField(
queryset=Account.objects, required=False, allow_empty=True, many=True
)
gather_accounts = serializers.PrimaryKeyRelatedField(
queryset=GatheredAccount.objects, required=False, allow_empty=True, many=True
)
task = serializers.CharField(read_only=True)
params = serializers.JSONField(
decoder=None, encoder=None, required=False,

View File

@@ -5,7 +5,7 @@ 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', 'types', 'recipients_part_one', 'recipients_part_two'
'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},

View File

@@ -5,6 +5,7 @@ from rest_framework import serializers
from accounts.const import SecretType
from accounts.models import BaseAccount
from accounts.utils import validate_password_for_ansible, validate_ssh_key
from common.serializers import ResourceLabelsMixin
from common.serializers.fields import EncryptedField, LabeledChoiceField
from orgs.mixins.serializers import BulkOrgResourceModelSerializer
@@ -60,8 +61,7 @@ class AuthValidateMixin(serializers.Serializer):
return super().update(instance, validated_data)
class BaseAccountSerializer(AuthValidateMixin, BulkOrgResourceModelSerializer):
class BaseAccountSerializer(AuthValidateMixin, ResourceLabelsMixin, BulkOrgResourceModelSerializer):
class Meta:
model = BaseAccount
fields_mini = ['id', 'name', 'username']
@@ -70,7 +70,7 @@ class BaseAccountSerializer(AuthValidateMixin, BulkOrgResourceModelSerializer):
'privileged', 'is_active', 'spec_info',
]
fields_other = ['created_by', 'date_created', 'date_updated', 'comment']
fields = fields_small + fields_other
fields = fields_small + fields_other + ['labels']
read_only_fields = [
'spec_info', 'date_verified', 'created_by', 'date_created',
]

View File

@@ -15,6 +15,9 @@ class PasswordRulesSerializer(serializers.Serializer):
uppercase = serializers.BooleanField(default=True, label=_('Uppercase'))
digit = serializers.BooleanField(default=True, label=_('Digit'))
symbol = serializers.BooleanField(default=True, label=_('Special symbol'))
exclude_symbols = serializers.CharField(
default='', allow_blank=True, max_length=16, label=_('Exclude symbol')
)
class AccountTemplateSerializer(BaseAccountSerializer):

View File

@@ -71,7 +71,6 @@ class ChangeSecretAutomationSerializer(AuthValidateMixin, BaseAutomationSerializ
return password_rules
length = password_rules.get('length')
symbol_set = password_rules.get('symbol_set', '')
try:
length = int(length)
@@ -84,10 +83,6 @@ class ChangeSecretAutomationSerializer(AuthValidateMixin, BaseAutomationSerializ
msg = _('* Password length range 6-30 bits')
raise serializers.ValidationError(msg)
if not isinstance(symbol_set, str):
symbol_set = str(symbol_set)
password_rules = {'length': length, 'symbol_set': ''.join(symbol_set)}
return password_rules
def validate(self, attrs):
@@ -117,8 +112,8 @@ class ChangeSecretRecordSerializer(serializers.ModelSerializer):
class Meta:
model = ChangeSecretRecord
fields = [
'id', 'asset', 'account', 'date_started', 'date_finished',
'timedelta', 'is_success', 'error', 'execution',
'id', 'asset', 'account', 'date_finished',
'status', 'is_success', 'error', 'execution',
]
read_only_fields = fields

View File

@@ -18,7 +18,7 @@ class GatherAccountAutomationSerializer(BaseAutomationSerializer):
model = GatherAccountsAutomation
read_only_fields = BaseAutomationSerializer.Meta.read_only_fields
fields = BaseAutomationSerializer.Meta.fields \
+ ['is_sync_account'] + read_only_fields
+ ['is_sync_account', 'recipients'] + read_only_fields
extra_kwargs = BaseAutomationSerializer.Meta.extra_kwargs

View File

@@ -21,7 +21,8 @@ def on_account_pre_save(sender, instance, **kwargs):
if instance.version == 0:
instance.version = 1
else:
instance.version = instance.history.count()
history_account = instance.history.first()
instance.version = history_account.version + 1 if history_account else 0
@merge_delay_run(ttl=5)
@@ -62,7 +63,7 @@ def create_accounts_activities(account, action='create'):
def on_account_create_by_template(sender, instance, created=False, **kwargs):
if not created or instance.source != 'template':
return
push_accounts_if_need(accounts=(instance,))
push_accounts_if_need.delay(accounts=(instance,))
create_accounts_activities(instance, action='create')

View File

@@ -2,5 +2,6 @@ from .automation import *
from .backup_account import *
from .gather_accounts import *
from .push_account import *
from .remove_account import *
from .template import *
from .verify_account import *

View File

@@ -1,7 +1,8 @@
from celery import shared_task
from django.utils.translation import gettext_lazy as _
from django.utils.translation import gettext_lazy as _, gettext_noop
from accounts.const import AutomationTypes
from accounts.tasks.common import quickstart_automation_by_snapshot
from common.utils import get_logger, get_object_or_none
from orgs.utils import tmp_to_org, tmp_to_root_org
@@ -33,3 +34,39 @@ def execute_account_automation_task(pid, trigger, tp):
return
with tmp_to_org(instance.org):
instance.execute(trigger)
def record_task_activity_callback(self, record_id, *args, **kwargs):
from accounts.models import ChangeSecretRecord
with tmp_to_root_org():
record = get_object_or_none(ChangeSecretRecord, id=record_id)
if not record:
return
resource_ids = [record.id]
org_id = record.execution.org_id
return resource_ids, org_id
@shared_task(
queue='ansible', verbose_name=_('Execute automation record'),
activity_callback=record_task_activity_callback
)
def execute_automation_record_task(record_id, tp):
from accounts.models import ChangeSecretRecord
with tmp_to_root_org():
instance = get_object_or_none(ChangeSecretRecord, pk=record_id)
if not instance:
logger.error("No automation record found: {}".format(record_id))
return
task_name = gettext_noop('Execute automation record')
task_snapshot = {
'secret': instance.new_secret,
'secret_type': instance.execution.snapshot.get('secret_type'),
'accounts': [str(instance.account_id)],
'assets': [str(instance.asset_id)],
'params': {},
'record_id': record_id,
}
with tmp_to_org(instance.execution.org_id):
quickstart_automation_by_snapshot(task_name, tp, task_snapshot)

View File

@@ -0,0 +1,77 @@
import uuid
from collections import defaultdict
from celery import shared_task, current_task
from django.conf import settings
from django.db.models import Count
from django.utils.translation import gettext_noop, gettext_lazy as _
from accounts.const import AutomationTypes
from accounts.models import Account
from accounts.tasks.common import quickstart_automation_by_snapshot
from audits.const import ActivityChoices
from common.const.crontab import CRONTAB_AT_AM_TWO
from common.utils import get_logger
from ops.celery.decorator import register_as_period_task
from orgs.utils import tmp_to_root_org
logger = get_logger(__file__)
__all__ = ['remove_accounts_task']
@shared_task(
queue="ansible", verbose_name=_('Remove account'),
activity_callback=lambda self, gather_account_ids, *args, **kwargs: (gather_account_ids, None)
)
def remove_accounts_task(gather_account_ids):
from accounts.models import GatheredAccount
gather_accounts = GatheredAccount.objects.filter(
id__in=gather_account_ids
)
task_name = gettext_noop("Remove account")
task_snapshot = {
'assets': [str(i.asset_id) for i in gather_accounts],
'gather_accounts': [str(i.id) for i in gather_accounts],
}
tp = AutomationTypes.remove_account
quickstart_automation_by_snapshot(task_name, tp, task_snapshot)
@shared_task(verbose_name=_('Clean historical accounts'))
@register_as_period_task(crontab=CRONTAB_AT_AM_TWO)
@tmp_to_root_org()
def clean_historical_accounts():
from audits.signal_handlers import create_activities
print("Clean historical accounts start.")
if settings.HISTORY_ACCOUNT_CLEAN_LIMIT >= 999:
return
limit = settings.HISTORY_ACCOUNT_CLEAN_LIMIT
history_ids_to_be_deleted = []
history_model = Account.history.model
history_id_mapper = defaultdict(list)
ids = history_model.objects.values('id').annotate(count=Count('id', distinct=True)) \
.filter(count__gte=limit).values_list('id', flat=True)
if not ids:
return
for i in history_model.objects.filter(id__in=ids):
_id = str(i.id)
history_id_mapper[_id].append(i.history_id)
for history_ids in history_id_mapper.values():
history_ids_to_be_deleted.extend(history_ids[limit:])
history_qs = history_model.objects.filter(history_id__in=history_ids_to_be_deleted)
resource_ids = list(history_qs.values_list('history_id', flat=True))
history_qs.delete()
task_id = current_task.request.id if current_task else str(uuid.uuid4())
detail = gettext_noop('Remove historical accounts that are out of range.')
create_activities(resource_ids, detail, task_id, action=ActivityChoices.task, org_id='')

View File

@@ -0,0 +1,20 @@
{% load i18n %}
<table style="width: 100%; border-collapse: collapse; max-width: 100%; text-align: left; margin-top: 20px;">
<caption></caption>
<tr style="background-color: #f2f2f2;">
<th style="border: 1px solid #ddd; padding: 10px; font-weight: bold;">{% trans 'Asset' %}</th>
<th style="border: 1px solid #ddd; padding: 10px;">{% trans 'Added account' %}</th>
<th style="border: 1px solid #ddd; padding: 10px;">{% trans 'Deleted account' %}</th>
</tr>
{% for name, change in change_info.items %}
<tr style="{% cycle 'background-color: #ebf5ff;' 'background-color: #fff;' %}">
<td style="border: 1px solid #ddd; padding: 10px;">{{ name }}</td>
<td style="border: 1px solid #ddd; padding: 10px;">{{ change.add_usernames }}</td>
<td style="border: 1px solid #ddd; padding: 10px;">{{ change.remove_usernames }}</td>
</tr>
{% endfor %}
</table>

View File

@@ -30,7 +30,8 @@ class SecretGenerator:
'lower': rules['lowercase'],
'upper': rules['uppercase'],
'digit': rules['digit'],
'special_char': rules['symbol']
'special_char': rules['symbol'],
'exclude_chars': rules.get('exclude_symbols', ''),
}
return random_string(**rules)
@@ -46,18 +47,10 @@ class SecretGenerator:
def validate_password_for_ansible(password):
""" 校验 Ansible 不支持的特殊字符 """
# validate password contains left double curly bracket
# check password not contains `{{`
# Ansible 推送的时候不支持
if '{{' in password:
raise serializers.ValidationError(_('Password can not contains `{{` '))
if '{%' in password:
raise serializers.ValidationError(_('Password can not contains `{%` '))
# Ansible Windows 推送的时候不支持
if "'" in password:
raise serializers.ValidationError(_("Password can not contains `'` "))
if '"' in password:
raise serializers.ValidationError(_('Password can not contains `"` '))
if password.startswith('{{') and password.endswith('}}'):
raise serializers.ValidationError(
_('If the password starts with {{` and ends with }} `, then the password is not allowed.')
)
def validate_ssh_key(ssh_key, passphrase=None):

View File

@@ -11,7 +11,7 @@ __all__ = ['CommandFilterACLViewSet', 'CommandGroupViewSet']
class CommandGroupViewSet(OrgBulkModelViewSet):
model = models.CommandGroup
filterset_fields = ('name', 'command_filters')
search_fields = filterset_fields
search_fields = ('name',)
serializer_class = serializers.CommandGroupSerializer

View File

@@ -1,6 +1,7 @@
from django.template.loader import render_to_string
from django.utils.translation import gettext_lazy as _
from accounts.models import Account
from assets.models import Asset
from audits.models import UserLoginLog
from notifications.notifications import UserMessage
@@ -16,12 +17,11 @@ class UserLoginReminderMsg(UserMessage):
def get_html_msg(self) -> dict:
user_log = self.user_log
context = {
'ip': user_log.ip,
'city': user_log.city,
'username': user_log.username,
'recipient': self.user.username,
'recipient': self.user,
'user_agent': user_log.user_agent,
}
message = render_to_string('acls/user_login_reminder.html', context)
@@ -41,18 +41,21 @@ class UserLoginReminderMsg(UserMessage):
class AssetLoginReminderMsg(UserMessage):
subject = _('Asset login reminder')
def __init__(self, user, asset: Asset, login_user: User, account_username):
def __init__(self, user, asset: Asset, login_user: User, account: Account, input_username):
self.asset = asset
self.login_user = login_user
self.account_username = account_username
self.account = account
self.input_username = input_username
super().__init__(user)
def get_html_msg(self) -> dict:
context = {
'recipient': self.user.username,
'recipient': self.user,
'username': self.login_user.username,
'name': self.login_user.name,
'asset': str(self.asset),
'account': self.account_username,
'account': self.input_username,
'account_name': self.account.name,
}
message = render_to_string('acls/asset_login_reminder.html', context)

View File

@@ -1,10 +1,10 @@
{% load i18n %}
<h3>{% trans 'Respectful' %}{{ recipient }}</h3>
<h3>{% trans 'Respectful' %}: {{ recipient.name }}[{{ recipient.username }}]</h3>
<hr>
<p><strong>{% trans 'Username' %}:</strong> [{{ username }}]</p>
<p><strong>{% trans 'User' %}:</strong> [{{ name }}({{ username }})]</p>
<p><strong>{% trans 'Assets' %}:</strong> [{{ asset }}]</p>
<p><strong>{% trans 'Account' %}:</strong> [{{ account }}]</p>
<p><strong>{% trans 'Account' %}:</strong> [{{ account_name }}({{ account }})]</p>
<hr>
<p>{% trans 'The user has just logged in to the asset. Please ensure that this is an authorized operation. If you suspect that this is an unauthorized access, please take appropriate measures immediately.' %}</p>

View File

@@ -1,8 +1,8 @@
{% load i18n %}
<h3>{% trans 'Respectful' %}{{ recipient }}</h3>
<h3>{% trans 'Respectful' %}: {{ recipient.name }}[{{ recipient.username }}]</h3>
<hr>
<p><strong>{% trans 'Username' %}:</strong> [{{ username }}]</p>
<p><strong>{% trans 'User' %}:</strong> [{{ username }}]</p>
<p><strong>IP:</strong> [{{ ip }}]</p>
<p><strong>{% trans 'Login city' %}:</strong> [{{ city }}]</p>
<p><strong>{% trans 'User agent' %}:</strong> [{{ user_agent }}]</p>

View File

@@ -2,8 +2,8 @@ from .asset import *
from .category import *
from .domain import *
from .favorite_asset import *
from .label import *
from .mixin import *
from .node import *
from .platform import *
from .protocol import *
from .tree import *

View File

@@ -3,7 +3,6 @@
from collections import defaultdict
import django_filters
from django.db.models import Q
from django.shortcuts import get_object_or_404
from django.utils.translation import gettext as _
from rest_framework import status
@@ -14,7 +13,7 @@ from rest_framework.status import HTTP_200_OK
from accounts.tasks import push_accounts_to_assets_task, verify_accounts_connectivity_task
from assets import serializers
from assets.exceptions import NotSupportedTemporarilyError
from assets.filters import IpInFilterBackend, LabelFilterBackend, NodeFilterBackend
from assets.filters import IpInFilterBackend, NodeFilterBackend
from assets.models import Asset, Gateway, Platform, Protocol
from assets.tasks import test_assets_connectivity_manual, update_assets_hardware_info_manual
from common.api import SuggestionMixin
@@ -22,7 +21,6 @@ from common.drf.filters import BaseFilterSet, AttrRulesFilterBackend
from common.utils import get_logger, is_uuid
from orgs.mixins import generics
from orgs.mixins.api import OrgBulkModelViewSet
from ..mixin import NodeFilterMixin
from ...notifications import BulkUpdatePlatformSkipAssetUserMsg
logger = get_logger(__file__)
@@ -33,7 +31,6 @@ __all__ = [
class AssetFilterSet(BaseFilterSet):
labels = django_filters.CharFilter(method='filter_labels')
platform = django_filters.CharFilter(method='filter_platform')
domain = django_filters.CharFilter(method='filter_domain')
type = django_filters.CharFilter(field_name="platform__type", lookup_expr="exact")
@@ -64,7 +61,7 @@ class AssetFilterSet(BaseFilterSet):
class Meta:
model = Asset
fields = [
"id", "name", "address", "is_active", "labels",
"id", "name", "address", "is_active",
"type", "category", "platform",
]
@@ -87,25 +84,16 @@ class AssetFilterSet(BaseFilterSet):
value = value.split(',')
return queryset.filter(protocols__name__in=value).distinct()
@staticmethod
def filter_labels(queryset, name, value):
if ':' in value:
n, v = value.split(':', 1)
queryset = queryset.filter(labels__name=n, labels__value=v)
else:
q = Q(labels__name__contains=value) | Q(labels__value__contains=value)
queryset = queryset.filter(q).distinct()
return queryset
class AssetViewSet(SuggestionMixin, NodeFilterMixin, OrgBulkModelViewSet):
class AssetViewSet(SuggestionMixin, OrgBulkModelViewSet):
"""
API endpoint that allows Asset to be viewed or edited.
"""
model = Asset
filterset_class = AssetFilterSet
search_fields = ("name", "address", "comment")
ordering_fields = ('name', 'connectivity', 'platform', 'date_updated')
ordering = ('name',)
ordering_fields = ('name', 'address', 'connectivity', 'platform', 'date_updated', 'date_created')
serializer_classes = (
("default", serializers.AssetSerializer),
("platform", serializers.PlatformSerializer),
@@ -121,14 +109,12 @@ class AssetViewSet(SuggestionMixin, NodeFilterMixin, OrgBulkModelViewSet):
("sync_platform_protocols", "assets.change_asset"),
)
extra_filter_backends = [
LabelFilterBackend, IpInFilterBackend,
IpInFilterBackend,
NodeFilterBackend, AttrRulesFilterBackend
]
def get_queryset(self):
queryset = super().get_queryset() \
.prefetch_related('nodes', 'protocols') \
.select_related('platform', 'domain')
queryset = super().get_queryset()
if queryset.model is not Asset:
queryset = queryset.select_related('asset_ptr')
return queryset

View File

@@ -48,7 +48,7 @@ class AssetPermUserListApi(BaseAssetPermUserOrUserGroupListApi):
def get_queryset(self):
perms = self.get_asset_related_perms()
users = User.objects.filter(
users = User.get_queryset().filter(
Q(assetpermissions__in=perms) | Q(groups__assetpermissions__in=perms)
).distinct()
return users

View File

@@ -13,7 +13,7 @@ __all__ = ['CategoryViewSet']
class CategoryViewSet(ListModelMixin, JMSGenericViewSet):
serializer_classes = {
'default': CategorySerializer,
'types': TypeSerializer
'types': TypeSerializer,
}
permission_classes = (IsValidUser,)

View File

@@ -20,14 +20,15 @@ class DomainViewSet(OrgBulkModelViewSet):
filterset_fields = ("name",)
search_fields = filterset_fields
ordering = ('name',)
serializer_classes = {
'default': serializers.DomainSerializer,
'list': serializers.DomainListSerializer,
}
def get_serializer_class(self):
if self.request.query_params.get('gateway'):
return serializers.DomainWithGatewaySerializer
return serializers.DomainSerializer
def get_queryset(self):
return super().get_queryset().prefetch_related('assets')
return super().get_serializer_class()
class GatewayViewSet(HostViewSet):

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