Compare commits

...

468 Commits

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

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

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

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

* perf: 合并支持sftp协议

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

---------

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

* perf: 修改 account

* perf: 修改 virtual account

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

* perf: 修改获取虚拟账号

* perf: 修改 virtual account

* perf: 修改一些写法

* perf: 添加说明

---------

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

* perf: 去掉格式化

---------

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

* perf: 优化代码

---------

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

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

* Update 0065_session_command_amount.py

* Update session.py

* Update session.py

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

* perf: 优化命令数量获取

---------

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

* perf: 优化 Vault

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

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

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

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

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

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

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

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

* perf: 小优化

* perf: 优化

---------

Co-authored-by: feng <1304903146@qq.com>
Co-authored-by: Bai <baijiangjie@gmail.com>
Co-authored-by: feng626 <57284900+feng626@users.noreply.github.com>
2023-07-31 17:39:30 +08:00
ibuler
7776158279 perf: 修改 django_cas_ng 的版本 2023-07-31 17:25:32 +08:00
fangfang.dong
47dd73eb4c fix: 修复es7创建index的错误 2023-07-31 14:54:35 +08:00
老广
bf30be2084 Merge pull request #11122 from jumpserver/pr@dev@fix_rdpfilemultimon
feat: rdp file 支持设置多屏显示 (multimon)
2023-07-31 09:22:40 +08:00
老广
39d651dd9b Merge pull request #11123 from jumpserver/pr@dev@fix_rdpfile
feat: rdp file 支持设置多屏显示
2023-07-31 09:22:27 +08:00
Bai
07f4fdd92d feat: rdp file 支持设置多屏显示 2023-07-28 18:06:38 +08:00
Bai
53c8c2d9ea feat: rdp file 支持设置多屏显示 (multimon) 2023-07-28 17:45:20 +08:00
fit2bot
c201914bc8 perf: change secret perf (#11120)
Co-authored-by: feng <1304903146@qq.com>
2023-07-28 17:00:55 +08:00
老广
83917cb440 Merge pull request #11118 from jumpserver/pr@dev@perf_filter_and_default_group
perf: 添加到默认组织中
2023-07-28 16:52:00 +08:00
ibuler
b55eb1236f perf: 添加到默认组织中 2023-07-28 16:15:12 +08:00
fit2bot
38cee8eaa4 fix: 修复migrations文件错误 (#11116)
Co-authored-by: fangfang.dong <fangfang.dong@fit2cloud.com>
2023-07-28 15:14:49 +08:00
jiangweidong
e339a56042 feat: 云同步增加同步策略 (#11001) 2023-07-28 14:34:38 +08:00
fit2bot
384b639dd3 perf: 优化隐藏 Chrome 的代填操作 (#11114)
Co-authored-by: Eric <xplzv@126.com>
2023-07-28 14:33:09 +08:00
jiangweidong
c86b28a305 feat: 支持批量审批工单 (#11014) 2023-07-28 14:32:31 +08:00
老广
dbfb9db5c5 Merge pull request #11113 from jumpserver/pr@dev@perf_account_select
perf: 修改发布机账号选择
2023-07-28 11:17:02 +08:00
ibuler
93350faa08 perf: 修改账号选择 2023-07-28 11:15:24 +08:00
ibuler
107fda0f99 perf: 修改发布机账号选择 2023-07-28 11:13:48 +08:00
老广
58124af1ce Merge pull request #11111 from jumpserver/pr@dev@perf_applet_host_account_create
perf: 修改应用发布机账号创建
2023-07-28 11:07:18 +08:00
ibuler
1a4c5dca33 perf: 修改翻译 2023-07-28 11:06:01 +08:00
ibuler
5380dc0c2d perf: 修改翻译 2023-07-28 11:02:21 +08:00
ibuler
2c22396093 perf: 修改去掉冲突 2023-07-28 10:49:33 +08:00
ibuler
31da139eb3 merge: with dev 2023-07-28 10:46:34 +08:00
ibuler
962354c50d perf: 修改应用发布机账号创建 2023-07-28 10:41:37 +08:00
jiangweidong
1907c795c3 feat: 系统工具增加服务器时间及nmap工具 (#11078) 2023-07-28 10:40:48 +08:00
fangfang.dong
1239ffd4c8 perf: 优化会话分享url的构造 2023-07-28 10:22:47 +08:00
nut
7a37f91964 Update sharing.py 2023-07-28 10:22:47 +08:00
fangfang.dong
2741d7cbdc feat: 终端会话分享增加消息通知功能 2023-07-28 10:22:47 +08:00
fit2bot
99adb6ab7a perf: 改造username_suggestions api 改为post请求 (#11110)
Co-authored-by: feng <1304903146@qq.com>
2023-07-27 14:04:29 +08:00
Bai
665c833479 fix: 修复创建 ES 存储 get_mapping index 使用位置参数 2023-07-27 10:43:21 +08:00
Bai
77944cc91b fix: 修复创建资产 is_valid 使用kw参数 2023-07-27 10:21:22 +08:00
ibuler
b5fc865cc6 perf: Oracle 支持 2023-07-26 19:27:34 +08:00
ibuler
3b6c2fc0c0 perf: 修改 sftp 的一些处理 2023-07-26 19:25:39 +08:00
Bai
114645732a perf: 用户授权账号 API 返回 id 字段 2023-07-26 19:24:58 +08:00
老广
1b338a9cd3 Merge pull request #11093 from jumpserver/pr@dev@fix_user_account
fix: 修复同名账号用户名代填问题
2023-07-26 19:16:45 +08:00
老广
59f12a3c14 Merge pull request #11091 from jumpserver/pr@dev@ssh_to_sftp
perf: 修改 sftp 协议
2023-07-26 18:21:45 +08:00
Eric
3fc52cbb68 fix: 修复同名账号用户名代填问题 2023-07-26 17:13:38 +08:00
ibuler
b0b6d19bc0 perf: 修改 sftp 协议 2023-07-26 15:31:02 +08:00
老广
9deb48b16b Merge pull request #11080 from jumpserver/pr@dev@fix_bulk_update_asset_error
perf: 修复批量更新资产导致的错误
2023-07-26 09:51:45 +08:00
ibuler
48510e98a2 merge: with dev 2023-07-25 17:13:38 +08:00
ibuler
c135837372 perf: 修改 connect method 2023-07-25 17:12:06 +08:00
老广
92ed189453 Merge pull request #11083 from jumpserver/pr@dev@perf_koko_support
perf: 移除 Koko 的部分数据库支持
2023-07-25 16:33:58 +08:00
Eric
418ac5a5ba perf: 移除 Koko 的部分数据库支持 2023-07-25 15:45:48 +08:00
fit2bot
539a6161e6 perf: 翻译 (#11082)
Co-authored-by: feng <1304903146@qq.com>
2023-07-25 15:40:57 +08:00
ibuler
806baeb136 perf: 修复批量更新资产导致的错误 2023-07-25 14:45:24 +08:00
老广
ae0daddbea Merge pull request #11077 from jumpserver/pr@dev@change_ansible_pkg
perf: 使用瘦身后的 ansible
2023-07-25 11:22:29 +08:00
ibuler
76903977eb perf: 使用瘦身后的 ansible 2023-07-25 11:21:01 +08:00
老广
c9fffa50a8 Merge pull request #11076 from jumpserver/pr@dev@perf_django_version
perf: 降级 Django 版本
2023-07-25 10:57:53 +08:00
ibuler
6478727cd2 perf: 修改依赖包 2023-07-25 10:53:14 +08:00
ibuler
a20b210514 perf: 降级 Django 版本 2023-07-25 10:41:16 +08:00
老广
04a34e8456 Merge pull request #11075 from jumpserver/pr@dev@perf_domains_get
perf: 优化 domains 获取
2023-07-25 10:23:35 +08:00
ibuler
4d2c4a9602 perf: 优化 domains 获取 2023-07-25 10:11:57 +08:00
老广
2a24fcc1bb Merge pull request #11073 from jumpserver/pr@dev@perf_req
perf: 修改 uvicon  的版本
2023-07-24 23:28:33 +08:00
ibuler
366693783c perf: 修改 uvicon 的版本 2023-07-24 23:27:25 +08:00
老广
0a611a4ce9 Merge pull request #11072 from jumpserver/pr@dev@perf_ws_asgi
perf: 优化 asgi 的位置
2023-07-24 23:23:36 +08:00
ibuler
5fedb5440c perf: 设置 application 到 __all__ 2023-07-24 23:23:04 +08:00
ibuler
160c99a01a perf: 修改 requirements 2023-07-24 23:21:30 +08:00
ibuler
089d769eb0 perf: 优化 asgi 的位置 2023-07-24 23:20:05 +08:00
老广
9195d4c43d Merge pull request #11071 from jumpserver/pr@dev@remove_unuse_app
perf: 去掉不用的 app
2023-07-24 22:54:08 +08:00
ibuler
f1d984898b perf: 去掉不用的 app 2023-07-24 22:53:10 +08:00
老广
ecfd9449f2 Merge pull request #11070 from jumpserver/pr@dev@remove_loong64
perf: 拆分 loong64 架构
2023-07-24 21:22:53 +08:00
吴小白
94d40efcad perf: 预构建 ansible-core 2023-07-24 21:17:53 +08:00
吴小白
d5461fe66f perf: 拆分 loong64 架构 2023-07-24 21:09:02 +08:00
老广
00f4ae97ed Merge pull request #11068 from jumpserver/pr@dev@perf_deps
perf: 修改版本以来
2023-07-24 19:31:34 +08:00
ibuler
554c1da38b perf: 修改版本以来 2023-07-24 19:30:27 +08:00
老广
f1a68ebd70 Merge pull request #11064 from jumpserver/pr@dev@change_python_version
perf: 修改 Python 的版本
2023-07-24 18:23:52 +08:00
ibuler
b443a89cb5 perf: 修改 Python 的版本 2023-07-24 18:22:48 +08:00
老广
5b1ae46153 Merge pull request #11062 from jumpserver/pr@dev@for_django4
perf: 修改写法
2023-07-24 18:10:04 +08:00
ibuler
98fd209498 perf: 修改为 Domain 2023-07-24 18:09:10 +08:00
ibuler
7af769f7d3 perf: es 修改导入 2023-07-24 18:05:28 +08:00
老广
89ec01003c Merge pull request #11057 from jumpserver/pr@dev@for_django4
perf: 修改支持 Django4
2023-07-24 17:59:30 +08:00
ibuler
148bf3b894 perf: 修改写法 2023-07-24 17:55:17 +08:00
ibuler
38e8e8734d perf: 添加 DEBUG 日志 2023-07-24 17:49:32 +08:00
ibuler
d8d487f770 perf: 修改 ALLOW_HOSTS 2023-07-24 15:32:30 +08:00
ibuler
e3aaba4798 perf: 去掉不用的 2023-07-24 14:57:49 +08:00
ibuler
95e92a45d5 perf: 修改 xpack requirements 2023-07-24 14:46:48 +08:00
ibuler
86a17b9955 perf: 支持 ws 2023-07-24 14:32:13 +08:00
ibuler
7ae52eb941 perf: 修改 gettext 2023-07-24 14:09:22 +08:00
ibuler
b4b9c805ff perf: 修改支持 Django4 2023-07-24 11:52:25 +08:00
老广
16660575b7 Merge pull request #11054 from jumpserver/pr@dev@change_req_version
perf: 修改 mssql
2023-07-24 10:16:01 +08:00
老广
e9c2351f83 Merge pull request #11048 from huiserwang/dev_huiserwang
fix a latent bug when field_type belongs to int, bool and list.
2023-07-24 10:15:28 +08:00
ibuler
ed49216625 perf: 修改 mssql 2023-07-24 10:14:26 +08:00
ibuler
2417a0930f perf: 修改依赖库版本 2023-07-24 10:07:32 +08:00
老广
c9ba3f4f05 Merge pull request #11045 from jumpserver/pr@dev@feat_python_v3.11
feat: python 支持使用 3.11 版本
2023-07-24 10:07:03 +08:00
Huiser WANG
78d8e410db fix a latent bug when field_type belongs to int, bool and list. 2023-07-22 14:04:21 +08:00
feng
1f25eaf413 perf: update requirements.txt 2023-07-21 19:58:01 +08:00
Eric
54e6200ffe feat: python 支持使用 3.11 版本 2023-07-21 18:21:24 +08:00
老广
bad8400e77 Merge pull request #11042 from jumpserver/pr@dev@chrome_change_readme
chore: 修改 README
2023-07-21 14:11:19 +08:00
ibuler
0fb01bd7fb chore: 还原 requirements 2023-07-21 14:10:21 +08:00
ibuler
34e7671f65 chore: 修改 README 2023-07-21 14:04:34 +08:00
老广
2d99fddaf8 Merge pull request #10842 from jumpserver/pr@dev@perf_support_tidb
perf: 修改支持 tidb
2023-07-21 10:25:36 +08:00
老广
5df4efa5a8 Merge pull request #11037 from jumpserver/pr@dev@chore_change_readme
chore: 修改 readme
2023-07-20 19:43:04 +08:00
ibuler
e2207cf8f1 chore: 修改 readme 2023-07-20 19:41:42 +08:00
Bryan
e90e61e8dd Merge pull request #11035 from jumpserver/dev
v3.5.0
2023-07-20 19:03:31 +08:00
fit2bot
4c48204e16 perf: translate (#11036)
Co-authored-by: feng <1304903146@qq.com>
2023-07-20 18:46:34 +08:00
老广
bddcd8475d Merge pull request #11034 from jumpserver/pr@dev@chore_change_readme
perf: 修改 README, 添加 GPT
2023-07-20 18:11:10 +08:00
ibuler
5f8d84df66 perf: 修改图标 2023-07-20 18:10:28 +08:00
ibuler
cee87ae4d7 perf: 修改 README, 添加 GPT 2023-07-20 17:59:58 +08:00
老广
79a2d4e039 Merge pull request #11033 from jumpserver/pr@dev@fix_create_serializer_default
perf: 优化动态创建 serializer
2023-07-20 15:48:11 +08:00
ibuler
4f5e360991 perf: 优化动态创建 serializer 2023-07-20 15:44:52 +08:00
Eric
8e86173cb8 perf: 修复手动输入的同名账号问题 2023-07-20 15:38:51 +08:00
ibuler
08bc3d14aa fix: 修复 json m2m field 中正则有问题匹配不正确 2023-07-20 15:38:04 +08:00
fit2bot
19b91a6c1f perf: 修复资产导入账号模版失败问题 导入文件不区分大小写 (#11031)
Co-authored-by: feng <1304903146@qq.com>
2023-07-20 14:57:51 +08:00
Bai
c50330e055 fix: 修复删除Oracle数据库时报错提示问题 2023-07-20 11:56:49 +08:00
Bai
f5d9dedae1 fix: 修复 Endpoint 获取 Oracle port 的逻辑 2023-07-20 11:51:02 +08:00
Bai
ffb400d70d fix: 修复创建 Oracle 数据库端口超过范围后报错 500 并且不回滚的问题; 2023-07-20 11:23:57 +08:00
Bai
2291cfeaae fix: 修复 ConnectionToken 默认值类型没有转化的问题 2023-07-20 10:42:23 +08:00
老广
400d37ffca Merge pull request #11024 from jumpserver/pr@dev@fix_perm_accounts_only_one
fix: 修复授权的账号,用户名相同的,只有一个的情况
2023-07-19 21:24:45 +08:00
ibuler
14efd9afc1 perf: 修复可能导致的问题 2023-07-19 20:27:06 +08:00
ibuler
cfca519158 fix: 修复授权的账号,用户名相同的,只有一个的情况 2023-07-19 20:16:40 +08:00
Bai
23361fdba9 fix: 修复资产平台导入失败的问题(ID没有返回) 2023-07-19 19:56:18 +08:00
fit2bot
1b0d23fbf4 fix: playbook 批量删除 500 (#11022)
Co-authored-by: feng <1304903146@qq.com>
2023-07-19 19:37:55 +08:00
fit2bot
de4ef7d1b5 perf: GPT资产修改节点导致资产协议变多 (#11021)
Co-authored-by: feng <1304903146@qq.com>
2023-07-19 19:00:15 +08:00
ibuler
046342ceee perf: 平台创建自动化设置默认值 2023-07-19 18:23:18 +08:00
Bai
47195e2c44 fix: 修复客户端方式访问资产 Endpoint 标签匹配策略不生效的问题 2023-07-19 18:14:30 +08:00
老广
947c9e6216 Merge pull request #11018 from jumpserver/pr@dev@perf_coreworker
perf: 优化 Core Worker 数量
2023-07-19 17:17:07 +08:00
Bai
e1af380ad5 perf: 优化 Core Worker 数量 2023-07-19 17:12:44 +08:00
fit2bot
9e8579d5b4 perf: proxy 添加校验 修改翻译 (#11017)
Co-authored-by: feng <1304903146@qq.com>
2023-07-19 17:05:42 +08:00
老广
b8397e7db9 Merge pull request #11012 from jumpserver/pr@dev@perf_change_ui_route
perf: 优化 url
2023-07-19 11:37:51 +08:00
ibuler
8ed8d6f01c perf: 优化 url 2023-07-19 11:36:42 +08:00
Bai
ea607c6177 fix: 优化命令告警,不增加跳转链接 2023-07-19 08:27:34 +05:00
Bai
fa52e2bf5e perf: 优化批量命令告警问题 2023-07-19 08:09:45 +05:00
fangfang.dong
02fc9a730b feat: 快速命令新增告警级别: Warning 2023-07-19 08:09:45 +05:00
Bai
aa744c0fec fix: 修复账号模版切换时报错的问题 2023-07-19 07:34:55 +05:00
fit2bot
02d0c7e4e7 perf: ansible 错误信息优化 (#11005)
Co-authored-by: feng <1304903146@qq.com>
2023-07-18 18:55:18 +08:00
老广
0c34a41381 Merge pull request #11003 from jumpserver/pr@dev@fix_ansiblejobrunerror
fix: 修复批量执行命令时资产名称包含 [ 特殊字符执行报错的问题(issue: 10986)
2023-07-18 18:14:32 +08:00
Bai
8ed3da85f2 fix: 修复批量执行命令时资产名称包含 [ 特殊字符执行报错的问题(issue: 10986) 2023-07-18 10:06:40 +00:00
feng
de5b501ebf fix: 工单时区错乱问题 2023-07-18 16:56:22 +08:00
Bai
ea5a54f9c7 fix: 修复命令告警的问题 2023-07-18 15:21:40 +08:00
halo
6338ecc6fe perf: 优化邮件参数 2023-07-18 15:21:18 +08:00
Bai
be17fe6c31 perf: 邮件同步发送 2023-07-18 15:21:18 +08:00
halo
a18c97aec0 perf: 异步发送 2023-07-18 15:21:18 +08:00
halo
27c10fcae1 fix: 邮件主题前缀设置不生效的问题 2023-07-18 15:21:18 +08:00
fangfang.dong
539babcc97 fix: 修复参数取值错误 2023-07-18 15:17:34 +08:00
fit2bot
0436487bdb fix: 替换ssh key 生成密钥方法 (#10995)
Co-authored-by: feng <1304903146@qq.com>
2023-07-18 15:01:47 +08:00
Bai
f466904a1c perf: 优化 LDAP 用户导入/同步时支持 is_active 为 -1 的情况 2023-07-18 11:03:32 +08:00
老广
1d6bdc9b6b Merge pull request #10990 from jumpserver/pr@dev@perf_gunicorn_max_request
perf: gunicon添加重启参数
2023-07-18 11:02:58 +08:00
ibuler
d965ac0781 perf: 修改参数值 2023-07-18 11:00:43 +08:00
ibuler
6035241efb perf: gunicon添加重启参数 2023-07-18 10:44:12 +08:00
fit2bot
0771b804d1 refactor: 重构危险命令告警类型: Warning (#10970)
* refactor: 重构危险命令告警类型: Warning

* Update _msg_command_warning.html

* Update _msg_command_warning.html

* Update command.py

* Update django.po

* perf: 优化 command acl warning 的代码逻辑

* perf: 优化 command acl warning 的代码逻辑

* perf: 优化 CommandWarningMessage 逻辑

---------

Co-authored-by: fangfang.dong <fangfang.dong@fit2cloud.com>
Co-authored-by: Bai <baijiangjie@gmail.com>
2023-07-17 20:52:54 +08:00
老广
a2c6e5f3fb Merge pull request #10985 from jumpserver/pr@dev@feat_db_mariadb_web_db_support
feat: mariadb 支持 webdb
2023-07-17 18:02:42 +08:00
Aaron3S
c39041fe7b feat: mariadb 支持 webdb 2023-07-17 17:55:05 +08:00
ibuler
22588c52a9 fix: 修复 json field value 可能为 None 导致的问题 2023-07-17 17:25:44 +08:00
ibuler
daef154622 perf: 优化 host api 和 gunicorn 参数 2023-07-17 17:16:18 +08:00
Bai
7b9c4b300d perf: 优化控制 ACL Action Choices 的选项 2023-07-17 16:02:27 +08:00
Bai
819853eae4 feat: 增加 DEBUG_ANSIBLE 配置项支持打印 Ansible 详细日志 2023-07-17 14:11:09 +08:00
老广
f686f9f107 Merge pull request #10978 from jumpserver/pr@dev@fix_platform_setting
perf: 优化平台创建时,协议 setting 必填
2023-07-17 14:02:24 +08:00
ibuler
8a89ee7ac0 perf: 优化平台创建时,协议 setting 必填 2023-07-17 13:53:27 +08:00
老广
696295cf0d Merge pull request #10973 from jumpserver/pr@dev@fix_reset_password_bug
fix: 忘记密码token失效发送验证码报错的问题
2023-07-17 10:54:21 +08:00
老广
d99a3455cd Merge pull request #10966 from jumpserver/pr@dev@perf_chrome_plugins
perf: 优化 chrome 插件
2023-07-17 10:48:22 +08:00
老广
7f5b0618c6 Merge pull request #10969 from jumpserver/pr@dev@fix_ansibletesterror
fix: 修复 Ansible 测试资产可连接性报错的问题(Connection to UNKNOWN port 65535 timed out)
2023-07-17 10:27:48 +08:00
halo
0f1d9bc3eb fix: 忘记密码token失效发送验证码报错的问题 2023-07-15 16:30:45 +08:00
fit2bot
8f6b8b5a11 perf: settings logo (#10971)
Co-authored-by: feng <1304903146@qq.com>
2023-07-14 23:01:48 +08:00
Bai
4da0fadcc4 fix: 修复 Ansible 测试资产可连接性报错的问题(Connection to UNKNOWN port 65535 timed out) 2023-07-14 11:19:31 +00:00
fit2bot
f504413d7f feat: 添加logo api (#10965)
Co-authored-by: feng <1304903146@qq.com>
2023-07-14 16:54:42 +08:00
ibuler
9b5803f2a2 perf: 修改版本号 2023-07-13 20:02:28 +08:00
ibuler
d95e7c2e24 perf: 优化 chrome 插件 2023-07-13 20:01:06 +08:00
ibuler
a1ded0c737 perf: 优化一些 rbac 权限位,着重 connection token 的 2023-07-13 19:57:26 +08:00
老广
bedc83bd3a Merge pull request #10961 from jumpserver/pr@dev@perf_readme
perf: 修改 readme
2023-07-13 14:34:15 +08:00
ibuler
c9f3e4b28d perf: 修改 readme 2023-07-13 14:29:47 +08:00
老广
05bbd22c44 Merge pull request #10959 from jumpserver/pr@dev@perf_add_url
perf: 修改 log 的位置
2023-07-13 14:13:24 +08:00
老广
d00ef2b051 Merge pull request #10960 from maninhill/patch-10
chore: 更新 README
2023-07-13 12:51:19 +08:00
maninhill
efc538a569 chore: 更新 README 2023-07-13 11:55:12 +08:00
ibuler
c1de9151b8 perf: 修改地址 2023-07-13 11:46:47 +08:00
ibuler
2898d25bf8 perf: 修改 log 的位置 2023-07-13 11:45:15 +08:00
jiangweidong
68e2de81d8 perf: windows winrm使用ntlm认证 2023-07-12 20:22:44 +08:00
fit2bot
dd5802316d perf: 修改 connect methods 支持 (#10945)
Co-authored-by: ibuler <ibuler@qq.com>
2023-07-11 19:29:56 +08:00
老广
6f1ab1e09a Merge pull request #10944 from jumpserver/pr@dev@perf_add_protocol_support
perf: 修改 protocols 默认值
2023-07-11 18:00:23 +08:00
ibuler
6096ccc30a perf: 修改 protocols 默认值 2023-07-11 17:59:18 +08:00
老广
ddbd142ea3 Merge pull request #10943 from jumpserver/pr@dev@perf_connect_method
perf: 修改组件支持
2023-07-11 17:29:05 +08:00
ibuler
61d8328337 perf: 修改 protocol 定义 2023-07-11 17:27:47 +08:00
ibuler
4caa704abe perf: 修改组件支持 2023-07-11 17:04:43 +08:00
fit2bot
b75d69de5d feat: 新增危险命令告警类型: Warning (#10929)
* feat: 新增危险命令告警类型: Warning

* feat: 新增危险命令告警类型: Warning

* feat: 新增危险命令告警类型: Warning

* feat: 新增危险命令告警类型: Warning

* feat: 新增危险命令告警类型: Warning

* perf: 优化命令告警 View 处理逻辑

---------

Co-authored-by: fangfang.dong <fangfang.dong@fit2cloud.com>
Co-authored-by: Bai <baijiangjie@gmail.com>
2023-07-11 12:06:11 +08:00
fangfang.dong
10fa122e2f perf: 清理无用代码 2023-07-11 11:59:02 +08:00
老广
00ff1644cb Merge pull request #10941 from jumpserver/pr@dev@add_help_text
perf: 修改 api mode 和 i18n
2023-07-11 11:47:06 +08:00
ibuler
2b51a7590e perf: 修改 api mode 和 i18n 2023-07-11 11:28:09 +08:00
老广
30d07820c7 Merge pull request #10914 from jumpserver/dependabot/pip/requirements/django-3.2.20
build(deps): bump django from 3.2.19 to 3.2.20 in /requirements
2023-07-11 10:55:54 +08:00
老广
c51ebd62df Merge pull request #10936 from jumpserver/pr@dev@fix_beat-task-repeated
fix: 修复 beat 定时任务重复执行的问题
2023-07-11 10:47:41 +08:00
老广
593e28d7fa Merge pull request #10938 from jumpserver/pr@dev@perf_add_kael
perf: 添加 kael terminal 类型
2023-07-11 10:38:32 +08:00
ibuler
89f1a1653d perf: 添加 kael terminal 类型 2023-07-11 10:31:36 +08:00
Bai
ad311c15ca fix: 增加 TypeError 捕获 2023-07-11 10:19:31 +08:00
老广
b10623c970 Merge pull request #10879 from jumpserver/pr@dev@feat_chatgpt_support
feat: 支持 chatgpt 资产
2023-07-11 09:59:04 +08:00
Bai
7d17c1a450 fix: 修复 beat 定时任务重复执行的问题 2023-07-10 19:28:19 +08:00
老广
100b1553b6 Merge pull request #10931 from jumpserver/pr@dev@perf_change_platform
perf: 修改 Platform 约束
2023-07-07 19:48:15 +08:00
ibuler
76af71bbbe perf: 修改 Platform 约束 2023-07-07 19:47:12 +08:00
fit2bot
9607ab5164 perf: 修改支持 AD (#10926)
* stash

* perf: 修改支持 AD

* perf: 优化 default

---------

Co-authored-by: ibuler <ibuler@qq.com>
2023-07-07 16:15:32 +08:00
Eric
61078ee2ed perf: 更新 Chrome 的 ChangeLog 路径 2023-07-06 19:41:11 +08:00
Eric
6a720cde0a perf: 更新 chrome 支持匿名账号 2023-07-06 19:41:11 +08:00
老广
a2a5d5e08b Merge pull request #10925 from jumpserver/pr@dev@wechat
perf: 去除readme 中的微信
2023-07-06 18:27:03 +08:00
feng
9c2cc65ce8 perf: 去除readme 中的微信 2023-07-06 18:26:05 +08:00
feng
ee3cdcd9e4 fix: 有默认值 required 为false 2023-07-06 10:33:36 +08:00
feng
89492410aa fix: 推送账号 不填写home 推送失败 2023-07-06 10:33:36 +08:00
dependabot[bot]
b324c6cc8a build(deps): bump django from 3.2.19 to 3.2.20 in /requirements
Bumps [django](https://github.com/django/django) from 3.2.19 to 3.2.20.
- [Commits](https://github.com/django/django/compare/3.2.19...3.2.20)

---
updated-dependencies:
- dependency-name: django
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-07-05 23:26:55 +00:00
Bai
6b189e6162 fix: 修复导入LDAP数据库超时导致 Lock wait timeout 的问题 2023-07-05 18:49:01 +08:00
吴小白
a07cab9ae7 Merge pull request #10910 from jumpserver/pr@dev@perf_chrome
perf: 修正 Chrome driver 路径
2023-07-05 18:38:18 +08:00
Eric
751bd35349 perf: 修正 Chrome driver 路径 2023-07-05 18:28:31 +08:00
Bai
d6aaf23abb fix: 修复用户导入时手机号为dict类型报错的问题 2023-07-05 16:49:52 +08:00
Eric
f096014d03 perf: 移除针对端点 host 的校验 2023-07-05 15:39:54 +08:00
Eric
7f03639c34 perf: 更新翻译 2023-07-04 19:14:53 +08:00
Eric
3963881226 perf: 日文翻译更正 2023-07-04 19:14:53 +08:00
Eric
fb279dbc39 perf: 新增 SFTP 会话类型 2023-07-04 19:14:53 +08:00
fangfang.dong
785e4cc3e4 perf: 接口sql优化 /api/v1/perms/asset-permissions/<uuid:pk>/assets/all/ 2023-07-04 19:14:21 +08:00
jiangweidong
dd846d4183 feat: 云同步支持公有云 2023-07-04 18:48:07 +08:00
Eric_Lee
9169f3546a Revert "perf: rdp7 可使用 web gui方式连接" 2023-07-04 18:09:33 +08:00
Eric_Lee
7e2c0d0a2d Merge pull request #10896 from jumpserver/revert-10880-pr@dev@perf_xrdp_rdp7
Revert "perf: add xrdp rdp7 port 3390"
2023-07-04 17:57:33 +08:00
老广
66c60ef5be Revert "perf: add xrdp rdp7 port 3390" 2023-07-04 17:35:58 +08:00
fit2bot
f095998096 perf: 改密与推送保持一致 (#10812)
* perf: 改密与推送保持一致

* perf: 增加 i18n

---------

Co-authored-by: feng <1304903146@qq.com>
Co-authored-by: Bai <baijiangjie@gmail.com>
2023-07-04 17:34:31 +08:00
老广
d06e5d0001 Merge pull request #10826 from jumpserver/pr@dev@perf_account_template
perf: 接口sql优化 /api/v1/accounts/account-templates/su-from-account-templates/
2023-07-04 13:42:04 +08:00
老广
c8f420f62d Merge pull request #10893 from jumpserver/pr@dev@perf_rdp7_web
perf: rdp7 可使用 web gui方式连接
2023-07-04 13:39:28 +08:00
Eric
02550b38f8 perf: rdp7 可使用 web gui方式连接 2023-07-04 12:52:36 +08:00
老广
50531d3b97 Merge pull request #10829 from jumpserver/pr@dev@perf_support_anonymous_account
perf: web 和 自定义类型资产支持匿名账号
2023-07-04 11:46:24 +08:00
ibuler
db7ad81103 merge: 合并 dev 2023-07-04 11:45:20 +08:00
ibuler
d72ec653f4 merge: 合并 dev 2023-07-04 11:43:33 +08:00
老广
7950718582 Merge pull request #10825 from jumpserver/pr@dev@perf_asset_node
perf: 接口sql优化 /api/v1/assets/nodes/children/tree/
2023-07-04 11:28:45 +08:00
老广
998321f090 Merge pull request #10882 from jumpserver/pr@dev@perf_dockerfile
feat: 合并 Dockerfile
2023-07-04 11:26:23 +08:00
老广
1fa258da3e Merge pull request #10889 from jumpserver/pr@dev@perf_connectiontoken
perf: 修复 ConnectionToken 中 account id 的问题
2023-07-04 11:18:25 +08:00
ibuler
8dbe61100b perf: 优化协议,支持 port from addr 2023-07-04 10:29:27 +08:00
Eric
d7f9f3b670 perf: 修复 ConnectionToken 中 account id 的问题 2023-07-03 19:19:25 +08:00
老广
8b18f46613 Merge pull request #10880 from jumpserver/pr@dev@perf_xrdp_rdp7
perf: add xrdp rdp7 port 3390
2023-07-03 16:29:06 +08:00
吴小白
eb49beaf46 fix: 修正 oracle 路径 2023-07-03 10:37:42 +08:00
吴小白
3971fce561 feat: 合并 Dockerfile 2023-07-03 10:28:25 +08:00
Eric
2f81196874 perf: 更新 rdp7 protocol 设置 2023-07-03 10:22:49 +08:00
Eric
411102ed85 perf: 完善 protocol 匹配 2023-07-03 10:14:39 +08:00
Eric
125dc2adf5 perf: 针对 rdp7 端口特殊处理 2023-07-03 10:14:39 +08:00
Eric
6001175629 perf: add xrdp rdp7 port 3390 2023-07-03 10:14:39 +08:00
ibuler
41e39c9614 perf: 修改 chatgpt 协议 2023-06-30 18:33:18 +08:00
ibuler
19de79fadf feat: 支持 chatgpt 资产 2023-06-30 17:35:49 +08:00
老广
6b7df10d50 Merge pull request #10877 from jumpserver/pr@dev@perf_applet_chrome
perf: 更新 Python
2023-06-30 16:01:18 +08:00
吴小白
ce269e315a perf: 更新 Python 2023-06-30 15:58:20 +08:00
老广
dfc8654d96 Merge pull request #10876 from jumpserver/pr@dev@perf_applet_chrome
perf: 更新 Chrome
2023-06-30 15:58:18 +08:00
吴小白
ea07f9e56a perf: 更新 Chrome 2023-06-30 15:55:32 +08:00
fit2bot
bbbd011cc2 perf: 修改 protocol setting (#10875)
* feat: 新增账号配置

* perf: 修改 platform protocol define

* perf: 修改 account config

* perf: 修改协议设置

---------

Co-authored-by: ibuler <ibuler@qq.com>
2023-06-30 15:54:06 +08:00
老广
6962430e6a Merge pull request #10874 from jumpserver/pr@dev@perf_accountsearch
perf: 账号搜索支持通过 secret_type 过滤
2023-06-30 15:22:14 +08:00
Bai
ca1b82330e perf: 账号搜索支持通过 secret_type 过滤 2023-06-30 11:12:23 +08:00
fit2bot
f4bd06b970 feat: 优化 Issue GitHub Actions,当研发团队成员评论后再移除 待处理 标签(12) (#10870)
Co-authored-by: Bai <baijiangjie@gmail.com>
Co-authored-by: Bryan <jiangjie.bai@fit2cloud.com>
2023-06-29 17:15:19 +08:00
Bai
d0bf5b46f6 feat: 优化 Issue GitHub Actions,当研发团队成员评论后再移除 待处理 标签(11) 2023-06-29 17:12:21 +08:00
Bai
3c707996e0 feat: 优化 Issue GitHub Actions,当研发团队成员评论后再移除 待处理 标签(10) 2023-06-29 17:05:38 +08:00
Bai
ac0a673818 feat: 优化 Issue GitHub Actions,当研发团队成员评论后再移除 待处理 标签(9) 2023-06-29 17:00:36 +08:00
Bai
1ed6c7e01d feat: 优化 Issue GitHub Actions,当研发团队成员评论后再移除 待处理 标签(8) 2023-06-29 16:54:28 +08:00
Bai
adcabf69ed feat: 优化 Issue GitHub Actions,当研发团队成员评论后再移除 待处理 标签(7) 2023-06-29 16:43:00 +08:00
Bai
0b92e43e20 feat: 优化 Issue GitHub Actions,当研发团队成员评论后再移除 待处理 标签(6) 2023-06-29 16:43:00 +08:00
Bai
9c1a6b8565 feat: 优化 Issue GitHub Actions,当研发团队成员评论后再移除 待处理 标签(5) 2023-06-29 16:07:04 +08:00
Bai
fc8d226005 feat: 优化 Issue GitHub Actions,当研发团队成员评论后再移除 待处理 标签(4) 2023-06-29 15:42:14 +08:00
Bai
f3955a47f6 feat: 优化 Issue GitHub Actions,当研发团队成员评论后再移除 待处理 标签(3) 2023-06-29 15:25:08 +08:00
Bai
0020fe7be0 feat: 优化 Issue GitHub Actions,当研发团队成员评论后再移除 待处理 标签(2) 2023-06-29 15:18:54 +08:00
Bai
cea56a2f7e feat: 优化 Issue GitHub Actions,当研发团队成员评论后再移除 待处理 标签(1) 2023-06-29 14:50:27 +08:00
Bai
e3cf6cc476 feat: 优化 Issue GitHub Actions,当研发团队成员评论后再移除 待处理 标签 2023-06-29 14:28:38 +08:00
Bai
57fccc9baf feat: 优化 Issue GitHub Actions,当研发团队成员评论后再移除 待处理 标签 2023-06-29 14:18:23 +08:00
Aaron3S
fbcb0da349 feat: 支持sqlserver 通过chen 链接 2023-06-29 11:41:06 +08:00
Bai
877a053717 feat: 优化 Issue GitHub Actions,当研发团队成员评论后再移除 待处理 标签 2023-06-29 11:40:43 +08:00
Bai
d293a03649 feat: 优化 Issue GitHub Actions,当研发团队成员评论后再移除 待处理 标签 2023-06-29 11:30:04 +08:00
Bai
08e0c5fdf5 feat: 优化 Issue GitHub Actions,当研发团队成员评论后再移除 待处理 标签 2023-06-29 11:17:37 +08:00
nut
ac906a5d52 Update api.py 2023-06-28 17:52:23 +08:00
fangfang.dong
9ad8e53743 perf: 接口sql优化 /api/v1/index/ 2023-06-28 17:52:23 +08:00
ibuler
bf29158be9 perf: 修改支持 tidb 2023-06-28 15:01:25 +08:00
ibuler
a67ee976b4 perf: 修改翻译 2023-06-27 16:03:19 +08:00
ibuler
dfa12239d6 perf: 修改翻译 2023-06-27 16:00:45 +08:00
ibuler
4737e2cf4a perf: 优化 匿名账号 2023-06-27 15:22:18 +08:00
ibuler
d3d8fcbbb3 perf: 修改经常遇到的登录超时 2023-06-27 14:50:04 +08:00
Eric
a64aa89b3f fix: 修复自定义远程应用的连接问题 2023-06-27 14:43:00 +08:00
ibuler
a22f36a06a perf: 去掉 debug 2023-06-27 14:31:20 +08:00
Bryan
17fa139bc9 feat: Update ----.md 2023-06-27 14:24:27 +08:00
ibuler
77bcb05d80 perf: web 和 自定义类型资产支持匿名账号 2023-06-27 11:23:56 +08:00
fangfang.dong
4e9012cc07 perf: 接口sql优化 /api/v1/accounts/account-templates/su-from-account-templates/ 2023-06-27 10:45:50 +08:00
fangfang.dong
b3dce27309 perf: 接口sql优化 /api/v1/assets/nodes/children/tree/ 2023-06-27 10:24:47 +08:00
老广
bccf3a0340 Merge pull request #10819 from jumpserver/pr@dev@perf_asset_asset
perf: 接口sql优化 /api/v1/assets/assets/
2023-06-27 09:55:11 +08:00
nut
358b3a1891 Update asset.py 2023-06-26 23:51:59 +08:00
feng
5a2f6bdfc9 perf: ldap sync任务开始时 先检查可连接性 2023-06-25 18:25:15 +08:00
feng
768eb033eb fix: 修复自动化任务原子性error 导致整个任务失败问题 2023-06-25 18:20:49 +08:00
fangfang.dong
d7d554daf5 perf: 接口sql优化 /api/v1/assets/assets/ 2023-06-25 18:08:59 +08:00
jiangweidong
780b1104de perf: 优化飞书接收到的工单审批的连接无法点击的问题 2023-06-25 11:08:14 +08:00
老广
eeba0a4bfc Merge pull request #10806 from jumpserver/pr@dev@feat_terminal_endpointrule
feat: 系统设置 - 终端设置 - 端点规则: 新增字段is_active控制是否启用
2023-06-21 18:36:37 +08:00
fangfang.dong
b2ee8c8216 feat: 系统设置 - 终端设置 - 端点规则: 新增字段is_active控制是否启用 2023-06-21 18:33:58 +08:00
ibuler
26edd2f040 perf: 修改去掉一些 debug 2023-06-21 17:49:16 +08:00
ibuler
270ed5e2f8 perf: 修改 logging 避免冲突 2023-06-21 17:49:16 +08:00
Eric
b2bff22387 fix: 修复远程应用会话无法监控的问题 2023-06-21 14:48:18 +08:00
ibuler
1ca71f78ed perf: 优化一下,去掉 rbac 引起的 sql查询 2023-06-21 14:46:59 +08:00
ibuler
fa24a8e2f3 perf: 添加 sql debug 2023-06-21 12:02:56 +08:00
Bai
b9c1a89f51 fix: 修复迁移文件时触发信号记录操作日志导致迁移失败的问题 2023-06-21 11:02:42 +08:00
ibuler
a2bbf11f9d perf: 添加 migrate debug msg 2023-06-21 11:01:21 +08:00
ibuler
1d084311c5 perf: 统一 connect token 配置名称 2023-06-20 16:40:21 +08:00
ibuler
cb0fd937c8 perf: 资产连接可以指定 AppletHost 2023-06-20 16:37:54 +08:00
ibuler
13fc2aa73c perf: 优化rbac 迁移 2023-06-20 16:35:01 +08:00
Eric
5d9979ec03 perf: 修复 terminal 显示问题 2023-06-20 16:34:03 +08:00
Eric
e4f21b8a5f perf: 移除 omnidb 2023-06-19 18:31:59 +08:00
feng
9403b76333 fix: 修改 push_account_params 数据迁移逻辑,不在导入公共方法生成数据 2023-06-19 18:23:57 +08:00
fit2bot
666df6ffef perf: 接口 /api/v1/tickets/tickets/ sql优化 (#10762)
* perf: 接口 /api/v1/tickets/tickets/ sql优化

* Update general.py

* Update general.py

* Update general.py

---------

Co-authored-by: fangfang.dong <fangfang.dong@fit2cloud.com>
Co-authored-by: nut <evicwork@gmail.com>
2023-06-19 18:19:52 +08:00
Chenyang Shen
9cc3942b3d Merge pull request #10779 from jumpserver/pr@dev@perf_terminal_chen
perf: 新增 chen 终端类型
2023-06-19 18:18:12 +08:00
Eric
42852c368c perf: 新增 chen 终端类型 2023-06-19 18:06:23 +08:00
ibuler
4d4644dddd fix: 修改原来 platform 为 device 时,导致的 asset 类型不对 2023-06-19 17:54:42 +08:00
cui fliter
471411a1aa fix some typos
Signed-off-by: cui fliter <imcusg@gmail.com>
2023-06-19 15:19:41 +08:00
老广
db12bc07e8 Merge pull request #10760 from jumpserver/pr@dev@perf_assets_domain
perf: 接口 /api/v1/assets/domains/ sql优化
2023-06-19 10:25:20 +08:00
老广
618ee0b2f9 Merge pull request #10761 from jumpserver/pr@dev@perf_assets_label
perf: 接口 /api/v1/assets/label/ sql优化
2023-06-19 10:24:52 +08:00
fangfang.dong
39ba52e4de perf: 接口 /api/v1/assets/label/ sql优化 2023-06-18 20:26:19 +08:00
fangfang.dong
a8ef405939 perf: 接口 /api/v1/assets/domains/ sql优化 2023-06-18 20:24:14 +08:00
老广
09f7ddd28a Merge pull request #10756 from jumpserver/pr@dev@fix_custom_asset_detail_error
perf: 修复自定义资产详情没有 auto_config 的问题
2023-06-16 18:48:24 +08:00
ibuler
da4337168f perf: 修复自定义资产详情没有 auto_config 的问题 2023-06-16 18:44:13 +08:00
老广
f13966e061 Merge pull request #10754 from jumpserver/pr@dev@fix_permed_asset_duplicate
fix: 修复授权资产根据协议搜索重复的问题
2023-06-16 16:53:43 +08:00
ibuler
f4b5a302a1 fix: 修复授权资产根据协议搜索重复的问题 2023-06-16 16:44:05 +08:00
老广
dd955530f1 Merge pull request #10746 from jumpserver/pr@dev@perf_category_api_sql
perf: 修改 category 引起的 sql 查询过多
2023-06-16 15:55:27 +08:00
ibuler
50b64f6cf5 perf: 修改 category 引起的 sql 查询过多
pref: stash

perf: 添加装饰器

perf: 优化 category api
2023-06-16 15:53:48 +08:00
老广
a5b21f94c2 Merge pull request #10752 from jumpserver/pr@dev@perf_custom_field_required
perf: 优化自定义 platform field
2023-06-16 15:16:58 +08:00
ibuler
9e3e183f95 perf: 优化自定义 platform field 2023-06-16 15:07:17 +08:00
ibuler
9ec3147b5f perf: 修改 login acls 迁移冲突问题
perf: 修改 login acls 迁移,避免冲突
2023-06-16 13:59:15 +08:00
老广
79fa134621 Merge pull request #10742 from jumpserver/pr@dev@windows_rdp_ping
feat: 添加自动化任务rdp ping
2023-06-15 18:34:45 +08:00
feng
ef4132d2c5 feat: 添加自动化任务rdp ping 2023-06-15 18:33:05 +08:00
老广
b31a08ed8d Merge pull request #10741 from jumpserver/pr@dev@fix_acl_migrate_not_work
perf: 修复 acl 迁移后无法使用
2023-06-15 18:32:34 +08:00
ibuler
cdd47f4bc6 perf: 修复 acl 迁移后无法使用 2023-06-15 18:13:51 +08:00
ibuler
269a5e9d52 perf: 龙芯使用 buster 镜像 2023-06-15 17:39:21 +08:00
老广
dd0d1d3592 Merge pull request #10735 from jumpserver/pr@dev@change_docker_base_image
perf: 修改基础镜像
2023-06-15 16:59:58 +08:00
ibuler
c06368d812 perf: 修改基础镜像 2023-06-15 16:53:28 +08:00
fit2bot
96ef56da67 perf: 修改翻译 (#10733)
Co-authored-by: feng <1304903146@qq.com>
2023-06-15 15:41:07 +08:00
494 changed files with 16738 additions and 5185 deletions

View File

@@ -1,5 +1,4 @@
.git
logs/*
data/*
.github
tmp/*

View File

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

View File

@@ -21,17 +21,44 @@ jobs:
actions: 'remove-labels'
labels: '状态:待反馈'
add-label-if-not-author:
add-label-if-is-member:
runs-on: ubuntu-latest
if: (github.event.issue.user.id != github.event.comment.user.id) && !github.event.issue.pull_request && (github.event.issue.state == 'open')
steps:
- name: Checkout repository
uses: actions/checkout@v2
- name: Get Organization name
id: org_name
run: echo "data=$(echo '${{ github.repository }}' | cut -d '/' -f 1)" >> $GITHUB_OUTPUT
- name: Get Organization public members
uses: octokit/request-action@v2.x
id: members
with:
route: GET /orgs/${{ steps.org_name.outputs.data }}/public_members
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Process public members data
# 将 members 中的数据转化为 login 字段的拼接字符串
id: member_names
run: echo "data=$(echo '${{ steps.members.outputs.data }}' | jq '[.[].login] | join(",")')" >> $GITHUB_OUTPUT
- run: "echo members: '${{ steps.members.outputs.data }}'"
- run: "echo member names: '${{ steps.member_names.outputs.data }}'"
- run: "echo comment user: '${{ github.event.comment.user.login }}'"
- run: "echo contains? : '${{ contains(steps.member_names.outputs.data, github.event.comment.user.login) }}'"
- name: Add require replay label
if: contains(steps.member_names.outputs.data, github.event.comment.user.login)
uses: actions-cool/issues-helper@v2
with:
actions: 'add-labels'
labels: '状态:待反馈'
- name: Remove require handle label
if: contains(steps.member_names.outputs.data, github.event.comment.user.login)
uses: actions-cool/issues-helper@v2
with:
actions: 'remove-labels'

1
.gitignore vendored
View File

@@ -35,7 +35,6 @@ celerybeat-schedule.db
docs/_build/
xpack
xpack.bak
logs/*
### Vagrant ###
.vagrant/
release/*

View File

@@ -1,4 +1,4 @@
FROM python:3.9-slim-buster as stage-build
FROM python:3.11-slim-bullseye as stage-build
ARG TARGETARCH
ARG VERSION
@@ -8,9 +8,8 @@ WORKDIR /opt/jumpserver
ADD . .
RUN cd utils && bash -ixeu build.sh
FROM python:3.9-slim-buster
FROM python:3.11-slim-bullseye
ARG TARGETARCH
MAINTAINER JumpServer Team <ibuler@qq.com>
ARG BUILD_DEPENDENCIES=" \
g++ \
@@ -22,8 +21,10 @@ ARG DEPENDENCIES=" \
libpq-dev \
libffi-dev \
libjpeg-dev \
libkrb5-dev \
libldap2-dev \
libsasl2-dev \
libssl-dev \
libxml2-dev \
libxmlsec1-dev \
libxmlsec1-openssl \
@@ -36,13 +37,11 @@ ARG TOOLS=" \
default-libmysqlclient-dev \
default-mysql-client \
locales \
nmap \
openssh-client \
procps \
sshpass \
telnet \
unzip \
vim \
git \
wget"
ARG APT_MIRROR=http://mirrors.ustc.edu.cn
@@ -64,37 +63,17 @@ RUN --mount=type=cache,target=/var/cache/apt,sharing=locked,id=core \
&& sed -i "s@# alias @alias @g" ~/.bashrc \
&& rm -rf /var/lib/apt/lists/*
ARG DOWNLOAD_URL=https://download.jumpserver.org
RUN mkdir -p /opt/oracle/ \
&& cd /opt/oracle/ \
&& wget ${DOWNLOAD_URL}/public/instantclient-basiclite-linux.${TARGETARCH}-19.10.0.0.0.zip \
&& unzip instantclient-basiclite-linux.${TARGETARCH}-19.10.0.0.0.zip \
&& sh -c "echo /opt/oracle/instantclient_19_10 > /etc/ld.so.conf.d/oracle-instantclient.conf" \
&& ldconfig \
&& rm -f instantclient-basiclite-linux.${TARGETARCH}-19.10.0.0.0.zip
WORKDIR /tmp/build
COPY ./requirements ./requirements
ARG PIP_MIRROR=https://pypi.douban.com/simple
ENV PIP_MIRROR=$PIP_MIRROR
ARG PIP_JMS_MIRROR=https://pypi.douban.com/simple
ENV PIP_JMS_MIRROR=$PIP_JMS_MIRROR
RUN --mount=type=cache,target=/root/.cache/pip \
set -ex \
&& pip config set global.index-url ${PIP_MIRROR} \
&& pip install --upgrade pip \
&& pip install --upgrade setuptools wheel \
&& pip install $(grep -E 'jms|jumpserver' requirements/requirements.txt) -i ${PIP_JMS_MIRROR} \
&& pip install -r requirements/requirements.txt
COPY --from=stage-build /opt/jumpserver/release/jumpserver /opt/jumpserver
RUN echo > /opt/jumpserver/config.yml \
&& rm -rf /tmp/build
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
VOLUME /opt/jumpserver/data
VOLUME /opt/jumpserver/logs

View File

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

View File

@@ -1,97 +0,0 @@
FROM python:3.9-slim-buster as stage-build
ARG TARGETARCH
ARG VERSION
ENV VERSION=$VERSION
WORKDIR /opt/jumpserver
ADD . .
RUN cd utils && bash -ixeu build.sh
FROM python:3.9-slim-buster
ARG TARGETARCH
MAINTAINER JumpServer Team <ibuler@qq.com>
ARG BUILD_DEPENDENCIES=" \
g++ \
make \
pkg-config"
ARG DEPENDENCIES=" \
freetds-dev \
libpq-dev \
libffi-dev \
libjpeg-dev \
libldap2-dev \
libsasl2-dev \
libssl-dev \
libxml2-dev \
libxmlsec1-dev \
libxmlsec1-openssl \
freerdp2-dev \
libaio-dev"
ARG TOOLS=" \
ca-certificates \
curl \
default-libmysqlclient-dev \
default-mysql-client \
locales \
openssh-client \
procps \
sshpass \
telnet \
unzip \
vim \
git \
wget"
RUN --mount=type=cache,target=/var/cache/apt,sharing=locked,id=core \
set -ex \
&& 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/*
WORKDIR /tmp/build
COPY ./requirements ./requirements
ARG PIP_MIRROR=https://pypi.douban.com/simple
ENV PIP_MIRROR=$PIP_MIRROR
ARG PIP_JMS_MIRROR=https://pypi.douban.com/simple
ENV PIP_JMS_MIRROR=$PIP_JMS_MIRROR
RUN --mount=type=cache,target=/root/.cache/pip \
set -ex \
&& pip config set global.index-url ${PIP_MIRROR} \
&& pip install --upgrade pip \
&& pip install --upgrade setuptools wheel \
&& pip install https://download.jumpserver.org/pypi/simple/cryptography/cryptography-38.0.4-cp39-cp39-linux_loongarch64.whl \
&& pip install https://download.jumpserver.org/pypi/simple/greenlet/greenlet-1.1.2-cp39-cp39-linux_loongarch64.whl \
&& pip install https://download.jumpserver.org/pypi/simple/PyNaCl/PyNaCl-1.5.0-cp39-cp39-linux_loongarch64.whl \
&& pip install https://download.jumpserver.org/pypi/simple/grpcio/grpcio-1.54.2-cp39-cp39-linux_loongarch64.whl \
&& pip install $(grep -E 'jms|jumpserver' requirements/requirements.txt) -i ${PIP_JMS_MIRROR} \
&& pip install -r requirements/requirements.txt
COPY --from=stage-build /opt/jumpserver/release/jumpserver /opt/jumpserver
RUN echo > /opt/jumpserver/config.yml \
&& rm -rf /tmp/build
WORKDIR /opt/jumpserver
VOLUME /opt/jumpserver/data
VOLUME /opt/jumpserver/logs
ENV LANG=zh_CN.UTF-8
EXPOSE 8080
ENTRYPOINT ["./entrypoint.sh"]

1
GITSHA Normal file
View File

@@ -0,0 +1 @@
f46c9f56e8f33768e26b4ed2741251ee77334fb6

View File

@@ -17,18 +17,17 @@
9 年时间,倾情投入,用心做好一款开源堡垒机。
</p>
| :warning: 注意 :warning: |
|:-------------------------------------------------------------------------------------------------------------------------:|
| 3.0 架构上和 2.0 变化较大,建议全新安装一套环境来体验。如需升级,请务必升级前进行备份,并[查阅文档](https://kb.fit2cloud.com/?p=06638d69-f109-4333-b5bf-65b17b297ed9) |
------------------------------
JumpServer 是广受欢迎的开源堡垒机,是符合 4A 规范的专业运维安全审计系统。
--------------------------
JumpServer 是广受欢迎的开源堡垒机,是符合 4A 规范的专业运维安全审计系统。JumpServer 堡垒机帮助企业以更安全的方式管控和登录各种类型的资产,包括:
JumpServer 堡垒机帮助企业以更安全的方式管控和登录各种类型的资产,包括:
- **SSH**: Linux / Unix / 网络设备 等;
- **Windows**: Web 方式连接 / 原生 RDP 连接;
- **数据库**: MySQL / Oracle / SQLServer / PostgreSQL 等;
- **Kubernetes**: 支持连接到 K8s 集群中的 Pods
- **数据库**: MySQL / MariaDB / PostgreSQL / Oracle / SQLServer / ClickHouse 等;
- **NoSQL**: Redis / MongoDB 等
- **GPT**: ChatGPT 等;
- **云服务**: Kubernetes / VMware vSphere 等;
- **Web 站点**: 各类系统的 Web 管理后台;
- **应用**: 通过 Remote App 连接各类应用。
@@ -81,29 +80,28 @@ JumpServer 是广受欢迎的开源堡垒机,是符合 4A 规范的专业运
如果您在使用过程中有任何疑问或对建议,欢迎提交 [GitHub Issue](https://github.com/jumpserver/jumpserver/issues/new/choose)。
您也可以到我们的 [社区论坛](https://bbs.fit2cloud.com/c/js/5) 及微信交流群当中进行交流沟通。
**微信交流群**
<img src="https://download.jumpserver.org/images/wecom-group.jpeg" alt="微信群二维码" width="200"/>
您也可以到我们的 [社区论坛](https://bbs.fit2cloud.com/c/js/5) 当中进行交流沟通。
### 参与贡献
欢迎提交 PR 参与贡献。感谢以下贡献者,他们让 JumpServer 变的越来越好。
<a href="https://github.com/jumpserver/jumpserver/graphs/contributors"><img src="https://opencollective.com/jumpserver/contributors.svg?width=890&button=false" /></a>
欢迎提交 PR 参与贡献。 参考 [CONTRIBUTING.md](https://github.com/jumpserver/jumpserver/blob/dev/CONTRIBUTING.md)
## 组件项目
| 项目 | 状态 | 描述 |
|--------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------|
| [Lina](https://github.com/jumpserver/lina) | <a href="https://github.com/jumpserver/lina/releases"><img alt="Lina release" src="https://img.shields.io/github/release/jumpserver/lina.svg" /></a> | JumpServer Web UI 项目 |
| [Luna](https://github.com/jumpserver/luna) | <a href="https://github.com/jumpserver/luna/releases"><img alt="Luna release" src="https://img.shields.io/github/release/jumpserver/luna.svg" /></a> | JumpServer Web Terminal 项目 |
| [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 项目,替代原来 Python 版本的 [Coco](https://github.com/jumpserver/coco) |
| [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/) |
| [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 项目 |
| [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 安装包 项目 |
| 项目 | 状态 | 描述 |
|--------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------|
| [Lina](https://github.com/jumpserver/lina) | <a href="https://github.com/jumpserver/lina/releases"><img alt="Lina release" src="https://img.shields.io/github/release/jumpserver/lina.svg" /></a> | JumpServer Web UI 项目 |
| [Luna](https://github.com/jumpserver/luna) | <a href="https://github.com/jumpserver/luna/releases"><img alt="Luna release" src="https://img.shields.io/github/release/jumpserver/luna.svg" /></a> | JumpServer Web Terminal 项目 |
| [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 项目 |
| [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 通信的组件项目 |
| [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 安装包 项目 |
## 安全说明
@@ -113,11 +111,6 @@ JumpServer是一款安全产品请参考 [基本安全建议](https://docs.ju
- 邮箱support@fit2cloud.com
- 电话400-052-0755
## 致谢开源
- [Apache Guacamole](https://guacamole.apache.org/) Web 页面连接 RDP、SSH、VNC 等协议资产JumpServer Lion 组件使用到该项目;
- [OmniDB](https://omnidb.org/) Web 页面连接使用数据库JumpServer Web 数据库组件使用到该项目。
## License & Copyright
Copyright (c) 2014-2023 飞致云 FIT2CLOUD, All rights reserved.

View File

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

View File

@@ -22,10 +22,11 @@ __all__ = [
class AccountViewSet(OrgBulkModelViewSet):
model = Account
search_fields = ('username', 'name', 'asset__name', 'asset__address')
search_fields = ('username', 'name', 'asset__name', 'asset__address', 'comment')
filterset_class = AccountFilterSet
serializer_classes = {
'default': serializers.AccountSerializer,
'retrieve': serializers.AccountDetailSerializer,
}
rbac_perms = {
'partial_update': ['accounts.change_account'],
@@ -52,20 +53,21 @@ class AccountViewSet(OrgBulkModelViewSet):
return Response(data=serializer.data)
@action(
methods=['get'], detail=False, url_path='username-suggestions',
methods=['post'], detail=False, url_path='username-suggestions',
permission_classes=[IsValidUser]
)
def username_suggestions(self, request, *args, **kwargs):
asset_ids = request.query_params.get('assets')
node_keys = request.query_params.get('keys')
username = request.query_params.get('username')
asset_ids = request.data.get('assets')
node_ids = request.data.get('nodes')
username = request.data.get('username')
assets = Asset.objects.all()
if asset_ids:
assets = assets.filter(id__in=asset_ids.split(','))
if node_keys:
patten = Node.get_node_all_children_key_pattern(node_keys.split(','))
assets = assets.filter(nodes__key__regex=patten)
assets = assets.filter(id__in=asset_ids)
if node_ids:
nodes = Node.objects.filter(id__in=node_ids)
node_asset_ids = Node.get_nodes_all_assets(*nodes).values_list('id', flat=True)
assets = assets.filter(id__in=set(list(asset_ids) + list(node_asset_ids)))
accounts = Account.objects.filter(asset__in=assets)
if username:
@@ -132,11 +134,13 @@ class AccountHistoriesSecretAPI(ExtraFilterFieldsMixin, RecordViewLogMixin, List
def get_queryset(self):
account = self.get_object()
histories = account.history.all()
last_history = account.history.first()
if not last_history:
latest_history = account.history.first()
if not latest_history:
return histories
if account.secret == last_history.secret \
and account.secret_type == last_history.secret_type:
histories = histories.exclude(history_id=last_history.history_id)
if account.secret != latest_history.secret:
return histories
if account.secret_type != latest_history.secret_type:
return histories
histories = histories.exclude(history_id=latest_history.history_id)
return histories

View File

@@ -49,8 +49,7 @@ class AccountTemplateViewSet(OrgBulkModelViewSet):
@action(methods=['get'], detail=False, url_path='su-from-account-templates')
def su_from_account_templates(self, request, *args, **kwargs):
pk = request.query_params.get('template_id')
template = AccountTemplate.objects.filter(pk=pk).first()
templates = AccountTemplate.get_su_from_account_templates(template)
templates = AccountTemplate.get_su_from_account_templates(pk)
templates = self.filter_queryset(templates)
serializer = self.get_serializer(templates, many=True)
return Response(data=serializer.data)

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,22 +1,17 @@
import os
import time
from openpyxl import Workbook
from collections import defaultdict, OrderedDict
from django.conf import settings
from django.db.models import F
from openpyxl import Workbook
from rest_framework import serializers
from accounts.models import Account
from assets.const import AllTypes
from accounts.serializers import AccountSecretSerializer
from accounts.notifications import AccountBackupExecutionTaskMsg
from users.models import User
from common.utils import get_logger
from common.utils.timezone import local_now_display
from accounts.serializers import AccountSecretSerializer
from assets.const import AllTypes
from common.utils.file import encrypt_and_compress_zip_file
logger = get_logger(__file__)
from common.utils.timezone import local_now_display
from users.models import User
PATH = os.path.join(os.path.dirname(settings.BASE_DIR), 'tmp')
@@ -76,8 +71,22 @@ class AssetAccountHandler(BaseAccountHandler):
)
return filename
@staticmethod
def handler_secret(data, section):
for account_data in data:
secret = account_data.get('secret')
if not secret:
continue
length = len(secret)
index = length // 2
if section == "front":
secret = secret[:index] + '*' * (length - index)
elif section == "back":
secret = '*' * (length - index) + secret[index:]
account_data['secret'] = secret
@classmethod
def create_data_map(cls, accounts):
def create_data_map(cls, accounts, section):
data_map = defaultdict(list)
if not accounts.exists():
@@ -97,9 +106,10 @@ class AssetAccountHandler(BaseAccountHandler):
for tp, _accounts in account_type_map.items():
sheet_name = type_dict.get(tp, tp)
data = AccountSecretSerializer(_accounts, many=True).data
cls.handler_secret(data, section)
data_map.update(cls.add_rows(data, header_fields, sheet_name))
logger.info('\n\033[33m- 共备份 {} 条账号\033[0m'.format(accounts.count()))
print('\n\033[33m- 共备份 {} 条账号\033[0m'.format(accounts.count()))
return data_map
@@ -109,8 +119,8 @@ class AccountBackupHandler:
self.plan_name = self.execution.plan.name
self.is_frozen = False # 任务状态冻结标志
def create_excel(self):
logger.info(
def create_excel(self, section='complete'):
print(
'\n'
'\033[32m>>> 正在生成资产或应用相关备份信息文件\033[0m'
''
@@ -119,7 +129,7 @@ class AccountBackupHandler:
time_start = time.time()
files = []
accounts = self.execution.backup_accounts
data_map = AssetAccountHandler.create_data_map(accounts)
data_map = AssetAccountHandler.create_data_map(accounts, section)
if not data_map:
return files
@@ -133,14 +143,14 @@ class AccountBackupHandler:
wb.save(filename)
files.append(filename)
timedelta = round((time.time() - time_start), 2)
logger.info('步骤完成: 用时 {}s'.format(timedelta))
print('步骤完成: 用时 {}s'.format(timedelta))
return files
def send_backup_mail(self, files, recipients):
if not files:
return
recipients = User.objects.filter(id__in=list(recipients))
logger.info(
print(
'\n'
'\033[32m>>> 发送备份邮件\033[0m'
''
@@ -155,7 +165,7 @@ class AccountBackupHandler:
encrypt_and_compress_zip_file(attachment, password, files)
attachment_list = [attachment, ]
AccountBackupExecutionTaskMsg(plan_name, user).publish(attachment_list)
logger.info('邮件已发送至{}({})'.format(user, user.email))
print('邮件已发送至{}({})'.format(user, user.email))
for file in files:
os.remove(file)
@@ -163,33 +173,42 @@ class AccountBackupHandler:
self.execution.reason = reason[:1024]
self.execution.is_success = is_success
self.execution.save()
logger.info('已完成对任务状态的更新')
print('已完成对任务状态的更新')
def step_finished(self, is_success):
@staticmethod
def step_finished(is_success):
if is_success:
logger.info('任务执行成功')
print('任务执行成功')
else:
logger.error('任务执行失败')
print('任务执行失败')
def _run(self):
is_success = False
error = '-'
try:
recipients = self.execution.plan_snapshot.get('recipients')
if not recipients:
logger.info(
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)
except Exception as e:
self.is_frozen = True
logger.error('任务执行被异常中断')
logger.info('下面打印发生异常的 Traceback 信息 : ')
logger.error(e, exc_info=True)
print('任务执行被异常中断')
print('下面打印发生异常的 Traceback 信息 : ')
print(e)
error = str(e)
else:
is_success = True
@@ -199,15 +218,15 @@ class AccountBackupHandler:
self.step_finished(is_success)
def run(self):
logger.info('任务开始: {}'.format(local_now_display()))
print('任务开始: {}'.format(local_now_display()))
time_start = time.time()
try:
self._run()
except Exception as e:
logger.error('任务运行出现异常')
logger.error('下面显示异常 Traceback 信息: ')
logger.error(e, exc_info=True)
print('任务运行出现异常')
print('下面显示异常 Traceback 信息: ')
print(e)
finally:
logger.info('\n任务结束: {}'.format(local_now_display()))
print('\n任务结束: {}'.format(local_now_display()))
timedelta = round((time.time() - time_start), 2)
logger.info('用时: {}'.format(timedelta))
print('用时: {}'.format(timedelta))

View File

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

View File

@@ -2,9 +2,10 @@
gather_facts: no
vars:
ansible_connection: local
ansible_become: false
tasks:
- name: Test privileged account
- name: Test privileged account (paramiko)
ssh_ping:
login_host: "{{ jms_asset.address }}"
login_port: "{{ jms_asset.port }}"
@@ -12,9 +13,14 @@
login_password: "{{ jms_account.secret }}"
login_secret_type: "{{ jms_account.secret_type }}"
login_private_key_path: "{{ jms_account.private_key_path }}"
become: "{{ custom_become | default(False) }}"
become_method: "{{ custom_become_method | default('su') }}"
become_user: "{{ custom_become_user | default('') }}"
become_password: "{{ custom_become_password | default('') }}"
become_private_key_path: "{{ custom_become_private_key_path | default(None) }}"
register: ping_info
- name: Change asset password
- name: Change asset password (paramiko)
custom_command:
login_user: "{{ jms_account.username }}"
login_password: "{{ jms_account.secret }}"
@@ -22,6 +28,11 @@
login_port: "{{ jms_asset.port }}"
login_secret_type: "{{ jms_account.secret_type }}"
login_private_key_path: "{{ jms_account.private_key_path }}"
become: "{{ custom_become | default(False) }}"
become_method: "{{ custom_become_method | default('su') }}"
become_user: "{{ custom_become_user | default('') }}"
become_password: "{{ custom_become_password | default('') }}"
become_private_key_path: "{{ custom_become_private_key_path | default(None) }}"
name: "{{ account.username }}"
password: "{{ account.secret }}"
commands: "{{ params.commands }}"
@@ -30,9 +41,10 @@
when: ping_info is succeeded
register: change_info
- name: Verify password
- name: Verify password (paramiko)
ssh_ping:
login_user: "{{ account.username }}"
login_password: "{{ account.secret }}"
login_host: "{{ jms_asset.address }}"
login_port: "{{ jms_asset.port }}"
become: false

View File

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

View File

@@ -4,9 +4,58 @@ category: host
type:
- AIX
method: change_secret
params:
- name: sudo
type: str
label: 'Sudo'
default: '/bin/whoami'
help_text: "{{ 'Params sudo help text' | trans }}"
- name: shell
type: str
label: 'Shell'
default: '/bin/bash'
- name: home
type: str
label: "{{ 'Params home label' | trans }}"
default: ''
help_text: "{{ 'Params home help text' | trans }}"
- name: groups
type: str
label: "{{ 'Params groups label' | trans }}"
default: ''
help_text: "{{ 'Params groups help text' | trans }}"
i18n:
AIX account change secret:
zh: 使用 Ansible 模块 user 执行账号改密 (DES)
ja: Ansible user モジュールを使用してアカウントのパスワード変更 (DES)
en: Using Ansible module user to change account secret (DES)
zh: '使用 Ansible 模块 user 执行账号改密 (DES)'
ja: 'Ansible user モジュールを使用してアカウントのパスワード変更 (DES)'
en: 'Using Ansible module user to change account secret (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

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

View File

@@ -5,9 +5,59 @@ type:
- unix
- linux
method: change_secret
params:
- name: sudo
type: str
label: 'Sudo'
default: '/bin/whoami'
help_text: "{{ 'Params sudo help text' | trans }}"
- name: shell
type: str
label: 'Shell'
default: '/bin/bash'
help_text: ''
- name: home
type: str
label: "{{ 'Params home label' | trans }}"
default: ''
help_text: "{{ 'Params home help text' | trans }}"
- name: groups
type: str
label: "{{ 'Params groups label' | trans }}"
default: ''
help_text: "{{ 'Params groups help text' | trans }}"
i18n:
Posix account change secret:
zh: 使用 Ansible 模块 user 执行账号改密 (SHA512)
ja: Ansible user モジュールを使用して アカウントのパスワード変更 (SHA512)
en: Using Ansible module user to change account secret (SHA512)
zh: '使用 Ansible 模块 user 执行账号改密 (SHA512)'
ja: 'Ansible user モジュールを使用して アカウントのパスワード変更 (SHA512)'
en: 'Using Ansible module user to change account secret (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

@@ -8,17 +8,13 @@
# debug:
# msg: "Username: {{ account.username }}, Password: {{ account.secret }}"
- name: Get groups of a Windows user
ansible.windows.win_user:
name: "{{ jms_account.username }}"
register: user_info
- name: Change password
ansible.windows.win_user:
fullname: "{{ account.username}}"
name: "{{ account.username }}"
password: "{{ account.secret }}"
groups: "{{ user_info.groups[0].name }}"
password_never_expires: yes
groups: "{{ params.groups }}"
groups_action: add
update_password: always
ignore_errors: true

View File

@@ -5,9 +5,22 @@ method: change_secret
category: host
type:
- windows
params:
- name: groups
type: str
label: '用户组'
default: 'Users,Remote Desktop Users'
help_text: "{{ 'Params groups help text' | trans }}"
i18n:
Windows account change secret:
zh: 使用 Ansible 模块 win_user 执行 Windows 账号改密
ja: Ansible win_user モジュールを使用して Windows アカウントのパスワード変更
en: Using Ansible module win_user to change Windows account secret
zh: '使用 Ansible 模块 win_user 执行 Windows 账号改密'
ja: 'Ansible win_user モジュールを使用して Windows アカウントのパスワード変更'
en: 'Using Ansible module win_user to change Windows account secret'
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,10 +1,17 @@
- hosts: demo
gather_facts: no
tasks:
- name: Test privileged account
- name: "Test privileged {{ jms_account.username }} account"
ansible.builtin.ping:
- name: Push user
- name: "Check if {{ account.username }} user exists"
getent:
database: passwd
key: "{{ account.username }}"
register: user_info
ignore_errors: yes # 忽略错误如果用户不存在时不会导致playbook失败
- name: "Add {{ account.username }} user"
ansible.builtin.user:
name: "{{ account.username }}"
shell: "{{ params.shell }}"
@@ -12,22 +19,26 @@
groups: "{{ params.groups }}"
expires: -1
state: present
when: user_info.failed
- name: "Add {{ account.username }} group"
ansible.builtin.group:
name: "{{ account.username }}"
state: present
when: user_info.failed
- name: Add user groups
- name: "Add {{ account.username }} user to group"
ansible.builtin.user:
name: "{{ account.username }}"
groups: "{{ params.groups }}"
when: params.groups
when:
- user_info.failed
- params.groups
- name: Push user password
- name: "Change {{ account.username }} password"
ansible.builtin.user:
name: "{{ account.username }}"
password: "{{ account.secret | password_hash('sha512') }}"
password: "{{ account.secret | password_hash('des') }}"
update_password: always
ignore_errors: true
when: account.secret_type == "password"
@@ -41,14 +52,14 @@
- account.secret_type == "ssh_key"
- ssh_params.strategy == "set_jms"
- name: Push SSH key
- name: "Change {{ account.username }} SSH key"
ansible.builtin.authorized_key:
user: "{{ account.username }}"
key: "{{ account.secret }}"
exclusive: "{{ ssh_params.exclusive }}"
when: account.secret_type == "ssh_key"
- name: Set sudo setting
- name: "Set {{ account.username }} sudo setting"
ansible.builtin.lineinfile:
dest: /etc/sudoers
state: present
@@ -56,25 +67,31 @@
line: "{{ account.username + ' ALL=(ALL) NOPASSWD: ' + params.sudo }}"
validate: visudo -cf %s
when:
- user_info.failed
- params.sudo
- name: Refresh connection
ansible.builtin.meta: reset_connection
- name: Verify password
ansible.builtin.ping:
become: no
vars:
ansible_user: "{{ account.username }}"
ansible_password: "{{ account.secret }}"
ansible_become: no
- name: "Verify {{ account.username }} password (paramiko)"
ssh_ping:
login_user: "{{ account.username }}"
login_password: "{{ account.secret }}"
login_host: "{{ jms_asset.address }}"
login_port: "{{ jms_asset.port }}"
gateway_args: "{{ jms_asset.ansible_ssh_common_args | default('') }}"
become: false
when: account.secret_type == "password"
delegate_to: localhost
- name: Verify SSH key
ansible.builtin.ping:
become: no
vars:
ansible_user: "{{ account.username }}"
ansible_ssh_private_key_file: "{{ account.private_key_path }}"
ansible_become: no
- name: "Verify {{ account.username }} SSH KEY (paramiko)"
ssh_ping:
login_host: "{{ jms_asset.address }}"
login_port: "{{ jms_asset.port }}"
login_user: "{{ account.username }}"
login_private_key_path: "{{ account.private_key_path }}"
gateway_args: "{{ jms_asset.ansible_ssh_common_args | default('') }}"
become: false
when: account.secret_type == "ssh_key"
delegate_to: localhost

View File

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

View File

@@ -1,11 +1,12 @@
- hosts: custom
gather_facts: no
vars:
ansible_shell_type: sh
ansible_connection: local
tasks:
- name: Verify account
ssh_ping:
- name: Verify account (pyfreerdp)
rdp_ping:
login_host: "{{ jms_asset.address }}"
login_port: "{{ jms_asset.port }}"
login_user: "{{ account.username }}"

View File

@@ -2,9 +2,10 @@
gather_facts: no
vars:
ansible_connection: local
ansible_become: false
tasks:
- name: Verify account
- name: Verify account (paramiko)
ssh_ping:
login_host: "{{ jms_asset.address }}"
login_port: "{{ jms_asset.port }}"
@@ -12,3 +13,8 @@
login_password: "{{ account.secret }}"
login_secret_type: "{{ account.secret_type }}"
login_private_key_path: "{{ account.private_key_path }}"
become: "{{ custom_become | default(False) }}"
become_method: "{{ custom_become_method | default('su') }}"
become_user: "{{ custom_become_user | default('') }}"
become_password: "{{ custom_become_password | default('') }}"
become_private_key_path: "{{ custom_become_private_key_path | default(None) }}"

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,5 +1,5 @@
from django.db.models import TextChoices
from django.utils.translation import ugettext_lazy as _
from django.utils.translation import gettext_lazy as _
class SecretType(TextChoices):
@@ -7,12 +7,18 @@ class SecretType(TextChoices):
SSH_KEY = 'ssh_key', _('SSH key')
ACCESS_KEY = 'access_key', _('Access key')
TOKEN = 'token', _('Token')
API_KEY = 'api_key', _("API key")
class AliasAccount(TextChoices):
ALL = '@ALL', _('All')
INPUT = '@INPUT', _('Manual input')
USER = '@USER', _('Dynamic user')
ANON = '@ANON', _('Anonymous account')
@classmethod
def virtual_choices(cls):
return [(k, v) for k, v in cls.choices if k not in (cls.ALL,)]
class Source(TextChoices):

View File

@@ -1,5 +1,5 @@
from django.db import models
from django.utils.translation import ugettext_lazy as _
from django.utils.translation import gettext_lazy as _
from assets.const import Connectivity
from common.db.fields import TreeChoices

View File

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

View File

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

View File

@@ -1,12 +1,14 @@
# Generated by Django 3.2.14 on 2022-12-28 07:29
import uuid
import django.db.models.deletion
import simple_history.models
from django.conf import settings
from django.db import migrations, models
import common.db.encoder
import common.db.fields
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
import simple_history.models
import uuid
class Migration(migrations.Migration):
@@ -29,13 +31,16 @@ class Migration(migrations.Migration):
('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)),
('org_id',
models.CharField(blank=True, db_index=True, default='', max_length=36, verbose_name='Organization')),
('connectivity', models.CharField(choices=[('-', 'Unknown'), ('ok', 'Ok'), ('err', 'Error')], default='-', max_length=16, verbose_name='Connectivity')),
('connectivity',
models.CharField(choices=[('-', 'Unknown'), ('ok', 'Ok'), ('err', 'Error')], default='-',
max_length=16, verbose_name='Connectivity')),
('date_verified', models.DateTimeField(null=True, verbose_name='Date verified')),
('name', models.CharField(max_length=128, verbose_name='Name')),
('username', models.CharField(blank=True, db_index=True, max_length=128, verbose_name='Username')),
('secret_type', models.CharField(
choices=[('password', 'Password'), ('ssh_key', 'SSH key'), ('access_key', 'Access key'),
('token', 'Token')], default='password', max_length=16, verbose_name='Secret type')),
('token', 'Token'), ('api_key', 'API key')], default='password', max_length=16,
verbose_name='Secret type')),
('secret', common.db.fields.EncryptTextField(blank=True, null=True, verbose_name='Secret')),
('privileged', models.BooleanField(default=False, verbose_name='Privileged')),
('is_active', models.BooleanField(default=True, verbose_name='Is active')),
@@ -61,7 +66,8 @@ class Migration(migrations.Migration):
('id', models.UUIDField(db_index=True, default=uuid.uuid4)),
('secret_type', models.CharField(
choices=[('password', 'Password'), ('ssh_key', 'SSH key'), ('access_key', 'Access key'),
('token', 'Token')], default='password', max_length=16, verbose_name='Secret type')),
('token', 'Token'), ('api_key', 'API key')], default='password', max_length=16,
verbose_name='Secret type')),
('secret', common.db.fields.EncryptTextField(blank=True, null=True, verbose_name='Secret')),
('version', models.IntegerField(default=0, verbose_name='Version')),
('history_id', models.AutoField(primary_key=True, serialize=False)),
@@ -96,7 +102,8 @@ class Migration(migrations.Migration):
('username', models.CharField(blank=True, db_index=True, max_length=128, verbose_name='Username')),
('secret_type', models.CharField(
choices=[('password', 'Password'), ('ssh_key', 'SSH key'), ('access_key', 'Access key'),
('token', 'Token')], default='password', max_length=16, verbose_name='Secret type')),
('token', 'Token'), ('api_key', 'API key')], default='password', max_length=16,
verbose_name='Secret type')),
('secret', common.db.fields.EncryptTextField(blank=True, null=True, verbose_name='Secret')),
('privileged', models.BooleanField(default=False, verbose_name='Privileged')),
('is_active', models.BooleanField(default=True, verbose_name='Is active')),

View File

@@ -1,11 +1,13 @@
# Generated by Django 3.2.16 on 2022-12-30 08:08
import uuid
import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models
import common.db.encoder
import common.db.fields
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
import uuid
class Migration(migrations.Migration):
@@ -53,7 +55,8 @@ class Migration(migrations.Migration):
primary_key=True, serialize=False, to='assets.baseautomation')),
('secret_type', models.CharField(
choices=[('password', 'Password'), ('ssh_key', 'SSH key'), ('access_key', 'Access key'),
('token', 'Token')], default='password', max_length=16, verbose_name='Secret type')),
('token', 'Token'), ('api_key', 'API key')], default='password', max_length=16,
verbose_name='Secret type')),
('secret_strategy', models.CharField(choices=[('specific', 'Specific password'),
('random_one', 'All assets use the same random password'),
('random_all',
@@ -156,7 +159,8 @@ class Migration(migrations.Migration):
primary_key=True, serialize=False, to='assets.baseautomation')),
('secret_type', models.CharField(
choices=[('password', 'Password'), ('ssh_key', 'SSH key'), ('access_key', 'Access key'),
('token', 'Token')], default='password', max_length=16, verbose_name='Secret type')),
('token', 'Token'), ('api_key', 'API key')], default='password', max_length=16,
verbose_name='Secret type')),
('secret_strategy', models.CharField(choices=[('specific', 'Specific password'),
('random_one', 'All assets use the same random password'),
('random_all',

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -6,7 +6,7 @@ import uuid
from celery import current_task
from django.db import models
from django.db.models import F
from django.utils.translation import ugettext_lazy as _
from django.utils.translation import gettext_lazy as _
from common.const.choices import Trigger
from common.db.encoder import ModelJSONFieldEncoder
@@ -22,9 +22,13 @@ logger = get_logger(__file__)
class AccountBackupAutomation(PeriodTaskModelMixin, JMSOrgBaseModel):
types = models.JSONField(default=list)
recipients = models.ManyToManyField(
'users.User', related_name='recipient_escape_route_plans', blank=True,
verbose_name=_("Recipient")
recipients_part_one = models.ManyToManyField(
'users.User', related_name='recipient_part_one_plans', blank=True,
verbose_name=_("Recipient part one")
)
recipients_part_two = models.ManyToManyField(
'users.User', related_name='recipient_part_two_plans', blank=True,
verbose_name=_("Recipient part two")
)
def __str__(self):
@@ -52,9 +56,13 @@ class AccountBackupAutomation(PeriodTaskModelMixin, JMSOrgBaseModel):
'org_id': self.org_id,
'created_by': self.created_by,
'types': self.types,
'recipients': {
str(recipient.id): (str(recipient), bool(recipient.secret_key))
for recipient in self.recipients.all()
'recipients_part_one': {
str(user.id): (str(user), bool(user.secret_key))
for user in self.recipients_part_one.all()
},
'recipients_part_two': {
str(user.id): (str(user), bool(user.secret_key))
for user in self.recipients_part_two.all()
}
}
@@ -68,7 +76,7 @@ class AccountBackupAutomation(PeriodTaskModelMixin, JMSOrgBaseModel):
except AttributeError:
hid = str(uuid.uuid4())
execution = AccountBackupExecution.objects.create(
id=hid, plan=self, plan_snapshot=self.to_attr_json(), trigger=trigger
id=hid, plan=self, snapshot=self.to_attr_json(), trigger=trigger
)
return execution.start()
@@ -85,7 +93,7 @@ class AccountBackupExecution(OrgModelMixin):
timedelta = models.FloatField(
default=0.0, verbose_name=_('Time'), null=True
)
plan_snapshot = models.JSONField(
snapshot = models.JSONField(
encoder=ModelJSONFieldEncoder, default=dict,
blank=True, null=True, verbose_name=_('Account backup snapshot')
)
@@ -108,16 +116,9 @@ class AccountBackupExecution(OrgModelMixin):
@property
def types(self):
types = self.plan_snapshot.get('types')
types = self.snapshot.get('types')
return types
@property
def recipients(self):
recipients = self.plan_snapshot.get('recipients')
if not recipients:
return []
return recipients.values()
@lazyproperty
def backup_accounts(self):
from accounts.models import Account

View File

@@ -1,5 +1,5 @@
from django.db import models
from django.utils.translation import ugettext_lazy as _
from django.utils.translation import gettext_lazy as _
from accounts.const import (
AutomationTypes, SecretType, SecretStrategy, SSHKeyStrategy
@@ -86,7 +86,7 @@ class ChangeSecretRecord(JMSBaseModel):
asset = models.ForeignKey('assets.Asset', on_delete=models.CASCADE, null=True)
account = models.ForeignKey('accounts.Account', on_delete=models.CASCADE, null=True)
old_secret = fields.EncryptTextField(blank=True, null=True, verbose_name=_('Old secret'))
new_secret = fields.EncryptTextField(blank=True, null=True, verbose_name=_('Secret'))
new_secret = fields.EncryptTextField(blank=True, null=True, verbose_name=_('New secret'))
date_started = models.DateTimeField(blank=True, null=True, verbose_name=_('Date started'))
date_finished = models.DateTimeField(blank=True, null=True, verbose_name=_('Date finished'))
status = models.CharField(max_length=16, default='pending')

View File

@@ -1,6 +1,6 @@
from django.db import models
from django.db.models import Q
from django.utils.translation import ugettext_lazy as _
from django.utils.translation import gettext_lazy as _
from accounts.const import AutomationTypes, Source
from accounts.models import Account

View File

@@ -1,5 +1,5 @@
from django.db import models
from django.utils.translation import ugettext_lazy as _
from django.utils.translation import gettext_lazy as _
from accounts.const import AutomationTypes
from accounts.models import Account

View File

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

View File

@@ -6,36 +6,35 @@ from hashlib import md5
import sshpubkeys
from django.conf import settings
from django.db import models
from django.utils.translation import ugettext_lazy as _
from django.utils.translation import gettext_lazy as _
from accounts.const import SecretType
from common.db import fields
from common.utils import (
ssh_key_string_to_obj, ssh_key_gen, get_logger,
random_string, lazyproperty, parse_ssh_public_key_str, is_openssh_format_key
)
from accounts.models.mixins import VaultModelMixin, VaultManagerMixin, VaultQuerySetMixin
from orgs.mixins.models import JMSOrgBaseModel, OrgManager
logger = get_logger(__file__)
class BaseAccountQuerySet(models.QuerySet):
class BaseAccountQuerySet(VaultQuerySetMixin, models.QuerySet):
def active(self):
return self.filter(is_active=True)
class BaseAccountManager(OrgManager):
class BaseAccountManager(VaultManagerMixin, OrgManager):
def active(self):
return self.get_queryset().active()
class BaseAccount(JMSOrgBaseModel):
class BaseAccount(VaultModelMixin, JMSOrgBaseModel):
name = models.CharField(max_length=128, verbose_name=_("Name"))
username = models.CharField(max_length=128, blank=True, verbose_name=_('Username'), db_index=True)
secret_type = models.CharField(
max_length=16, choices=SecretType.choices, default=SecretType.PASSWORD, verbose_name=_('Secret type')
)
secret = fields.EncryptTextField(blank=True, null=True, verbose_name=_('Secret'))
privileged = models.BooleanField(verbose_name=_("Privileged"), default=False)
is_active = models.BooleanField(default=True, verbose_name=_("Is active"))

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,4 +1,4 @@
from django.utils.translation import ugettext_lazy as _
from django.utils.translation import gettext_lazy as _
from common.tasks import send_mail_attachment_async
from users.models import User

View File

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

View File

@@ -3,7 +3,7 @@ from copy import deepcopy
from django.db import IntegrityError
from django.db.models import Q
from django.utils.translation import ugettext_lazy as _
from django.utils.translation import gettext_lazy as _
from rest_framework import serializers
from rest_framework.generics import get_object_or_404
from rest_framework.validators import UniqueTogetherValidator
@@ -95,6 +95,8 @@ class AccountCreateUpdateSerializerMixin(serializers.Serializer):
field.name for field in template._meta.fields
if field.name not in ignore_fields
]
field_names = [name if name != '_secret' else 'secret' for name in field_names]
attrs = {}
for name in field_names:
value = getattr(template, name, None)
@@ -198,7 +200,6 @@ class AccountAssetSerializer(serializers.ModelSerializer):
class AccountSerializer(AccountCreateUpdateSerializerMixin, BaseAccountSerializer):
asset = AccountAssetSerializer(label=_('Asset'))
has_secret = serializers.BooleanField(label=_("Has secret"), read_only=True)
source = LabeledChoiceField(
choices=Source.choices, label=_("Source"), required=False,
allow_null=True, default=Source.LOCAL
@@ -233,6 +234,15 @@ class AccountSerializer(AccountCreateUpdateSerializerMixin, BaseAccountSerialize
return queryset
class AccountDetailSerializer(AccountSerializer):
has_secret = serializers.BooleanField(label=_("Has secret"), read_only=True)
class Meta(AccountSerializer.Meta):
model = Account
fields = AccountSerializer.Meta.fields + ['has_secret']
read_only_fields = AccountSerializer.Meta.read_only_fields + ['has_secret']
class AssetAccountBulkSerializerResultSerializer(serializers.Serializer):
asset = serializers.CharField(read_only=True, label=_('Asset'))
state = serializers.CharField(read_only=True, label=_('State'))

View File

@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
#
from django.utils.translation import ugettext_lazy as _
from django.utils.translation import gettext_lazy as _
from rest_framework import serializers
from accounts.models import AccountBackupAutomation, AccountBackupExecution
@@ -24,7 +24,7 @@ class AccountBackupSerializer(PeriodTaskSerializerMixin, BulkOrgResourceModelSer
]
fields = read_only_fields + [
'id', 'name', 'is_periodic', 'interval', 'crontab',
'comment', 'recipients', 'types'
'comment', 'types', 'recipients_part_one', 'recipients_part_two'
]
extra_kwargs = {
'name': {'required': True},
@@ -44,7 +44,7 @@ class AccountBackupPlanExecutionSerializer(serializers.ModelSerializer):
class Meta:
model = AccountBackupExecution
read_only_fields = [
'id', 'date_start', 'timedelta', 'plan_snapshot',
'trigger', 'reason', 'is_success', 'org_id', 'recipients'
'id', 'date_start', 'timedelta', 'snapshot',
'trigger', 'reason', 'is_success', 'org_id'
]
fields = read_only_fields + ['plan']

View File

@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
from django.utils.translation import ugettext_lazy as _
from django.utils.translation import gettext_lazy as _
from rest_framework import serializers
from accounts.const import SecretType
@@ -61,22 +61,23 @@ class AuthValidateMixin(serializers.Serializer):
class BaseAccountSerializer(AuthValidateMixin, BulkOrgResourceModelSerializer):
has_secret = serializers.BooleanField(label=_("Has secret"), read_only=True)
class Meta:
model = BaseAccount
fields_mini = ['id', 'name', 'username']
fields_small = fields_mini + [
'secret_type', 'secret', 'has_secret', 'passphrase',
'secret_type', 'secret', 'passphrase',
'privileged', 'is_active', 'spec_info',
]
fields_other = ['created_by', 'date_created', 'date_updated', 'comment']
fields = fields_small + fields_other
read_only_fields = [
'has_secret', 'spec_info',
'date_verified', 'created_by', 'date_created',
'spec_info', 'date_verified', 'created_by', 'date_created',
]
extra_kwargs = {
'spec_info': {'label': _('Spec info')},
'username': {'help_text': _("Tip: If no username is required for authentication, fill in `null`")}
'username': {'help_text': _(
"Tip: If no username is required for authentication, fill in `null`, "
"If AD account, like `username@domain`"
)},
}

View File

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

View File

@@ -1,4 +1,4 @@
from django.utils.translation import ugettext_lazy as _
from django.utils.translation import gettext_lazy as _
from rest_framework import serializers
from accounts.models import AccountTemplate, Account

View File

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

View File

@@ -1,4 +1,4 @@
from django.utils.translation import ugettext_lazy as _
from django.utils.translation import gettext_lazy as _
from rest_framework import serializers
from accounts.models import AutomationExecution
@@ -63,15 +63,17 @@ class AutomationExecutionSerializer(serializers.ModelSerializer):
@staticmethod
def get_snapshot(obj):
tp = obj.snapshot['type']
tp = obj.snapshot.get('type', '')
type_display = tp if not hasattr(AutomationTypes, tp) \
else getattr(AutomationTypes, tp).label
snapshot = {
'type': tp,
'name': obj.snapshot['name'],
'comment': obj.snapshot['comment'],
'accounts': obj.snapshot['accounts'],
'node_amount': len(obj.snapshot['nodes']),
'asset_amount': len(obj.snapshot['assets']),
'type_display': getattr(AutomationTypes, tp).label,
'name': obj.snapshot.get('name'),
'comment': obj.snapshot.get('comment'),
'accounts': obj.snapshot.get('accounts'),
'node_amount': len(obj.snapshot.get('nodes', [])),
'asset_amount': len(obj.snapshot.get('assets', [])),
'type_display': type_display,
}
return snapshot

View File

@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
#
from django.utils.translation import ugettext_lazy as _
from django.utils.translation import gettext_lazy as _
from rest_framework import serializers
from accounts.const import (
@@ -50,7 +50,7 @@ class ChangeSecretAutomationSerializer(AuthValidateMixin, BaseAutomationSerializ
read_only_fields = BaseAutomationSerializer.Meta.read_only_fields
fields = BaseAutomationSerializer.Meta.fields + read_only_fields + [
'secret_type', 'secret_strategy', 'secret', 'password_rules',
'ssh_key_change_strategy', 'passphrase', 'recipients',
'ssh_key_change_strategy', 'passphrase', 'recipients', 'params'
]
extra_kwargs = {**BaseAutomationSerializer.Meta.extra_kwargs, **{
'accounts': {'required': True},

View File

@@ -10,7 +10,7 @@ class PushAccountAutomationSerializer(ChangeSecretAutomationSerializer):
class Meta(ChangeSecretAutomationSerializer.Meta):
model = PushAccountAutomation
fields = ['params'] + [
fields = [
n for n in ChangeSecretAutomationSerializer.Meta.fields
if n not in ['recipients']
]

View File

@@ -1,8 +1,9 @@
from django.db.models.signals import pre_save
from django.db.models.signals import pre_save, post_save, post_delete
from django.dispatch import receiver
from accounts.backends import vault_client
from common.utils import get_logger
from .models import Account
from .models import Account, AccountTemplate
logger = get_logger(__name__)
@@ -13,3 +14,23 @@ def on_account_pre_save(sender, instance, **kwargs):
instance.version = 1
else:
instance.version = instance.history.count()
class VaultSignalHandler(object):
""" 处理 Vault 相关的信号 """
@staticmethod
def save_to_vault(sender, instance, created, **kwargs):
if created:
vault_client.create(instance)
else:
vault_client.update(instance)
@staticmethod
def delete_to_vault(sender, instance, **kwargs):
vault_client.delete(instance)
for model in (Account, AccountTemplate, Account.history.model):
post_save.connect(VaultSignalHandler.save_to_vault, sender=model)
post_delete.connect(VaultSignalHandler.delete_to_vault, sender=model)

View File

@@ -23,7 +23,7 @@ def task_activity_callback(self, pid, trigger, *args, **kwargs):
@shared_task(verbose_name=_('Execute account backup plan'), activity_callback=task_activity_callback)
def execute_account_backup_task(pid, trigger):
def execute_account_backup_task(pid, trigger, **kwargs):
from accounts.models import AccountBackupAutomation
with tmp_to_root_org():
plan = get_object_or_none(AccountBackupAutomation, pk=pid)

View File

@@ -1,5 +1,5 @@
from celery import shared_task
from django.utils.translation import gettext_noop, ugettext_lazy as _
from django.utils.translation import gettext_noop, gettext_lazy as _
from accounts.const import AutomationTypes
from accounts.tasks.common import quickstart_automation_by_snapshot

View File

@@ -0,0 +1,68 @@
from concurrent.futures import ThreadPoolExecutor, as_completed
from datetime import datetime
from celery import shared_task
from django.utils.translation import gettext_lazy as _
from accounts.backends import vault_client
from accounts.models import Account, AccountTemplate
from common.utils import get_logger
from orgs.utils import tmp_to_root_org
logger = get_logger(__name__)
def sync_instance(instance):
instance_desc = f'[{instance._meta.verbose_name}-{instance.id}-{instance}]'
if instance.secret_has_save_to_vault:
msg = f'\033[32m- 跳过同步: {instance_desc}, 原因: [已同步]'
return "skipped", msg
try:
vault_client.create(instance)
except Exception as e:
msg = f'\033[31m- 同步失败: {instance_desc}, 原因: [{e}]'
return "failed", msg
else:
msg = f'\033[32m- 同步成功: {instance_desc}'
return "succeeded", msg
@shared_task(verbose_name=_('Sync secret to vault'))
def sync_secret_to_vault():
if not vault_client.enabled:
# 这里不能判断 settings.VAULT_ENABLED, 必须判断当前 vault_client 的类型
print('\033[35m>>> 当前 Vault 功能未开启, 不需要同步')
return
failed, skipped, succeeded = 0, 0, 0
to_sync_models = [Account, AccountTemplate, Account.history.model]
print(f'\033[33m>>> 开始同步密钥数据到 Vault ({datetime.now().strftime("%Y-%m-%d %H:%M:%S")})')
with tmp_to_root_org():
instances = []
for model in to_sync_models:
instances += list(model.objects.all())
with ThreadPoolExecutor(max_workers=10) as executor:
tasks = [executor.submit(sync_instance, instance) for instance in instances]
for future in as_completed(tasks):
status, msg = future.result()
print(msg)
if status == "succeeded":
succeeded += 1
elif status == "failed":
failed += 1
elif status == "skipped":
skipped += 1
total = succeeded + failed + skipped
print(
f'\033[33m>>> 同步完成: {model.__module__}, '
f'共计: {total}, '
f'成功: {succeeded}, '
f'失败: {failed}, '
f'跳过: {skipped}'
)
print(f'\033[33m>>> 全部同步完成 ({datetime.now().strftime("%Y-%m-%d %H:%M:%S")})')
print('\033[0m')

View File

@@ -9,6 +9,7 @@ app_name = 'accounts'
router = BulkRouter()
router.register(r'accounts', api.AccountViewSet, 'account')
router.register(r'virtual-accounts', api.VirtualAccountViewSet, 'virtual-account')
router.register(r'gathered-accounts', api.GatheredAccountViewSet, 'gathered-account')
router.register(r'account-secrets', api.AccountSecretsViewSet, 'account-secret')
router.register(r'account-templates', api.AccountTemplateViewSet, 'account-template')
@@ -39,7 +40,7 @@ urlpatterns = [
path('push-account/<uuid:pk>/asset/remove/', api.PushAccountRemoveAssetApi.as_view(),
name='push-account-remove-asset'),
path('push-accountt/<uuid:pk>/asset/add/', api.PushAccountAddAssetApi.as_view(), name='push-account-add-asset'),
path('push-account/<uuid:pk>/asset/add/', api.PushAccountAddAssetApi.as_view(), name='push-account-add-asset'),
path('push-account/<uuid:pk>/nodes/', api.PushAccountNodeAddRemoveApi.as_view(),
name='push-account-add-or-remove-node'),
path('push-account/<uuid:pk>/assets/', api.PushAccountAssetsListApi.as_view(), name='push-account-assets'),

View File

@@ -1,10 +1,8 @@
from django.utils.translation import ugettext_lazy as _
from django.utils.translation import gettext_lazy as _
from rest_framework import serializers
from accounts.const import (
SecretType, DEFAULT_PASSWORD_RULES
)
from common.utils import gen_key_pair, random_string
from accounts.const import SecretType, DEFAULT_PASSWORD_RULES
from common.utils import ssh_key_gen, random_string
from common.utils import validate_ssh_private_key, parse_ssh_private_key_str
@@ -16,7 +14,7 @@ class SecretGenerator:
@staticmethod
def generate_ssh_key():
private_key, public_key = gen_key_pair()
private_key, public_key = ssh_key_gen()
return private_key
def generate_password(self):
@@ -41,6 +39,8 @@ def validate_password_for_ansible(password):
# 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 `'` "))

View File

@@ -1,5 +1,5 @@
from django.apps import AppConfig
from django.utils.translation import ugettext_lazy as _
from django.utils.translation import gettext_lazy as _
class AclsConfig(AppConfig):

9
apps/acls/const.py Normal file
View File

@@ -0,0 +1,9 @@
from django.db import models
from django.utils.translation import gettext_lazy as _
class ActionChoices(models.TextChoices):
reject = 'reject', _('Reject')
accept = 'accept', _('Accept')
review = 'review', _('Review')
warning = 'warning', _('Warning')

View File

@@ -1,5 +1,4 @@
# Generated by Django 3.2.17 on 2023-06-06 10:57
from collections import defaultdict
from django.db import migrations, models
@@ -8,17 +7,20 @@ import common.db.fields
def migrate_users_login_acls(apps, schema_editor):
login_acl_model = apps.get_model('acls', 'LoginACL')
name_used = defaultdict(int)
for login_acl in login_acl_model.objects.all():
name = login_acl.name
if name_used[name] > 0:
login_acl.name += "_{}".format(name_used[name])
name_used[name] += 1
name_used = []
login_acls = []
for login_acl in login_acl_model.objects.all().select_related('user'):
name = '{}_{}'.format(login_acl.name, login_acl.user.username)
if name.lower() in name_used:
name += '_{}'.format(str(login_acl.user_id)[:4])
name_used.append(name.lower())
login_acl.name = name
login_acl.users = {
"type": "ids", "ids": [str(login_acl.user_id)]
}
login_acl.save()
login_acls.append(login_acl)
login_acl_model.objects.bulk_update(login_acls, ['name', 'users'])
class Migration(migrations.Migration):

View File

@@ -7,6 +7,7 @@ from common.db.models import JMSBaseModel
from common.utils import contains_ip
from common.utils.time_period import contains_time_period
from orgs.mixins.models import OrgModelMixin, OrgManager
from ..const import ActionChoices
__all__ = [
'BaseACL', 'UserBaseACL', 'UserAssetAccountBaseACL',
@@ -16,12 +17,6 @@ from orgs.utils import tmp_to_root_org
from orgs.utils import tmp_to_org
class ActionChoices(models.TextChoices):
reject = 'reject', _('Reject')
accept = 'accept', _('Accept')
review = 'review', _('Review')
class BaseACLQuerySet(models.QuerySet):
def active(self):
return self.filter(is_active=True)

View File

@@ -3,7 +3,7 @@
import re
from django.db import models
from django.utils.translation import ugettext_lazy as _
from django.utils.translation import gettext_lazy as _
from common.utils import lazyproperty, get_logger
from orgs.mixins.models import JMSOrgBaseModel

View File

@@ -1,5 +1,5 @@
from django.db import models
from django.utils.translation import ugettext_lazy as _
from django.utils.translation import gettext_lazy as _
from common.utils import get_request_ip, get_ip_city
from common.utils.timezone import local_now_display

View File

@@ -1,5 +1,5 @@
from django.db import models
from django.utils.translation import ugettext_lazy as _
from django.utils.translation import gettext_lazy as _
from .base import UserAssetAccountBaseACL

View File

@@ -1,10 +1,11 @@
from django.utils.translation import ugettext_lazy as _
from django.utils.translation import gettext_lazy as _
from rest_framework import serializers
from acls.models.base import ActionChoices, BaseACL
from acls.models.base import BaseACL
from common.serializers.fields import JSONManyToManyField, LabeledChoiceField
from jumpserver.utils import has_valid_xpack_license
from orgs.models import Organization
from ..const import ActionChoices
common_help_text = _(
"With * indicating a match all. "
@@ -60,18 +61,21 @@ class ActionAclSerializer(serializers.Serializer):
super().__init__(*args, **kwargs)
self.set_action_choices()
def set_action_choices(self):
action = self.fields.get("action")
if not action:
return
choices = action.choices
if not has_valid_xpack_license():
choices.pop(ActionChoices.review, None)
action._choices = choices
class BaserACLSerializer(ActionAclSerializer, serializers.Serializer):
class Meta:
action_choices_exclude = [ActionChoices.warning]
def set_action_choices(self):
field_action = self.fields.get("action")
if not field_action:
return
if not has_valid_xpack_license():
field_action._choices.pop(ActionChoices.review, None)
for choice in self.Meta.action_choices_exclude:
field_action._choices.pop(choice, None)
class BaseACLSerializer(ActionAclSerializer, serializers.Serializer):
class Meta(ActionAclSerializer.Meta):
model = BaseACL
fields_mini = ["id", "name"]
fields_small = fields_mini + [
@@ -84,6 +88,7 @@ class BaserACLSerializer(ActionAclSerializer, serializers.Serializer):
extra_kwargs = {
"priority": {"default": 50},
"is_active": {"default": True},
'reviewers': {'label': _('Recipients')},
}
def validate_reviewers(self, reviewers):
@@ -107,16 +112,16 @@ class BaserACLSerializer(ActionAclSerializer, serializers.Serializer):
return valid_reviewers
class BaserUserACLSerializer(BaserACLSerializer):
class BaseUserACLSerializer(BaseACLSerializer):
users = JSONManyToManyField(label=_('User'))
class Meta(BaserACLSerializer.Meta):
fields = BaserACLSerializer.Meta.fields + ['users']
class Meta(BaseACLSerializer.Meta):
fields = BaseACLSerializer.Meta.fields + ['users']
class BaseUserAssetAccountACLSerializer(BaserUserACLSerializer):
class BaseUserAssetAccountACLSerializer(BaseUserACLSerializer):
assets = JSONManyToManyField(label=_('Asset'))
accounts = serializers.ListField(label=_('Account'))
class Meta(BaserUserACLSerializer.Meta):
fields = BaserUserACLSerializer.Meta.fields + ['assets', 'accounts']
class Meta(BaseUserACLSerializer.Meta):
fields = BaseUserACLSerializer.Meta.fields + ['assets', 'accounts']

View File

@@ -1,4 +1,4 @@
from django.utils.translation import ugettext_lazy as _
from django.utils.translation import gettext_lazy as _
from rest_framework import serializers
from acls.models import CommandGroup, CommandFilterACL
@@ -31,6 +31,8 @@ class CommandFilterACLSerializer(BaseSerializer, BulkOrgResourceModelSerializer)
class Meta(BaseSerializer.Meta):
model = CommandFilterACL
fields = BaseSerializer.Meta.fields + ['command_groups']
# 默认都支持所有的 actions
action_choices_exclude = []
class CommandReviewSerializer(serializers.Serializer):

View File

@@ -1,6 +1,7 @@
from orgs.mixins.serializers import BulkOrgResourceModelSerializer
from .base import BaseUserAssetAccountACLSerializer as BaseSerializer
from ..models import ConnectMethodACL
from ..const import ActionChoices
__all__ = ["ConnectMethodACLSerializer"]
@@ -12,12 +13,6 @@ class ConnectMethodACLSerializer(BaseSerializer, BulkOrgResourceModelSerializer)
i for i in BaseSerializer.Meta.fields + ['connect_methods']
if i not in ['assets', 'accounts']
]
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
field_action = self.fields.get('action')
if not field_action:
return
# 仅支持拒绝
for k in ['review', 'accept']:
field_action._choices.pop(k, None)
action_choices_exclude = BaseSerializer.Meta.action_choices_exclude + [
ActionChoices.review, ActionChoices.accept
]

View File

@@ -1,8 +1,8 @@
from django.utils.translation import ugettext as _
from django.utils.translation import gettext as _
from common.serializers import MethodSerializer
from orgs.mixins.serializers import BulkOrgResourceModelSerializer
from .base import BaserUserACLSerializer
from .base import BaseUserACLSerializer
from .rules import RuleSerializer
from ..models import LoginACL
@@ -11,12 +11,12 @@ __all__ = ["LoginACLSerializer"]
common_help_text = _("With * indicating a match all. ")
class LoginACLSerializer(BaserUserACLSerializer, BulkOrgResourceModelSerializer):
class LoginACLSerializer(BaseUserACLSerializer, BulkOrgResourceModelSerializer):
rules = MethodSerializer(label=_('Rule'))
class Meta(BaserUserACLSerializer.Meta):
class Meta(BaseUserACLSerializer.Meta):
model = LoginACL
fields = BaserUserACLSerializer.Meta.fields + ['rules', ]
fields = BaseUserACLSerializer.Meta.fields + ['rules', ]
def get_rules_serializer(self):
return RuleSerializer()

View File

@@ -1,7 +1,7 @@
# coding: utf-8
#
from django.utils.translation import gettext_lazy as _
from rest_framework import serializers
from django.utils.translation import ugettext_lazy as _
from common.utils import get_logger
from common.utils.ip import is_ip_address, is_ip_network, is_ip_segment

View File

@@ -1,7 +1,7 @@
from __future__ import unicode_literals
from django.utils.translation import ugettext_lazy as _
from django.apps import AppConfig
from django.utils.translation import gettext_lazy as _
class ApplicationsConfig(AppConfig):
@@ -9,5 +9,4 @@ class ApplicationsConfig(AppConfig):
verbose_name = _('Applications')
def ready(self):
from . import signal_handlers
super().ready()

View File

@@ -2,7 +2,6 @@
from django.db import migrations, models
import django.db.models.deletion
import django_mysql.models
import uuid
@@ -127,7 +126,7 @@ class Migration(migrations.Migration):
('name', models.CharField(max_length=128, verbose_name='Name')),
('category', models.CharField(choices=[('db', 'Database'), ('remote_app', 'Remote app'), ('cloud', 'Cloud')], max_length=16, verbose_name='Category')),
('type', models.CharField(choices=[('mysql', 'MySQL'), ('oracle', 'Oracle'), ('postgresql', 'PostgreSQL'), ('mariadb', 'MariaDB'), ('chrome', 'Chrome'), ('mysql_workbench', 'MySQL Workbench'), ('vmware_client', 'vSphere Client'), ('custom', 'Custom'), ('k8s', 'Kubernetes')], max_length=16, verbose_name='Type')),
('attrs', django_mysql.models.JSONField(default=dict)),
('attrs', models.JSONField(default=dict)),
('comment', models.TextField(blank=True, default='', max_length=128, verbose_name='Comment')),
('domain', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='applications', to='assets.Domain', verbose_name='Domain')),
],

View File

@@ -1,5 +1,5 @@
from django.db import models
from django.utils.translation import ugettext_lazy as _
from django.utils.translation import gettext_lazy as _
from common.db.models import JMSBaseModel
from orgs.mixins.models import OrgModelMixin

View File

@@ -3,6 +3,7 @@ from .cloud import *
from .custom import *
from .database import *
from .device import *
from .gpt import *
from .host import *
from .permission import *
from .web import *

View File

@@ -82,7 +82,7 @@ class AssetFilterSet(BaseFilterSet):
@staticmethod
def filter_protocols(queryset, name, value):
value = value.split(',')
return queryset.filter(protocols__name__in=value)
return queryset.filter(protocols__name__in=value).distinct()
@staticmethod
def filter_labels(queryset, name, value):
@@ -91,7 +91,7 @@ class AssetFilterSet(BaseFilterSet):
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)
queryset = queryset.filter(q).distinct()
return queryset
@@ -121,6 +121,14 @@ class AssetViewSet(SuggestionMixin, NodeFilterMixin, OrgBulkModelViewSet):
NodeFilterBackend, AttrRulesFilterBackend
]
def get_queryset(self):
queryset = super().get_queryset() \
.prefetch_related('nodes', 'protocols') \
.select_related('platform', 'domain')
if queryset.model is not Asset:
queryset = queryset.select_related('asset_ptr')
return queryset
def get_serializer_class(self):
cls = super().get_serializer_class()
if self.action == "retrieve":

View File

@@ -0,0 +1,16 @@
from assets.models import GPT, Asset
from assets.serializers import GPTSerializer
from .asset import AssetViewSet
__all__ = ['GPTViewSet']
class GPTViewSet(AssetViewSet):
model = GPT
perm_model = Asset
def get_serializer_classes(self):
serializer_classes = super().get_serializer_classes()
serializer_classes['default'] = GPTSerializer
return serializer_classes

View File

@@ -1,11 +1,11 @@
from rest_framework.mixins import ListModelMixin
from rest_framework.decorators import action
from rest_framework.mixins import ListModelMixin
from rest_framework.response import Response
from assets.const import AllTypes
from assets.serializers import CategorySerializer, TypeSerializer
from common.api import JMSGenericViewSet
from common.permissions import IsValidUser
from assets.serializers import CategorySerializer, TypeSerializer
from assets.const import AllTypes
__all__ = ['CategoryViewSet']
@@ -32,4 +32,3 @@ class CategoryViewSet(ListModelMixin, JMSGenericViewSet):
tp = request.query_params.get('type')
constraints = AllTypes.get_constraints(category, tp)
return Response(constraints)

View File

@@ -1,5 +1,5 @@
# ~*~ coding: utf-8 ~*~
from django.utils.translation import ugettext as _
from django.utils.translation import gettext as _
from django.views.generic.detail import SingleObjectMixin
from rest_framework.serializers import ValidationError
from rest_framework.views import APIView, Response
@@ -26,6 +26,9 @@ class DomainViewSet(OrgBulkModelViewSet):
return serializers.DomainWithGatewaySerializer
return serializers.DomainSerializer
def get_queryset(self):
return super().get_queryset().prefetch_related('assets')
class GatewayViewSet(HostViewSet):
perm_model = Gateway

View File

@@ -38,5 +38,6 @@ class LabelViewSet(OrgBulkModelViewSet):
return super().list(request, *args, **kwargs)
def get_queryset(self):
self.queryset = Label.objects.annotate(asset_count=Count("assets"))
self.queryset = Label.objects.prefetch_related(
'assets').annotate(asset_count=Count("assets"))
return self.queryset

View File

@@ -2,7 +2,7 @@ from typing import List
from rest_framework.request import Request
from assets.models import Node, PlatformProtocol, Protocol
from assets.models import Node, Protocol
from assets.utils import get_node_from_request, is_query_node_all_assets
from common.utils import lazyproperty, timeit
@@ -42,7 +42,7 @@ class SerializeToTreeNodeMixin:
'name': _name(node),
'title': _name(node),
'pId': node.parent_key,
'isParent': True,
'isParent': node.assets_amount > 0,
'open': _open(node),
'meta': {
'data': {
@@ -70,25 +70,18 @@ class SerializeToTreeNodeMixin:
@timeit
def serialize_assets(self, assets, node_key=None, pid=None):
sftp_enabled_platform = PlatformProtocol.objects \
.filter(name='ssh', setting__sftp_enabled=True) \
.values_list('platform', flat=True) \
.distinct()
if node_key is None:
get_pid = lambda asset: getattr(asset, 'parent_key', '')
else:
get_pid = lambda asset: node_key
ssh_asset_ids = [
str(i) for i in
Protocol.objects.filter(name='ssh').values_list('asset_id', flat=True)
]
sftp_asset_ids = Protocol.objects.filter(name='sftp') \
.values_list('asset_id', flat=True)
sftp_asset_ids = list(sftp_asset_ids)
data = [
{
'id': str(asset.id),
'name': asset.name,
'title':
f'{asset.address}\n{asset.comment}'
if asset.comment else asset.address,
'title': f'{asset.address}\n{asset.comment}',
'pId': pid or get_pid(asset),
'isParent': False,
'open': False,
@@ -99,8 +92,7 @@ class SerializeToTreeNodeMixin:
'data': {
'platform_type': asset.platform.type,
'org_name': asset.org_name,
'sftp': (asset.platform_id in sftp_enabled_platform) \
and (str(asset.id) in ssh_asset_ids),
'sftp': asset.id in sftp_asset_ids,
'name': asset.name,
'address': asset.address
},

View File

@@ -3,7 +3,7 @@ from collections import namedtuple, defaultdict
from functools import partial
from django.db.models.signals import m2m_changed
from django.utils.translation import ugettext_lazy as _
from django.utils.translation import gettext_lazy as _
from rest_framework import status
from rest_framework.decorators import action
from rest_framework.generics import get_object_or_404

View File

@@ -4,20 +4,20 @@ from rest_framework.decorators import action
from rest_framework.response import Response
from assets.const import AllTypes
from assets.models import Platform, Node, Asset
from assets.serializers import PlatformSerializer
from assets.models import Platform, Node, Asset, PlatformProtocol
from assets.serializers import PlatformSerializer, PlatformProtocolSerializer
from common.api import JMSModelViewSet
from common.permissions import IsValidUser
from common.serializers import GroupedChoiceSerializer
__all__ = ['AssetPlatformViewSet', 'PlatformAutomationMethodsApi']
__all__ = ['AssetPlatformViewSet', 'PlatformAutomationMethodsApi', 'PlatformProtocolViewSet']
class AssetPlatformViewSet(JMSModelViewSet):
queryset = Platform.objects.all()
serializer_classes = {
'default': PlatformSerializer,
'categories': GroupedChoiceSerializer
'categories': GroupedChoiceSerializer,
}
filterset_fields = ['name', 'category', 'type']
search_fields = ['name']
@@ -25,7 +25,7 @@ class AssetPlatformViewSet(JMSModelViewSet):
'categories': 'assets.view_platform',
'type_constraints': 'assets.view_platform',
'ops_methods': 'assets.view_platform',
'filter_nodes_assets': 'assets.view_platform'
'filter_nodes_assets': 'assets.view_platform',
}
def get_queryset(self):
@@ -61,6 +61,15 @@ class AssetPlatformViewSet(JMSModelViewSet):
return Response(serializer.data)
class PlatformProtocolViewSet(JMSModelViewSet):
queryset = PlatformProtocol.objects.all()
serializer_class = PlatformProtocolSerializer
filterset_fields = ['name', 'platform__name']
rbac_perms = {
'*': 'assets.add_platform'
}
class PlatformAutomationMethodsApi(generics.ListAPIView):
permission_classes = (IsValidUser,)

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