Compare commits

...

327 Commits

Author SHA1 Message Date
ibuler
3384c206cb fix: wechat or phone decrypt err 2025-09-04 11:58:58 +08:00
Bai
6683af3e74 fix: temp token backend 2025-09-03 18:10:05 +08:00
Bai
8ebcfb5b6f perf: aks encrypt 2025-09-03 11:25:59 +08:00
ibuler
000bb100cd perf: try to decrypt then origin value 2025-08-29 11:00:19 +08:00
wangruidong
36f3071eed fix: Ensure command arguments are safely quoted in safe_run_cmd 2025-08-28 14:14:36 +08:00
老广
15259fc10c Update base.py 2025-08-21 22:05:51 +08:00
Bryan
f31994fdcd Merge pull request #15899 from jumpserver/dev 2025-08-21 19:03:18 +08:00
feng
b65ff0d84c perf: Translate 2025-08-21 18:52:38 +08:00
wangruidong
30d781dd12 fix: Export PDF wait for render done 2025-08-21 18:44:09 +08:00
wangruidong
9551cd4da9 fix: Export PDF with org id 2025-08-21 17:56:26 +08:00
mikebofs
87b456c941 perf: change default width 2025-08-21 16:19:56 +08:00
mikebofs
d4d5224c17 perf: support export dashboard 2025-08-21 16:19:56 +08:00
wangruidong
dabb30d90a perf: Change report name 2025-08-21 16:19:25 +08:00
feng
82192d38e1 perf: Translate 2025-08-21 15:32:04 +08:00
feng
571d2b4575 perf: Custom platform translate 2025-08-21 14:51:38 +08:00
Eric
ea64313c4e perf: fix conenct token platform fields 2025-08-21 14:03:15 +08:00
Bai
8764cdb733 feat: support protocols search 2025-08-21 11:49:18 +08:00
feng
980394efed perf: Transalte 2025-08-21 11:31:29 +08:00
wangruidong
2c94f10d64 fix: The approval setting org admin, and the approver is blank 2025-08-21 10:25:10 +08:00
wangruidong
e1c9f5180d perf: Export pdf using days parameter 2025-08-21 10:23:00 +08:00
wangruidong
3f1d7fa230 perf: Pdf file i18n 2025-08-21 10:23:00 +08:00
wangruidong
44bcd6e399 fix: Send email pdf deps 2025-08-21 10:23:00 +08:00
feng
5f87d98c31 perf: Translate 2025-08-20 18:17:46 +08:00
feng
540becdcbe perf: org admin view settings 2025-08-20 17:11:27 +08:00
feng
6929c4968e perf: Check api 2025-08-20 11:16:46 +08:00
Aaron3S
63b213d3a8 feat: add translate 2025-08-19 19:19:23 +08:00
feng
64fe7a55ec perf: Mongodb ping 2025-08-19 19:08:52 +08:00
feng
27829e09ef perf: Translate 2025-08-19 18:57:23 +08:00
jiangweidong
1bfc7daef6 perf: Avoid Oracle password modification SQL injection risks 2025-08-19 18:55:46 +08:00
Bai
9422aebc5e perf: email i18n 2025-08-19 18:49:25 +08:00
wangruidong
8c0cd20b48 fix: Disable passkey mfa in safe mode 2025-08-19 18:21:33 +08:00
Bai
0c612648a0 perf: email protocol rename 2025-08-19 17:04:32 +08:00
feng
36e01a316c perf: Regular command groups can be filled in with new lines 2025-08-19 15:51:39 +08:00
feng
e1b96e01eb perf: Translate 2025-08-19 15:05:13 +08:00
wangruidong
144f4b4466 fix: Virtual apps manifest i18n 2025-08-19 14:54:03 +08:00
wangruidong
8e007004c2 perf: Translate label for groups parameter 2025-08-19 14:51:52 +08:00
github-actions[bot]
c14f740209 perf: Update Dockerfile with new base image tag 2025-08-19 14:50:45 +08:00
Eric
13a85f062c perf: fix uv pip resolution 2025-08-19 14:50:45 +08:00
fit2bot
7f9d027bd3 perf: Send command translate (#15820)
Co-authored-by: wangruidong <940853815@qq.com>
Co-authored-by: Bryan <jiangjie.bai@fit2cloud.com>
2025-08-18 19:14:48 +08:00
wangruidong
c037ce1c29 perf: Send report email 2025-08-18 19:12:29 +08:00
wangruidong
ee7c6b4708 fix: Init db error 2025-08-18 19:11:59 +08:00
feng
d0e625e322 perf: Translate 2025-08-18 19:08:34 +08:00
feng
c65794a99d perf: KOKO translate 2025-08-18 18:39:42 +08:00
Eric
1e4bca6e24 perf: add lion i18n 2025-08-18 18:28:22 +08:00
feng
c1c5025fbb perf: Account automation report 2025-08-18 17:40:49 +08:00
Eric
96020fa6b4 perf: add lion i18n 2025-08-18 11:42:33 +08:00
wangruidong
5ad6f87a9e fix: Docker build error 2025-08-18 10:53:33 +08:00
feng
9b0c73c9f9 perf: translate 2025-08-15 18:57:46 +08:00
wangruidong
c029714ffd fix: Export pdf failed 2025-08-15 17:42:48 +08:00
wangruidong
c1e8a1b561 fix: Install export pdf deps 2025-08-15 17:42:48 +08:00
feng
21126de2c1 perf: get_cpu_model_count 2025-08-15 16:45:39 +08:00
feng
7d06819bbe perf: foot_js 2025-08-15 16:35:43 +08:00
Eric
92b20fe2ef perf: add lion i18n 2025-08-15 16:24:18 +08:00
feng
4326d35065 perf: User report 2025-08-14 18:55:15 +08:00
feng
4810eae725 perf: group_stats 2025-08-14 16:09:43 +08:00
fit2bot
24f7946b7b perf: change some field to encrypt field (#15842)
* perf: conn token add remote addr

* perf: change some field to encrypt field

---------

Co-authored-by: ibuler <ibuler@qq.com>
2025-08-14 15:05:18 +08:00
王晓阳
4b9c4a550e feat: support vastbase 2025-08-14 14:31:31 +08:00
feng
d3ec23ba85 perf: group_stats 2025-08-14 11:45:36 +08:00
feng
e3c33bca32 perf: User report 2025-08-14 11:12:58 +08:00
feng
0fb7e84678 perf: user asset account report 2025-08-13 18:51:08 +08:00
feng
ab30bfb2d2 perf: mysql pg playbook 2025-08-13 15:15:53 +08:00
feng
d9d034488f fix: report 2025-08-12 19:19:00 +08:00
feng
24bd7b7e1a fix rbac pam 2025-08-12 14:48:16 +08:00
wangruidong
7fb5fd3956 fix: set ansible_timeout for account connectivity tasks 2025-08-11 10:37:23 +08:00
feng
9c621f5ff5 perf: rbac pam 2025-08-08 13:52:38 +08:00
feng
ac8998b9ee perf: Account risk delete normal account 2025-08-06 17:02:53 +08:00
wangruidong
b258537890 fix: Fallback to browser language if user language is not set 2025-08-06 14:15:30 +08:00
fit2bot
b38d83c578 feat: report charts (#15630)
* perf: initial

* perf: basic finished

* perf: depend

* perf: Update Dockerfile with new base image tag

* perf: Add user report api

* perf: Update Dockerfile with new base image tag

* perf: Use user report api

* perf: Update Dockerfile with new base image tag

* perf: user login report

* perf: Update Dockerfile with new base image tag

* perf: user change password

* perf: change password dashboard

* perf: Update Dockerfile with new base image tag

* perf: Translate

* perf: asset api

* perf: asset activity

* perf: Asset report

* perf: add charts_map

* perf: account report

* perf: Translate

* perf: account automation

* perf: Account automation

* perf: title

* perf: Update Dockerfile with new base image tag

---------

Co-authored-by: ibuler <ibuler@qq.com>
Co-authored-by: feng <1304903146@qq.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: wangruidong <940853815@qq.com>
Co-authored-by: feng626 <57284900+feng626@users.noreply.github.com>
2025-08-06 14:05:38 +08:00
feng
257f290d18 perf: Translate 2025-08-06 11:33:52 +08:00
wangruidong
d185be2180 perf: Optimize redis connection number 2025-08-04 18:53:34 +08:00
ibuler
4e33b5b478 perf: some risk example file path 2025-08-01 10:35:15 +08:00
wangruidong
1406437d4e fix: Failed to switch languages 2025-08-01 10:24:17 +08:00
feng
e46aa95980 perf: check_asset_permission_will_expired filter is_active=True 2025-08-01 10:18:52 +08:00
Eric
c619a35a04 perf: update lion i18n tip 2025-08-01 10:18:12 +08:00
wangruidong
29f10bf10e perf: ES connect error detail 2025-07-31 17:15:55 +08:00
wangruidong
a822905ae7 fix: When the cas user doesn't exist, you will be prompted with an error when logging in. 2025-07-31 17:15:18 +08:00
zhaojisen
dc5a743f4f revert style 2025-07-30 14:27:52 +08:00
zhaojisen
1de8781704 Fixed: Fix the issue with the login page footer 2025-07-30 14:27:52 +08:00
wangruidong
f3d9f4c446 fix: Failed to switch languages 2025-07-29 16:40:30 +08:00
jiangweidong
6b5d5c15ae feat: Add an embedded form to ChatAI 2025-07-29 14:15:01 +08:00
feng
1074a0df19 perf: MFA coce reuse 2025-07-29 11:00:39 +08:00
Eric
04dca794dd fix: fix chrome_app password_manager dialog 2025-07-29 10:21:46 +08:00
ibuler
14e0396508 perf: change ip db path 2025-07-29 10:20:37 +08:00
wangruidong
835eb2e3d0 perf: Improve error handling for email sending in tasks 2025-07-28 10:30:42 +08:00
ibuler
be24f28d9b perf: in safe mode passkey cannot be as mfa 2025-07-25 10:50:46 +08:00
wangruidong
26cea550c4 fix: The applet list is not translated. 2025-07-25 10:49:47 +08:00
wangruidong
36ae076cb0 fix: Open redirect security vulnerability 2025-07-24 15:50:05 +08:00
feng
51c5294fb4 perf: Ticket filter org 2025-07-24 14:36:15 +08:00
feng
da083fffa3 perf: Translate email help text 2025-07-24 14:35:21 +08:00
feng
1df04d2a94 perf: Pam rbac 2025-07-23 10:21:38 +08:00
Eric
299e52cd11 perf: vnc_guide method only by xpack 2025-07-22 14:37:38 +08:00
feng
38b268b104 fix: Circular import 2025-07-22 14:36:22 +08:00
wangruidong
6095e9c9bd perf: Modify the layout to flex 2025-07-22 14:35:05 +08:00
ibuler
c4a348aac6 perf: remove client redirect api 2025-07-22 14:34:11 +08:00
feng
75575af56f perf: Callback client 2025-07-22 13:51:08 +08:00
Bryan
71766418bb Merge pull request #15742 from jumpserver/dev
merge: v4.10.4-lts
2025-07-17 15:12:58 +08:00
feng
8f91cb1473 perf: Translate 2025-07-17 15:12:01 +08:00
feng
b72e8eba7c perf: Change the secret and retry in batches 2025-07-17 14:21:31 +08:00
feng
d1d6f3fe9c perf: string_punctuation remove > ^ 2025-07-17 14:02:19 +08:00
wangruidong
6095c9865f fix: Action tips translate 2025-07-17 11:48:28 +08:00
wangruidong
6c374cb41f fix: View replay generate multiple operation logs 2025-07-17 11:24:16 +08:00
Eric
df64145adc perf: lion i18n 2025-07-16 19:49:07 +08:00
ibuler
44d77ba03f perf: random password exclude some char 2025-07-16 19:35:04 +08:00
wangruidong
3af188492f fix: Gather account failed 2025-07-16 19:19:43 +08:00
feng
9e798cd0b6 perf: Translate and tools version 2025-07-16 17:43:35 +08:00
feng
4d22c0722b fix: Exclude special char failed 2025-07-16 16:10:17 +08:00
Eric
e6a1662780 perf: add lion i18n 2025-07-15 18:58:42 +08:00
wangruidong
cc4be36752 perf: Log IntegrityError details during user authentication 2025-07-15 18:58:16 +08:00
wangruidong
e1f5d3c737 fix: Delete user failed(DoesNotExist) when user create share session 2025-07-15 18:43:43 +08:00
wangruidong
c0adc1fe74 fix: Gather account error 2025-07-15 18:43:14 +08:00
feng
613715135b perf: Translate 2025-07-15 11:46:39 +08:00
Eric
fe1d5f9828 perf: add en i18n 2025-07-11 15:34:24 +08:00
Eric
1d375e15c5 perf: add i18n keys 2025-07-11 15:34:24 +08:00
Eric
ac21d260ea perf: add lion i18n 2025-07-11 15:34:24 +08:00
wangruidong
accde77307 fix: Add third party login check is block 2025-07-11 15:33:48 +08:00
ibuler
c7dcf1ba59 perf: playbook task db save if conn timeout 2025-07-11 11:00:20 +08:00
wangruidong
b564bbebb3 perf: Translate 2025-07-11 10:30:40 +08:00
Eric
9440c855f4 perf: add lion i18n 2025-07-10 12:50:11 +08:00
w940853815
f282b2079e Update comment 2025-07-10 11:39:37 +08:00
wangruidong
1790cd8345 fix: Add additional third-party authentication backends and adjust MFA check 2025-07-10 11:39:37 +08:00
ibuler
7da74dc6e8 fix: integrate with azure oidc 2025-07-10 11:33:41 +08:00
Ewall555
33b0068f49 feat: exclude SSO token permissions for change and delete actions 2025-07-10 11:29:18 +08:00
Ewall555
9a446c118b feat: support rbac SSO token 2025-07-10 11:29:18 +08:00
Eric
4bf337b2b4 perf: add VNC terminal type 2025-07-10 11:28:32 +08:00
wangruidong
2acbb80920 perf: Add account date_expired 2025-07-09 10:47:06 +08:00
gerry-f2c
ae859c5562 perf: dbeaver uses a fixed driver directory (#15689) 2025-07-08 18:02:24 +08:00
Eric
a9bc716af5 perf: add encrypt field for sqlserver 2008 2025-07-08 18:01:31 +08:00
feng
2d5401e76e perf: Translate 2025-07-08 16:01:52 +08:00
Gerry.tan
d933e296bc perf: ES command log supports fuzzy search 2025-07-08 11:25:44 +08:00
wangruidong
1e5a995917 fix: Ticket filter error 2025-07-08 10:42:40 +08:00
wangruidong
baaaf83ab9 perf: Translate 2025-07-08 10:35:04 +08:00
wangruidong
ab06ac1f1f perf: Update IP group validation to include address validation 2025-07-08 10:34:34 +08:00
jiangweidong
99c4622ccb fix: SSO access to web assets with encrypted password auto-filling 2025-07-08 10:19:32 +08:00
Eric
9bdfab966f perf: add replay_size on session 2025-07-08 10:18:54 +08:00
老广
1a1acb62de Update README.md 2025-07-08 10:16:43 +08:00
wanghe-fit2cloud
2a128ea01b docs: Add GitCode badges 2025-07-07 15:33:08 +08:00
王贺
5a720b41bf docs: Add GitCode badge 2025-07-07 13:42:15 +08:00
feng
726c5cf34d fix: View replay record operate log 2025-07-07 10:37:29 +08:00
wangruidong
06afc8a0e1 perf: Translate 2025-07-02 19:04:15 +08:00
ibuler
276fd928a7 perf: add pg client 2025-07-01 16:18:25 +08:00
dependabot[bot]
05c6272d7e chore(deps): bump requests from 2.31.0 to 2.32.4
Bumps [requests](https://github.com/psf/requests) from 2.31.0 to 2.32.4.
- [Release notes](https://github.com/psf/requests/releases)
- [Changelog](https://github.com/psf/requests/blob/main/HISTORY.md)
- [Commits](https://github.com/psf/requests/compare/v2.31.0...v2.32.4)

---
updated-dependencies:
- dependency-name: requests
  dependency-version: 2.32.4
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-07-01 15:59:44 +08:00
Bai
c3f877d116 fix: check count 2025-07-01 15:35:17 +08:00
wangruidong
60deef2abf fix: Org admin cannot use system tools. 2025-07-01 15:23:34 +08:00
wangruidong
058754dc1b perf: Translate time cost 2025-06-30 10:05:13 +08:00
feng
a238c5d34b perf: operate record 2025-06-27 19:03:24 +08:00
feng626
76c6ed0f95 Merge pull request #15649 from jumpserver/pr@dev@translate
perf: Translate
2025-06-27 14:01:16 +08:00
feng626
0d07f7421b Merge branch 'dev' into pr@dev@translate 2025-06-27 14:01:05 +08:00
Ewall555
b07d4e207c perf: Translate 2025-06-27 13:59:57 +08:00
feng
dc92963059 perf: Translate 2025-06-27 13:15:07 +08:00
feng
9abd708a0a fix: ES search session count 2025-06-27 10:32:28 +08:00
jiangweidong
c9270877eb fix: According to the CMPP2.0 protocol standard, modify the attribute alignment. 2025-06-26 18:41:30 +08:00
feng
b5518dd2ba perf: Pam perm tree 2025-06-26 18:14:57 +08:00
wangruidong
1d40f5ecbc fix: Handle ValidationError in account_obj property 2025-06-26 15:23:16 +08:00
ibuler
91fee6c034 perf: change some 18n 2025-06-26 14:54:06 +08:00
feng
1b65055c5e perf: Account backup backup_type limit 2025-06-23 18:27:27 +08:00
feng
e79ef516a5 perf: Change secret windoes translate 2025-06-23 15:59:55 +08:00
Ewall555
8843f247d6 fix: Use local Python interpreter variable in RDP automation scripts 2025-06-23 14:15:46 +08:00
ibuler
cb42df542d fix: bitwardne request data encode 2025-06-23 14:13:15 +08:00
Ewall555
46ddad1d59 perf: Update metismenu plugin to version 3.0.7 2025-06-23 14:11:23 +08:00
Bryan
a9399dd709 Merge pull request #15608 from jumpserver/dev
v4.10.2
2025-06-19 20:14:21 +08:00
feng
f55a6ae364 perf: Translate 2025-06-19 20:09:06 +08:00
feng
34772b8fb4 fix: client login get session_key 2025-06-19 18:48:46 +08:00
feng
286891061b perf: face acl 2025-06-19 17:58:08 +08:00
feng
269bf1283e perf: Translate 2025-06-19 17:07:25 +08:00
Bai
d32c11bced fix: login page i18n same with personal settings 2025-06-19 16:47:53 +08:00
feng
fb64af2eb2 perf: Translate 2025-06-19 16:17:09 +08:00
Eric
82f32cbba3 perf: replace koko category to luna 2025-06-19 16:09:41 +08:00
wangruidong
411b485448 fix: The title of the risk detection email and server performance check notification i18n 2025-06-19 15:33:46 +08:00
feng
60608e92ea fix: Connection failure after directly connecting to asset ACL 2025-06-19 15:28:12 +08:00
feng
6922c62b50 perf: Translate 2025-06-19 11:49:47 +08:00
Eric
531c23d983 perf: add lina i18n 2025-06-19 10:22:24 +08:00
feng
df9e6cf866 perf: Translate 2025-06-18 16:37:33 +08:00
halo
65f2b92eb3 perf: Update client version and perf translate 2025-06-17 19:26:59 +08:00
feng626
bac621991e Merge pull request #15592 from jumpserver/pr@dev@translate
perf: Translate
2025-06-17 19:26:52 +08:00
feng626
db24d34b64 Merge branch 'dev' into pr@dev@translate 2025-06-17 19:25:59 +08:00
feng
265c066054 perf: Translate 2025-06-17 19:23:55 +08:00
feng
dad6b5def0 perf: Translate 2025-06-17 18:51:39 +08:00
feng
00f6c3a5de perf: Change secret record 2025-06-17 17:11:25 +08:00
wangruidong
cfbd162890 fix: Correct language retrieval in profile to use the provided object 2025-06-16 19:04:45 +08:00
wangruidong
17e8f25cb4 fix: Update language preference setting to include category 2025-06-16 19:04:45 +08:00
wangruidong
71bf8c8699 fix: The luna page cannot switch language settings 2025-06-16 19:04:45 +08:00
wangruidong
98342e0b70 fix: The login page cannot switch language settings 2025-06-16 19:04:45 +08:00
wangruidong
70aaa9cf8f fix: Activate user language when sending emails 2025-06-16 19:04:45 +08:00
Aaron3S
70b2d28760 feat: remove fuzzy 2025-06-16 18:40:18 +08:00
Aaron3S
8265a069e2 feat: translate 2025-06-16 18:40:18 +08:00
feng
9ec48aae0c perf: Translate 2025-06-16 14:55:23 +08:00
feng
41658af8fd perf: Suggestion api 2025-06-16 14:03:27 +08:00
fit2bot
7dfb31840e tinkner request ak first 2025-06-16 11:39:20 +08:00
ibuler
2f55db60ec perf: change redirect client auth to session 2025-06-13 10:41:28 +08:00
ibuler
551e6d0479 perf: client login redirect 2025-06-13 10:41:28 +08:00
feng
61c54314d7 perf: Face translate 2025-06-12 18:55:35 +08:00
wangruidong
4e7cd37c1d fix: Ensure user language is activated when sending notifications 2025-06-12 18:30:22 +08:00
wangruidong
e89f43dcd3 perf: Translate 2025-06-12 18:30:22 +08:00
wangruidong
259ead4c6e fix: Prevent nested resource issues in type nodes tree API 2025-06-12 18:29:22 +08:00
feng
348b2a833a perf: Translate 2025-06-12 18:28:33 +08:00
feng
8aec1604ce perf: Change secret clear account queue status 2025-06-12 16:55:04 +08:00
feng
be28a6954a perf: Login to change password and filter out useless accounts 2025-06-11 19:16:29 +08:00
feng
79c2284a01 perf: Change secret after successful login 2025-06-11 18:41:31 +08:00
feng
c2b44cfd84 perf: Translate 2025-06-11 16:36:55 +08:00
ibuler
1e07cba545 perf: open svc account register on deploy 2025-06-11 13:32:00 +08:00
ibuler
48a9b2664a perf: change ftplog asset length 2025-06-11 13:27:23 +08:00
ZhaoJiSen
b3bfbf5046 Merge pull request #15550 from jumpserver/pr@dev@send_mail_async
perf: send_mail_async func log subject recipients info
2025-06-11 11:17:25 +08:00
feng
08aa1e48b9 perf: send_mail_async func log subject recipients info 2025-06-11 11:16:03 +08:00
ZhaoJiSen
97d7427090 Merge pull request #15549 from jumpserver/pr@dev@translate
perf: Translate
2025-06-10 19:14:21 +08:00
feng
9f9d5855c4 perf: Translate 2025-06-10 19:12:31 +08:00
ZhaoJiSen
2db8f0f444 Merge pull request #15546 from jumpserver/pr@dev@send_mail_async
perf: send_mail_async add log
2025-06-10 17:47:22 +08:00
feng
b75210b0c3 perf: send_mail_async add log 2025-06-10 17:45:59 +08:00
wangruidong
4713c6ddf6 fix: Task search error 2025-06-10 16:58:37 +08:00
feng
b70fb58faf perf: Change secret after successful login 2025-06-10 16:57:28 +08:00
Aaron3S
3991976a00 feat: magnus support mongodb 2025-06-10 15:51:12 +08:00
ewall555
90256208dd perf: Update jsencrypt library version 2025-06-09 18:43:18 +08:00
wangruidong
bbd3b32aa1 perf: Remove username hint 2025-06-09 16:58:51 +08:00
wangruidong
ec20a4fd02 fix: Failed to update database assets 2025-06-09 15:04:27 +08:00
wangruidong
d179ce1cd4 perf: Add celery worker count config 2025-06-09 14:02:31 +08:00
ZhaoJiSen
caf23f5b05 Merge pull request #15529 from jumpserver/pr@dev@translate
perf: Translate
2025-06-06 18:23:10 +08:00
feng626
4bb19d59ef Merge branch 'dev' into pr@dev@translate 2025-06-06 18:22:24 +08:00
feng
74ed693a95 perf: Translate 2025-06-06 18:19:44 +08:00
feng
4a7a1fd95c perf: Optimize the results returned by the suggestion api for different organizations 2025-06-06 18:09:05 +08:00
wangruidong
56268433e0 perf: Translate adhoc 2025-06-06 17:56:53 +08:00
ibuler
ea59677b13 perf: swagger auth required 2025-06-06 17:56:25 +08:00
ibuler
94ed26e115 perf: change i18n 2025-06-06 17:56:25 +08:00
wangruidong
284d793253 perf: leak password can bulk delete 2025-06-06 17:05:13 +08:00
wangruidong
570566d9dd perf: set ansible_timeout for account connectivity tasks 2025-06-04 18:41:41 +08:00
wangruidong
3f85c67aee perf: Add retention period for expired user tokens and implement cleanup task 2025-06-04 18:39:49 +08:00
wangruidong
53a84850dc fix: Ensure platform_id is a digit before querying Platform 2025-06-04 18:37:05 +08:00
feng
e4be9621bb perf: Custom push account 2025-06-03 14:52:06 +08:00
Eric
f8b778ada2 perf: tinker to v0.2.2 2025-06-03 13:54:21 +08:00
fit2bot
5c28b15e39 perf: update chrome applet to support language setting (#15509)
* perf: update chrome applet to support language setting

* perf: fix field name

---------

Co-authored-by: Eric <xplzv@126.com>
2025-06-03 13:54:04 +08:00
wangruidong
5e0babdba8 perf: Language settings in personal settings 2025-05-29 11:13:04 +08:00
feng
8a3acb649e fix: ES non-global organizations cannot be queried 2025-05-27 14:31:27 +08:00
Eric
1ade652381 perf: upgrade tinker to v0.2.1 2025-05-23 11:20:17 +08:00
github-actions[bot]
7472f83d7a Auto-translate README 2025-05-22 18:34:57 +08:00
Bai
c56a3d0a2e perf: add ko readme 2025-05-22 18:10:34 +08:00
feng
1a10225823 perf: view task log 2025-05-22 17:44:19 +08:00
feng
56c94d7b3c fix: The account suggestions api cannot find the account associated with the DS 2025-05-22 11:50:17 +08:00
ibuler
16e7a12974 perf: static file download and catch 2025-05-20 13:14:47 +08:00
ibuler
1364889083 fix: aggregate resource api 2025-05-20 13:14:21 +08:00
feng
4f19954640 perf: SSO add mfa 2025-05-20 13:12:13 +08:00
feng
1b2e376681 perf: Account list not display spec_info field 2025-05-19 16:13:36 +08:00
ibuler
14c5162153 perf: client auth changed 2025-05-19 11:27:31 +08:00
Bai
f9245e17cd perf: readme 2025-05-16 18:43:39 +08:00
Aaron3S
6bd1ec960b feat: add a new piico gm alg 2025-05-16 15:07:39 +08:00
ibuler
77cc02ae60 perf: change google authenticator apk download 2025-05-15 17:41:49 +08:00
Bryan
d0cb9e5432 Merge pull request #15412 from jumpserver/dev
v4.10.0
2025-05-15 17:11:43 +08:00
feng
9969395500 fix: perm del node and user group 500 2025-05-15 15:55:32 +08:00
wangruidong
e1f03a194b fix: Asset list gather account raise connection already closed 2025-05-15 15:48:19 +08:00
Eric
aa0125385a perf: fix NoneType error 2025-05-15 15:01:57 +08:00
feng
8e8579bebe perf: translate 2025-05-15 14:41:19 +08:00
feng
ad5ce5d4cf perf: translate 2025-05-15 14:01:51 +08:00
ibuler
4f009504ad perf: load custom protocols 2025-05-15 11:41:33 +08:00
ibuler
986bc926fc perf: iframe set to sameorigin 2025-05-14 19:29:16 +08:00
feng
6aafb0f01a perf: Translate 2025-05-14 18:18:45 +08:00
feng
43775096d1 perf: Login switch language 2025-05-14 17:40:09 +08:00
ibuler
f826f43495 perf: simplify db using 2025-05-14 15:11:46 +08:00
ibuler
e9ff988d8c perf: db connection close if needt 2025-05-14 14:43:14 +08:00
feng
a72e6456d9 perf: Connect method exclude face 2025-05-14 14:02:27 +08:00
ibuler
941a784a5b perf: 修改 migrations 2025-05-13 19:15:01 +08:00
ibuler
edaf9bb0b2 perf: domain enabled to gateway enabled 2025-05-13 16:36:56 +08:00
feng
e8ca177fe4 perf: translate 2025-05-13 14:50:14 +08:00
ewall555
a88ebeff15 feat: Set the default expiration days for adding user and asset permissions 2025-05-13 10:35:21 +08:00
ibuler
bd0c50a3e4 fix: account username has domain, then set again 2025-05-12 18:36:26 +08:00
feng
9f121723c4 perf: auditor add asset user view perm 2025-05-12 17:57:48 +08:00
feng
245ed79b17 perf: Translate 2025-05-12 17:05:37 +08:00
ibuler
01c07a834b perf: 修改 adhoc 翻译 2025-05-12 10:50:43 +08:00
ibuler
4fb61e0af6 perf: org id error 2025-05-12 10:24:33 +08:00
Emmanuel Ferdman
19b7be33ae Resolve warnings of logger library
Signed-off-by: Emmanuel Ferdman <emmanuelferdman@gmail.com>
2025-05-12 09:51:30 +08:00
feng
7797c76032 perf: migrate 2025-05-09 18:25:41 +08:00
老广
eb777854d4 Pr@dev@fix django version (#15374)
* fix: Downgrade django and djangorestframework versions for compatibility

* perf: Update Dockerfile with new base image tag

* perf: Update Dockerfile with new base image tag

---------

Co-authored-by: wangruidong <940853815@qq.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2025-05-09 17:50:11 +08:00
Eric
22d4bd5292 perf: optimize file removal 2025-05-09 17:21:24 +08:00
ibuler
47d0d8b7a0 perf: stop dep check 2025-05-09 17:20:33 +08:00
wangruidong
5e298e5749 fix: Downgrade django and djangorestframework versions for compatibility 2025-05-09 16:10:49 +08:00
w940853815
ad3ba5034e Revert "fix: Upgrading django version causes json serialization problems"
This reverts commit ad56845d89.
2025-05-09 16:04:58 +08:00
feng
f1a1c3660d perf: Luna en translate 2025-05-09 15:14:56 +08:00
feng
eaf5bc5eb3 perf: I18n api 2025-05-09 14:46:48 +08:00
feng
30e680ad91 fix: ES the calculated quantity is 0 2025-05-08 17:23:15 +08:00
wangruidong
c9f281e8f7 fix: Handle exceptions in leak password check 2025-05-08 17:13:23 +08:00
wangruidong
ad56845d89 fix: Upgrading django version causes json serialization problems 2025-05-08 17:12:53 +08:00
feng
c3dceec3c7 perf: Connectivity add rdp error 2025-05-08 16:07:30 +08:00
wangruidong
100dad75f1 fix: i18n error 2025-05-08 14:50:54 +08:00
feng
aa52060f24 perf: Upgrade bootstrap js to 5.3.6 2025-05-08 14:12:17 +08:00
wangruidong
089a5f50f4 feat: Add LeakPasswords config 2025-05-07 17:47:22 +08:00
feng626
0bdbb6fd84 Merge pull request #15352 from jumpserver/pr@dev@connectivity_choice
perf: Connectivity choice
2025-05-07 17:30:48 +08:00
feng626
dd5bcab4ff Merge branch 'dev' into pr@dev@connectivity_choice 2025-05-07 17:30:30 +08:00
feng
49f0e51769 perf: Connectivity choice 2025-05-07 17:25:10 +08:00
wangruidong
5577e39f21 perf: Support watermark customization 2025-05-07 16:52:58 +08:00
老广
e2830ecdd6 perf: passkey auth auto mfa 2025-05-07 16:24:39 +08:00
feng
8065e04f26 perf: Translate 2025-05-07 15:50:04 +08:00
feng
fe70b60e95 perf: Users lina translate 2025-05-07 11:31:22 +08:00
feng
1f7836353a perf: translate 2025-05-07 10:32:08 +08:00
feng
a4296b3129 perf: Clean push record period 2025-05-06 18:32:56 +08:00
feng
ffc92fa7b4 perf: Clean push record period 2025-05-06 18:22:54 +08:00
github-actions[bot]
f94e032858 perf: Update Dockerfile with new base image tag 2025-04-30 11:13:34 +08:00
ibuler
25429e30ba perf: update quirements 2025-04-30 11:13:34 +08:00
CaptainB
5c7d539c6f chore: Configure Dependabot to group Python dependencies 2025-04-30 11:04:21 +08:00
fit2bot
a8a6e03428 perf: update deps 2025-04-30 10:58:19 +08:00
wangruidong
71b9b2df74 fix: Add AdminConnectionToken to operate log exclude_models 2025-04-30 10:51:25 +08:00
Eric
f1bc69b253 perf: add luna i18n 2025-04-29 18:12:10 +08:00
wangruidong
282ca25504 perf: Skip alert if login city seen in past 7 days 2025-04-29 17:46:10 +08:00
fit2bot
1bb44e783a perf: some i18n (#15312)
Co-authored-by: ibuler <ibuler@qq.com>
2025-04-29 17:45:29 +08:00
feng
a64fe4b0be perf: Account translate 2025-04-29 14:44:22 +08:00
feng
a75faf8da6 perf: Discover account translate 2025-04-29 14:36:13 +08:00
Bryan
537a9325a3 Update README.md (#15305)
* Update README.md

* Update README.md

* Update README.md

* Update README.md

* Update README.md

* Update README.md

* Update README.md

* Update README.md

* Update README.md
2025-04-28 11:36:05 +08:00
feng
a9d455e867 perf: ntlm_err 2025-04-27 18:53:43 +08:00
feng
d06d26ac54 perf: Display asset/account connectivity error message 2025-04-27 18:50:00 +08:00
fit2bot
e992c44e11 perf: change lfs files download (#15293)
* perf: change lfs files download

* perf: clean unused ansible module

* perf: update lfs download

* perf: Update Dockerfile with new base image tag

* perf: change download path

* perf: Update Dockerfile with new base image tag

---------

Co-authored-by: ibuler <ibuler@qq.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2025-04-27 14:35:10 +08:00
feng
24fe058fd9 perf: lina translate 2025-04-25 18:07:09 +08:00
feng
a3fef9cc54 perf: Update the key when the integration-application is updated or created 2025-04-25 17:29:44 +08:00
ibuler
471053e62a perf: change mcp integrate 2025-04-25 17:28:03 +08:00
jiangweidong
dc6308b030 perf: if the apply-asset-ticket name is 128 characters long, will raise 500 2025-04-25 17:27:13 +08:00
feng
f016ae6161 perf: add sftplog command models field index 2025-04-25 15:21:55 +08:00
feng
14a8d877e0 perf: ko translate 2025-04-25 15:04:03 +08:00
feng
ddf20570a1 perf: device support ad 2025-04-23 19:38:01 +08:00
feng
1ad9616b7f perf: gather facts gpu info 2025-04-22 17:48:21 +08:00
刘瑞斌
d7bc6bb201 chore: use uv as package-ecosystem 2025-04-21 13:36:24 +08:00
feng
f855043468 perf: luna ru translate 2025-04-21 11:34:51 +08:00
fit2bot
3159a4e794 perf: change domain to zone (#15255)
* perf: change domain to zone

* perf: change domain to zone

* perf: change some word

* perf: update gateway enabled i18n

* perf: change migrations

---------

Co-authored-by: ibuler <ibuler@qq.com>
2025-04-21 10:30:18 +08:00
feng
57fcebfdd3 fix: No data found for the carrying organization 2025-04-18 16:50:07 +08:00
feng626
c500bb4e4c Revert "Revert "perf:Stored command records in ES support accurate searching.""
This reverts commit 6bc1c5bd50.
2025-04-18 16:50:07 +08:00
feng
fd062b0da6 perf: ru translate 2025-04-18 14:52:44 +08:00
ibuler
bcb112d5c6 perf: user profile api 2025-04-18 14:11:56 +08:00
fit2bot
533dbf316c perf: add ali rds dependencies (#15247)
* perf: add ali rds dependencies

* perf: Update Dockerfile with new base image tag

---------

Co-authored-by: Eric <xplzv@126.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2025-04-18 12:11:51 +08:00
github-actions[bot]
9cce94b709 perf: Update Dockerfile with new base image tag 2025-04-18 11:53:40 +08:00
Eric
8b815d812b perf: modify Dockerfile-base 2025-04-18 11:53:40 +08:00
github-actions[bot]
a168fc8a62 perf: Update Dockerfile with new base image tag 2025-04-18 11:29:07 +08:00
Eric
faae1a09d1 perf: lint dependencies 2025-04-18 11:29:07 +08:00
github-actions[bot]
26e819e120 perf: Update Dockerfile with new base image tag 2025-04-18 11:29:07 +08:00
Eric
79579654a1 perf: use uv tool
perf: add Homepage
perf: add env
2025-04-18 11:29:07 +08:00
425 changed files with 32992 additions and 19251 deletions

View File

@@ -8,4 +8,6 @@ celerybeat.pid
.vagrant/
apps/xpack/.git
.history/
.idea
.idea
.venv/
.env

4
.gitattributes vendored
View File

@@ -1,4 +0,0 @@
*.mmdb filter=lfs diff=lfs merge=lfs -text
*.mo filter=lfs diff=lfs merge=lfs -text
*.ipdb filter=lfs diff=lfs merge=lfs -text
leak_passwords.db filter=lfs diff=lfs merge=lfs -text

View File

@@ -1,10 +1,14 @@
version: 2
updates:
- package-ecosystem: "pip"
- package-ecosystem: "uv"
directory: "/"
schedule:
interval: "weekly"
day: "monday"
time: "09:30"
timezone: "Asia/Shanghai"
target-branch: dev
target-branch: dev
groups:
python-dependencies:
patterns:
- "*"

3
.gitignore vendored
View File

@@ -46,3 +46,6 @@ test.py
.test/
*.mo
apps.iml
*.db
*.mmdb
*.ipdb

11
.prettierrc Normal file
View File

@@ -0,0 +1,11 @@
{
"tabWidth": 4,
"useTabs": false,
"semi": true,
"singleQuote": true,
"trailingComma": "es5",
"bracketSpacing": true,
"arrowParens": "avoid",
"printWidth": 100,
"endOfLine": "lf"
}

View File

@@ -1,4 +1,4 @@
FROM jumpserver/core-base:20250415_032719 AS stage-build
FROM jumpserver/core-base:20250819_064003 AS stage-build
ARG VERSION

View File

@@ -1,6 +1,6 @@
FROM python:3.11-slim-bullseye
ARG TARGETARCH
COPY --from=ghcr.io/astral-sh/uv:0.6.14 /uv /uvx /usr/local/bin/
# Install APT dependencies
ARG DEPENDENCIES=" \
ca-certificates \
@@ -43,18 +43,19 @@ WORKDIR /opt/jumpserver
ARG PIP_MIRROR=https://pypi.org/simple
ENV POETRY_PYPI_MIRROR_URL=${PIP_MIRROR}
ENV ANSIBLE_COLLECTIONS_PATHS=/opt/py3/lib/python3.11/site-packages/ansible_collections
ENV LANG=en_US.UTF-8 \
PATH=/opt/py3/bin:$PATH
ENV UV_LINK_MODE=copy
RUN --mount=type=cache,target=/root/.cache \
--mount=type=bind,source=poetry.lock,target=poetry.lock \
--mount=type=bind,source=pyproject.toml,target=pyproject.toml \
--mount=type=bind,source=utils/clean_site_packages.sh,target=clean_site_packages.sh \
--mount=type=bind,source=requirements/clean_site_packages.sh,target=clean_site_packages.sh \
--mount=type=bind,source=requirements/collections.yml,target=collections.yml \
--mount=type=bind,source=requirements/static_files.sh,target=utils/static_files.sh \
set -ex \
&& python3 -m venv /opt/py3 \
&& pip install poetry poetry-plugin-pypi-mirror -i ${PIP_MIRROR} \
&& . /opt/py3/bin/activate \
&& poetry config virtualenvs.create false \
&& poetry install --no-cache --only main \
&& ansible-galaxy collection install -r collections.yml --force --ignore-certs \
&& bash clean_site_packages.sh \
&& poetry cache clear pypi --all
&& uv venv \
&& uv pip install -i${PIP_MIRROR} -r pyproject.toml \
&& ln -sf $(pwd)/.venv /opt/py3 \
&& bash utils/static_files.sh \
&& bash clean_site_packages.sh

View File

@@ -13,7 +13,9 @@ ARG TOOLS=" \
nmap \
telnet \
vim \
wget"
postgresql-client-13 \
wget \
poppler-utils"
RUN set -ex \
&& apt-get update \
@@ -24,11 +26,7 @@ RUN set -ex \
WORKDIR /opt/jumpserver
ARG PIP_MIRROR=https://pypi.org/simple
ENV POETRY_PYPI_MIRROR_URL=${PIP_MIRROR}
COPY poetry.lock pyproject.toml ./
RUN set -ex \
&& . /opt/py3/bin/activate \
&& pip install poetry poetry-plugin-pypi-mirror -i ${PIP_MIRROR} \
&& poetry install --only xpack \
&& poetry cache clear pypi --all
RUN set -ex \
&& uv pip install -i${PIP_MIRROR} --group xpack \
&& playwright install chromium --with-deps --only-shell

View File

@@ -5,12 +5,14 @@
## An open-source PAM tool (Bastion Host)
[![][license-shield]][license-link]
[![][docs-shield]][docs-link]
[![][deepwiki-shield]][deepwiki-link]
[![][discord-shield]][discord-link]
[![][docker-shield]][docker-link]
[![][github-release-shield]][github-release-link]
[![][github-stars-shield]][github-stars-link]
[English](/README.md) · [中文(简体)](/readmes/README.zh-hans.md) · [中文(繁體)](/readmes/README.zh-hant.md) · [日本語](/readmes/README.ja.md) · [Português (Brasil)](/readmes/README.pt-br.md) · [Español](/readmes/README.es.md) · [Русский](/readmes/README.ru.md)
[English](/README.md) · [中文(简体)](/readmes/README.zh-hans.md) · [中文(繁體)](/readmes/README.zh-hant.md) · [日本語](/readmes/README.ja.md) · [Português (Brasil)](/readmes/README.pt-br.md) · [Español](/readmes/README.es.md) · [Русский](/readmes/README.ru.md) · [한국어](/readmes/README.ko.md)
</div>
<br/>
@@ -21,8 +23,8 @@ JumpServer is an open-source Privileged Access Management (PAM) tool that provid
<picture>
<source media="(prefers-color-scheme: light)" srcset="https://github.com/user-attachments/assets/dd612f3d-c958-4f84-b164-f31b75454d7f">
<source media="(prefers-color-scheme: dark)" srcset="https://github.com/user-attachments/assets/28676212-2bc4-4a9f-ae10-3be9320647e3">
<source media="(prefers-color-scheme: light)" srcset="https://www.jumpserver.com/images/jumpserver-arch-light.png">
<source media="(prefers-color-scheme: dark)" srcset="https://www.jumpserver.com/images/jumpserver-arch-dark.png">
<img src="https://github.com/user-attachments/assets/dd612f3d-c958-4f84-b164-f31b75454d7f" alt="Theme-based Image">
</picture>
@@ -83,6 +85,8 @@ JumpServer consists of multiple key components, which collectively form the func
| [Nec](https://github.com/jumpserver/nec) | <img alt="Nec" src="https://img.shields.io/badge/release-private-red" /> | JumpServer EE VNC Proxy Connector |
| [Facelive](https://github.com/jumpserver/facelive) | <img alt="Facelive" src="https://img.shields.io/badge/release-private-red" /> | JumpServer EE Facial Recognition |
## Third-party projects
- [jumpserver-grafana-dashboard](https://github.com/acerrah/jumpserver-grafana-dashboard) JumpServer with grafana dashboard
## Contributing
@@ -101,6 +105,7 @@ Unless required by applicable law or agreed to in writing, software distributed
<!-- JumpServer official link -->
[docs-link]: https://jumpserver.com/docs
[discord-link]: https://discord.com/invite/W6vYXmAQG2
[deepwiki-link]: https://deepwiki.com/jumpserver/jumpserver/
[contributing-link]: https://github.com/jumpserver/jumpserver/blob/dev/CONTRIBUTING.md
<!-- JumpServer Other link-->
@@ -111,8 +116,10 @@ Unless required by applicable law or agreed to in writing, software distributed
[github-issues-link]: https://github.com/jumpserver/jumpserver/issues
<!-- Shield link-->
[docs-shield]: https://img.shields.io/badge/documentation-148F76
[github-release-shield]: https://img.shields.io/github/v/release/jumpserver/jumpserver
[github-stars-shield]: https://img.shields.io/github/stars/jumpserver/jumpserver?color=%231890FF&style=flat-square
[github-stars-shield]: https://img.shields.io/github/stars/jumpserver/jumpserver?color=%231890FF&style=flat-square   
[docker-shield]: https://img.shields.io/docker/pulls/jumpserver/jms_all.svg
[license-shield]: https://img.shields.io/github/license/jumpserver/jumpserver
[deepwiki-shield]: https://img.shields.io/badge/deepwiki-devin?color=blue
[discord-shield]: https://img.shields.io/discord/1194233267294052363?style=flat&logo=discord&logoColor=%23f5f5f5&labelColor=%235462eb&color=%235462eb

View File

@@ -41,8 +41,8 @@ class AccountViewSet(OrgBulkModelViewSet):
'partial_update': ['accounts.change_account'],
'su_from_accounts': 'accounts.view_account',
'clear_secret': 'accounts.change_account',
'move_to_assets': 'accounts.create_account',
'copy_to_assets': 'accounts.create_account',
'move_to_assets': 'accounts.delete_account',
'copy_to_assets': 'accounts.add_account',
}
export_as_zip = True
@@ -78,18 +78,25 @@ class AccountViewSet(OrgBulkModelViewSet):
permission_classes=[IsValidUser]
)
def username_suggestions(self, request, *args, **kwargs):
asset_ids = request.data.get('assets', [])
raw_asset_ids = request.data.get('assets', [])
node_ids = request.data.get('nodes', [])
username = request.data.get('username', '')
accounts = Account.objects.all()
asset_ids = set(raw_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)
asset_ids.extend(node_asset_ids)
node_asset_qs = Node.get_nodes_all_assets(*nodes).values_list('id', flat=True)
asset_ids |= {str(u) for u in node_asset_qs}
if asset_ids:
accounts = accounts.filter(asset_id__in=list(set(asset_ids)))
through = Asset.directory_services.through
ds_qs = through.objects.filter(asset_id__in=asset_ids) \
.values_list('directoryservice_id', flat=True)
asset_ids |= {str(u) for u in ds_qs}
accounts = Account.objects.filter(asset_id__in=list(asset_ids))
else:
accounts = Account.objects.all()
if username:
accounts = accounts.filter(username__icontains=username)

View File

@@ -62,8 +62,7 @@ class IntegrationApplicationViewSet(OrgBulkModelViewSet):
)
def get_once_secret(self, request, *args, **kwargs):
instance = self.get_object()
secret = instance.get_secret()
return Response(data={'id': instance.id, 'secret': secret})
return Response(data={'id': instance.id, 'secret': instance.secret})
@action(['GET'], detail=False, url_path='account-secret',
permission_classes=[RBACPermission])

View File

@@ -20,7 +20,7 @@ __all__ = ['PamDashboardApi']
class PamDashboardApi(APIView):
http_method_names = ['get']
rbac_perms = {
'GET': 'accounts.view_account',
'GET': 'rbac.view_pam',
}
@staticmethod

View File

@@ -43,6 +43,7 @@ class AccountTemplateViewSet(OrgBulkModelViewSet):
search_fields = ('username', 'name')
serializer_classes = {
'default': serializers.AccountTemplateSerializer,
'retrieve': serializers.AccountDetailTemplateSerializer,
}
rbac_perms = {
'su_from_account_templates': 'accounts.view_accounttemplate',

View File

@@ -6,10 +6,13 @@ from rest_framework.decorators import action
from rest_framework.response import Response
from accounts import serializers
from accounts.const import AutomationTypes, ChangeSecretRecordStatusChoice
from accounts.filters import ChangeSecretRecordFilterSet
from accounts.models import ChangeSecretAutomation, ChangeSecretRecord
from accounts.const import (
AutomationTypes, ChangeSecretRecordStatusChoice
)
from accounts.filters import ChangeSecretRecordFilterSet, ChangeSecretStatusFilterSet
from accounts.models import ChangeSecretAutomation, ChangeSecretRecord, Account
from accounts.tasks import execute_automation_record_task
from accounts.utils import account_secret_task_status
from authentication.permissions import UserConfirmation, ConfirmType
from common.permissions import IsValidLicense
from orgs.mixins.api import OrgBulkModelViewSet, OrgGenericViewSet
@@ -23,7 +26,7 @@ __all__ = [
'ChangeSecretAutomationViewSet', 'ChangeSecretRecordViewSet',
'ChangSecretExecutionViewSet', 'ChangSecretAssetsListApi',
'ChangSecretRemoveAssetApi', 'ChangSecretAddAssetApi',
'ChangSecretNodeAddRemoveApi'
'ChangSecretNodeAddRemoveApi', 'ChangeSecretStatusViewSet'
]
@@ -94,12 +97,13 @@ class ChangeSecretRecordViewSet(mixins.ListModelMixin, OrgGenericViewSet):
def execute(self, request, *args, **kwargs):
record_ids = request.data.get('record_ids')
records = self.get_queryset().filter(id__in=record_ids)
execution_count = records.values_list('execution_id', flat=True).distinct().count()
if execution_count != 1:
if not records.exists():
return Response(
{'detail': 'Only one execution is allowed to execute'},
{'detail': 'No valid records found'},
status=status.HTTP_400_BAD_REQUEST
)
record_ids = [str(_id) for _id in records.values_list('id', flat=True)]
task = execute_automation_record_task.delay(record_ids, self.tp)
return Response({'task': task.id}, status=status.HTTP_200_OK)
@@ -154,3 +158,25 @@ class ChangSecretAddAssetApi(AutomationAddAssetApi):
class ChangSecretNodeAddRemoveApi(AutomationNodeAddRemoveApi):
model = ChangeSecretAutomation
serializer_class = serializers.ChangeSecretUpdateNodeSerializer
class ChangeSecretStatusViewSet(OrgBulkModelViewSet):
perm_model = ChangeSecretAutomation
filterset_class = ChangeSecretStatusFilterSet
serializer_class = serializers.ChangeSecretAccountSerializer
search_fields = ('username',)
permission_classes = [RBACPermission, IsValidLicense]
http_method_names = ["get", "delete", "options"]
def get_queryset(self):
account_ids = list(account_secret_task_status.account_ids)
return Account.objects.filter(id__in=account_ids).select_related('asset')
def bulk_destroy(self, request, *args, **kwargs):
account_ids = request.data.get('account_ids')
if isinstance(account_ids, str):
account_ids = [account_ids]
for _id in account_ids:
account_secret_task_status.clear(_id)
return Response(status=status.HTTP_200_OK)

View File

@@ -90,10 +90,10 @@ class ChangeSecretDashboardApi(APIView):
def get_change_secret_asset_queryset(self):
qs = self.change_secrets_queryset
node_ids = qs.filter(nodes__isnull=False).values_list('nodes', flat=True).distinct()
nodes = Node.objects.filter(id__in=node_ids)
node_ids = qs.values_list('nodes', flat=True).distinct()
nodes = Node.objects.filter(id__in=node_ids).only('id', 'key')
node_asset_ids = Node.get_nodes_all_assets(*nodes).values_list('id', flat=True)
direct_asset_ids = qs.filter(assets__isnull=False).values_list('assets', flat=True).distinct()
direct_asset_ids = qs.values_list('assets', flat=True).distinct()
asset_ids = set(list(direct_asset_ids) + list(node_asset_ids))
return Asset.objects.filter(id__in=asset_ids)

View File

@@ -45,10 +45,10 @@ class CheckAccountAutomationViewSet(OrgBulkModelViewSet):
class CheckAccountExecutionViewSet(AutomationExecutionViewSet):
rbac_perms = (
("list", "accounts.view_checkaccountexecution"),
("retrieve", "accounts.view_checkaccountsexecution"),
("retrieve", "accounts.view_checkaccountexecution"),
("create", "accounts.add_checkaccountexecution"),
("adhoc", "accounts.add_checkaccountexecution"),
("report", "accounts.view_checkaccountsexecution"),
("report", "accounts.view_checkaccountexecution"),
)
ordering = ("-date_created",)
tp = AutomationTypes.check_account

View File

@@ -5,12 +5,13 @@ from django.utils import timezone
from django.utils.translation import gettext_lazy as _
from accounts.automations.methods import platform_automation_methods
from accounts.const import SSHKeyStrategy, SecretStrategy, SecretType, ChangeSecretRecordStatusChoice
from accounts.const import SSHKeyStrategy, SecretStrategy, SecretType, ChangeSecretRecordStatusChoice, \
ChangeSecretAccountStatus
from accounts.models import BaseAccountQuerySet
from accounts.utils import SecretGenerator
from accounts.utils import SecretGenerator, account_secret_task_status
from assets.automations.base.manager import BasePlaybookManager
from assets.const import HostTypes
from common.db.utils import safe_db_connection
from common.db.utils import safe_atomic_db_connection
from common.utils import get_logger
logger = get_logger(__name__)
@@ -36,7 +37,7 @@ class BaseChangeSecretPushManager(AccountBasePlaybookManager):
)
self.account_ids = self.execution.snapshot['accounts']
self.record_map = self.execution.snapshot.get('record_map', {}) # 这个是某个失败的记录重试
self.name_recorder_mapper = {} # 做个映射,方便后面处理
self.name_record_mapper = {} # 做个映射,方便后面处理
def gen_account_inventory(self, account, asset, h, path_dir):
raise NotImplementedError
@@ -112,10 +113,15 @@ class BaseChangeSecretPushManager(AccountBasePlaybookManager):
if host.get('error'):
return host
host['check_conn_after_change'] = self.execution.snapshot.get('check_conn_after_change', True)
host['ssh_params'] = {}
accounts = self.get_accounts(account)
existing_ids = set(map(str, accounts.values_list('id', flat=True)))
missing_ids = set(map(str, self.account_ids)) - existing_ids
for account_id in missing_ids:
self.clear_account_queue_status(account_id)
error_msg = _("No pending accounts found")
if not accounts:
print(f'{asset}: {error_msg}')
@@ -132,31 +138,50 @@ class BaseChangeSecretPushManager(AccountBasePlaybookManager):
for account in accounts:
h = deepcopy(host)
h['name'] += '(' + account.username + ')' # To distinguish different accounts
account_status = account_secret_task_status.get_status(account.id)
if account_status == ChangeSecretAccountStatus.PROCESSING:
h['error'] = f'Account is already being processed, skipping: {account}'
inventory_hosts.append(h)
continue
try:
h = self.gen_account_inventory(account, asset, h, path_dir)
h, record = self.gen_account_inventory(account, asset, h, path_dir)
h['check_conn_after_change'] = record.execution.snapshot.get('check_conn_after_change', True)
account_secret_task_status.set_status(
account.id,
ChangeSecretAccountStatus.PROCESSING,
metadata={'execution_id': self.execution.id}
)
except Exception as e:
h['error'] = str(e)
self.clear_account_queue_status(account.id)
inventory_hosts.append(h)
return inventory_hosts
@staticmethod
def save_record(recorder):
recorder.save(update_fields=['error', 'status', 'date_finished'])
def save_record(record):
record.save(update_fields=['error', 'status', 'date_finished'])
@staticmethod
def clear_account_queue_status(account_id):
account_secret_task_status.clear(account_id)
def on_host_success(self, host, result):
recorder = self.name_recorder_mapper.get(host)
if not recorder:
record = self.name_record_mapper.get(host)
if not record:
return
recorder.status = ChangeSecretRecordStatusChoice.success.value
recorder.date_finished = timezone.now()
record.status = ChangeSecretRecordStatusChoice.success.value
record.date_finished = timezone.now()
account = recorder.account
account = record.account
if not account:
print("Account not found, deleted ?")
return
account.secret = getattr(recorder, 'new_secret', account.secret)
account.secret = getattr(record, 'new_secret', account.secret)
account.date_updated = timezone.now()
account.date_change_secret = timezone.now()
account.change_secret_status = ChangeSecretRecordStatusChoice.success
@@ -170,18 +195,19 @@ class BaseChangeSecretPushManager(AccountBasePlaybookManager):
)
super().on_host_success(host, result)
with safe_db_connection():
with safe_atomic_db_connection():
account.save(update_fields=['secret', 'date_updated', 'date_change_secret', 'change_secret_status'])
self.save_record(recorder)
self.save_record(record)
self.clear_account_queue_status(account.id)
def on_host_error(self, host, error, result):
recorder = self.name_recorder_mapper.get(host)
if not recorder:
record = self.name_record_mapper.get(host)
if not record:
return
recorder.status = ChangeSecretRecordStatusChoice.failed.value
recorder.date_finished = timezone.now()
recorder.error = error
account = recorder.account
record.status = ChangeSecretRecordStatusChoice.failed.value
record.date_finished = timezone.now()
record.error = error
account = record.account
if not account:
print("Account not found, deleted ?")
return
@@ -192,12 +218,13 @@ class BaseChangeSecretPushManager(AccountBasePlaybookManager):
self.summary['fail_accounts'] += 1
self.result['fail_accounts'].append(
{
"asset": str(recorder.asset),
"username": recorder.account.username,
"asset": str(record.asset),
"username": record.account.username,
}
)
super().on_host_error(host, error, result)
with safe_db_connection():
with safe_atomic_db_connection():
account.save(update_fields=['change_secret_status', 'date_change_secret', 'date_updated'])
self.save_record(recorder)
self.save_record(record)
self.clear_account_queue_status(account.id)

View File

@@ -53,4 +53,6 @@
ssl_certfile: "{{ jms_asset.secret_info.client_key | default('') }}"
connection_options:
- tlsAllowInvalidHostnames: "{{ jms_asset.spec_info.allow_invalid_cert}}"
when: check_conn_after_change
when: check_conn_after_change
register: result
failed_when: not result.is_available

View File

@@ -39,7 +39,8 @@
name: "{{ account.username }}"
password: "{{ account.secret }}"
host: "%"
priv: "{{ account.username + '.*:USAGE' if db_name == '' else db_name + '.*:ALL' }}"
priv: "{{ omit if db_name == '' else db_name + '.*:ALL' }}"
append_privs: "{{ db_name != '' | bool }}"
ignore_errors: true
when: db_info is succeeded

View File

@@ -56,3 +56,5 @@
ssl_key: "{{ ssl_key if check_ssl and ssl_key | length > 0 else omit }}"
ssl_mode: "{{ jms_asset.spec_info.pg_ssl_mode }}"
when: check_conn_after_change
register: result
failed_when: not result.is_available

View File

@@ -8,7 +8,7 @@ type:
params:
- name: groups
type: str
label: '用户组'
label: "{{ 'Params groups label' | trans }}"
default: 'Users,Remote Desktop Users'
help_text: "{{ 'Params groups help text' | trans }}"
@@ -24,3 +24,7 @@ i18n:
ja: 'グループを入力してください。複数のグループはコンマで区切ってください(既存のグループを入力してください)'
en: 'Please enter the group. Multiple groups are separated by commas (please enter the existing group)'
Params groups label:
zh: '用户组'
ja: 'グループ'
en: 'Groups'

View File

@@ -9,7 +9,7 @@ type:
params:
- name: groups
type: str
label: '用户组'
label: "{{ 'Params groups label' | trans }}"
default: 'Users,Remote Desktop Users'
help_text: "{{ 'Params groups help text' | trans }}"
@@ -25,3 +25,8 @@ i18n:
ja: 'グループを入力してください。複数のグループはコンマで区切ってください(既存のグループを入力してください)'
en: 'Please enter the group. Multiple groups are separated by commas (please enter the existing group)'
Params groups label:
zh: '用户组'
ja: 'グループ'
en: 'Groups'

View File

@@ -9,19 +9,24 @@ priority: 49
params:
- name: groups
type: str
label: '用户组'
label: "{{ 'Params groups label' | trans }}"
default: 'Users,Remote Desktop Users'
help_text: "{{ 'Params groups help text' | trans }}"
i18n:
Windows account change secret rdp verify:
zh: '使用 Ansible 模块 win_user 执行 Windows 账号改密 RDP 协议测试最后的可连接性'
ja: 'Ansibleモジュールwin_userWindowsアカウントの改密RDPプロトコルテストの最後の接続性を実行する'
en: 'Using the Ansible module win_user performs Windows account encryption RDP protocol testing for final connectivity'
zh: '使用 Ansible 模块 win_user 执行 Windows 账号改密(最后使用 Python 模块 pyfreerdp 验证账号的可连接性'
ja: 'Ansible モジュール win_user を使用して Windows アカウントのパスワードを変更します (最後に Python モジュール pyfreerdp を使用してアカウントの接続を確認します)'
en: 'Use the Ansible module win_user to change the Windows account password (finally use the Python module pyfreerdp to verify the account connectivity)'
Params groups help text:
zh: '请输入用户组,多个用户组使用逗号分隔(需填写已存在的用户组)'
ja: 'グループを入力してください。複数のグループはコンマで区切ってください(既存のグループを入力してください)'
en: 'Please enter the group. Multiple groups are separated by commas (please enter the existing group)'
Params groups label:
zh: '用户组'
ja: 'グループ'
en: 'Groups'

View File

@@ -30,28 +30,28 @@ class ChangeSecretManager(BaseChangeSecretPushManager):
record = self.get_or_create_record(asset, account, h['name'])
new_secret, private_key_path = self.handle_ssh_secret(account.secret_type, record.new_secret, path_dir)
h = self.gen_inventory(h, account, new_secret, private_key_path, asset)
return h
return h, record
def get_or_create_record(self, asset, account, name):
asset_account_id = f'{asset.id}-{account.id}'
if asset_account_id in self.record_map:
record_id = self.record_map[asset_account_id]
recorder = ChangeSecretRecord.objects.filter(id=record_id).first()
record = ChangeSecretRecord.objects.filter(id=record_id).first()
else:
new_secret = self.get_secret(account)
recorder = self.create_record(asset, account, new_secret)
record = self.create_record(asset, account, new_secret)
self.name_recorder_mapper[name] = recorder
return recorder
self.name_record_mapper[name] = record
return record
def create_record(self, asset, account, new_secret):
recorder = ChangeSecretRecord(
record = ChangeSecretRecord(
asset=asset, account=account, execution=self.execution,
old_secret=account.secret, new_secret=new_secret,
comment=f'{account.username}@{asset.address}'
)
return recorder
return record
def check_secret(self):
if self.secret_strategy == SecretStrategy.custom \
@@ -61,10 +61,10 @@ class ChangeSecretManager(BaseChangeSecretPushManager):
return True
@staticmethod
def get_summary(recorders):
def get_summary(records):
total, succeed, failed = 0, 0, 0
for recorder in recorders:
if recorder.status == ChangeSecretRecordStatusChoice.success.value:
for record in records:
if record.status == ChangeSecretRecordStatusChoice.success.value:
succeed += 1
else:
failed += 1
@@ -73,8 +73,8 @@ class ChangeSecretManager(BaseChangeSecretPushManager):
return summary
def print_summary(self):
recorders = list(self.name_recorder_mapper.values())
summary = self.get_summary(recorders)
records = list(self.name_record_mapper.values())
summary = self.get_summary(records)
print('\n\n' + '-' * 80)
plan_execution_end = _('Plan execution end')
print('{} {}\n'.format(plan_execution_end, local_now_filename()))
@@ -86,7 +86,7 @@ class ChangeSecretManager(BaseChangeSecretPushManager):
if self.secret_type and not self.check_secret():
return
recorders = list(self.name_recorder_mapper.values())
records = list(self.name_record_mapper.values())
if self.record_map:
return
@@ -98,17 +98,17 @@ class ChangeSecretManager(BaseChangeSecretPushManager):
for user in recipients:
ChangeSecretReportMsg(user, context).publish()
if not recorders:
if not records:
return
summary = self.get_summary(recorders)
self.send_recorder_mail(recipients, recorders, summary)
summary = self.get_summary(records)
self.send_record_mail(recipients, records, summary)
def send_recorder_mail(self, recipients, recorders, summary):
def send_record_mail(self, recipients, records, summary):
name = self.execution.snapshot['name']
path = os.path.join(os.path.dirname(settings.BASE_DIR), 'tmp')
filename = os.path.join(path, f'{name}-{local_now_filename()}-{time.time()}.xlsx')
if not self.create_file(recorders, filename):
if not self.create_file(records, filename):
return
for user in recipients:
@@ -121,9 +121,9 @@ class ChangeSecretManager(BaseChangeSecretPushManager):
os.remove(filename)
@staticmethod
def create_file(recorders, filename):
def create_file(records, filename):
serializer_cls = ChangeSecretRecordBackUpSerializer
serializer = serializer_cls(recorders, many=True)
serializer = serializer_cls(records, many=True)
header = [str(v.label) for v in serializer.child.fields.values()]
rows = [[str(i) for i in row.values()] for row in serializer.data]

View File

@@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:a2805a0264fc07ae597704841ab060edef8bf74654f525bc778cb9195d8cad0e
size 2547712

View File

@@ -12,13 +12,16 @@ from accounts.models import Account, AccountRisk, RiskChoice
from assets.automations.base.manager import BaseManager
from common.const import ConfirmOrIgnore
from common.decorators import bulk_create_decorator, bulk_update_decorator
from settings.models import LeakPasswords
# 已设置手动 finish
@bulk_create_decorator(AccountRisk)
def create_risk(data):
return AccountRisk(**data)
# 已设置手动 finish
@bulk_update_decorator(AccountRisk, update_fields=["details", "status"])
def update_risk(risk):
return risk
@@ -157,10 +160,8 @@ class CheckLeakHandler(BaseCheckHandler):
if not account.secret:
return False
sql = 'SELECT 1 FROM passwords WHERE password = ? LIMIT 1'
self.cursor.execute(sql, (account.secret,))
leak = self.cursor.fetchone() is not None
return leak
is_exist = LeakPasswords.objects.using('sqlite').filter(password=account.secret).exists()
return is_exist
def clean(self):
self.cursor.close()
@@ -218,6 +219,9 @@ class CheckAccountManager(BaseManager):
"details": [{"datetime": now, 'type': 'init'}],
})
create_risk.finish()
update_risk.finish()
def pre_run(self):
super().pre_run()
self.assets = self.execution.get_all_assets()
@@ -236,6 +240,11 @@ class CheckAccountManager(BaseManager):
print("Check: {} => {}".format(account, msg))
if not error:
AccountRisk.objects.filter(
asset=account.asset,
username=account.username,
risk=handler.risk
).delete()
continue
self.add_risk(handler.risk, account)
self.commit_risks(_assets)
@@ -265,7 +274,7 @@ class CheckAccountManager(BaseManager):
handler.clean()
def get_report_subject(self):
return "Check account report of %s" % self.execution.id
return _("Check account report of {}").format(self.execution.id)
def get_report_template(self):
return "accounts/check_account_report.html"

View File

@@ -30,6 +30,16 @@ common_risk_items = [
diff_items = risk_items + common_risk_items
@bulk_create_decorator(AccountRisk)
def _create_risk(data):
return AccountRisk(**data)
@bulk_update_decorator(AccountRisk, update_fields=["details"])
def _update_risk(account):
return account
def format_datetime(value):
if isinstance(value, timezone.datetime):
return value.strftime("%Y-%m-%d %H:%M:%S")
@@ -141,25 +151,17 @@ class AnalyseAccountRisk:
found = assets_risks.get(key)
if not found:
self._create_risk(dict(**d, details=[detail]))
_create_risk(dict(**d, details=[detail]))
continue
found.details.append(detail)
self._update_risk(found)
@bulk_create_decorator(AccountRisk)
def _create_risk(self, data):
return AccountRisk(**data)
@bulk_update_decorator(AccountRisk, update_fields=["details"])
def _update_risk(self, account):
return account
_update_risk(found)
def lost_accounts(self, asset, lost_users):
if not self.check_risk:
return
for user in lost_users:
self._create_risk(
_create_risk(
dict(
asset_id=str(asset.id),
username=user,
@@ -176,7 +178,7 @@ class AnalyseAccountRisk:
self._analyse_item_changed(ga, d)
if not sys_found:
basic = {"asset": asset, "username": d["username"], 'gathered_account': ga}
self._create_risk(
_create_risk(
dict(
**basic,
risk=RiskChoice.new_found,
@@ -388,6 +390,7 @@ class GatherAccountsManager(AccountBasePlaybookManager):
self.update_gathered_account(ori_account, d)
ori_found = username in ori_users
need_analyser_gather_account.append((asset, ga, d, ori_found))
# 这里顺序不能调整risk 外键关联了 gathered_account 主键 id所以在创建 risk 需要保证 gathered_account 已经创建完成
self.create_gathered_account.finish()
self.update_gathered_account.finish()
for analysis_data in need_analyser_gather_account:
@@ -403,6 +406,9 @@ class GatherAccountsManager(AccountBasePlaybookManager):
present=True
)
# 因为有 bulk create, bulk update, 所以这里需要 sleep 一下,等待数据同步
_update_risk.finish()
_create_risk.finish()
time.sleep(0.5)
def get_report_template(self):

View File

@@ -20,10 +20,11 @@
become_private_key_path: "{{ jms_custom_become_private_key_path | default(None) }}"
old_ssh_version: "{{ jms_asset.old_ssh_version | default(False) }}"
gateway_args: "{{ jms_asset.ansible_ssh_common_args | default(None) }}"
recv_timeout: "{{ params.recv_timeout | default(30) }}"
register: ping_info
delegate_to: localhost
- name: Change asset password (paramiko)
- name: Push asset password (paramiko)
custom_command:
login_user: "{{ jms_account.username }}"
login_password: "{{ jms_account.secret }}"
@@ -39,7 +40,10 @@
name: "{{ account.username }}"
password: "{{ account.secret }}"
commands: "{{ params.commands }}"
first_conn_delay_time: "{{ first_conn_delay_time | default(0.5) }}"
answers: "{{ params.answers }}"
recv_timeout: "{{ params.recv_timeout | default(30) }}"
delay_time: "{{ params.delay_time | default(2) }}"
prompt: "{{ params.prompt | default('.*') }}"
ignore_errors: true
when: ping_info is succeeded and check_conn_after_change
register: change_info
@@ -58,5 +62,6 @@
become_private_key_path: "{{ account.become.ansible_ssh_private_key_file | default(None) }}"
old_ssh_version: "{{ jms_asset.old_ssh_version | default(False) }}"
gateway_args: "{{ jms_asset.ansible_ssh_common_args | default(None) }}"
recv_timeout: "{{ params.recv_timeout | default(30) }}"
delegate_to: localhost
when: check_conn_after_change

View File

@@ -10,10 +10,30 @@ protocol: ssh
priority: 50
params:
- name: commands
type: list
type: text
label: "{{ 'Params commands label' | trans }}"
default: [ '' ]
default: ''
help_text: "{{ 'Params commands help text' | trans }}"
- name: recv_timeout
type: int
label: "{{ 'Params recv_timeout label' | trans }}"
default: 30
help_text: "{{ 'Params recv_timeout help text' | trans }}"
- name: delay_time
type: int
label: "{{ 'Params delay_time label' | trans }}"
default: 2
help_text: "{{ 'Params delay_time help text' | trans }}"
- name: prompt
type: str
label: "{{ 'Params prompt label' | trans }}"
default: '.*'
help_text: "{{ 'Params prompt help text' | trans }}"
- name: answers
type: text
label: "{{ 'Params answer label' | trans }}"
default: '.*'
help_text: "{{ 'Params answer help text' | trans }}"
i18n:
SSH account push:
@@ -22,11 +42,91 @@ i18n:
en: 'Custom push using SSH command line'
Params commands help text:
zh: '自定义命令中如需包含账号的 账号、密码、SSH 连接的用户密码 字段,<br />请使用 &#123;username&#125;、&#123;password&#125;、&#123;login_password&#125;格式,执行任务时会进行替换 。<br />比如针对 Cisco 主机进行改密,一般需要配置五条命令:<br />1. enable<br />2. &#123;login_password&#125;<br />3. configure terminal<br />4. username &#123;username&#125; privilege 0 password &#123;password&#125; <br />5. end'
ja: 'カスタム コマンドに SSH 接続用のアカウント番号、パスワード、ユーザー パスワード フィールドを含める必要がある場合は、<br />&#123;ユーザー名&#125;、&#123;パスワード&#125;、&#123;login_password& を使用してください。 # 125; 形式。タスクの実行時に置き換えられます。 <br />たとえば、Cisco ホストのパスワードを変更するには、通常、次の 5 つのコマンドを設定する必要があります:<br />1.enable<br />2.&#123;login_password&#125;<br />3 .ターミナルの設定<br / >4. ユーザー名 &#123;ユーザー名&#125; 権限 0 パスワード &#123;パスワード&#125; <br />5. 終了'
en: 'If the custom command needs to include the account number, password, and user password field for SSH connection,<br />Please use &#123;username&#125;, &#123;password&#125;, &#123;login_password&# 125; format, which will be replaced when executing the task. <br />For example, to change the password of a Cisco host, you generally need to configure five commands:<br />1. enable<br />2. &#123;login_password&#125;<br />3. configure terminal<br / >4. username &#123;username&#125; privilege 0 password &#123;password&#125; <br />5. end'
zh: |
请将命令中的指定位置改成特殊符号 <br />
1. 推送账号 -> {username} <br />
2. 推送密码 -> {password} <br />
3. 登录用户密码 -> {login_password} <br />
<strong>多条命令使用换行分割,</strong>执行任务时系统会根据特殊符号替换真实数据。<br />
比如针对 Cisco 主机进行推送,一般需要配置五条命令:<br />
enable <br />
{login_password} <br />
configure terminal <br />
username {username} privilege 0 password {password} <br />
end <br />
ja: |
コマンド内の指定された位置を特殊記号に変更してください。<br />
新しいパスワード(アカウント押す) -> {username} <br />
新しいパスワード(パスワード押す) -> {password} <br />
ログインユーザーパスワード -> {login_password} <br />
<strong>複数のコマンドは改行で区切り、</strong>タスクを実行するときにシステムは特殊記号を使用して実際のデータを置き換えます。<br />
例えば、Cisco機器のパスワードを変更する場合、一般的には5つのコマンドを設定する必要があります<br />
enable <br />
{login_password} <br />
configure terminal <br />
username {username} privilege 0 password {password} <br />
end <br />
en: |
Please change the specified positions in the command to special symbols. <br />
Change password account -> {username} <br />
Change password -> {password} <br />
Login user password -> {login_password} <br />
<strong>Multiple commands are separated by new lines,</strong> and when executing tasks, <br />
the system will replace the special symbols with real data. <br />
For example, to push the password for a Cisco device, you generally need to configure five commands: <br />
enable <br />
{login_password} <br />
configure terminal <br />
username {username} privilege 0 password {password} <br />
end <br />
Params commands label:
zh: '自定义命令'
ja: 'カスタムコマンド'
en: 'Custom command'
Params recv_timeout label:
zh: '超时时间'
ja: 'タイムアウト'
en: 'Timeout'
Params recv_timeout help text:
zh: '等待命令结果返回的超时时间(秒)'
ja: 'コマンドの結果を待つタイムアウト時間(秒)'
en: 'The timeout for waiting for the command result to return (Seconds)'
Params delay_time label:
zh: '延迟发送时间'
ja: '遅延送信時間'
en: 'Delayed send time'
Params delay_time help text:
zh: '每条命令延迟发送的时间间隔(秒)'
ja: '各コマンド送信の遅延間隔(秒)'
en: 'Time interval for each command delay in sending (Seconds)'
Params prompt label:
zh: '提示符'
ja: 'ヒント'
en: 'Prompt'
Params prompt help text:
zh: '终端连接后显示的提示符信息(正则表达式)'
ja: 'ターミナル接続後に表示されるプロンプト情報(正規表現)'
en: 'Prompt information displayed after terminal connection (Regular expression)'
Params answer label:
zh: '命令结果'
ja: 'コマンド結果'
en: 'Command result'
Params answer help text:
zh: |
根据结果匹配度决定是否执行下一条命令,输入框的内容和上方 “自定义命令” 内容按行一一对应(正则表达式)
ja: |
結果の一致度に基づいて次のコマンドを実行するかどうかを決定します。
入力欄の内容は、上の「カスタムコマンド」の内容と行ごとに対応しています(せいきひょうげん)
en: |
Decide whether to execute the next command based on the result match.
The input content corresponds line by line with the content
of the `Custom command` above. (Regular expression)

View File

@@ -54,3 +54,5 @@
connection_options:
- tlsAllowInvalidHostnames: "{{ jms_asset.spec_info.allow_invalid_cert}}"
when: check_conn_after_change
register: result
failed_when: not result.is_available

View File

@@ -39,7 +39,8 @@
name: "{{ account.username }}"
password: "{{ account.secret }}"
host: "%"
priv: "{{ account.username + '.*:USAGE' if db_name == '' else db_name + '.*:ALL' }}"
priv: "{{ omit if db_name == '' else db_name + '.*:ALL' }}"
append_privs: "{{ db_name != '' | bool }}"
ignore_errors: true
when: db_info is succeeded

View File

@@ -8,7 +8,7 @@ type:
params:
- name: groups
type: str
label: '用户组'
label: "{{ 'Params groups label' | trans }}"
default: 'Users,Remote Desktop Users'
help_text: "{{ 'Params groups help text' | trans }}"
@@ -22,3 +22,8 @@ i18n:
zh: '请输入用户组,多个用户组使用逗号分隔(需填写已存在的用户组)'
ja: 'グループを入力してください。複数のグループはコンマで区切ってください(既存のグループを入力してください)'
en: 'Please enter the group. Multiple groups are separated by commas (please enter the existing group)'
Params groups label:
zh: '用户组'
ja: 'グループ'
en: 'Groups'

View File

@@ -9,7 +9,7 @@ type:
params:
- name: groups
type: str
label: '用户组'
label: "{{ 'Params groups label' | trans }}"
default: 'Users,Remote Desktop Users'
help_text: "{{ 'Params groups help text' | trans }}"
@@ -23,3 +23,8 @@ i18n:
zh: '请输入用户组,多个用户组使用逗号分隔(需填写已存在的用户组)'
ja: 'グループを入力してください。複数のグループはコンマで区切ってください(既存のグループを入力してください)'
en: 'Please enter the group. Multiple groups are separated by commas (please enter the existing group)'
Params groups label:
zh: '用户组'
ja: 'グループ'
en: 'Groups'

View File

@@ -9,7 +9,7 @@ priority: 49
params:
- name: groups
type: str
label: '用户组'
label: "{{ 'Params groups label' | trans }}"
default: 'Users,Remote Desktop Users'
help_text: "{{ 'Params groups help text' | trans }}"
@@ -23,3 +23,8 @@ i18n:
zh: '请输入用户组,多个用户组使用逗号分隔(需填写已存在的用户组)'
ja: 'グループを入力してください。複数のグループはコンマで区切ってください(既存のグループを入力してください)'
en: 'Please enter the group. Multiple groups are separated by commas (please enter the existing group)'
Params groups label:
zh: '用户组'
ja: 'グループ'
en: 'Groups'

View File

@@ -12,7 +12,7 @@ logger = get_logger(__name__)
class PushAccountManager(BaseChangeSecretPushManager):
@staticmethod
def require_update_version(account, recorder):
def require_update_version(account, record):
account.skip_history_when_saving = True
return False
@@ -31,29 +31,29 @@ class PushAccountManager(BaseChangeSecretPushManager):
secret_type = account.secret_type
if not secret:
raise ValueError(_('Secret cannot be empty'))
self.get_or_create_record(asset, account, h['name'])
record = self.get_or_create_record(asset, account, h['name'])
new_secret, private_key_path = self.handle_ssh_secret(secret_type, secret, path_dir)
h = self.gen_inventory(h, account, new_secret, private_key_path, asset)
return h
return h, record
def get_or_create_record(self, asset, account, name):
asset_account_id = f'{asset.id}-{account.id}'
if asset_account_id in self.record_map:
record_id = self.record_map[asset_account_id]
recorder = PushSecretRecord.objects.filter(id=record_id).first()
record = PushSecretRecord.objects.filter(id=record_id).first()
else:
recorder = self.create_record(asset, account)
record = self.create_record(asset, account)
self.name_recorder_mapper[name] = recorder
return recorder
self.name_record_mapper[name] = record
return record
def create_record(self, asset, account):
recorder = PushSecretRecord(
record = PushSecretRecord(
asset=asset, account=account, execution=self.execution,
comment=f'{account.username}@{asset.address}'
)
return recorder
return record
def print_summary(self):
print('\n\n' + '-' * 80)

View File

@@ -3,7 +3,7 @@
vars:
ansible_shell_type: sh
ansible_connection: local
ansible_python_interpreter: /opt/py3/bin/python
ansible_python_interpreter: "{{ local_python_interpreter }}"
tasks:
- name: Verify account (pyfreerdp)

View File

@@ -16,3 +16,5 @@
ssl_certfile: "{{ jms_asset.secret_info.client_key | default('') }}"
connection_options:
- tlsAllowInvalidHostnames: "{{ jms_asset.spec_info.allow_invalid_cert }}"
register: result
failed_when: not result.is_available

View File

@@ -8,6 +8,7 @@
ansible_user: "{{ account.username }}"
ansible_password: "{{ account.secret }}"
ansible_ssh_private_key_file: "{{ account.private_key_path }}"
ansible_timeout: 30
when: not account.become.ansible_become
- name: Verify account connectivity(Switch)
@@ -20,4 +21,5 @@
ansible_become_method: "{{ account.become.ansible_become_method }}"
ansible_become_user: "{{ account.become.ansible_become_user }}"
ansible_become_password: "{{ account.become.ansible_become_password }}"
ansible_timeout: 30
when: account.become.ansible_become

View File

@@ -9,3 +9,4 @@
vars:
ansible_user: "{{ account.full_username }}"
ansible_password: "{{ account.secret }}"
ansible_timeout: 30

View File

@@ -85,6 +85,7 @@ class VerifyAccountManager(AccountBasePlaybookManager):
def on_host_error(self, host, error, result):
account = self.host_account_mapper.get(host)
try:
account.set_connectivity(Connectivity.ERR)
error_tp = account.get_err_connectivity(error)
account.set_connectivity(error_tp)
except Exception as e:
print(f'\033[31m Update account {account.name} connectivity failed: {e} \033[0m\n')

View File

@@ -17,7 +17,7 @@ __all__ = [
'AutomationTypes', 'SecretStrategy', 'SSHKeyStrategy', 'Connectivity',
'DEFAULT_PASSWORD_LENGTH', 'DEFAULT_PASSWORD_RULES', 'TriggerChoice',
'PushAccountActionChoice', 'AccountBackupType', 'ChangeSecretRecordStatusChoice',
'GatherAccountDetailField'
'GatherAccountDetailField', 'ChangeSecretAccountStatus'
]
@@ -117,6 +117,12 @@ class ChangeSecretRecordStatusChoice(models.TextChoices):
pending = 'pending', _('Pending')
class ChangeSecretAccountStatus(models.TextChoices):
QUEUED = 'queued', _('Queued')
READY = 'ready', _('Ready')
PROCESSING = 'processing', _('Processing')
class GatherAccountDetailField(models.TextChoices):
can_login = 'can_login', _('Can login')
superuser = 'superuser', _('Superuser')

View File

@@ -17,6 +17,7 @@ from common.utils.timezone import local_zero_hour, local_now
from .const.automation import ChangeSecretRecordStatusChoice
from .models import Account, GatheredAccount, ChangeSecretRecord, PushSecretRecord, IntegrationApplication, \
AutomationExecution
from .utils import account_secret_task_status
logger = get_logger(__file__)
@@ -233,7 +234,7 @@ class AutomationExecutionFilterSet(DaysExecutionFilterMixin, BaseFilterSet):
class Meta:
model = AutomationExecution
fields = ["days", 'trigger', 'automation_id', 'automation__name']
fields = ["days", 'trigger', 'automation__name']
class PushAccountRecordFilterSet(SecretRecordMixin, UUIDFilterMixin, BaseFilterSet):
@@ -242,3 +243,25 @@ class PushAccountRecordFilterSet(SecretRecordMixin, UUIDFilterMixin, BaseFilterS
class Meta:
model = PushSecretRecord
fields = ["id", "status", "asset_id", "execution_id"]
class ChangeSecretStatusFilterSet(BaseFilterSet):
asset_name = drf_filters.CharFilter(
field_name="asset__name", lookup_expr="icontains"
)
status = drf_filters.CharFilter(method='filter_dynamic')
execution_id = drf_filters.CharFilter(method='filter_dynamic')
class Meta:
model = Account
fields = ["username"]
@staticmethod
def filter_dynamic(queryset, name, value):
_ids = list(queryset.values_list('id', flat=True))
data_map = {
_id: account_secret_task_status.get(str(_id)).get(name)
for _id in _ids
}
matched = [_id for _id, v in data_map.items() if v == value]
return queryset.filter(id__in=matched)

View File

@@ -46,11 +46,16 @@ class Migration(migrations.Migration):
],
options={
'verbose_name': 'Account',
'permissions': [('view_accountsecret', 'Can view asset account secret'),
('view_historyaccount', 'Can view asset history account'),
('view_historyaccountsecret', 'Can view asset history account secret'),
('verify_account', 'Can verify account'), ('push_account', 'Can push account'),
('remove_account', 'Can remove account')],
'permissions': [
('view_accountsecret', 'Can view asset account secret'),
('view_historyaccount', 'Can view asset history account'),
('view_historyaccountsecret', 'Can view asset history account secret'),
('verify_account', 'Can verify account'),
('push_account', 'Can push account'),
('remove_account', 'Can remove account'),
('view_accountsession', 'Can view session'),
('view_accountactivity', 'Can view activity')
],
},
),
migrations.CreateModel(

View File

@@ -335,6 +335,7 @@ class Migration(migrations.Migration):
],
options={
"abstract": False,
"verbose_name": "Check engine",
},
),
migrations.CreateModel(
@@ -629,10 +630,15 @@ class Migration(migrations.Migration):
name="connectivity",
field=models.CharField(
choices=[
("-", "Unknown"),
("na", "N/A"),
("ok", "OK"),
("err", "Error"),
('-', 'Unknown'),
('na', 'N/A'),
('ok', 'OK'),
('err', 'Error'),
('auth_err', 'Authentication error'),
('password_err', 'Invalid password error'),
('openssh_key_err', 'OpenSSH key error'),
('ntlm_err', 'NTLM credentials rejected error'),
('create_temp_err', 'Create temporary error')
],
default="-",
max_length=16,

View File

@@ -0,0 +1,29 @@
# Generated by Django 4.1.13 on 2025-05-06 10:23
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('accounts', '0006_alter_accountrisk_username_and_more'),
]
operations = [
migrations.AlterField(
model_name='account',
name='connectivity',
field=models.CharField(choices=[
('-', 'Unknown'),
('na', 'N/A'),
('ok', 'OK'),
('err', 'Error'),
('rdp_err', 'RDP error'),
('auth_err', 'Authentication error'),
('password_err', 'Invalid password error'),
('openssh_key_err', 'OpenSSH key error'),
('ntlm_err', 'NTLM credentials rejected error'),
('create_temp_err', 'Create temporary error')
],
default='-', max_length=16, verbose_name='Connectivity'),
),
]

View File

@@ -116,6 +116,8 @@ class Account(AbsConnectivity, LabeledMixin, BaseAccount, JSONFilterMixin):
('verify_account', _('Can verify account')),
('push_account', _('Can push account')),
('remove_account', _('Can remove account')),
('view_accountsession', _('Can view session')),
('view_accountactivity', _('Can view activity')),
]
def __str__(self):
@@ -166,9 +168,12 @@ class Account(AbsConnectivity, LabeledMixin, BaseAccount, JSONFilterMixin):
return self.ds.domain_name
return ''
def username_has_domain(self):
return '@' in self.username or '\\' in self.username
@property
def full_username(self):
if self.ds_domain:
if not self.username_has_domain() and self.ds_domain:
return '{}@{}'.format(self.username, self.ds_domain)
return self.username

View File

@@ -68,8 +68,10 @@ class AccountRisk(JMSOrgBaseModel):
related_name='risks', null=True
)
risk = models.CharField(max_length=128, verbose_name=_('Risk'), choices=RiskChoice.choices)
status = models.CharField(max_length=32, choices=ConfirmOrIgnore.choices, default=ConfirmOrIgnore.pending,
blank=True, verbose_name=_('Status'))
status = models.CharField(
max_length=32, choices=ConfirmOrIgnore.choices, default=ConfirmOrIgnore.pending,
blank=True, verbose_name=_('Status')
)
details = models.JSONField(default=list, verbose_name=_('Detail'))
class Meta:
@@ -119,6 +121,9 @@ class CheckAccountEngine(JMSBaseModel):
def __str__(self):
return self.name
class Meta:
verbose_name = _('Check engine')
@staticmethod
def get_default_engines():
data = [
@@ -128,7 +133,7 @@ class CheckAccountEngine(JMSBaseModel):
"name": _("Check the discovered accounts"),
"comment": _(
"Perform checks and analyses based on automatically discovered account results, "
"including user groups, public keys, sudoers, and other information"
"including user groups, public keys, sudoers, and other information."
)
},
{
@@ -144,13 +149,13 @@ class CheckAccountEngine(JMSBaseModel):
"id": "00000000-0000-0000-0000-000000000003",
"slug": "check_account_repeat",
"name": _("Check if the account and password are repeated"),
"comment": _("Check if the account is the same as other accounts")
"comment": _("Check if the account is the same as other accounts.")
},
{
"id": "00000000-0000-0000-0000-000000000004",
"slug": "check_account_leak",
"name": _("Check whether the account password is a common password"),
"comment": _("Check whether the account password is a commonly leaked password")
"comment": _("Check whether the account password is a commonly leaked password.")
},
]
return data

View File

@@ -6,6 +6,7 @@ from common.tasks import send_mail_attachment_async, upload_backup_to_obj_storag
from notifications.notifications import UserMessage
from terminal.models.component.storage import ReplayStorage
from users.models import User
from users.utils import activate_user_language
class AccountBackupExecutionTaskMsg:
@@ -28,9 +29,10 @@ class AccountBackupExecutionTaskMsg:
).format(name)
def publish(self, attachment_list=None):
send_mail_attachment_async(
self.subject, self.message, [self.user.email], attachment_list
)
with activate_user_language(self.user):
send_mail_attachment_async(
self.subject, self.message, [self.user.email], attachment_list
)
class AccountBackupByObjStorageExecutionTaskMsg:
@@ -74,9 +76,10 @@ class ChangeSecretExecutionTaskMsg:
return self.summary + '\n' + default_message
def publish(self, attachments=None):
send_mail_attachment_async(
self.subject, self.message, [self.user.email], attachments
)
with activate_user_language(self.user):
send_mail_attachment_async(
self.subject, self.message, [self.user.email], attachments
)
class GatherAccountChangeMsg(UserMessage):

View File

@@ -23,7 +23,7 @@ TYPE_CHOICES = [
("delete_both", _("Delete remote")),
("add_account", _("Add account")),
("change_password_add", _("Change password and Add")),
("change_password", _("Change password")),
("change_password", _("Change secret")),
]

View File

@@ -246,6 +246,7 @@ class AccountSerializer(AccountCreateUpdateSerializerMixin, BaseAccountSerialize
'source', 'source_id', 'secret_reset',
] + AccountCreateUpdateSerializerMixin.Meta.fields + automation_fields
read_only_fields = BaseAccountSerializer.Meta.read_only_fields + automation_fields
fields = [f for f in fields if f not in ['spec_info']]
extra_kwargs = {
**BaseAccountSerializer.Meta.extra_kwargs,
'name': {'required': False},
@@ -268,7 +269,7 @@ class AccountDetailSerializer(AccountSerializer):
class Meta(AccountSerializer.Meta):
model = Account
fields = AccountSerializer.Meta.fields + ['has_secret']
fields = AccountSerializer.Meta.fields + ['has_secret', 'spec_info']
read_only_fields = AccountSerializer.Meta.read_only_fields + ['has_secret']

View File

@@ -75,7 +75,7 @@ class BaseAccountSerializer(
fields_mini = ["id", "name", "username"]
fields_small = fields_mini + [
"secret_type", "secret", "passphrase",
"privileged", "is_active", "spec_info",
"privileged", "is_active",
]
fields_other = ["created_by", "date_created", "date_updated", "comment"]
fields = fields_small + fields_other + ["labels"]

View File

@@ -5,6 +5,7 @@ from rest_framework import serializers
from accounts.models import IntegrationApplication
from acls.serializers.rules import ip_group_child_validator, ip_group_help_text
from common.serializers.fields import JSONManyToManyField
from common.utils import random_string
from orgs.mixins.serializers import BulkOrgResourceModelSerializer
@@ -37,6 +38,10 @@ class IntegrationApplicationSerializer(BulkOrgResourceModelSerializer):
data['logo'] = static('img/logo.png')
return data
def validate(self, attrs):
attrs['secret'] = random_string(36)
return attrs
class IntegrationAccountSecretSerializer(serializers.Serializer):
asset = serializers.CharField(required=False, allow_blank=True)

View File

@@ -57,11 +57,15 @@ class AccountTemplateSerializer(BaseAccountSerializer):
fields_unimport_template = ['push_params']
class AccountTemplateSecretSerializer(SecretReadableMixin, AccountTemplateSerializer):
class AccountDetailTemplateSerializer(AccountTemplateSerializer):
class Meta(AccountTemplateSerializer.Meta):
fields = AccountTemplateSerializer.Meta.fields + ['spec_info']
class AccountTemplateSecretSerializer(SecretReadableMixin, AccountDetailTemplateSerializer):
class Meta(AccountDetailTemplateSerializer.Meta):
fields = AccountDetailTemplateSerializer.Meta.fields
extra_kwargs = {
**AccountTemplateSerializer.Meta.extra_kwargs,
**AccountDetailTemplateSerializer.Meta.extra_kwargs,
'secret': {'write_only': False},
'spec_info': {'label': _('Spec info')},
}

View File

@@ -1,8 +1,9 @@
# -*- coding: utf-8 -*-
#
from django.conf import settings
from django.utils.translation import gettext_lazy as _
from accounts.const import AutomationTypes
from accounts.const import AutomationTypes, AccountBackupType
from accounts.models import BackupAccountAutomation
from common.serializers.fields import EncryptedField
from common.utils import get_logger
@@ -41,6 +42,17 @@ class BackupAccountSerializer(BaseAutomationSerializer):
'types': {'label': _('Asset type')}
}
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.set_backup_type_choices()
def set_backup_type_choices(self):
field_backup_type = self.fields.get("backup_type")
if not field_backup_type:
return
if not settings.XPACK_LICENSE_IS_VALID:
field_backup_type._choices.pop(AccountBackupType.object_storage, None)
@property
def model_type(self):
return AutomationTypes.backup_account

View File

@@ -16,6 +16,7 @@ from assets.models import Asset
from common.serializers.fields import LabeledChoiceField, ObjectRelatedField
from common.utils import get_logger
from .base import BaseAutomationSerializer
from ...utils import account_secret_task_status
logger = get_logger(__file__)
@@ -26,6 +27,7 @@ __all__ = [
'ChangeSecretRecordBackUpSerializer',
'ChangeSecretUpdateAssetSerializer',
'ChangeSecretUpdateNodeSerializer',
'ChangeSecretAccountSerializer'
]
@@ -179,3 +181,24 @@ class ChangeSecretUpdateNodeSerializer(serializers.ModelSerializer):
class Meta:
model = ChangeSecretAutomation
fields = ['id', 'nodes']
class ChangeSecretAccountSerializer(serializers.ModelSerializer):
asset = ObjectRelatedField(
queryset=Asset.objects.all(), required=False, label=_("Asset")
)
ttl = serializers.SerializerMethodField(label=_('TTL'))
meta = serializers.SerializerMethodField(label=_('Meta'))
class Meta:
model = Account
fields = ['id', 'username', 'asset', 'meta', 'ttl']
read_only_fields = fields
@staticmethod
def get_meta(obj):
return account_secret_task_status.get(str(obj.id))
@staticmethod
def get_ttl(obj):
return account_secret_task_status.get_ttl(str(obj.id))

View File

@@ -28,7 +28,7 @@ class DiscoverAccountAutomationSerializer(BaseAutomationSerializer):
+ read_only_fields)
extra_kwargs = {
'check_risk': {
'help_text': _('Whether to check the risk of the gathered accounts.'),
'help_text': _('Whether to check the risk of the discovered accounts.'),
},
**BaseAutomationSerializer.Meta.extra_kwargs
}

View File

@@ -1,4 +1,5 @@
import datetime
from collections import defaultdict
from celery import shared_task
from django.db.models import Q
@@ -72,24 +73,43 @@ def execute_automation_record_task(record_ids, tp):
task_name = gettext_noop('Execute automation record')
with tmp_to_root_org():
records = ChangeSecretRecord.objects.filter(id__in=record_ids)
records = ChangeSecretRecord.objects.filter(id__in=record_ids).order_by('-date_updated')
if not records:
logger.error('No automation record found: {}'.format(record_ids))
logger.error(f'No automation record found: {record_ids}')
return
record = records[0]
record_map = {f'{record.asset_id}-{record.account_id}': str(record.id) for record in records}
task_snapshot = {
'params': {},
'record_map': record_map,
'secret': record.new_secret,
'secret_type': record.execution.snapshot.get('secret_type'),
'assets': [str(instance.asset_id) for instance in records],
'accounts': [str(instance.account_id) for instance in records],
}
with tmp_to_org(record.execution.org_id):
quickstart_automation_by_snapshot(task_name, tp, task_snapshot)
seen_accounts = set()
unique_records = []
for rec in records:
acct = str(rec.account_id)
if acct not in seen_accounts:
seen_accounts.add(acct)
unique_records.append(rec)
exec_groups = defaultdict(list)
for rec in unique_records:
exec_groups[rec.execution_id].append(rec)
for __, group in exec_groups.items():
latest_rec = group[0]
snapshot = getattr(latest_rec.execution, 'snapshot', {}) or {}
record_map = {f"{r.asset_id}-{r.account_id}": str(r.id) for r in group}
assets = [str(r.asset_id) for r in group]
accounts = [str(r.account_id) for r in group]
task_snapshot = {
'params': {},
'record_map': record_map,
'secret': latest_rec.new_secret,
'secret_type': snapshot.get('secret_type'),
'assets': assets,
'accounts': accounts,
}
with tmp_to_org(latest_rec.execution.org_id):
quickstart_automation_by_snapshot(task_name, tp, task_snapshot)
@shared_task(
@@ -107,16 +127,18 @@ def execute_automation_record_task(record_ids, tp):
)
@register_as_period_task(crontab=CRONTAB_AT_AM_THREE)
def clean_change_secret_and_push_record_period():
from accounts.models import ChangeSecretRecord
from accounts.models import ChangeSecretRecord, PushSecretRecord
print('Start clean change secret and push record period')
with tmp_to_root_org():
now = timezone.now()
days = get_log_keep_day('ACCOUNT_CHANGE_SECRET_RECORD_KEEP_DAYS')
expired_day = now - datetime.timedelta(days=days)
records = ChangeSecretRecord.objects.filter(
date_updated__lt=expired_day
).filter(
Q(execution__isnull=True) | Q(asset__isnull=True) | Q(account__isnull=True)
)
expired_time = now - datetime.timedelta(days=days)
records.delete()
null_related_q = Q(execution__isnull=True) | Q(asset__isnull=True) | Q(account__isnull=True)
expired_q = Q(date_updated__lt=expired_time)
ChangeSecretRecord.objects.filter(null_related_q).delete()
ChangeSecretRecord.objects.filter(expired_q).delete()
PushSecretRecord.objects.filter(null_related_q).delete()
PushSecretRecord.objects.filter(expired_q).delete()

View File

@@ -1,37 +1,107 @@
from collections import defaultdict
from celery import shared_task
from django.utils.translation import gettext_noop, gettext_lazy as _
from accounts.const import AutomationTypes
from accounts.const import AutomationTypes, ChangeSecretAccountStatus
from accounts.tasks.common import quickstart_automation_by_snapshot
from accounts.utils import account_secret_task_status
from common.utils import get_logger
from orgs.utils import tmp_to_org
logger = get_logger(__file__)
__all__ = [
'push_accounts_to_assets_task',
'push_accounts_to_assets_task', 'change_secret_accounts_to_assets_task'
]
def _process_accounts(account_ids, automation_model, default_name, automation_type, snapshot=None):
from accounts.models import Account
accounts = Account.objects.filter(id__in=account_ids)
if not accounts:
logger.warning(
"No accounts found for automation task %s with ids %s",
automation_type, account_ids
)
return
task_name = automation_model.generate_unique_name(gettext_noop(default_name))
snapshot = snapshot or {}
snapshot.update({
'accounts': [str(a.id) for a in accounts],
'assets': [str(a.asset_id) for a in accounts],
})
quickstart_automation_by_snapshot(task_name, automation_type, snapshot)
@shared_task(
queue="ansible",
verbose_name=_('Push accounts to assets'),
activity_callback=lambda self, account_ids, *args, **kwargs: (account_ids, None),
description=_(
"When creating or modifying an account requires account push, this task is executed"
"Whenever an account is created or modified and needs pushing to assets, run this task"
)
)
def push_accounts_to_assets_task(account_ids, params=None):
from accounts.models import PushAccountAutomation
from accounts.models import Account
accounts = Account.objects.filter(id__in=account_ids)
task_name = gettext_noop("Push accounts to assets")
task_name = PushAccountAutomation.generate_unique_name(task_name)
task_snapshot = {
'accounts': [str(account.id) for account in accounts],
'assets': [str(account.asset_id) for account in accounts],
snapshot = {
'params': params or {},
}
_process_accounts(
account_ids,
PushAccountAutomation,
_("Push accounts to assets"),
AutomationTypes.push_account,
snapshot=snapshot
)
tp = AutomationTypes.push_account
quickstart_automation_by_snapshot(task_name, tp, task_snapshot)
@shared_task(
queue="ansible",
verbose_name=_('Change secret accounts to assets'),
activity_callback=lambda self, account_ids, *args, **kwargs: (account_ids, None),
description=_(
"When a secret on an account changes and needs pushing to assets, run this task"
)
)
def change_secret_accounts_to_assets_task(account_ids, params=None, snapshot=None, trigger='manual'):
from accounts.models import ChangeSecretAutomation, Account
manager = account_secret_task_status
if trigger == 'delay':
for _id in manager.account_ids:
status = manager.get_status(_id)
# Check if the account is in QUEUED status
if status == ChangeSecretAccountStatus.QUEUED:
account_ids.append(_id)
manager.set_status(_id, ChangeSecretAccountStatus.READY)
if not account_ids:
return
accounts = Account.objects.filter(id__in=account_ids)
if not accounts:
logger.warning(
"No accounts found for change secret automation task with ids %s",
account_ids
)
return
grouped_ids = defaultdict(lambda: defaultdict(list))
for account in accounts:
grouped_ids[account.org_id][account.secret_type].append(str(account.id))
snapshot = snapshot or {}
for org_id, secret_map in grouped_ids.items():
with tmp_to_org(org_id):
for secret_type, ids in secret_map.items():
snapshot['secret_type'] = secret_type
_process_accounts(
ids,
ChangeSecretAutomation,
_("Change secret accounts to assets"),
AutomationTypes.change_secret,
snapshot=snapshot
)

View File

@@ -17,6 +17,7 @@ router.register(r'account-template-secrets', api.AccountTemplateSecretsViewSet,
router.register(r'account-backup-plans', api.BackupAccountViewSet, 'account-backup')
router.register(r'account-backup-plan-executions', api.BackupAccountExecutionViewSet, 'account-backup-execution')
router.register(r'change-secret-automations', api.ChangeSecretAutomationViewSet, 'change-secret-automation')
router.register(r'change-secret-status', api.ChangeSecretStatusViewSet, 'change-secret-status')
router.register(r'change-secret-executions', api.ChangSecretExecutionViewSet, 'change-secret-execution')
router.register(r'change-secret-records', api.ChangeSecretRecordViewSet, 'change-secret-record')
router.register(r'gather-account-automations', api.DiscoverAccountsAutomationViewSet, 'gather-account-automation')

View File

@@ -1,10 +1,11 @@
import copy
from django.conf import settings
from django.core.cache import cache
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 ssh_key_gen, random_string
from common.utils import validate_ssh_private_key, parse_ssh_private_key_str
@@ -61,3 +62,80 @@ def validate_ssh_key(ssh_key, passphrase=None):
if not valid:
raise serializers.ValidationError(_("private key invalid or passphrase error"))
return parse_ssh_private_key_str(ssh_key, passphrase)
class AccountSecretTaskStatus:
def __init__(
self,
prefix='queue:change_secret:',
debounce_key='debounce:change_secret:task',
debounce_timeout=10,
queue_status_timeout=60,
default_timeout=3600,
delayed_task_countdown=20,
):
self.prefix = prefix
self.debounce_key = debounce_key
self.debounce_timeout = debounce_timeout
self.queue_status_timeout = queue_status_timeout
self.default_timeout = default_timeout
self.delayed_task_countdown = delayed_task_countdown
self.enabled = getattr(settings, 'CHANGE_SECRET_AFTER_SESSION_END', False)
def _key(self, identifier):
return f"{self.prefix}{identifier}"
@property
def account_ids(self):
for key in cache.iter_keys(f"{self.prefix}*"):
yield key.split(':')[-1]
def is_debounced(self):
return cache.add(self.debounce_key, True, self.debounce_timeout)
def get_queue_key(self, identifier):
return self._key(identifier)
def set_status(
self,
identifier,
status,
timeout=None,
metadata=None,
use_add=False
):
if not self.enabled:
return
key = self._key(identifier)
data = {"status": status}
if metadata:
data.update(metadata)
if use_add:
return cache.add(key, data, timeout or self.queue_status_timeout)
cache.set(key, data, timeout or self.default_timeout)
def get(self, identifier):
return cache.get(self._key(identifier), {})
def get_status(self, identifier):
if not self.enabled:
return
record = cache.get(self._key(identifier), {})
return record.get("status")
def get_ttl(self, identifier):
return cache.ttl(self._key(identifier))
def clear(self, identifier):
if not self.enabled:
return
cache.delete(self._key(identifier))
account_secret_task_status = AccountSecretTaskStatus()

View File

@@ -9,5 +9,6 @@ class ActionChoices(models.TextChoices):
warning = 'warning', _('Warn')
notice = 'notice', _('Notify')
notify_and_warn = 'notify_and_warn', _('Prompt and warn')
face_verify = 'face_verify', _('Face Verify')
face_online = 'face_online', _('Face Online')
face_verify = 'face_verify', _('Face verify')
face_online = 'face_online', _('Face online')
change_secret = 'change_secret', _('Secret rotation')

View File

@@ -5,7 +5,7 @@ from django.utils.translation import gettext_lazy as _
from common.db.fields import JSONManyToManyField
from common.db.models import JMSBaseModel
from common.utils import contains_ip
from common.utils.time_period import contains_time_period
from common.utils.timezone import contains_time_period
from orgs.mixins.models import OrgModelMixin, OrgManager
from ..const import ActionChoices

View File

@@ -34,16 +34,16 @@ class CommandGroup(JMSOrgBaseModel):
@lazyproperty
def pattern(self):
content = self.content.replace('\r\n', '\n')
if self.type == 'command':
s = self.construct_command_regex(self.content)
s = self.construct_command_regex(content)
else:
s = r'{0}'.format(self.content)
s = r'{0}'.format(r'{}'.format('|'.join(content.split('\n'))))
return s
@classmethod
def construct_command_regex(cls, content):
regex = []
content = content.replace('\r\n', '\n')
for _cmd in content.split('\n'):
cmd = re.sub(r'\s+', ' ', _cmd)
cmd = re.escape(cmd)

View File

@@ -79,6 +79,8 @@ class ActionAclSerializer(serializers.Serializer):
field_action._choices.pop(ActionChoices.face_online, None)
for choice in self.Meta.action_choices_exclude:
field_action._choices.pop(choice, None)
if not settings.XPACK_LICENSE_IS_VALID or not settings.CHANGE_SECRET_AFTER_SESSION_END:
field_action._choices.pop(ActionChoices.change_secret, None)
class BaseACLSerializer(ActionAclSerializer, serializers.Serializer):

View File

@@ -32,9 +32,12 @@ class CommandFilterACLSerializer(BaseSerializer, BulkOrgResourceModelSerializer)
class Meta(BaseSerializer.Meta):
model = CommandFilterACL
fields = BaseSerializer.Meta.fields + ['command_groups']
action_choices_exclude = [ActionChoices.notice,
ActionChoices.face_verify,
ActionChoices.face_online]
action_choices_exclude = [
ActionChoices.notice,
ActionChoices.face_verify,
ActionChoices.face_online,
ActionChoices.change_secret
]
class CommandReviewSerializer(serializers.Serializer):

View File

@@ -14,5 +14,10 @@ class ConnectMethodACLSerializer(BaseSerializer, BulkOrgResourceModelSerializer)
if i not in ['assets', 'accounts']
]
action_choices_exclude = BaseSerializer.Meta.action_choices_exclude + [
ActionChoices.review, ActionChoices.accept, ActionChoices.notice
ActionChoices.review,
ActionChoices.accept,
ActionChoices.notice,
ActionChoices.face_verify,
ActionChoices.face_online,
ActionChoices.change_secret
]

View File

@@ -22,7 +22,8 @@ class LoginACLSerializer(BaseUserACLSerializer, BulkOrgResourceModelSerializer):
ActionChoices.warning,
ActionChoices.notify_and_warn,
ActionChoices.face_online,
ActionChoices.face_verify
ActionChoices.face_verify,
ActionChoices.change_secret
]
def get_rules_serializer(self):

View File

@@ -1,5 +1,7 @@
# coding: utf-8
#
from urllib.parse import urlparse
from django.utils.translation import gettext_lazy as _
from rest_framework import serializers
@@ -8,7 +10,7 @@ from common.utils.ip import is_ip_address, is_ip_network, is_ip_segment
logger = get_logger(__file__)
__all__ = ['RuleSerializer', 'ip_group_child_validator', 'ip_group_help_text']
__all__ = ['RuleSerializer', 'ip_group_child_validator', 'ip_group_help_text', 'address_validator']
def ip_group_child_validator(ip_group_child):
@@ -21,6 +23,19 @@ def ip_group_child_validator(ip_group_child):
raise serializers.ValidationError(error)
def address_validator(value):
parsed = urlparse(value)
is_basic_url = parsed.scheme in ('http', 'https') and parsed.netloc
is_valid = value == '*' \
or is_ip_address(value) \
or is_ip_network(value) \
or is_ip_segment(value) \
or is_basic_url
if not is_valid:
error = _('address invalid: `{}`').format(value)
raise serializers.ValidationError(error)
ip_group_help_text = _(
'With * indicating a match all. '
'Such as: '

View File

@@ -1,10 +1,10 @@
from .asset import *
from .category import *
from .domain import *
from .favorite_asset import *
from .mixin import *
from .my_asset import *
from .node import *
from .platform import *
from .protocol import *
from .tree import *
from .my_asset import *
from .zone import *

View File

@@ -37,12 +37,12 @@ class AssetFilterSet(BaseFilterSet):
platform = drf_filters.CharFilter(method='filter_platform')
is_gateway = drf_filters.BooleanFilter(method='filter_is_gateway')
exclude_platform = drf_filters.CharFilter(field_name="platform__name", lookup_expr='exact', exclude=True)
domain = drf_filters.CharFilter(method='filter_domain')
zone = drf_filters.CharFilter(method='filter_zone')
type = drf_filters.CharFilter(field_name="platform__type", lookup_expr="exact")
category = drf_filters.CharFilter(field_name="platform__category", lookup_expr="exact")
protocols = drf_filters.CharFilter(method='filter_protocols')
domain_enabled = drf_filters.BooleanFilter(
field_name="platform__domain_enabled", lookup_expr="exact"
gateway_enabled = drf_filters.BooleanFilter(
field_name="platform__gateway_enabled", lookup_expr="exact"
)
ping_enabled = drf_filters.BooleanFilter(
field_name="platform__automation__ping_enabled", lookup_expr="exact"
@@ -85,11 +85,11 @@ class AssetFilterSet(BaseFilterSet):
return queryset
@staticmethod
def filter_domain(queryset, name, value):
def filter_zone(queryset, name, value):
if is_uuid(value):
return queryset.filter(domain_id=value)
return queryset.filter(zone_id=value)
else:
return queryset.filter(domain__name__contains=value)
return queryset.filter(zone__name__contains=value)
@staticmethod
def filter_protocols(queryset, name, value):
@@ -171,10 +171,10 @@ class AssetViewSet(SuggestionMixin, BaseAssetViewSet):
@action(methods=["GET"], detail=True, url_path="gateways")
def gateways(self, *args, **kwargs):
asset = self.get_object()
if not asset.domain:
if not asset.zone:
gateways = Gateway.objects.none()
else:
gateways = asset.domain.gateways
gateways = asset.zone.gateways
return self.get_paginated_response_from_queryset(gateways)
@action(methods=['post'], detail=False, url_path='sync-platform-protocols')

View File

@@ -12,10 +12,13 @@ from assets.serializers import PlatformSerializer, PlatformProtocolSerializer, P
from common.api import JMSModelViewSet
from common.permissions import IsValidUser
from common.serializers import GroupedChoiceSerializer
from rbac.models import RoleBinding
__all__ = ['AssetPlatformViewSet', 'PlatformAutomationMethodsApi', 'PlatformProtocolViewSet']
class PlatformFilter(filters.FilterSet):
name__startswith = filters.CharFilter(field_name='name', lookup_expr='istartswith')
@@ -63,6 +66,13 @@ class AssetPlatformViewSet(JMSModelViewSet):
return super().get_object()
return self.get_queryset().get(name=pk)
def check_permissions(self, request):
if self.action == 'list' and RoleBinding.is_org_admin(request.user):
return True
else:
return super().check_permissions(request)
def check_object_permissions(self, request, obj):
if request.method.lower() in ['delete', 'put', 'patch'] and obj.internal:
self.permission_denied(

View File

@@ -13,3 +13,13 @@ class ProtocolListApi(ListAPIView):
def get_queryset(self):
return list(Protocol.protocols())
def filter_queryset(self, queryset):
search = self.request.query_params.get("search", "").lower().strip()
if not search:
return queryset
queryset = [
p for p in queryset
if search in p['label'].lower() or search in p['value'].lower()
]
return queryset

View File

@@ -9,24 +9,24 @@ from common.utils import get_logger
from orgs.mixins.api import OrgBulkModelViewSet
from .asset import HostViewSet
from .. import serializers
from ..models import Domain, Gateway
from ..models import Zone, Gateway
logger = get_logger(__file__)
__all__ = ['DomainViewSet', 'GatewayViewSet', "GatewayTestConnectionApi"]
__all__ = ['ZoneViewSet', 'GatewayViewSet', "GatewayTestConnectionApi"]
class DomainViewSet(OrgBulkModelViewSet):
model = Domain
class ZoneViewSet(OrgBulkModelViewSet):
model = Zone
filterset_fields = ("name",)
search_fields = filterset_fields
serializer_classes = {
'default': serializers.DomainSerializer,
'list': serializers.DomainListSerializer,
'default': serializers.ZoneSerializer,
'list': serializers.ZoneListSerializer,
}
def get_serializer_class(self):
if self.request.query_params.get('gateway'):
return serializers.DomainWithGatewaySerializer
return serializers.ZoneWithGatewaySerializer
return super().get_serializer_class()
def partial_update(self, request, *args, **kwargs):
@@ -36,8 +36,8 @@ class DomainViewSet(OrgBulkModelViewSet):
class GatewayViewSet(HostViewSet):
perm_model = Gateway
filterset_fields = ("domain__name", "name", "domain")
search_fields = ("domain__name",)
filterset_fields = ("zone__name", "name", "zone")
search_fields = ("zone__name",)
def get_serializer_classes(self):
serializer_classes = super().get_serializer_classes()
@@ -45,7 +45,7 @@ class GatewayViewSet(HostViewSet):
return serializer_classes
def get_queryset(self):
queryset = Domain.get_gateway_queryset()
queryset = Zone.get_gateway_queryset()
return queryset
@@ -55,7 +55,7 @@ class GatewayTestConnectionApi(SingleObjectMixin, APIView):
}
def get_queryset(self):
queryset = Domain.get_gateway_queryset()
queryset = Zone.get_gateway_queryset()
return queryset
def post(self, request, *args, **kwargs):

View File

@@ -17,11 +17,12 @@ from sshtunnel import SSHTunnelForwarder
from assets.automations.methods import platform_automation_methods
from common.const import Status
from common.db.utils import safe_db_connection
from common.db.utils import safe_atomic_db_connection
from common.tasks import send_mail_async
from common.utils import get_logger, lazyproperty, is_openssh_format_key, ssh_pubkey_gen
from ops.ansible import JMSInventory, DefaultCallback, SuperPlaybookRunner
from ops.ansible.interface import interface
from users.utils import activate_user_language
logger = get_logger(__name__)
@@ -122,9 +123,7 @@ class BaseManager:
self.execution.summary = self.summary
self.execution.result = self.result
self.execution.status = self.status
with safe_db_connection():
self.execution.save()
self.execution.save()
def print_summary(self):
content = "\nSummery: \n"
@@ -151,12 +150,13 @@ class BaseManager:
if not recipients:
return
print(f"Send report to: {','.join([str(u) for u in recipients])}")
report = self.gen_report()
report = transform(report, cssutils_logging_level="CRITICAL")
subject = self.get_report_subject()
emails = [r.email for r in recipients if r.email]
send_mail_async(subject, report, emails, html_message=report)
for user in recipients:
with activate_user_language(user):
report = self.gen_report()
report = transform(report, cssutils_logging_level="CRITICAL")
subject = self.get_report_subject()
emails = [user.email]
send_mail_async(subject, report, emails, html_message=report)
def gen_report(self):
template_path = self.get_report_template()
@@ -165,9 +165,10 @@ class BaseManager:
return data
def post_run(self):
self.update_execution()
self.print_summary()
self.send_report_if_need()
with safe_atomic_db_connection():
self.update_execution()
self.print_summary()
self.send_report_if_need()
def run(self, *args, **kwargs):
self.pre_run()
@@ -546,7 +547,8 @@ class BasePlaybookManager(PlaybookPrepareMixin, BaseManager):
try:
kwargs.update({"clean_workspace": False})
cb = runner.run(**kwargs)
self.on_runner_success(runner, cb)
with safe_atomic_db_connection():
self.on_runner_success(runner, cb)
except Exception as e:
self.on_runner_failed(runner, e, **info)
finally:

View File

@@ -1,3 +1,5 @@
from collections import Counter
__all__ = ['FormatAssetInfo']
@@ -7,13 +9,42 @@ class FormatAssetInfo:
self.tp = tp
@staticmethod
def posix_format(info):
for cpu_model in info.get('cpu_model', []):
if cpu_model.endswith('GHz') or cpu_model.startswith("Intel"):
break
else:
cpu_model = ''
info['cpu_model'] = cpu_model[:48]
def get_cpu_model_count(cpus):
try:
if len(cpus) % 3 == 0:
step = 3
models = [cpus[i + 2] for i in range(0, len(cpus), step)]
elif len(cpus) % 2 == 0:
step = 2
models = [cpus[i + 1] for i in range(0, len(cpus), step)]
else:
raise ValueError("CPU list format not recognized")
model_counts = Counter(models)
result = ', '.join([f"{model} x{count}" for model, count in model_counts.items()])
except Exception as e:
print(f"Error processing CPU model list: {e}")
result = ''
return result
@staticmethod
def get_gpu_model_count(gpus):
try:
model_counts = Counter(gpus)
result = ', '.join([f"{model} x{count}" for model, count in model_counts.items()])
except Exception as e:
print(f"Error processing GPU model list: {e}")
result = ''
return result
def posix_format(self, info):
cpus = self.get_cpu_model_count(info.get('cpu_model', []))
gpus = self.get_gpu_model_count(info.get('gpu_model', []))
info['gpu_model'] = gpus
info['cpu_model'] = cpus
info['cpu_count'] = info.get('cpu_count', 0)
return info

View File

@@ -23,5 +23,16 @@
arch: "{{ ansible_architecture }}"
kernel: "{{ ansible_kernel }}"
- name: Get GPU info with nvidia-smi
shell: |
nvidia-smi --query-gpu=name,memory.total,driver_version --format=csv,noheader,nounits
register: gpu_info
ignore_errors: yes
- name: Merge GPU info into final info
set_fact:
info: "{{ info | combine({'gpu_model': gpu_info.stdout_lines | default([])}) }}"
- debug:
var: info

View File

@@ -3,7 +3,8 @@
vars:
ansible_shell_type: sh
ansible_connection: local
ansible_python_interpreter: /opt/py3/bin/python
ansible_python_interpreter: "{{ local_python_interpreter }}"
ansible_timeout: 30
tasks:
- name: Test asset connection (pyfreerdp)

View File

@@ -4,7 +4,7 @@
ansible_connection: local
ansible_shell_type: sh
ansible_become: false
ansible_timeout: 30
tasks:
- name: Test asset connection (paramiko)
ssh_ping:

View File

@@ -3,7 +3,7 @@
vars:
ansible_connection: local
ansible_shell_type: sh
ansible_timeout: 30
tasks:
- name: Test asset connection (telnet)
telnet_ping:

View File

@@ -2,6 +2,7 @@
gather_facts: no
vars:
ansible_python_interpreter: "{{ local_python_interpreter }}"
ansible_timeout: 30
tasks:
- name: Test MongoDB connection
@@ -16,3 +17,5 @@
ssl_certfile: "{{ jms_asset.secret_info.client_key | default('') }}"
connection_options:
- tlsAllowInvalidHostnames: "{{ jms_asset.spec_info.allow_invalid_cert}}"
register: result
failed_when: not result.is_available

View File

@@ -6,6 +6,7 @@
ca_cert: "{{ jms_asset.secret_info.ca_cert | default('') }}"
ssl_cert: "{{ jms_asset.secret_info.client_cert | default('') }}"
ssl_key: "{{ jms_asset.secret_info.client_key | default('') }}"
ansible_timeout: 30
tasks:
- name: Test MySQL connection

View File

@@ -2,6 +2,7 @@
gather_facts: no
vars:
ansible_python_interpreter: "{{ local_python_interpreter }}"
ansible_timeout: 30
tasks:
- name: Test Oracle connection

View File

@@ -6,7 +6,7 @@
ca_cert: "{{ jms_asset.secret_info.ca_cert | default('') }}"
ssl_cert: "{{ jms_asset.secret_info.client_cert | default('') }}"
ssl_key: "{{ jms_asset.secret_info.client_key | default('') }}"
ansible_timeout: 30
tasks:
- name: Test PostgreSQL connection
community.postgresql.postgresql_ping:

View File

@@ -2,6 +2,7 @@
gather_facts: no
vars:
ansible_python_interpreter: "{{ local_python_interpreter }}"
ansible_timeout: 30
tasks:
- name: Test SQLServer connection

View File

@@ -1,5 +1,7 @@
- hosts: demo
gather_facts: no
vars:
ansible_timeout: 30
tasks:
- name: Posix ping
ansible.builtin.ping:

View File

@@ -1,5 +1,7 @@
- hosts: windows
gather_facts: no
vars:
ansible_timeout: 30
tasks:
- name: Refresh connection
ansible.builtin.meta: reset_connection

View File

@@ -37,10 +37,11 @@ class PingManager(BasePlaybookManager):
def on_host_error(self, host, error, result):
asset, account = self.host_asset_and_account_mapper.get(host)
try:
asset.set_connectivity(Connectivity.ERR)
error_tp = asset.get_err_connectivity(error)
asset.set_connectivity(error_tp)
if not account:
return
account.set_connectivity(Connectivity.ERR)
account.set_connectivity(error_tp)
except Exception as e:
print(f'\033[31m Update account {account.name} or '
f'update asset {asset.name} connectivity failed: {e} \033[0m\n')

View File

@@ -7,6 +7,16 @@ class Connectivity(TextChoices):
NA = 'na', _('N/A')
OK = 'ok', _('OK')
ERR = 'err', _('Error')
RDP_ERR = 'rdp_err', _('RDP error')
AUTH_ERR = 'auth_err', _('Authentication error')
PASSWORD_ERR = 'password_err', _('Invalid password error')
OPENSSH_KEY_ERR = 'openssh_key_err', _('OpenSSH key error')
NTLM_ERR = 'ntlm_err', _('NTLM credentials rejected error')
CREATE_TEMPORARY_ERR = 'create_temp_err', _('Create temporary error')
@classmethod
def as_dict(cls):
return {choice.value: choice.label for choice in cls}
class AutomationTypes(TextChoices):

View File

@@ -37,7 +37,7 @@ class FillType(models.TextChoices):
class BaseType(TextChoices):
"""
约束应该考虑代是对平台对限制,避免多余对选项,如: mysql 开启 ssh,
或者开启了也没有作用, 比如 k8s 开启了 domain目前还不支持
或者开启了也没有作用, 比如 k8s 开启了 gateway 目前还不支持
"""
@classmethod

View File

@@ -20,3 +20,7 @@ class Category(ChoicesMixin, models.TextChoices):
_category = getattr(cls, category.upper(), None)
choices = [(_category.value, _category.label)] if _category else cls.choices
return choices
@classmethod
def as_dict(cls):
return {choice.value: choice.label for choice in cls}

View File

@@ -13,11 +13,11 @@ class CloudTypes(BaseType):
return {
'*': {
'charset_enabled': False,
'domain_enabled': False,
'gateway_enabled': False,
'su_enabled': False,
},
cls.K8S: {
'domain_enabled': True,
'gateway_enabled': True,
}
}

View File

@@ -20,7 +20,7 @@ class CustomTypes(BaseType):
return {
'*': {
'charset_enabled': False,
'domain_enabled': False,
'gateway_enabled': False,
'su_enabled': False,
},
}

View File

@@ -20,7 +20,7 @@ class DatabaseTypes(BaseType):
return {
'*': {
'charset_enabled': False,
'domain_enabled': True,
'gateway_enabled': True,
'su_enabled': False,
}
}

View File

@@ -19,8 +19,8 @@ class DeviceTypes(BaseType):
return {
'*': {
'charset_enabled': False,
'domain_enabled': True,
'ds_enabled': False,
'gateway_enabled': True,
'ds_enabled': True,
'su_enabled': True,
'su_methods': ['enable', 'super', 'super_level']
}

View File

@@ -16,7 +16,7 @@ class DirectoryTypes(BaseType):
return {
'*': {
'charset_enabled': True,
'domain_enabled': True,
'gateway_enabled': True,
'ds_enabled': False,
'su_enabled': True,
},

View File

@@ -11,7 +11,7 @@ class GPTTypes(BaseType):
return {
'*': {
'charset_enabled': False,
'domain_enabled': False,
'gateway_enabled': False,
'su_enabled': False,
}
}

View File

@@ -18,7 +18,7 @@ class HostTypes(BaseType):
'*': {
'charset_enabled': True,
'charset': 'utf-8', # default
'domain_enabled': True,
'gateway_enabled': True,
'su_enabled': True,
'ds_enabled': True,
'su_methods': ['sudo', 'su', 'only_sudo', 'only_su'],
@@ -81,7 +81,7 @@ class HostTypes(BaseType):
{'name': 'Linux'},
{
'name': GATEWAY_NAME,
'domain_enabled': True,
'gateway_enabled': True,
}
],
cls.UNIX: [

View File

@@ -194,6 +194,12 @@ class Protocol(ChoicesMixin, models.TextChoices):
'default': '>=2014',
'label': _('Version'),
'help_text': _('SQL Server version, Different versions have different connection drivers')
},
'encrypt': {
'type': 'bool',
'default': True,
'label': _('Encrypt'),
'help_text': _('Whether to use TLS encryption.')
}
}
},
@@ -343,7 +349,21 @@ class Protocol(ChoicesMixin, models.TextChoices):
for protocol, config in cls.settings().items():
if not xpack_enabled and config.get('xpack', False):
continue
protocols.append(protocol)
protocols.append({'label': protocol.label, 'value': protocol.value})
from assets.models.platform import PlatformProtocol
custom_protocols = (
PlatformProtocol.objects
.filter(platform__category='custom')
.values_list('name', flat=True)
.distinct()
)
for protocol in custom_protocols:
if protocol not in protocols:
if not protocol:
continue
label = protocol[0].upper() + protocol[1:]
protocols.append({'label': label, 'value': protocol})
return protocols
@classmethod

View File

@@ -312,7 +312,7 @@ class AllTypes(ChoicesMixin):
'category': category,
'type': tp, 'internal': True,
'charset': constraints.get('charset', 'utf-8'),
'domain_enabled': constraints.get('domain_enabled', False),
'gateway_enabled': constraints.get('gateway_enabled', False),
'su_enabled': constraints.get('su_enabled', False),
}
if data['su_enabled'] and data.get('su_methods'):

View File

@@ -11,7 +11,7 @@ class WebTypes(BaseType):
return {
'*': {
'charset_enabled': False,
'domain_enabled': False,
'gateway_enabled': False,
'su_enabled': False,
}
}

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