mirror of
https://github.com/jumpserver/jumpserver.git
synced 2025-12-17 17:42:37 +00:00
Compare commits
542 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e77c8a0ec6 | ||
|
|
a9620a3cbe | ||
|
|
769e7dc8a0 | ||
|
|
4b961a626b | ||
|
|
2a70449411 | ||
|
|
653a6752b6 | ||
|
|
32255c6077 | ||
|
|
7a708156ee | ||
|
|
b72a446bbd | ||
|
|
219fad9b62 | ||
|
|
6c1c8b241e | ||
|
|
a4d0e3fd17 | ||
|
|
af44ffab0a | ||
|
|
a09b7b29e2 | ||
|
|
8f67922c80 | ||
|
|
f1db5d6f44 | ||
|
|
33ea5eb41f | ||
|
|
48bcbc6c53 | ||
|
|
3e090eb701 | ||
|
|
6ac956c626 | ||
|
|
edb2d1bd7b | ||
|
|
81b4909016 | ||
|
|
f6f1be423c | ||
|
|
fae5392a03 | ||
|
|
d5224968bc | ||
|
|
6565f8c0a8 | ||
|
|
8df720f19e | ||
|
|
bc5494bbb0 | ||
|
|
febf08629a | ||
|
|
b6774aa749 | ||
|
|
bc668f3e9f | ||
|
|
dc56b019b1 | ||
|
|
a38624d198 | ||
|
|
ca026040fe | ||
|
|
88b9a4d693 | ||
|
|
4d15e46ceb | ||
|
|
55575e9f7f | ||
|
|
98c9cddcbf | ||
|
|
9f67ba573c | ||
|
|
533f13c634 | ||
|
|
c66b1db784 | ||
|
|
d03ba7c391 | ||
|
|
6544f8ade8 | ||
|
|
ac5991fc43 | ||
|
|
9b2b71dddc | ||
|
|
e18e019460 | ||
|
|
ef1875d9b5 | ||
|
|
0b7552a6ee | ||
|
|
45425b11d2 | ||
|
|
fda3e6ec9b | ||
|
|
2b41486f2a | ||
|
|
59d9a3d4ec | ||
|
|
3c7ba029dd | ||
|
|
1335556272 | ||
|
|
8eab87f40d | ||
|
|
c441e5bb92 | ||
|
|
da8d78f384 | ||
|
|
83b91cb739 | ||
|
|
1afad40dd3 | ||
|
|
1358cf532f | ||
|
|
1e7f268f0c | ||
|
|
d6b5590505 | ||
|
|
79b3b31492 | ||
|
|
4f2b3fbb43 | ||
|
|
1f2db65dba | ||
|
|
006faac326 | ||
|
|
f7fee0f430 | ||
|
|
714c44fbf4 | ||
|
|
84b316e2c1 | ||
|
|
6955a3db11 | ||
|
|
d92736e624 | ||
|
|
9d0da64ea1 | ||
|
|
b9e1d6093e | ||
|
|
c3820b30b8 | ||
|
|
6955fc1734 | ||
|
|
32178b2344 | ||
|
|
e3c0518cfb | ||
|
|
438e9dee2a | ||
|
|
3c9239eb09 | ||
|
|
81fb080c67 | ||
|
|
6cf05435bf | ||
|
|
65718c5a84 | ||
|
|
27daebbe1b | ||
|
|
dce1079fdc | ||
|
|
d07db68426 | ||
|
|
6d37300a30 | ||
|
|
0c96af32c2 | ||
|
|
1c6b1b0625 | ||
|
|
4f7b4842f6 | ||
|
|
c4fef5899c | ||
|
|
5b51a8231c | ||
|
|
54417dd6d3 | ||
|
|
2c7ad90524 | ||
|
|
01fcdad489 | ||
|
|
8801003461 | ||
|
|
696397fdb0 | ||
|
|
87a24991f1 | ||
|
|
3ec93b8f04 | ||
|
|
4f1826d3ed | ||
|
|
9260f26c99 | ||
|
|
93da3e58f2 | ||
|
|
1eff33f3f7 | ||
|
|
8e89d42343 | ||
|
|
d0b0c87d3c | ||
|
|
e3ac26e377 | ||
|
|
4ea20a9103 | ||
|
|
dd57b14562 | ||
|
|
c312cdb625 | ||
|
|
85fedf0704 | ||
|
|
8b05260a6c | ||
|
|
47cb6b1ec0 | ||
|
|
79b5dff210 | ||
|
|
b08e1f6a47 | ||
|
|
2e3184cbd6 | ||
|
|
fb903e53a4 | ||
|
|
cc7220a4ad | ||
|
|
81de527e32 | ||
|
|
7ad2abe104 | ||
|
|
9a2da98bd4 | ||
|
|
eca50874f0 | ||
|
|
8f82ca9856 | ||
|
|
e193d7a942 | ||
|
|
d2429f7883 | ||
|
|
a43bb25b5a | ||
|
|
ffe3e8a70c | ||
|
|
0e7e499a1e | ||
|
|
e812e3ff89 | ||
|
|
d2eacad97b | ||
|
|
8291a81efd | ||
|
|
a91cb1afd5 | ||
|
|
2cad97065f | ||
|
|
cf18300360 | ||
|
|
3cd22f05d2 | ||
|
|
eee41008cc | ||
|
|
0fdae00722 | ||
|
|
575562c416 | ||
|
|
e2b7f67fdc | ||
|
|
d2498c0d53 | ||
|
|
01e40fd238 | ||
|
|
370ef11486 | ||
|
|
089cadeae3 | ||
|
|
6b748e5ac5 | ||
|
|
6d611bbbbd | ||
|
|
18670d493e | ||
|
|
ba38852354 | ||
|
|
64f3509c8c | ||
|
|
805c78c0de | ||
|
|
11accf8854 | ||
|
|
18f6ffe0ce | ||
|
|
6b7119ea74 | ||
|
|
efc7ca1164 | ||
|
|
a6de9bdde6 | ||
|
|
6e7074ba40 | ||
|
|
2edcb2f2d3 | ||
|
|
07e1918fa1 | ||
|
|
452b383278 | ||
|
|
ed92f10208 | ||
|
|
e8331ca708 | ||
|
|
814130204a | ||
|
|
e7dc9a2f6f | ||
|
|
dabbb45f6e | ||
|
|
ded1b4bba1 | ||
|
|
2630ea39a1 | ||
|
|
9e10029bdd | ||
|
|
d1391cb5d5 | ||
|
|
44f029774d | ||
|
|
23fce9e426 | ||
|
|
0778a39894 | ||
|
|
9cc6d6a9af | ||
|
|
8f309dee92 | ||
|
|
d166b26252 | ||
|
|
1ef51563b5 | ||
|
|
3e7b4682e4 | ||
|
|
994b42aa93 | ||
|
|
d6aea54722 | ||
|
|
88afabdd1d | ||
|
|
b2327c0c5a | ||
|
|
7610f64433 | ||
|
|
b15c314384 | ||
|
|
7a5cffac91 | ||
|
|
8667943443 | ||
|
|
7c51d90a3d | ||
|
|
9996b200f9 | ||
|
|
ae364ac373 | ||
|
|
fef4a97931 | ||
|
|
d63c4d6cc4 | ||
|
|
4e5a44bd98 | ||
|
|
fcce03f7bd | ||
|
|
5f121934a7 | ||
|
|
521c1f0dfa | ||
|
|
5673698a57 | ||
|
|
d6b75ac700 | ||
|
|
0ee14e6d85 | ||
|
|
9babe977d8 | ||
|
|
0f9223331c | ||
|
|
f8a4a0e108 | ||
|
|
ba76f30af9 | ||
|
|
e5e0c841a2 | ||
|
|
c41fdf1786 | ||
|
|
69c0eb2f50 | ||
|
|
e077afe2cc | ||
|
|
c1f572df05 | ||
|
|
d60fe464ca | ||
|
|
f47895b8a8 | ||
|
|
3eb1583c69 | ||
|
|
5ab8ff4fde | ||
|
|
7746491e19 | ||
|
|
5e54792d94 | ||
|
|
621c7a31fe | ||
|
|
75bab70ccf | ||
|
|
30683ed859 | ||
|
|
7c52cec5fb | ||
|
|
f01bfc44b8 | ||
|
|
54b89f6fee | ||
|
|
c0de0b0d8e | ||
|
|
06275a09ac | ||
|
|
7b86938b58 | ||
|
|
44624d0ce0 | ||
|
|
9b8c817a16 | ||
|
|
927fe1f128 | ||
|
|
eee119eba1 | ||
|
|
53d8f716eb | ||
|
|
f48aec2bcb | ||
|
|
78e9f51786 | ||
|
|
af33ad6631 | ||
|
|
864da49ae6 | ||
|
|
e6b8b3982d | ||
|
|
49b3df218e | ||
|
|
0858d67098 | ||
|
|
ffa242e635 | ||
|
|
4021b1955e | ||
|
|
204258f058 | ||
|
|
dc841650cf | ||
|
|
bc54685a31 | ||
|
|
ee586954f8 | ||
|
|
e56a37afd2 | ||
|
|
7669744312 | ||
|
|
ad8aba88a3 | ||
|
|
7659846df4 | ||
|
|
f93979eb2d | ||
|
|
badf83c560 | ||
|
|
f6466a3a20 | ||
|
|
996394ba29 | ||
|
|
09f8470d34 | ||
|
|
fdb3f6409c | ||
|
|
73b0b23910 | ||
|
|
c1185e989a | ||
|
|
1239082649 | ||
|
|
ff073185f1 | ||
|
|
d7a682b462 | ||
|
|
4df2bdd9b6 | ||
|
|
2437072768 | ||
|
|
08a2d96213 | ||
|
|
de7d7b41c0 | ||
|
|
b04c7f022f | ||
|
|
bf0d9f4b80 | ||
|
|
314257f790 | ||
|
|
6d2a62e413 | ||
|
|
1734ddc2bd | ||
|
|
7c796e8201 | ||
|
|
62a74418ea | ||
|
|
32461078fe | ||
|
|
939b517e34 | ||
|
|
66eac762ff | ||
|
|
ce24c1c3fd | ||
|
|
db9ee71ab3 | ||
|
|
db2331521d | ||
|
|
4aa4c6854b | ||
|
|
26a18a1f5c | ||
|
|
6870df6d75 | ||
|
|
03d1a187df | ||
|
|
ca0dca26c7 | ||
|
|
25a1989157 | ||
|
|
fef26c38fe | ||
|
|
a2fcc47436 | ||
|
|
00450121bc | ||
|
|
bdd885069f | ||
|
|
25d0c021e1 | ||
|
|
095c23ea4f | ||
|
|
3c3c112b07 | ||
|
|
d95a44fe44 | ||
|
|
e713bdab0b | ||
|
|
78f1b2b002 | ||
|
|
e0762573ae | ||
|
|
16e8c7faba | ||
|
|
9b019e45ae | ||
|
|
71d70501d6 | ||
|
|
5cd44ebfce | ||
|
|
03c27ab5b8 | ||
|
|
d3a283232f | ||
|
|
f088bbce12 | ||
|
|
b313598227 | ||
|
|
3a118b6753 | ||
|
|
578c2af57c | ||
|
|
b5ef239c6c | ||
|
|
e88e4438ba | ||
|
|
73b75df524 | ||
|
|
772684d24c | ||
|
|
741705b85b | ||
|
|
f5176bcc6f | ||
|
|
c917d8f346 | ||
|
|
5c0905b3b5 | ||
|
|
bda23b3d2a | ||
|
|
8b6526211c | ||
|
|
86e8f3a80b | ||
|
|
70661242c1 | ||
|
|
6f4082f800 | ||
|
|
edd65f965b | ||
|
|
7dcae1e05a | ||
|
|
0a28c5650c | ||
|
|
f55c84ce3b | ||
|
|
ac11790192 | ||
|
|
f80ff279d0 | ||
|
|
d7ac08f6d9 | ||
|
|
b5714f7e14 | ||
|
|
d6b450f32a | ||
|
|
1daf1acaf3 | ||
|
|
ea0e852412 | ||
|
|
ce976f215f | ||
|
|
ffc057f844 | ||
|
|
588723a76c | ||
|
|
1ca912373f | ||
|
|
452ee1224c | ||
|
|
7eb497f9d3 | ||
|
|
58fd578ddd | ||
|
|
e1278360af | ||
|
|
c0de27ff7a | ||
|
|
116d0ba5c6 | ||
|
|
9f042cfa04 | ||
|
|
ce63ea7528 | ||
|
|
8b3fd2c117 | ||
|
|
23ccd6df8c | ||
|
|
614e019f14 | ||
|
|
38aa828eb8 | ||
|
|
7cd2736e82 | ||
|
|
443f6d25e8 | ||
|
|
e8652af054 | ||
|
|
fd6a8dd807 | ||
|
|
499eedd83e | ||
|
|
ca7d164034 | ||
|
|
3ef8e9603a | ||
|
|
09f71d80eb | ||
|
|
73db1bf50c | ||
|
|
6017f804a6 | ||
|
|
affa562384 | ||
|
|
0d101bc5ad | ||
|
|
70f0f55ddb | ||
|
|
333746e7c4 | ||
|
|
30b19d31eb | ||
|
|
a844ce23e4 | ||
|
|
d6c0139fef | ||
|
|
11157563ba | ||
|
|
95e7bde5d7 | ||
|
|
814350ab80 | ||
|
|
3ac35eec68 | ||
|
|
3d27986c96 | ||
|
|
c981e9cd9f | ||
|
|
e00c804a5a | ||
|
|
ef2b7b464e | ||
|
|
ae5d4257ad | ||
|
|
b42014d58e | ||
|
|
e71e8cd595 | ||
|
|
dd50044b89 | ||
|
|
68707085fa | ||
|
|
60399fae29 | ||
|
|
f206d963a0 | ||
|
|
42b4e7697d | ||
|
|
0c1f4d99f8 | ||
|
|
2aed3fcaea | ||
|
|
28196573bb | ||
|
|
27c505853b | ||
|
|
896d42c53e | ||
|
|
f79084c2df | ||
|
|
15a5dda9e0 | ||
|
|
2069fee795 | ||
|
|
56a26481a4 | ||
|
|
cbe3d66b39 | ||
|
|
7c67d882aa | ||
|
|
9bde2ff6e1 | ||
|
|
1f00c00183 | ||
|
|
c369b5478c | ||
|
|
10363dcc5b | ||
|
|
42bdb2cf14 | ||
|
|
d64e77db30 | ||
|
|
4065baf785 | ||
|
|
0f3ddc3bf1 | ||
|
|
138adeff76 | ||
|
|
0cf17310e1 | ||
|
|
43dbb4c226 | ||
|
|
cefd9f4ab2 | ||
|
|
7128593502 | ||
|
|
5d4fa22058 | ||
|
|
3c54c82ce9 | ||
|
|
91dce82b38 | ||
|
|
d102db7a7b | ||
|
|
1de7af4984 | ||
|
|
9892ff7dd6 | ||
|
|
4cb499953c | ||
|
|
0397bdeb46 | ||
|
|
1d6d92c160 | ||
|
|
cdbe5d31e9 | ||
|
|
b023ca0c69 | ||
|
|
803d590096 | ||
|
|
e11367088a | ||
|
|
1c74dd00ba | ||
|
|
ed832af631 | ||
|
|
948c499d9e | ||
|
|
a51549cf1c | ||
|
|
39baf88055 | ||
|
|
90131db55a | ||
|
|
ea3ff1ebcb | ||
|
|
f3ca45aa74 | ||
|
|
74cc174d7a | ||
|
|
0eba6d2175 | ||
|
|
58592a13e3 | ||
|
|
b8fb23a0a0 | ||
|
|
f5c43488fd | ||
|
|
19c76ba01c | ||
|
|
68c4cd5928 | ||
|
|
e5bfa29c7b | ||
|
|
cbb772def7 | ||
|
|
e6fe7c489e | ||
|
|
0b30f5cf88 | ||
|
|
018f1a0e8d | ||
|
|
24ed57b98a | ||
|
|
04a790c4ee | ||
|
|
2d9a3ef7d4 | ||
|
|
0d2adeccf2 | ||
|
|
886f977311 | ||
|
|
9367e79bcf | ||
|
|
af733ecbad | ||
|
|
09f9775eab | ||
|
|
1c2a362beb | ||
|
|
bb1e674367 | ||
|
|
a75677ab08 | ||
|
|
b1daa4d357 | ||
|
|
c32271ec6f | ||
|
|
beb4f14be9 | ||
|
|
e719904874 | ||
|
|
664bc2a4d9 | ||
|
|
b91db8c146 | ||
|
|
500aeeb77f | ||
|
|
3abc8bddfa | ||
|
|
5cbbf9e737 | ||
|
|
7204a86f87 | ||
|
|
829194420a | ||
|
|
61dc95d9ae | ||
|
|
a9f60a9117 | ||
|
|
82f96d6ed2 | ||
|
|
f6c56d4979 | ||
|
|
54d0a1b871 | ||
|
|
5b4a267ccd | ||
|
|
a6d78834e7 | ||
|
|
07da98e438 | ||
|
|
7c973616cd | ||
|
|
b9997b07db | ||
|
|
bcda879f3b | ||
|
|
d0f79c2df2 | ||
|
|
1249935bab | ||
|
|
5fa1ae9ee5 | ||
|
|
d0755c4719 | ||
|
|
72b215ed03 | ||
|
|
d7ca1a09d4 | ||
|
|
04e341a1bb | ||
|
|
a41909ec8d | ||
|
|
f9d6de9c39 | ||
|
|
816b284a51 | ||
|
|
d4c5dcf069 | ||
|
|
73037c21e8 | ||
|
|
c7f9259a2e | ||
|
|
8632bd2480 | ||
|
|
23723f4eda | ||
|
|
38601a84c2 | ||
|
|
e50189e284 | ||
|
|
da9bd11db5 | ||
|
|
9acb7d6183 | ||
|
|
dbd9a9fdac | ||
|
|
25301aa396 | ||
|
|
8cc1ca2770 | ||
|
|
bad01aefa2 | ||
|
|
56a989bfb9 | ||
|
|
578f66d5e2 | ||
|
|
8d6083bfb2 | ||
|
|
1138cd3334 | ||
|
|
db0b43ee84 | ||
|
|
40a460870a | ||
|
|
51910ea2c1 | ||
|
|
266a360a97 | ||
|
|
24194b4e4d | ||
|
|
992e34d652 | ||
|
|
894249a3d1 | ||
|
|
21c6fe19a1 | ||
|
|
e4e4f82143 | ||
|
|
2a5c635dc5 | ||
|
|
7dbaa28539 | ||
|
|
5bae4cde58 | ||
|
|
35c0d7be35 | ||
|
|
1f2a4b0fb5 | ||
|
|
7c3a3d599b | ||
|
|
d70770775a | ||
|
|
bc217e1bad | ||
|
|
d4469aeaf7 | ||
|
|
904406c5c1 | ||
|
|
09db2ad3e1 | ||
|
|
859268f7f3 | ||
|
|
72bb5a4037 | ||
|
|
6f3871d5fe | ||
|
|
2f0c346365 | ||
|
|
e9c090f656 | ||
|
|
7b0b07cf52 | ||
|
|
bebb90f688 | ||
|
|
ac14a70c51 | ||
|
|
642f92c0a3 | ||
|
|
04f4ecb3d1 | ||
|
|
60703c920c | ||
|
|
9634f397df | ||
|
|
f9a7a95191 | ||
|
|
bced33fd93 | ||
|
|
1044ff004b | ||
|
|
e11c7a264e | ||
|
|
3c497aa81e | ||
|
|
c8a1f4b092 | ||
|
|
9dd2dc8907 | ||
|
|
56285d906f | ||
|
|
44b536a23b | ||
|
|
a97003a03a | ||
|
|
4315cbe6d0 | ||
|
|
b2d9670721 | ||
|
|
78f66c46e8 | ||
|
|
f3af9c3108 | ||
|
|
822a124dbc | ||
|
|
20799ece93 | ||
|
|
4e2c7d7aab | ||
|
|
75e4895314 | ||
|
|
ea7b409a7f | ||
|
|
01d10a25e9 | ||
|
|
61ce39b4ba | ||
|
|
7506c7ea43 | ||
|
|
f6f162ec3a | ||
|
|
2e840e3b05 | ||
|
|
ff4560c2a7 | ||
|
|
deeb8da226 |
1
.github/ISSUE_TEMPLATE/----.md
vendored
1
.github/ISSUE_TEMPLATE/----.md
vendored
@@ -6,7 +6,6 @@ labels: 类型:需求
|
|||||||
assignees:
|
assignees:
|
||||||
- ibuler
|
- ibuler
|
||||||
- baijiangjie
|
- baijiangjie
|
||||||
- wojiushixiaobai
|
|
||||||
---
|
---
|
||||||
|
|
||||||
**请描述您的需求或者改进建议.**
|
**请描述您的需求或者改进建议.**
|
||||||
|
|||||||
4
.github/ISSUE_TEMPLATE/bug---.md
vendored
4
.github/ISSUE_TEMPLATE/bug---.md
vendored
@@ -2,11 +2,9 @@
|
|||||||
name: Bug 提交
|
name: Bug 提交
|
||||||
about: 提交产品缺陷帮助我们更好的改进
|
about: 提交产品缺陷帮助我们更好的改进
|
||||||
title: "[Bug] "
|
title: "[Bug] "
|
||||||
labels: 类型:bug
|
labels: 类型:Bug
|
||||||
assignees:
|
assignees:
|
||||||
- wojiushixiaobai
|
|
||||||
- baijiangjie
|
- baijiangjie
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
**JumpServer 版本( v2.28 之前的版本不再支持 )**
|
**JumpServer 版本( v2.28 之前的版本不再支持 )**
|
||||||
|
|||||||
2
.github/ISSUE_TEMPLATE/question.md
vendored
2
.github/ISSUE_TEMPLATE/question.md
vendored
@@ -4,9 +4,7 @@ about: 提出针对本项目安装部署、使用及其他方面的相关问题
|
|||||||
title: "[Question] "
|
title: "[Question] "
|
||||||
labels: 类型:提问
|
labels: 类型:提问
|
||||||
assignees:
|
assignees:
|
||||||
- wojiushixiaobai
|
|
||||||
- baijiangjie
|
- baijiangjie
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
**请描述您的问题.**
|
**请描述您的问题.**
|
||||||
|
|||||||
4
.github/workflows/jms-build-test.yml
vendored
4
.github/workflows/jms-build-test.yml
vendored
@@ -19,8 +19,8 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
push: false
|
push: false
|
||||||
tags: jumpserver/core:test
|
tags: jumpserver/core-ce:test
|
||||||
file: Dockerfile
|
file: Dockerfile-ce
|
||||||
build-args: |
|
build-args: |
|
||||||
APT_MIRROR=http://deb.debian.org
|
APT_MIRROR=http://deb.debian.org
|
||||||
PIP_MIRROR=https://pypi.org/simple
|
PIP_MIRROR=https://pypi.org/simple
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
FROM python:3.11-slim-bullseye as stage-build
|
FROM python:3.11-slim-bullseye as stage-1
|
||||||
ARG TARGETARCH
|
ARG TARGETARCH
|
||||||
|
|
||||||
ARG VERSION
|
ARG VERSION
|
||||||
@@ -6,9 +6,10 @@ ENV VERSION=$VERSION
|
|||||||
|
|
||||||
WORKDIR /opt/jumpserver
|
WORKDIR /opt/jumpserver
|
||||||
ADD . .
|
ADD . .
|
||||||
RUN cd utils && bash -ixeu build.sh
|
RUN echo > /opt/jumpserver/config.yml \
|
||||||
|
&& cd utils && bash -ixeu build.sh
|
||||||
|
|
||||||
FROM python:3.11-slim-bullseye
|
FROM python:3.11-slim-bullseye as stage-2
|
||||||
ARG TARGETARCH
|
ARG TARGETARCH
|
||||||
|
|
||||||
ARG BUILD_DEPENDENCIES=" \
|
ARG BUILD_DEPENDENCIES=" \
|
||||||
@@ -36,17 +37,15 @@ ARG TOOLS=" \
|
|||||||
curl \
|
curl \
|
||||||
default-libmysqlclient-dev \
|
default-libmysqlclient-dev \
|
||||||
default-mysql-client \
|
default-mysql-client \
|
||||||
locales \
|
git \
|
||||||
nmap \
|
git-lfs \
|
||||||
openssh-client \
|
unzip \
|
||||||
sshpass \
|
xz-utils \
|
||||||
telnet \
|
|
||||||
vim \
|
|
||||||
wget"
|
wget"
|
||||||
|
|
||||||
ARG APT_MIRROR=http://mirrors.ustc.edu.cn
|
ARG APT_MIRROR=http://mirrors.ustc.edu.cn
|
||||||
|
RUN --mount=type=cache,target=/var/cache/apt,sharing=locked,id=core-apt \
|
||||||
RUN --mount=type=cache,target=/var/cache/apt,sharing=locked,id=core \
|
--mount=type=cache,target=/var/lib/apt,sharing=locked,id=core-apt \
|
||||||
sed -i "s@http://.*.debian.org@${APT_MIRROR}@g" /etc/apt/sources.list \
|
sed -i "s@http://.*.debian.org@${APT_MIRROR}@g" /etc/apt/sources.list \
|
||||||
&& rm -f /etc/apt/apt.conf.d/docker-clean \
|
&& rm -f /etc/apt/apt.conf.d/docker-clean \
|
||||||
&& ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime \
|
&& ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime \
|
||||||
@@ -54,30 +53,72 @@ RUN --mount=type=cache,target=/var/cache/apt,sharing=locked,id=core \
|
|||||||
&& apt-get -y install --no-install-recommends ${BUILD_DEPENDENCIES} \
|
&& apt-get -y install --no-install-recommends ${BUILD_DEPENDENCIES} \
|
||||||
&& apt-get -y install --no-install-recommends ${DEPENDENCIES} \
|
&& apt-get -y install --no-install-recommends ${DEPENDENCIES} \
|
||||||
&& apt-get -y install --no-install-recommends ${TOOLS} \
|
&& apt-get -y install --no-install-recommends ${TOOLS} \
|
||||||
&& mkdir -p /root/.ssh/ \
|
&& echo "no" | dpkg-reconfigure dash
|
||||||
&& echo "Host *\n\tStrictHostKeyChecking no\n\tUserKnownHostsFile /dev/null\n\tCiphers +aes128-cbc\n\tKexAlgorithms +diffie-hellman-group1-sha1\n\tHostKeyAlgorithms +ssh-rsa" > /root/.ssh/config \
|
|
||||||
&& echo "set mouse-=a" > ~/.vimrc \
|
|
||||||
&& echo "no" | dpkg-reconfigure dash \
|
|
||||||
&& echo "zh_CN.UTF-8" | dpkg-reconfigure locales \
|
|
||||||
&& sed -i "s@# export @export @g" ~/.bashrc \
|
|
||||||
&& sed -i "s@# alias @alias @g" ~/.bashrc \
|
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
|
||||||
|
|
||||||
COPY --from=stage-build /opt/jumpserver/release/jumpserver /opt/jumpserver
|
|
||||||
WORKDIR /opt/jumpserver
|
WORKDIR /opt/jumpserver
|
||||||
|
|
||||||
ARG PIP_MIRROR=https://pypi.tuna.tsinghua.edu.cn/simple
|
ARG PIP_MIRROR=https://pypi.tuna.tsinghua.edu.cn/simple
|
||||||
RUN --mount=type=cache,target=/root/.cache \
|
RUN --mount=type=cache,target=/root/.cache \
|
||||||
|
--mount=type=bind,source=poetry.lock,target=/opt/jumpserver/poetry.lock \
|
||||||
|
--mount=type=bind,source=pyproject.toml,target=/opt/jumpserver/pyproject.toml \
|
||||||
set -ex \
|
set -ex \
|
||||||
&& echo > /opt/jumpserver/config.yml \
|
&& python3 -m venv /opt/py3 \
|
||||||
&& pip install poetry -i ${PIP_MIRROR} \
|
&& pip install poetry -i ${PIP_MIRROR} \
|
||||||
&& poetry config virtualenvs.create false \
|
&& poetry config virtualenvs.create false \
|
||||||
&& poetry install --only=main
|
&& . /opt/py3/bin/activate \
|
||||||
|
&& poetry install
|
||||||
|
|
||||||
|
FROM python:3.11-slim-bullseye
|
||||||
|
ARG TARGETARCH
|
||||||
|
ENV LANG=zh_CN.UTF-8 \
|
||||||
|
PATH=/opt/py3/bin:$PATH
|
||||||
|
|
||||||
|
ARG DEPENDENCIES=" \
|
||||||
|
libjpeg-dev \
|
||||||
|
libx11-dev \
|
||||||
|
freerdp2-dev \
|
||||||
|
libxmlsec1-openssl"
|
||||||
|
|
||||||
|
ARG TOOLS=" \
|
||||||
|
ca-certificates \
|
||||||
|
curl \
|
||||||
|
default-libmysqlclient-dev \
|
||||||
|
default-mysql-client \
|
||||||
|
iputils-ping \
|
||||||
|
locales \
|
||||||
|
nmap \
|
||||||
|
openssh-client \
|
||||||
|
patch \
|
||||||
|
sshpass \
|
||||||
|
telnet \
|
||||||
|
vim \
|
||||||
|
wget"
|
||||||
|
|
||||||
|
ARG APT_MIRROR=http://mirrors.ustc.edu.cn
|
||||||
|
RUN --mount=type=cache,target=/var/cache/apt,sharing=locked,id=core-apt \
|
||||||
|
--mount=type=cache,target=/var/lib/apt,sharing=locked,id=core-apt \
|
||||||
|
sed -i "s@http://.*.debian.org@${APT_MIRROR}@g" /etc/apt/sources.list \
|
||||||
|
&& rm -f /etc/apt/apt.conf.d/docker-clean \
|
||||||
|
&& ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime \
|
||||||
|
&& apt-get update \
|
||||||
|
&& apt-get -y install --no-install-recommends ${DEPENDENCIES} \
|
||||||
|
&& apt-get -y install --no-install-recommends ${TOOLS} \
|
||||||
|
&& mkdir -p /root/.ssh/ \
|
||||||
|
&& echo "Host *\n\tStrictHostKeyChecking no\n\tUserKnownHostsFile /dev/null\n\tCiphers +aes128-cbc\n\tKexAlgorithms +diffie-hellman-group1-sha1\n\tHostKeyAlgorithms +ssh-rsa" > /root/.ssh/config \
|
||||||
|
&& echo "no" | dpkg-reconfigure dash \
|
||||||
|
&& echo "zh_CN.UTF-8" | dpkg-reconfigure locales \
|
||||||
|
&& sed -i "s@# export @export @g" ~/.bashrc \
|
||||||
|
&& sed -i "s@# alias @alias @g" ~/.bashrc
|
||||||
|
|
||||||
|
COPY --from=stage-2 /opt/py3 /opt/py3
|
||||||
|
COPY --from=stage-1 /opt/jumpserver/release/jumpserver /opt/jumpserver
|
||||||
|
|
||||||
|
WORKDIR /opt/jumpserver
|
||||||
|
|
||||||
|
ARG VERSION
|
||||||
|
ENV VERSION=$VERSION
|
||||||
|
|
||||||
VOLUME /opt/jumpserver/data
|
VOLUME /opt/jumpserver/data
|
||||||
VOLUME /opt/jumpserver/logs
|
|
||||||
|
|
||||||
ENV LANG=zh_CN.UTF-8
|
|
||||||
|
|
||||||
EXPOSE 8080
|
EXPOSE 8080
|
||||||
|
|
||||||
@@ -1,9 +1,5 @@
|
|||||||
ARG VERSION
|
ARG VERSION
|
||||||
FROM registry.fit2cloud.com/jumpserver/xpack:${VERSION} as build-xpack
|
FROM registry.fit2cloud.com/jumpserver/xpack:${VERSION} as build-xpack
|
||||||
FROM jumpserver/core:${VERSION}
|
FROM registry.fit2cloud.com/jumpserver/core-ce:${VERSION}
|
||||||
|
|
||||||
COPY --from=build-xpack /opt/xpack /opt/jumpserver/apps/xpack
|
COPY --from=build-xpack /opt/xpack /opt/jumpserver/apps/xpack
|
||||||
|
|
||||||
RUN --mount=type=cache,target=/root/.cache \
|
|
||||||
set -ex \
|
|
||||||
&& poetry install --only=xpack
|
|
||||||
@@ -12,8 +12,6 @@
|
|||||||
|
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
JumpServer <a href="https://github.com/jumpserver/jumpserver/releases/tag/v3.0.0">v3.0</a> 正式发布。
|
|
||||||
<br>
|
|
||||||
9 年时间,倾情投入,用心做好一款开源堡垒机。
|
9 年时间,倾情投入,用心做好一款开源堡垒机。
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
@@ -63,6 +61,7 @@ JumpServer 堡垒机帮助企业以更安全的方式管控和登录各种类型
|
|||||||
|
|
||||||
## 案例研究
|
## 案例研究
|
||||||
|
|
||||||
|
- [腾讯音乐娱乐集团:基于JumpServer的安全运维审计解决方案](https://blog.fit2cloud.com/?p=a04cdf0d-6704-4d18-9b40-9180baecd0e2)
|
||||||
- [腾讯海外游戏:基于JumpServer构建游戏安全运营能力](https://blog.fit2cloud.com/?p=3704)
|
- [腾讯海外游戏:基于JumpServer构建游戏安全运营能力](https://blog.fit2cloud.com/?p=3704)
|
||||||
- [万华化学:通过JumpServer管理全球化分布式IT资产,并且实现与云管平台的联动](https://blog.fit2cloud.com/?p=3504)
|
- [万华化学:通过JumpServer管理全球化分布式IT资产,并且实现与云管平台的联动](https://blog.fit2cloud.com/?p=3504)
|
||||||
- [雪花啤酒:JumpServer堡垒机使用体会](https://blog.fit2cloud.com/?p=3412)
|
- [雪花啤酒:JumpServer堡垒机使用体会](https://blog.fit2cloud.com/?p=3412)
|
||||||
@@ -95,11 +94,12 @@ JumpServer 堡垒机帮助企业以更安全的方式管控和登录各种类型
|
|||||||
| [KoKo](https://github.com/jumpserver/koko) | <a href="https://github.com/jumpserver/koko/releases"><img alt="Koko release" src="https://img.shields.io/github/release/jumpserver/koko.svg" /></a> | JumpServer 字符协议 Connector 项目 |
|
| [KoKo](https://github.com/jumpserver/koko) | <a href="https://github.com/jumpserver/koko/releases"><img alt="Koko release" src="https://img.shields.io/github/release/jumpserver/koko.svg" /></a> | JumpServer 字符协议 Connector 项目 |
|
||||||
| [Lion](https://github.com/jumpserver/lion-release) | <a href="https://github.com/jumpserver/lion-release/releases"><img alt="Lion release" src="https://img.shields.io/github/release/jumpserver/lion-release.svg" /></a> | JumpServer 图形协议 Connector 项目,依赖 [Apache Guacamole](https://guacamole.apache.org/) |
|
| [Lion](https://github.com/jumpserver/lion-release) | <a href="https://github.com/jumpserver/lion-release/releases"><img alt="Lion release" src="https://img.shields.io/github/release/jumpserver/lion-release.svg" /></a> | JumpServer 图形协议 Connector 项目,依赖 [Apache Guacamole](https://guacamole.apache.org/) |
|
||||||
| [Razor](https://github.com/jumpserver/razor) | <img alt="Chen" src="https://img.shields.io/badge/release-私有发布-red" /> | JumpServer RDP 代理 Connector 项目 |
|
| [Razor](https://github.com/jumpserver/razor) | <img alt="Chen" src="https://img.shields.io/badge/release-私有发布-red" /> | JumpServer RDP 代理 Connector 项目 |
|
||||||
| [Tinker](https://github.com/jumpserver/tinker) | <img alt="Tinker" src="https://img.shields.io/badge/release-私有发布-red" /> | JumpServer 远程应用 Connector 项目 |
|
| [Tinker](https://github.com/jumpserver/tinker) | <img alt="Tinker" src="https://img.shields.io/badge/release-私有发布-red" /> | JumpServer 远程应用 Connector 项目 (Windows) |
|
||||||
|
| [Panda](https://github.com/jumpserver/Panda) | <img alt="Panda" src="https://img.shields.io/badge/release-私有发布-red" /> | JumpServer 远程应用 Connector 项目 (Linux) |
|
||||||
| [Magnus](https://github.com/jumpserver/magnus-release) | <a href="https://github.com/jumpserver/magnus-release/releases"><img alt="Magnus release" src="https://img.shields.io/github/release/jumpserver/magnus-release.svg" /> | JumpServer 数据库代理 Connector 项目 |
|
| [Magnus](https://github.com/jumpserver/magnus-release) | <a href="https://github.com/jumpserver/magnus-release/releases"><img alt="Magnus release" src="https://img.shields.io/github/release/jumpserver/magnus-release.svg" /> | JumpServer 数据库代理 Connector 项目 |
|
||||||
| [Chen](https://github.com/jumpserver/chen-release) | <a href="https://github.com/jumpserver/chen-release/releases"><img alt="Chen release" src="https://img.shields.io/github/release/jumpserver/chen-release.svg" /> | JumpServer Web DB 项目,替代原来的 OmniDB |
|
| [Chen](https://github.com/jumpserver/chen-release) | <a href="https://github.com/jumpserver/chen-release/releases"><img alt="Chen release" src="https://img.shields.io/github/release/jumpserver/chen-release.svg" /> | JumpServer Web DB 项目,替代原来的 OmniDB |
|
||||||
| [Kael](https://github.com/jumpserver/kael) | <a href="https://github.com/jumpserver/kael/releases"><img alt="Kael release" src="https://img.shields.io/github/release/jumpserver/kael.svg" /> | JumpServer 连接 GPT 资产的组件项目 |
|
| [Kael](https://github.com/jumpserver/kael) | <a href="https://github.com/jumpserver/kael/releases"><img alt="Kael release" src="https://img.shields.io/github/release/jumpserver/kael.svg" /> | JumpServer 连接 GPT 资产的组件项目 |
|
||||||
| [Wisp](https://github.com/jumpserver/wisp) | <a href="https://github.com/jumpserver/wisp/releases"><img alt="Magnus release" src="https://img.shields.io/github/release/jumpserver/wisp.svg" /> | JumpServer 各系统终端组件和 Core Api 通信的组件项目 |
|
| [Wisp](https://github.com/jumpserver/wisp) | <a href="https://github.com/jumpserver/wisp/releases"><img alt="Magnus release" src="https://img.shields.io/github/release/jumpserver/wisp.svg" /> | JumpServer 各系统终端组件和 Core API 通信的组件项目 |
|
||||||
| [Clients](https://github.com/jumpserver/clients) | <a href="https://github.com/jumpserver/clients/releases"><img alt="Clients release" src="https://img.shields.io/github/release/jumpserver/clients.svg" /> | JumpServer 客户端 项目 |
|
| [Clients](https://github.com/jumpserver/clients) | <a href="https://github.com/jumpserver/clients/releases"><img alt="Clients release" src="https://img.shields.io/github/release/jumpserver/clients.svg" /> | JumpServer 客户端 项目 |
|
||||||
| [Installer](https://github.com/jumpserver/installer) | <a href="https://github.com/jumpserver/installer/releases"><img alt="Installer release" src="https://img.shields.io/github/release/jumpserver/installer.svg" /> | JumpServer 安装包 项目 |
|
| [Installer](https://github.com/jumpserver/installer) | <a href="https://github.com/jumpserver/installer/releases"><img alt="Installer release" src="https://img.shields.io/github/release/jumpserver/installer.svg" /> | JumpServer 安装包 项目 |
|
||||||
|
|
||||||
|
|||||||
@@ -6,11 +6,12 @@ from rest_framework.status import HTTP_200_OK
|
|||||||
|
|
||||||
from accounts import serializers
|
from accounts import serializers
|
||||||
from accounts.filters import AccountFilterSet
|
from accounts.filters import AccountFilterSet
|
||||||
|
from accounts.mixins import AccountRecordViewLogMixin
|
||||||
from accounts.models import Account
|
from accounts.models import Account
|
||||||
from assets.models import Asset, Node
|
from assets.models import Asset, Node
|
||||||
from common.api import ExtraFilterFieldsMixin
|
from authentication.permissions import UserConfirmation, ConfirmType
|
||||||
from common.permissions import UserConfirmation, ConfirmType, IsValidUser
|
from common.api.mixin import ExtraFilterFieldsMixin
|
||||||
from common.views.mixins import RecordViewLogMixin
|
from common.permissions import IsValidUser
|
||||||
from orgs.mixins.api import OrgBulkModelViewSet
|
from orgs.mixins.api import OrgBulkModelViewSet
|
||||||
from rbac.permissions import RBACPermission
|
from rbac.permissions import RBACPermission
|
||||||
|
|
||||||
@@ -57,19 +58,19 @@ class AccountViewSet(OrgBulkModelViewSet):
|
|||||||
permission_classes=[IsValidUser]
|
permission_classes=[IsValidUser]
|
||||||
)
|
)
|
||||||
def username_suggestions(self, request, *args, **kwargs):
|
def username_suggestions(self, request, *args, **kwargs):
|
||||||
asset_ids = request.data.get('assets')
|
asset_ids = request.data.get('assets', [])
|
||||||
node_ids = request.data.get('nodes')
|
node_ids = request.data.get('nodes', [])
|
||||||
username = request.data.get('username')
|
username = request.data.get('username', '')
|
||||||
|
|
||||||
assets = Asset.objects.all()
|
accounts = Account.objects.all()
|
||||||
if asset_ids:
|
|
||||||
assets = assets.filter(id__in=asset_ids)
|
|
||||||
if node_ids:
|
if node_ids:
|
||||||
nodes = Node.objects.filter(id__in=node_ids)
|
nodes = Node.objects.filter(id__in=node_ids)
|
||||||
node_asset_ids = Node.get_nodes_all_assets(*nodes).values_list('id', flat=True)
|
node_asset_ids = Node.get_nodes_all_assets(*nodes).values_list('id', flat=True)
|
||||||
assets = assets.filter(id__in=set(list(asset_ids) + list(node_asset_ids)))
|
asset_ids.extend(node_asset_ids)
|
||||||
|
|
||||||
|
if asset_ids:
|
||||||
|
accounts = accounts.filter(asset_id__in=list(set(asset_ids)))
|
||||||
|
|
||||||
accounts = Account.objects.filter(asset__in=assets)
|
|
||||||
if username:
|
if username:
|
||||||
accounts = accounts.filter(username__icontains=username)
|
accounts = accounts.filter(username__icontains=username)
|
||||||
usernames = list(accounts.values_list('username', flat=True).distinct()[:10])
|
usernames = list(accounts.values_list('username', flat=True).distinct()[:10])
|
||||||
@@ -86,7 +87,7 @@ class AccountViewSet(OrgBulkModelViewSet):
|
|||||||
return Response(status=HTTP_200_OK)
|
return Response(status=HTTP_200_OK)
|
||||||
|
|
||||||
|
|
||||||
class AccountSecretsViewSet(RecordViewLogMixin, AccountViewSet):
|
class AccountSecretsViewSet(AccountRecordViewLogMixin, AccountViewSet):
|
||||||
"""
|
"""
|
||||||
因为可能要导出所有账号,所以单独建立了一个 viewset
|
因为可能要导出所有账号,所以单独建立了一个 viewset
|
||||||
"""
|
"""
|
||||||
@@ -115,7 +116,7 @@ class AssetAccountBulkCreateApi(CreateAPIView):
|
|||||||
return Response(data=serializer.data, status=HTTP_200_OK)
|
return Response(data=serializer.data, status=HTTP_200_OK)
|
||||||
|
|
||||||
|
|
||||||
class AccountHistoriesSecretAPI(ExtraFilterFieldsMixin, RecordViewLogMixin, ListAPIView):
|
class AccountHistoriesSecretAPI(ExtraFilterFieldsMixin, AccountRecordViewLogMixin, ListAPIView):
|
||||||
model = Account.history.model
|
model = Account.history.model
|
||||||
serializer_class = serializers.AccountHistorySerializer
|
serializer_class = serializers.AccountHistorySerializer
|
||||||
http_method_names = ['get', 'options']
|
http_method_names = ['get', 'options']
|
||||||
@@ -143,4 +144,3 @@ class AccountHistoriesSecretAPI(ExtraFilterFieldsMixin, RecordViewLogMixin, List
|
|||||||
return histories
|
return histories
|
||||||
histories = histories.exclude(history_id=latest_history.history_id)
|
histories = histories.exclude(history_id=latest_history.history_id)
|
||||||
return histories
|
return histories
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,12 @@
|
|||||||
from rest_framework.generics import CreateAPIView
|
from rest_framework.generics import CreateAPIView
|
||||||
from rest_framework.response import Response
|
|
||||||
|
|
||||||
from accounts import serializers
|
from accounts import serializers
|
||||||
from accounts.tasks import verify_accounts_connectivity_task, push_accounts_to_assets_task
|
from accounts.permissions import AccountTaskActionPermission
|
||||||
|
from accounts.tasks import (
|
||||||
|
remove_accounts_task, verify_accounts_connectivity_task, push_accounts_to_assets_task
|
||||||
|
)
|
||||||
from assets.exceptions import NotSupportedTemporarilyError
|
from assets.exceptions import NotSupportedTemporarilyError
|
||||||
|
from authentication.permissions import UserConfirmation, ConfirmType
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
'AccountsTaskCreateAPI',
|
'AccountsTaskCreateAPI',
|
||||||
@@ -12,14 +15,16 @@ __all__ = [
|
|||||||
|
|
||||||
class AccountsTaskCreateAPI(CreateAPIView):
|
class AccountsTaskCreateAPI(CreateAPIView):
|
||||||
serializer_class = serializers.AccountTaskSerializer
|
serializer_class = serializers.AccountTaskSerializer
|
||||||
|
permission_classes = (AccountTaskActionPermission,)
|
||||||
|
|
||||||
def check_permissions(self, request):
|
def get_permissions(self):
|
||||||
act = request.data.get('action')
|
act = self.request.data.get('action')
|
||||||
if act == 'push':
|
if act == 'remove':
|
||||||
code = 'accounts.push_account'
|
self.permission_classes = [
|
||||||
else:
|
AccountTaskActionPermission,
|
||||||
code = 'accounts.verify_account'
|
UserConfirmation.require(ConfirmType.PASSWORD)
|
||||||
return request.user.has_perm(code)
|
]
|
||||||
|
return super().get_permissions()
|
||||||
|
|
||||||
def perform_create(self, serializer):
|
def perform_create(self, serializer):
|
||||||
data = serializer.validated_data
|
data = serializer.validated_data
|
||||||
@@ -29,6 +34,10 @@ class AccountsTaskCreateAPI(CreateAPIView):
|
|||||||
|
|
||||||
if data['action'] == 'push':
|
if data['action'] == 'push':
|
||||||
task = push_accounts_to_assets_task.delay(account_ids, params)
|
task = push_accounts_to_assets_task.delay(account_ids, params)
|
||||||
|
elif data['action'] == 'remove':
|
||||||
|
gather_accounts = data.get('gather_accounts', [])
|
||||||
|
gather_account_ids = [str(a.id) for a in gather_accounts]
|
||||||
|
task = remove_accounts_task.delay(gather_account_ids)
|
||||||
else:
|
else:
|
||||||
account = accounts[0]
|
account = accounts[0]
|
||||||
asset = account.asset
|
asset = account.asset
|
||||||
@@ -41,9 +50,3 @@ class AccountsTaskCreateAPI(CreateAPIView):
|
|||||||
data["task"] = task.id
|
data["task"] = task.id
|
||||||
setattr(serializer, '_data', data)
|
setattr(serializer, '_data', data)
|
||||||
return task
|
return task
|
||||||
|
|
||||||
def get_exception_handler(self):
|
|
||||||
def handler(e, context):
|
|
||||||
return Response({"error": str(e)}, status=400)
|
|
||||||
|
|
||||||
return handler
|
|
||||||
|
|||||||
@@ -1,13 +1,15 @@
|
|||||||
from django_filters import rest_framework as drf_filters
|
from django_filters import rest_framework as drf_filters
|
||||||
|
from rest_framework import status
|
||||||
from rest_framework.decorators import action
|
from rest_framework.decorators import action
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
|
|
||||||
from accounts import serializers
|
from accounts import serializers
|
||||||
|
from accounts.mixins import AccountRecordViewLogMixin
|
||||||
from accounts.models import AccountTemplate
|
from accounts.models import AccountTemplate
|
||||||
|
from accounts.tasks import template_sync_related_accounts
|
||||||
from assets.const import Protocol
|
from assets.const import Protocol
|
||||||
|
from authentication.permissions import UserConfirmation, ConfirmType
|
||||||
from common.drf.filters import BaseFilterSet
|
from common.drf.filters import BaseFilterSet
|
||||||
from common.permissions import UserConfirmation, ConfirmType
|
|
||||||
from common.views.mixins import RecordViewLogMixin
|
|
||||||
from orgs.mixins.api import OrgBulkModelViewSet
|
from orgs.mixins.api import OrgBulkModelViewSet
|
||||||
from rbac.permissions import RBACPermission
|
from rbac.permissions import RBACPermission
|
||||||
|
|
||||||
@@ -44,6 +46,7 @@ class AccountTemplateViewSet(OrgBulkModelViewSet):
|
|||||||
}
|
}
|
||||||
rbac_perms = {
|
rbac_perms = {
|
||||||
'su_from_account_templates': 'accounts.view_accounttemplate',
|
'su_from_account_templates': 'accounts.view_accounttemplate',
|
||||||
|
'sync_related_accounts': 'accounts.change_account',
|
||||||
}
|
}
|
||||||
|
|
||||||
@action(methods=['get'], detail=False, url_path='su-from-account-templates')
|
@action(methods=['get'], detail=False, url_path='su-from-account-templates')
|
||||||
@@ -54,8 +57,15 @@ class AccountTemplateViewSet(OrgBulkModelViewSet):
|
|||||||
serializer = self.get_serializer(templates, many=True)
|
serializer = self.get_serializer(templates, many=True)
|
||||||
return Response(data=serializer.data)
|
return Response(data=serializer.data)
|
||||||
|
|
||||||
|
@action(methods=['patch'], detail=True, url_path='sync-related-accounts')
|
||||||
|
def sync_related_accounts(self, request, *args, **kwargs):
|
||||||
|
instance = self.get_object()
|
||||||
|
user_id = str(request.user.id)
|
||||||
|
task = template_sync_related_accounts.delay(str(instance.id), user_id)
|
||||||
|
return Response({'task': task.id}, status=status.HTTP_200_OK)
|
||||||
|
|
||||||
class AccountTemplateSecretsViewSet(RecordViewLogMixin, AccountTemplateViewSet):
|
|
||||||
|
class AccountTemplateSecretsViewSet(AccountRecordViewLogMixin, AccountTemplateViewSet):
|
||||||
serializer_classes = {
|
serializer_classes = {
|
||||||
'default': serializers.AccountTemplateSecretSerializer,
|
'default': serializers.AccountTemplateSecretSerializer,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,13 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
#
|
#
|
||||||
|
from rest_framework import status, mixins
|
||||||
from rest_framework import mixins
|
from rest_framework.decorators import action
|
||||||
|
from rest_framework.response import Response
|
||||||
|
|
||||||
from accounts import serializers
|
from accounts import serializers
|
||||||
from accounts.const import AutomationTypes
|
from accounts.const import AutomationTypes
|
||||||
from accounts.models import ChangeSecretAutomation, ChangeSecretRecord, AutomationExecution
|
from accounts.models import ChangeSecretAutomation, ChangeSecretRecord
|
||||||
from common.utils import get_object_or_none
|
from accounts.tasks import execute_automation_record_task
|
||||||
from orgs.mixins.api import OrgBulkModelViewSet, OrgGenericViewSet
|
from orgs.mixins.api import OrgBulkModelViewSet, OrgGenericViewSet
|
||||||
from .base import (
|
from .base import (
|
||||||
AutomationAssetsListApi, AutomationRemoveAssetApi, AutomationAddAssetApi,
|
AutomationAssetsListApi, AutomationRemoveAssetApi, AutomationAddAssetApi,
|
||||||
@@ -30,21 +31,27 @@ class ChangeSecretAutomationViewSet(OrgBulkModelViewSet):
|
|||||||
|
|
||||||
class ChangeSecretRecordViewSet(mixins.ListModelMixin, OrgGenericViewSet):
|
class ChangeSecretRecordViewSet(mixins.ListModelMixin, OrgGenericViewSet):
|
||||||
serializer_class = serializers.ChangeSecretRecordSerializer
|
serializer_class = serializers.ChangeSecretRecordSerializer
|
||||||
filter_fields = ['asset', 'execution_id']
|
filterset_fields = ('asset_id', 'execution_id')
|
||||||
search_fields = ['asset__hostname']
|
search_fields = ('asset__address',)
|
||||||
|
tp = AutomationTypes.change_secret
|
||||||
|
rbac_perms = {
|
||||||
|
'execute': 'accounts.add_changesecretexecution',
|
||||||
|
}
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
return ChangeSecretRecord.objects.filter(
|
return ChangeSecretRecord.objects.all()
|
||||||
execution__automation__type=AutomationTypes.change_secret
|
|
||||||
)
|
|
||||||
|
|
||||||
def filter_queryset(self, queryset):
|
@action(methods=['post'], detail=False, url_path='execute')
|
||||||
queryset = super().filter_queryset(queryset)
|
def execute(self, request, *args, **kwargs):
|
||||||
eid = self.request.query_params.get('execution_id')
|
record_id = request.data.get('record_id')
|
||||||
execution = get_object_or_none(AutomationExecution, pk=eid)
|
record = self.get_queryset().filter(pk=record_id)
|
||||||
if execution:
|
if not record:
|
||||||
queryset = queryset.filter(execution=execution)
|
return Response(
|
||||||
return queryset
|
{'detail': 'record not found'},
|
||||||
|
status=status.HTTP_404_NOT_FOUND
|
||||||
|
)
|
||||||
|
task = execute_automation_record_task.delay(record_id, self.tp)
|
||||||
|
return Response({'task': task.id}, status=status.HTTP_200_OK)
|
||||||
|
|
||||||
|
|
||||||
class ChangSecretExecutionViewSet(AutomationExecutionViewSet):
|
class ChangSecretExecutionViewSet(AutomationExecutionViewSet):
|
||||||
|
|||||||
@@ -42,6 +42,7 @@ class PushAccountExecutionViewSet(AutomationExecutionViewSet):
|
|||||||
|
|
||||||
class PushAccountRecordViewSet(ChangeSecretRecordViewSet):
|
class PushAccountRecordViewSet(ChangeSecretRecordViewSet):
|
||||||
serializer_class = serializers.ChangeSecretRecordSerializer
|
serializer_class = serializers.ChangeSecretRecordSerializer
|
||||||
|
tp = AutomationTypes.push_account
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
return ChangeSecretRecord.objects.filter(
|
return ChangeSecretRecord.objects.filter(
|
||||||
|
|||||||
@@ -3,19 +3,26 @@ import time
|
|||||||
from collections import defaultdict, OrderedDict
|
from collections import defaultdict, OrderedDict
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from openpyxl import Workbook
|
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
|
from xlsxwriter import Workbook
|
||||||
|
|
||||||
from accounts.notifications import AccountBackupExecutionTaskMsg
|
from accounts.const.automation import AccountBackupType
|
||||||
|
from accounts.models.automations.backup_account import AccountBackupAutomation
|
||||||
|
from accounts.notifications import AccountBackupExecutionTaskMsg, AccountBackupByObjStorageExecutionTaskMsg
|
||||||
from accounts.serializers import AccountSecretSerializer
|
from accounts.serializers import AccountSecretSerializer
|
||||||
from assets.const import AllTypes
|
from assets.const import AllTypes
|
||||||
from common.utils.file import encrypt_and_compress_zip_file
|
from common.utils.file import encrypt_and_compress_zip_file, zip_files
|
||||||
from common.utils.timezone import local_now_display
|
from common.utils.timezone import local_now_filename, local_now_display
|
||||||
|
from terminal.models.component.storage import ReplayStorage
|
||||||
from users.models import User
|
from users.models import User
|
||||||
|
|
||||||
PATH = os.path.join(os.path.dirname(settings.BASE_DIR), 'tmp')
|
PATH = os.path.join(os.path.dirname(settings.BASE_DIR), 'tmp')
|
||||||
|
|
||||||
|
|
||||||
|
class RecipientsNotFound(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
class BaseAccountHandler:
|
class BaseAccountHandler:
|
||||||
@classmethod
|
@classmethod
|
||||||
def unpack_data(cls, serializer_data, data=None):
|
def unpack_data(cls, serializer_data, data=None):
|
||||||
@@ -67,7 +74,7 @@ class AssetAccountHandler(BaseAccountHandler):
|
|||||||
@staticmethod
|
@staticmethod
|
||||||
def get_filename(plan_name):
|
def get_filename(plan_name):
|
||||||
filename = os.path.join(
|
filename = os.path.join(
|
||||||
PATH, f'{plan_name}-{local_now_display()}-{time.time()}.xlsx'
|
PATH, f'{plan_name}-{local_now_filename()}-{time.time()}.xlsx'
|
||||||
)
|
)
|
||||||
return filename
|
return filename
|
||||||
|
|
||||||
@@ -137,13 +144,14 @@ class AccountBackupHandler:
|
|||||||
|
|
||||||
wb = Workbook(filename)
|
wb = Workbook(filename)
|
||||||
for sheet, data in data_map.items():
|
for sheet, data in data_map.items():
|
||||||
ws = wb.create_sheet(str(sheet))
|
ws = wb.add_worksheet(str(sheet))
|
||||||
for row in data:
|
for row in data:
|
||||||
ws.append(row)
|
for col, _data in enumerate(row):
|
||||||
wb.save(filename)
|
ws.write_string(0, col, _data)
|
||||||
|
wb.close()
|
||||||
files.append(filename)
|
files.append(filename)
|
||||||
timedelta = round((time.time() - time_start), 2)
|
timedelta = round((time.time() - time_start), 2)
|
||||||
print('步骤完成: 用时 {}s'.format(timedelta))
|
print('创建备份文件完成: 用时 {}s'.format(timedelta))
|
||||||
return files
|
return files
|
||||||
|
|
||||||
def send_backup_mail(self, files, recipients):
|
def send_backup_mail(self, files, recipients):
|
||||||
@@ -152,7 +160,7 @@ class AccountBackupHandler:
|
|||||||
recipients = User.objects.filter(id__in=list(recipients))
|
recipients = User.objects.filter(id__in=list(recipients))
|
||||||
print(
|
print(
|
||||||
'\n'
|
'\n'
|
||||||
'\033[32m>>> 发送备份邮件\033[0m'
|
'\033[32m>>> 开始发送备份邮件\033[0m'
|
||||||
''
|
''
|
||||||
)
|
)
|
||||||
plan_name = self.plan_name
|
plan_name = self.plan_name
|
||||||
@@ -161,7 +169,7 @@ class AccountBackupHandler:
|
|||||||
attachment_list = []
|
attachment_list = []
|
||||||
else:
|
else:
|
||||||
password = user.secret_key.encode('utf8')
|
password = user.secret_key.encode('utf8')
|
||||||
attachment = os.path.join(PATH, f'{plan_name}-{local_now_display()}-{time.time()}.zip')
|
attachment = os.path.join(PATH, f'{plan_name}-{local_now_filename()}-{time.time()}.zip')
|
||||||
encrypt_and_compress_zip_file(attachment, password, files)
|
encrypt_and_compress_zip_file(attachment, password, files)
|
||||||
attachment_list = [attachment, ]
|
attachment_list = [attachment, ]
|
||||||
AccountBackupExecutionTaskMsg(plan_name, user).publish(attachment_list)
|
AccountBackupExecutionTaskMsg(plan_name, user).publish(attachment_list)
|
||||||
@@ -169,11 +177,35 @@ class AccountBackupHandler:
|
|||||||
for file in files:
|
for file in files:
|
||||||
os.remove(file)
|
os.remove(file)
|
||||||
|
|
||||||
|
def send_backup_obj_storage(self, files, recipients, password):
|
||||||
|
if not files:
|
||||||
|
return
|
||||||
|
recipients = ReplayStorage.objects.filter(id__in=list(recipients))
|
||||||
|
print(
|
||||||
|
'\n'
|
||||||
|
'\033[32m>>> 开始发送备份文件到sftp服务器\033[0m'
|
||||||
|
''
|
||||||
|
)
|
||||||
|
plan_name = self.plan_name
|
||||||
|
for rec in recipients:
|
||||||
|
attachment = os.path.join(PATH, f'{plan_name}-{local_now_filename()}-{time.time()}.zip')
|
||||||
|
if password:
|
||||||
|
print('\033[32m>>> 使用加密密码对文件进行加密中\033[0m')
|
||||||
|
password = password.encode('utf8')
|
||||||
|
encrypt_and_compress_zip_file(attachment, password, files)
|
||||||
|
else:
|
||||||
|
zip_files(attachment, files)
|
||||||
|
attachment_list = attachment
|
||||||
|
AccountBackupByObjStorageExecutionTaskMsg(plan_name, rec).publish(attachment_list)
|
||||||
|
print('备份文件将发送至{}({})'.format(rec.name, rec.id))
|
||||||
|
for file in files:
|
||||||
|
os.remove(file)
|
||||||
|
|
||||||
def step_perform_task_update(self, is_success, reason):
|
def step_perform_task_update(self, is_success, reason):
|
||||||
self.execution.reason = reason[:1024]
|
self.execution.reason = reason[:1024]
|
||||||
self.execution.is_success = is_success
|
self.execution.is_success = is_success
|
||||||
self.execution.save()
|
self.execution.save()
|
||||||
print('已完成对任务状态的更新')
|
print('\n已完成对任务状态的更新\n')
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def step_finished(is_success):
|
def step_finished(is_success):
|
||||||
@@ -186,24 +218,11 @@ class AccountBackupHandler:
|
|||||||
is_success = False
|
is_success = False
|
||||||
error = '-'
|
error = '-'
|
||||||
try:
|
try:
|
||||||
recipients_part_one = self.execution.snapshot.get('recipients_part_one', [])
|
backup_type = self.execution.snapshot.get('backup_type', AccountBackupType.email.value)
|
||||||
recipients_part_two = self.execution.snapshot.get('recipients_part_two', [])
|
if backup_type == AccountBackupType.email.value:
|
||||||
if not recipients_part_one and not recipients_part_two:
|
self.backup_by_email()
|
||||||
print(
|
elif backup_type == AccountBackupType.object_storage.value:
|
||||||
'\n'
|
self.backup_by_obj_storage()
|
||||||
'\033[32m>>> 该备份任务未分配收件人\033[0m'
|
|
||||||
''
|
|
||||||
)
|
|
||||||
if recipients_part_one and recipients_part_two:
|
|
||||||
files = self.create_excel(section='front')
|
|
||||||
self.send_backup_mail(files, recipients_part_one)
|
|
||||||
|
|
||||||
files = self.create_excel(section='back')
|
|
||||||
self.send_backup_mail(files, recipients_part_two)
|
|
||||||
else:
|
|
||||||
recipients = recipients_part_one or recipients_part_two
|
|
||||||
files = self.create_excel()
|
|
||||||
self.send_backup_mail(files, recipients)
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.is_frozen = True
|
self.is_frozen = True
|
||||||
print('任务执行被异常中断')
|
print('任务执行被异常中断')
|
||||||
@@ -217,6 +236,52 @@ class AccountBackupHandler:
|
|||||||
self.step_perform_task_update(is_success, reason)
|
self.step_perform_task_update(is_success, reason)
|
||||||
self.step_finished(is_success)
|
self.step_finished(is_success)
|
||||||
|
|
||||||
|
def backup_by_obj_storage(self):
|
||||||
|
object_id = self.execution.snapshot.get('id')
|
||||||
|
zip_encrypt_password = AccountBackupAutomation.objects.get(id=object_id).zip_encrypt_password
|
||||||
|
obj_recipients_part_one = self.execution.snapshot.get('obj_recipients_part_one', [])
|
||||||
|
obj_recipients_part_two = self.execution.snapshot.get('obj_recipients_part_two', [])
|
||||||
|
if not obj_recipients_part_one and not obj_recipients_part_two:
|
||||||
|
print(
|
||||||
|
'\n'
|
||||||
|
'\033[31m>>> 该备份任务未分配sftp服务器\033[0m'
|
||||||
|
''
|
||||||
|
)
|
||||||
|
raise RecipientsNotFound('Not Found Recipients')
|
||||||
|
if obj_recipients_part_one and obj_recipients_part_two:
|
||||||
|
print('\033[32m>>> 账号的密钥将被拆分成前后两部分发送\033[0m')
|
||||||
|
files = self.create_excel(section='front')
|
||||||
|
self.send_backup_obj_storage(files, obj_recipients_part_one, zip_encrypt_password)
|
||||||
|
|
||||||
|
files = self.create_excel(section='back')
|
||||||
|
self.send_backup_obj_storage(files, obj_recipients_part_two, zip_encrypt_password)
|
||||||
|
else:
|
||||||
|
recipients = obj_recipients_part_one or obj_recipients_part_two
|
||||||
|
files = self.create_excel()
|
||||||
|
self.send_backup_obj_storage(files, recipients, zip_encrypt_password)
|
||||||
|
|
||||||
|
def backup_by_email(self):
|
||||||
|
recipients_part_one = self.execution.snapshot.get('recipients_part_one', [])
|
||||||
|
recipients_part_two = self.execution.snapshot.get('recipients_part_two', [])
|
||||||
|
if not recipients_part_one and not recipients_part_two:
|
||||||
|
print(
|
||||||
|
'\n'
|
||||||
|
'\033[31m>>> 该备份任务未分配收件人\033[0m'
|
||||||
|
''
|
||||||
|
)
|
||||||
|
raise RecipientsNotFound('Not Found Recipients')
|
||||||
|
if recipients_part_one and recipients_part_two:
|
||||||
|
print('\033[32m>>> 账号的密钥将被拆分成前后两部分发送\033[0m')
|
||||||
|
files = self.create_excel(section='front')
|
||||||
|
self.send_backup_mail(files, recipients_part_one)
|
||||||
|
|
||||||
|
files = self.create_excel(section='back')
|
||||||
|
self.send_backup_mail(files, recipients_part_two)
|
||||||
|
else:
|
||||||
|
recipients = recipients_part_one or recipients_part_two
|
||||||
|
files = self.create_excel()
|
||||||
|
self.send_backup_mail(files, recipients)
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
print('任务开始: {}'.format(local_now_display()))
|
print('任务开始: {}'.format(local_now_display()))
|
||||||
time_start = time.time()
|
time_start = time.time()
|
||||||
@@ -229,4 +294,4 @@ class AccountBackupHandler:
|
|||||||
finally:
|
finally:
|
||||||
print('\n任务结束: {}'.format(local_now_display()))
|
print('\n任务结束: {}'.format(local_now_display()))
|
||||||
timedelta = round((time.time() - time_start), 2)
|
timedelta = round((time.time() - time_start), 2)
|
||||||
print('用时: {}'.format(timedelta))
|
print('用时: {}s'.format(timedelta))
|
||||||
|
|||||||
@@ -19,6 +19,7 @@
|
|||||||
become_password: "{{ custom_become_password | default('') }}"
|
become_password: "{{ custom_become_password | default('') }}"
|
||||||
become_private_key_path: "{{ custom_become_private_key_path | default(None) }}"
|
become_private_key_path: "{{ custom_become_private_key_path | default(None) }}"
|
||||||
register: ping_info
|
register: ping_info
|
||||||
|
delegate_to: localhost
|
||||||
|
|
||||||
- name: Change asset password (paramiko)
|
- name: Change asset password (paramiko)
|
||||||
custom_command:
|
custom_command:
|
||||||
@@ -40,6 +41,7 @@
|
|||||||
ignore_errors: true
|
ignore_errors: true
|
||||||
when: ping_info is succeeded
|
when: ping_info is succeeded
|
||||||
register: change_info
|
register: change_info
|
||||||
|
delegate_to: localhost
|
||||||
|
|
||||||
- name: Verify password (paramiko)
|
- name: Verify password (paramiko)
|
||||||
ssh_ping:
|
ssh_ping:
|
||||||
@@ -47,4 +49,9 @@
|
|||||||
login_password: "{{ account.secret }}"
|
login_password: "{{ account.secret }}"
|
||||||
login_host: "{{ jms_asset.address }}"
|
login_host: "{{ jms_asset.address }}"
|
||||||
login_port: "{{ jms_asset.port }}"
|
login_port: "{{ jms_asset.port }}"
|
||||||
become: false
|
become: "{{ account.become.ansible_become | default(False) }}"
|
||||||
|
become_method: su
|
||||||
|
become_user: "{{ account.become.ansible_user | default('') }}"
|
||||||
|
become_password: "{{ account.become.ansible_password | default('') }}"
|
||||||
|
become_private_key_path: "{{ account.become.ansible_ssh_private_key_file | default(None) }}"
|
||||||
|
delegate_to: localhost
|
||||||
|
|||||||
@@ -6,15 +6,26 @@ category:
|
|||||||
type:
|
type:
|
||||||
- all
|
- all
|
||||||
method: change_secret
|
method: change_secret
|
||||||
|
protocol: ssh
|
||||||
params:
|
params:
|
||||||
- name: commands
|
- name: commands
|
||||||
type: list
|
type: list
|
||||||
label: '自定义命令'
|
label: "{{ 'Params commands label' | trans }}"
|
||||||
default: [ '' ]
|
default: [ '' ]
|
||||||
help_text: '自定义命令中如需包含账号的 账号、密码、SSH 连接的用户密码 字段,<br />请使用 {username}、{password}、{login_password}格式,执行任务时会进行替换 。<br />比如针对 Cisco 主机进行改密,一般需要配置五条命令:<br />1. enable<br />2. {login_password}<br />3. configure terminal<br />4. username {username} privilege 0 password {password} <br />5. end'
|
help_text: "{{ 'Params commands help text' | trans }}"
|
||||||
|
|
||||||
i18n:
|
i18n:
|
||||||
SSH account change secret:
|
SSH account change secret:
|
||||||
zh: 使用 SSH 命令行自定义改密
|
zh: '使用 SSH 命令行自定义改密'
|
||||||
ja: SSH コマンドライン方式でカスタムパスワード変更
|
ja: 'SSH コマンドライン方式でカスタムパスワード変更'
|
||||||
en: Custom password change by SSH command line
|
en: 'Custom password change by SSH command line'
|
||||||
|
|
||||||
|
Params commands help text:
|
||||||
|
zh: '自定义命令中如需包含账号的 账号、密码、SSH 连接的用户密码 字段,<br />请使用 {username}、{password}、{login_password}格式,执行任务时会进行替换 。<br />比如针对 Cisco 主机进行改密,一般需要配置五条命令:<br />1. enable<br />2. {login_password}<br />3. configure terminal<br />4. username {username} privilege 0 password {password} <br />5. end'
|
||||||
|
ja: 'カスタム コマンドに SSH 接続用のアカウント番号、パスワード、ユーザー パスワード フィールドを含める必要がある場合は、<br />{ユーザー名}、{パスワード}、{login_password& を使用してください。 # 125; 形式。タスクの実行時に置き換えられます。 <br />たとえば、Cisco ホストのパスワードを変更するには、通常、次の 5 つのコマンドを設定する必要があります:<br />1.enable<br />2.{login_password}<br />3 .ターミナルの設定<br / >4. ユーザー名 {ユーザー名} 権限 0 パスワード {パスワード} <br />5. 終了'
|
||||||
|
en: 'If the custom command needs to include the account number, password, and user password field for SSH connection,<br />Please use {username}, {password}, {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. {login_password}<br />3. configure terminal<br / >4. username {username} privilege 0 password {password} <br />5. end'
|
||||||
|
|
||||||
|
Params commands label:
|
||||||
|
zh: '自定义命令'
|
||||||
|
ja: 'カスタムコマンド'
|
||||||
|
en: 'Custom command'
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
- hosts: mongodb
|
- hosts: mongodb
|
||||||
gather_facts: no
|
gather_facts: no
|
||||||
vars:
|
vars:
|
||||||
ansible_python_interpreter: /usr/local/bin/python
|
ansible_python_interpreter: /opt/py3/bin/python
|
||||||
|
|
||||||
tasks:
|
tasks:
|
||||||
- name: Test MongoDB connection
|
- name: Test MongoDB connection
|
||||||
@@ -11,9 +11,9 @@
|
|||||||
login_host: "{{ jms_asset.address }}"
|
login_host: "{{ jms_asset.address }}"
|
||||||
login_port: "{{ jms_asset.port }}"
|
login_port: "{{ jms_asset.port }}"
|
||||||
login_database: "{{ jms_asset.spec_info.db_name }}"
|
login_database: "{{ jms_asset.spec_info.db_name }}"
|
||||||
ssl: "{{ jms_asset.spec_info.use_ssl }}"
|
ssl: "{{ jms_asset.spec_info.use_ssl | default('') }}"
|
||||||
ssl_ca_certs: "{{ jms_asset.secret_info.ca_cert }}"
|
ssl_ca_certs: "{{ jms_asset.secret_info.ca_cert | default('') }}"
|
||||||
ssl_certfile: "{{ jms_asset.secret_info.client_key }}"
|
ssl_certfile: "{{ jms_asset.secret_info.client_key | default('') }}"
|
||||||
connection_options:
|
connection_options:
|
||||||
- tlsAllowInvalidHostnames: "{{ jms_asset.spec_info.allow_invalid_cert}}"
|
- tlsAllowInvalidHostnames: "{{ jms_asset.spec_info.allow_invalid_cert}}"
|
||||||
register: db_info
|
register: db_info
|
||||||
@@ -31,8 +31,8 @@
|
|||||||
login_port: "{{ jms_asset.port }}"
|
login_port: "{{ jms_asset.port }}"
|
||||||
login_database: "{{ jms_asset.spec_info.db_name }}"
|
login_database: "{{ jms_asset.spec_info.db_name }}"
|
||||||
ssl: "{{ jms_asset.spec_info.use_ssl }}"
|
ssl: "{{ jms_asset.spec_info.use_ssl }}"
|
||||||
ssl_ca_certs: "{{ jms_asset.secret_info.ca_cert }}"
|
ssl_ca_certs: "{{ jms_asset.secret_info.ca_cert | default('') }}"
|
||||||
ssl_certfile: "{{ jms_asset.secret_info.client_key }}"
|
ssl_certfile: "{{ jms_asset.secret_info.client_key | default('') }}"
|
||||||
connection_options:
|
connection_options:
|
||||||
- tlsAllowInvalidHostnames: "{{ jms_asset.spec_info.allow_invalid_cert}}"
|
- tlsAllowInvalidHostnames: "{{ jms_asset.spec_info.allow_invalid_cert}}"
|
||||||
db: "{{ jms_asset.spec_info.db_name }}"
|
db: "{{ jms_asset.spec_info.db_name }}"
|
||||||
@@ -49,7 +49,7 @@
|
|||||||
login_port: "{{ jms_asset.port }}"
|
login_port: "{{ jms_asset.port }}"
|
||||||
login_database: "{{ jms_asset.spec_info.db_name }}"
|
login_database: "{{ jms_asset.spec_info.db_name }}"
|
||||||
ssl: "{{ jms_asset.spec_info.use_ssl }}"
|
ssl: "{{ jms_asset.spec_info.use_ssl }}"
|
||||||
ssl_ca_certs: "{{ jms_asset.secret_info.ca_cert }}"
|
ssl_ca_certs: "{{ jms_asset.secret_info.ca_cert | default('') }}"
|
||||||
ssl_certfile: "{{ jms_asset.secret_info.client_key }}"
|
ssl_certfile: "{{ jms_asset.secret_info.client_key | default('') }}"
|
||||||
connection_options:
|
connection_options:
|
||||||
- tlsAllowInvalidHostnames: "{{ jms_asset.spec_info.allow_invalid_cert}}"
|
- tlsAllowInvalidHostnames: "{{ jms_asset.spec_info.allow_invalid_cert}}"
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
- hosts: mysql
|
- hosts: mysql
|
||||||
gather_facts: no
|
gather_facts: no
|
||||||
vars:
|
vars:
|
||||||
ansible_python_interpreter: /usr/local/bin/python
|
ansible_python_interpreter: /opt/py3/bin/python
|
||||||
db_name: "{{ jms_asset.spec_info.db_name }}"
|
db_name: "{{ jms_asset.spec_info.db_name }}"
|
||||||
|
check_ssl: "{{ jms_asset.spec_info.use_ssl and not jms_asset.spec_info.allow_invalid_cert }}"
|
||||||
|
|
||||||
tasks:
|
tasks:
|
||||||
- name: Test MySQL connection
|
- name: Test MySQL connection
|
||||||
@@ -11,6 +12,10 @@
|
|||||||
login_password: "{{ jms_account.secret }}"
|
login_password: "{{ jms_account.secret }}"
|
||||||
login_host: "{{ jms_asset.address }}"
|
login_host: "{{ jms_asset.address }}"
|
||||||
login_port: "{{ jms_asset.port }}"
|
login_port: "{{ jms_asset.port }}"
|
||||||
|
check_hostname: "{{ check_ssl if check_ssl else omit }}"
|
||||||
|
ca_cert: "{{ jms_asset.secret_info.ca_cert | default(omit) if check_ssl else omit }}"
|
||||||
|
client_cert: "{{ jms_asset.secret_info.client_cert | default(omit) if check_ssl else omit }}"
|
||||||
|
client_key: "{{ jms_asset.secret_info.client_key | default(omit) if check_ssl else omit }}"
|
||||||
filter: version
|
filter: version
|
||||||
register: db_info
|
register: db_info
|
||||||
|
|
||||||
@@ -24,6 +29,10 @@
|
|||||||
login_password: "{{ jms_account.secret }}"
|
login_password: "{{ jms_account.secret }}"
|
||||||
login_host: "{{ jms_asset.address }}"
|
login_host: "{{ jms_asset.address }}"
|
||||||
login_port: "{{ jms_asset.port }}"
|
login_port: "{{ jms_asset.port }}"
|
||||||
|
check_hostname: "{{ check_ssl if check_ssl else omit }}"
|
||||||
|
ca_cert: "{{ jms_asset.secret_info.ca_cert | default(omit) if check_ssl else omit }}"
|
||||||
|
client_cert: "{{ jms_asset.secret_info.client_cert | default(omit) if check_ssl else omit }}"
|
||||||
|
client_key: "{{ jms_asset.secret_info.client_key | default(omit) if check_ssl else omit }}"
|
||||||
name: "{{ account.username }}"
|
name: "{{ account.username }}"
|
||||||
password: "{{ account.secret }}"
|
password: "{{ account.secret }}"
|
||||||
host: "%"
|
host: "%"
|
||||||
@@ -37,4 +46,8 @@
|
|||||||
login_password: "{{ account.secret }}"
|
login_password: "{{ account.secret }}"
|
||||||
login_host: "{{ jms_asset.address }}"
|
login_host: "{{ jms_asset.address }}"
|
||||||
login_port: "{{ jms_asset.port }}"
|
login_port: "{{ jms_asset.port }}"
|
||||||
|
check_hostname: "{{ check_ssl if check_ssl else omit }}"
|
||||||
|
ca_cert: "{{ jms_asset.secret_info.ca_cert | default(omit) if check_ssl else omit }}"
|
||||||
|
client_cert: "{{ jms_asset.secret_info.client_cert | default(omit) if check_ssl else omit }}"
|
||||||
|
client_key: "{{ jms_asset.secret_info.client_key | default(omit) if check_ssl else omit }}"
|
||||||
filter: version
|
filter: version
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
- hosts: oracle
|
- hosts: oracle
|
||||||
gather_facts: no
|
gather_facts: no
|
||||||
vars:
|
vars:
|
||||||
ansible_python_interpreter: /usr/local/bin/python
|
ansible_python_interpreter: /opt/py3/bin/python
|
||||||
|
|
||||||
tasks:
|
tasks:
|
||||||
- name: Test Oracle connection
|
- name: Test Oracle connection
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
- hosts: postgre
|
- hosts: postgre
|
||||||
gather_facts: no
|
gather_facts: no
|
||||||
vars:
|
vars:
|
||||||
ansible_python_interpreter: /usr/local/bin/python
|
ansible_python_interpreter: /opt/py3/bin/python
|
||||||
|
|
||||||
tasks:
|
tasks:
|
||||||
- name: Test PostgreSQL connection
|
- name: Test PostgreSQL connection
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
- hosts: sqlserver
|
- hosts: sqlserver
|
||||||
gather_facts: no
|
gather_facts: no
|
||||||
vars:
|
vars:
|
||||||
ansible_python_interpreter: /usr/local/bin/python
|
ansible_python_interpreter: /opt/py3/bin/python
|
||||||
|
|
||||||
tasks:
|
tasks:
|
||||||
- name: Test SQLServer connection
|
- name: Test SQLServer connection
|
||||||
@@ -40,7 +40,7 @@
|
|||||||
login_host: "{{ jms_asset.address }}"
|
login_host: "{{ jms_asset.address }}"
|
||||||
login_port: "{{ jms_asset.port }}"
|
login_port: "{{ jms_asset.port }}"
|
||||||
name: '{{ jms_asset.spec_info.db_name }}'
|
name: '{{ jms_asset.spec_info.db_name }}'
|
||||||
script: "ALTER LOGIN {{ account.username }} WITH PASSWORD = '{{ account.secret }}'; select @@version"
|
script: "ALTER LOGIN {{ account.username }} WITH PASSWORD = '{{ account.secret }}', DEFAULT_DATABASE = {{ jms_asset.spec_info.db_name }}; select @@version"
|
||||||
ignore_errors: true
|
ignore_errors: true
|
||||||
when: user_exist.query_results[0] | length != 0
|
when: user_exist.query_results[0] | length != 0
|
||||||
|
|
||||||
@@ -51,7 +51,7 @@
|
|||||||
login_host: "{{ jms_asset.address }}"
|
login_host: "{{ jms_asset.address }}"
|
||||||
login_port: "{{ jms_asset.port }}"
|
login_port: "{{ jms_asset.port }}"
|
||||||
name: '{{ jms_asset.spec_info.db_name }}'
|
name: '{{ jms_asset.spec_info.db_name }}'
|
||||||
script: "CREATE LOGIN {{ account.username }} WITH PASSWORD = '{{ account.secret }}'; select @@version"
|
script: "CREATE LOGIN {{ account.username }} WITH PASSWORD = '{{ account.secret }}', DEFAULT_DATABASE = {{ jms_asset.spec_info.db_name }}; CREATE USER {{ account.username }} FOR LOGIN {{ account.username }}; select @@version"
|
||||||
ignore_errors: true
|
ignore_errors: true
|
||||||
when: user_exist.query_results[0] | length == 0
|
when: user_exist.query_results[0] | length == 0
|
||||||
|
|
||||||
|
|||||||
@@ -80,7 +80,11 @@
|
|||||||
login_host: "{{ jms_asset.address }}"
|
login_host: "{{ jms_asset.address }}"
|
||||||
login_port: "{{ jms_asset.port }}"
|
login_port: "{{ jms_asset.port }}"
|
||||||
gateway_args: "{{ jms_asset.ansible_ssh_common_args | default('') }}"
|
gateway_args: "{{ jms_asset.ansible_ssh_common_args | default('') }}"
|
||||||
become: false
|
become: "{{ account.become.ansible_become | default(False) }}"
|
||||||
|
become_method: su
|
||||||
|
become_user: "{{ account.become.ansible_user | default('') }}"
|
||||||
|
become_password: "{{ account.become.ansible_password | default('') }}"
|
||||||
|
become_private_key_path: "{{ account.become.ansible_ssh_private_key_file | default(None) }}"
|
||||||
when: account.secret_type == "password"
|
when: account.secret_type == "password"
|
||||||
delegate_to: localhost
|
delegate_to: localhost
|
||||||
|
|
||||||
@@ -91,6 +95,5 @@
|
|||||||
login_user: "{{ account.username }}"
|
login_user: "{{ account.username }}"
|
||||||
login_private_key_path: "{{ account.private_key_path }}"
|
login_private_key_path: "{{ account.private_key_path }}"
|
||||||
gateway_args: "{{ jms_asset.ansible_ssh_common_args | default('') }}"
|
gateway_args: "{{ jms_asset.ansible_ssh_common_args | default('') }}"
|
||||||
become: false
|
|
||||||
when: account.secret_type == "ssh_key"
|
when: account.secret_type == "ssh_key"
|
||||||
delegate_to: localhost
|
delegate_to: localhost
|
||||||
|
|||||||
@@ -80,7 +80,11 @@
|
|||||||
login_host: "{{ jms_asset.address }}"
|
login_host: "{{ jms_asset.address }}"
|
||||||
login_port: "{{ jms_asset.port }}"
|
login_port: "{{ jms_asset.port }}"
|
||||||
gateway_args: "{{ jms_asset.ansible_ssh_common_args | default('') }}"
|
gateway_args: "{{ jms_asset.ansible_ssh_common_args | default('') }}"
|
||||||
become: false
|
become: "{{ account.become.ansible_become | default(False) }}"
|
||||||
|
become_method: su
|
||||||
|
become_user: "{{ account.become.ansible_user | default('') }}"
|
||||||
|
become_password: "{{ account.become.ansible_password | default('') }}"
|
||||||
|
become_private_key_path: "{{ account.become.ansible_ssh_private_key_file | default(None) }}"
|
||||||
when: account.secret_type == "password"
|
when: account.secret_type == "password"
|
||||||
delegate_to: localhost
|
delegate_to: localhost
|
||||||
|
|
||||||
@@ -91,6 +95,5 @@
|
|||||||
login_user: "{{ account.username }}"
|
login_user: "{{ account.username }}"
|
||||||
login_private_key_path: "{{ account.private_key_path }}"
|
login_private_key_path: "{{ account.private_key_path }}"
|
||||||
gateway_args: "{{ jms_asset.ansible_ssh_common_args | default('') }}"
|
gateway_args: "{{ jms_asset.ansible_ssh_common_args | default('') }}"
|
||||||
become: false
|
|
||||||
when: account.secret_type == "ssh_key"
|
when: account.secret_type == "ssh_key"
|
||||||
delegate_to: localhost
|
delegate_to: localhost
|
||||||
|
|||||||
@@ -0,0 +1,35 @@
|
|||||||
|
- hosts: demo
|
||||||
|
gather_facts: no
|
||||||
|
tasks:
|
||||||
|
- name: Test privileged account
|
||||||
|
ansible.windows.win_ping:
|
||||||
|
|
||||||
|
# - name: Print variables
|
||||||
|
# debug:
|
||||||
|
# msg: "Username: {{ account.username }}, Password: {{ account.secret }}"
|
||||||
|
|
||||||
|
- name: Change password
|
||||||
|
ansible.windows.win_user:
|
||||||
|
fullname: "{{ account.username}}"
|
||||||
|
name: "{{ account.username }}"
|
||||||
|
password: "{{ account.secret }}"
|
||||||
|
password_never_expires: yes
|
||||||
|
groups: "{{ params.groups }}"
|
||||||
|
groups_action: add
|
||||||
|
update_password: always
|
||||||
|
ignore_errors: true
|
||||||
|
when: account.secret_type == "password"
|
||||||
|
|
||||||
|
- name: Refresh connection
|
||||||
|
ansible.builtin.meta: reset_connection
|
||||||
|
|
||||||
|
- name: Verify password (pyfreerdp)
|
||||||
|
rdp_ping:
|
||||||
|
login_host: "{{ jms_asset.address }}"
|
||||||
|
login_port: "{{ jms_asset.protocols | selectattr('name', 'equalto', 'rdp') | map(attribute='port') | first }}"
|
||||||
|
login_user: "{{ account.username }}"
|
||||||
|
login_password: "{{ account.secret }}"
|
||||||
|
login_secret_type: "{{ account.secret_type }}"
|
||||||
|
login_private_key_path: "{{ account.private_key_path }}"
|
||||||
|
when: account.secret_type == "password"
|
||||||
|
delegate_to: localhost
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
id: change_secret_windows_rdp_verify
|
||||||
|
name: "{{ 'Windows account change secret rdp verify' | trans }}"
|
||||||
|
version: 1
|
||||||
|
method: change_secret
|
||||||
|
category: host
|
||||||
|
type:
|
||||||
|
- windows
|
||||||
|
params:
|
||||||
|
- name: groups
|
||||||
|
type: str
|
||||||
|
label: '用户组'
|
||||||
|
default: 'Users,Remote Desktop Users'
|
||||||
|
help_text: "{{ 'Params groups help text' | trans }}"
|
||||||
|
|
||||||
|
|
||||||
|
i18n:
|
||||||
|
Windows account change secret rdp verify:
|
||||||
|
zh: '使用 Ansible 模块 win_user 执行 Windows 账号改密 RDP 协议测试最后的可连接性'
|
||||||
|
ja: 'Ansibleモジュールwin_userはWindowsアカウントの改密RDPプロトコルテストの最後の接続性を実行する'
|
||||||
|
en: 'Using the Ansible module win_user performs Windows account encryption RDP protocol testing for final connectivity'
|
||||||
|
|
||||||
|
Params groups help text:
|
||||||
|
zh: '请输入用户组,多个用户组使用逗号分隔(需填写已存在的用户组)'
|
||||||
|
ja: 'グループを入力してください。複数のグループはコンマで区切ってください(既存のグループを入力してください)'
|
||||||
|
en: 'Please enter the group. Multiple groups are separated by commas (please enter the existing group)'
|
||||||
|
|
||||||
@@ -1,11 +1,10 @@
|
|||||||
import os
|
import os
|
||||||
import time
|
import time
|
||||||
from collections import defaultdict
|
|
||||||
from copy import deepcopy
|
from copy import deepcopy
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
from openpyxl import Workbook
|
from xlsxwriter import Workbook
|
||||||
|
|
||||||
from accounts.const import AutomationTypes, SecretType, SSHKeyStrategy, SecretStrategy
|
from accounts.const import AutomationTypes, SecretType, SSHKeyStrategy, SecretStrategy
|
||||||
from accounts.models import ChangeSecretRecord
|
from accounts.models import ChangeSecretRecord
|
||||||
@@ -14,7 +13,7 @@ from accounts.serializers import ChangeSecretRecordBackUpSerializer
|
|||||||
from assets.const import HostTypes
|
from assets.const import HostTypes
|
||||||
from common.utils import get_logger
|
from common.utils import get_logger
|
||||||
from common.utils.file import encrypt_and_compress_zip_file
|
from common.utils.file import encrypt_and_compress_zip_file
|
||||||
from common.utils.timezone import local_now_display
|
from common.utils.timezone import local_now_filename
|
||||||
from users.models import User
|
from users.models import User
|
||||||
from ..base.manager import AccountBasePlaybookManager
|
from ..base.manager import AccountBasePlaybookManager
|
||||||
from ...utils import SecretGenerator
|
from ...utils import SecretGenerator
|
||||||
@@ -27,7 +26,7 @@ class ChangeSecretManager(AccountBasePlaybookManager):
|
|||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
self.method_hosts_mapper = defaultdict(list)
|
self.record_id = self.execution.snapshot.get('record_id')
|
||||||
self.secret_type = self.execution.snapshot.get('secret_type')
|
self.secret_type = self.execution.snapshot.get('secret_type')
|
||||||
self.secret_strategy = self.execution.snapshot.get(
|
self.secret_strategy = self.execution.snapshot.get(
|
||||||
'secret_strategy', SecretStrategy.custom
|
'secret_strategy', SecretStrategy.custom
|
||||||
@@ -50,7 +49,9 @@ class ChangeSecretManager(AccountBasePlaybookManager):
|
|||||||
kwargs['exclusive'] = 'yes' if kwargs['strategy'] == SSHKeyStrategy.set else 'no'
|
kwargs['exclusive'] = 'yes' if kwargs['strategy'] == SSHKeyStrategy.set else 'no'
|
||||||
|
|
||||||
if kwargs['strategy'] == SSHKeyStrategy.set_jms:
|
if kwargs['strategy'] == SSHKeyStrategy.set_jms:
|
||||||
kwargs['dest'] = '/home/{}/.ssh/authorized_keys'.format(account.username)
|
username = account.username
|
||||||
|
path = f'/{username}' if username == "root" else f'/home/{username}'
|
||||||
|
kwargs['dest'] = f'{path}/.ssh/authorized_keys'
|
||||||
kwargs['regexp'] = '.*{}$'.format(secret.split()[2].strip())
|
kwargs['regexp'] = '.*{}$'.format(secret.split()[2].strip())
|
||||||
return kwargs
|
return kwargs
|
||||||
|
|
||||||
@@ -96,17 +97,13 @@ class ChangeSecretManager(AccountBasePlaybookManager):
|
|||||||
|
|
||||||
accounts = self.get_accounts(account)
|
accounts = self.get_accounts(account)
|
||||||
if not accounts:
|
if not accounts:
|
||||||
print('没有发现待改密账号: %s 用户ID: %s 类型: %s' % (
|
print('没有发现待处理的账号: %s 用户ID: %s 类型: %s' % (
|
||||||
asset.name, self.account_ids, self.secret_type
|
asset.name, self.account_ids, self.secret_type
|
||||||
))
|
))
|
||||||
return []
|
return []
|
||||||
|
|
||||||
method_attr = getattr(automation, self.method_type() + '_method')
|
|
||||||
method_hosts = self.method_hosts_mapper[method_attr]
|
|
||||||
method_hosts = [h for h in method_hosts if h != host['name']]
|
|
||||||
inventory_hosts = []
|
|
||||||
records = []
|
records = []
|
||||||
|
inventory_hosts = []
|
||||||
if asset.type == HostTypes.WINDOWS and self.secret_type == SecretType.SSH_KEY:
|
if asset.type == HostTypes.WINDOWS and self.secret_type == SecretType.SSH_KEY:
|
||||||
print(f'Windows {asset} does not support ssh key push')
|
print(f'Windows {asset} does not support ssh key push')
|
||||||
return inventory_hosts
|
return inventory_hosts
|
||||||
@@ -116,13 +113,20 @@ class ChangeSecretManager(AccountBasePlaybookManager):
|
|||||||
h = deepcopy(host)
|
h = deepcopy(host)
|
||||||
secret_type = account.secret_type
|
secret_type = account.secret_type
|
||||||
h['name'] += '(' + account.username + ')'
|
h['name'] += '(' + account.username + ')'
|
||||||
|
if self.secret_type is None:
|
||||||
|
new_secret = account.secret
|
||||||
|
else:
|
||||||
new_secret = self.get_secret(secret_type)
|
new_secret = self.get_secret(secret_type)
|
||||||
|
|
||||||
|
if self.record_id is None:
|
||||||
recorder = ChangeSecretRecord(
|
recorder = ChangeSecretRecord(
|
||||||
asset=asset, account=account, execution=self.execution,
|
asset=asset, account=account, execution=self.execution,
|
||||||
old_secret=account.secret, new_secret=new_secret,
|
old_secret=account.secret, new_secret=new_secret,
|
||||||
)
|
)
|
||||||
records.append(recorder)
|
records.append(recorder)
|
||||||
|
else:
|
||||||
|
recorder = ChangeSecretRecord.objects.get(id=self.record_id)
|
||||||
|
|
||||||
self.name_recorder_mapper[h['name']] = recorder
|
self.name_recorder_mapper[h['name']] = recorder
|
||||||
|
|
||||||
private_key_path = None
|
private_key_path = None
|
||||||
@@ -135,14 +139,13 @@ class ChangeSecretManager(AccountBasePlaybookManager):
|
|||||||
'name': account.name,
|
'name': account.name,
|
||||||
'username': account.username,
|
'username': account.username,
|
||||||
'secret_type': secret_type,
|
'secret_type': secret_type,
|
||||||
'secret': new_secret,
|
'secret': account.escape_jinja2_syntax(new_secret),
|
||||||
'private_key_path': private_key_path
|
'private_key_path': private_key_path,
|
||||||
|
'become': account.get_ansible_become_auth(),
|
||||||
}
|
}
|
||||||
if asset.platform.type == 'oracle':
|
if asset.platform.type == 'oracle':
|
||||||
h['account']['mode'] = 'sysdba' if account.privileged else None
|
h['account']['mode'] = 'sysdba' if account.privileged else None
|
||||||
inventory_hosts.append(h)
|
inventory_hosts.append(h)
|
||||||
method_hosts.append(h['name'])
|
|
||||||
self.method_hosts_mapper[method_attr] = method_hosts
|
|
||||||
ChangeSecretRecord.objects.bulk_create(records)
|
ChangeSecretRecord.objects.bulk_create(records)
|
||||||
return inventory_hosts
|
return inventory_hosts
|
||||||
|
|
||||||
@@ -170,7 +173,7 @@ class ChangeSecretManager(AccountBasePlaybookManager):
|
|||||||
recorder.save()
|
recorder.save()
|
||||||
|
|
||||||
def on_runner_failed(self, runner, e):
|
def on_runner_failed(self, runner, e):
|
||||||
logger.error("Change secret error: ", e)
|
logger.error("Account error: ", e)
|
||||||
|
|
||||||
def check_secret(self):
|
def check_secret(self):
|
||||||
if self.secret_strategy == SecretStrategy.custom \
|
if self.secret_strategy == SecretStrategy.custom \
|
||||||
@@ -180,9 +183,11 @@ class ChangeSecretManager(AccountBasePlaybookManager):
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
def run(self, *args, **kwargs):
|
def run(self, *args, **kwargs):
|
||||||
if not self.check_secret():
|
if self.secret_type and not self.check_secret():
|
||||||
return
|
return
|
||||||
super().run(*args, **kwargs)
|
super().run(*args, **kwargs)
|
||||||
|
if self.record_id:
|
||||||
|
return
|
||||||
recorders = self.name_recorder_mapper.values()
|
recorders = self.name_recorder_mapper.values()
|
||||||
recorders = list(recorders)
|
recorders = list(recorders)
|
||||||
self.send_recorder_mail(recorders)
|
self.send_recorder_mail(recorders)
|
||||||
@@ -196,7 +201,7 @@ class ChangeSecretManager(AccountBasePlaybookManager):
|
|||||||
|
|
||||||
name = self.execution.snapshot['name']
|
name = self.execution.snapshot['name']
|
||||||
path = os.path.join(os.path.dirname(settings.BASE_DIR), 'tmp')
|
path = os.path.join(os.path.dirname(settings.BASE_DIR), 'tmp')
|
||||||
filename = os.path.join(path, f'{name}-{local_now_display()}-{time.time()}.xlsx')
|
filename = os.path.join(path, f'{name}-{local_now_filename()}-{time.time()}.xlsx')
|
||||||
if not self.create_file(recorders, filename):
|
if not self.create_file(recorders, filename):
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -204,7 +209,7 @@ class ChangeSecretManager(AccountBasePlaybookManager):
|
|||||||
attachments = []
|
attachments = []
|
||||||
if user.secret_key:
|
if user.secret_key:
|
||||||
password = user.secret_key.encode('utf8')
|
password = user.secret_key.encode('utf8')
|
||||||
attachment = os.path.join(path, f'{name}-{local_now_display()}-{time.time()}.zip')
|
attachment = os.path.join(path, f'{name}-{local_now_filename()}-{time.time()}.zip')
|
||||||
encrypt_and_compress_zip_file(attachment, password, [filename])
|
encrypt_and_compress_zip_file(attachment, password, [filename])
|
||||||
attachments = [attachment]
|
attachments = [attachment]
|
||||||
ChangeSecretExecutionTaskMsg(name, user).publish(attachments)
|
ChangeSecretExecutionTaskMsg(name, user).publish(attachments)
|
||||||
@@ -222,8 +227,9 @@ class ChangeSecretManager(AccountBasePlaybookManager):
|
|||||||
|
|
||||||
rows.insert(0, header)
|
rows.insert(0, header)
|
||||||
wb = Workbook(filename)
|
wb = Workbook(filename)
|
||||||
ws = wb.create_sheet('Sheet1')
|
ws = wb.add_worksheet('Sheet1')
|
||||||
for row in rows:
|
for row in rows:
|
||||||
ws.append(row)
|
for col, data in enumerate(row):
|
||||||
wb.save(filename)
|
ws.write_string(0, col, data)
|
||||||
|
wb.close()
|
||||||
return True
|
return True
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
from .push_account.manager import PushAccountManager
|
|
||||||
from .change_secret.manager import ChangeSecretManager
|
|
||||||
from .verify_account.manager import VerifyAccountManager
|
|
||||||
from .backup_account.manager import AccountBackupManager
|
from .backup_account.manager import AccountBackupManager
|
||||||
|
from .change_secret.manager import ChangeSecretManager
|
||||||
from .gather_accounts.manager import GatherAccountsManager
|
from .gather_accounts.manager import GatherAccountsManager
|
||||||
|
from .push_account.manager import PushAccountManager
|
||||||
|
from .remove_account.manager import RemoveAccountManager
|
||||||
|
from .verify_account.manager import VerifyAccountManager
|
||||||
from .verify_gateway_account.manager import VerifyGatewayAccountManager
|
from .verify_gateway_account.manager import VerifyGatewayAccountManager
|
||||||
from ..const import AutomationTypes
|
from ..const import AutomationTypes
|
||||||
|
|
||||||
@@ -12,6 +13,7 @@ class ExecutionManager:
|
|||||||
AutomationTypes.push_account: PushAccountManager,
|
AutomationTypes.push_account: PushAccountManager,
|
||||||
AutomationTypes.change_secret: ChangeSecretManager,
|
AutomationTypes.change_secret: ChangeSecretManager,
|
||||||
AutomationTypes.verify_account: VerifyAccountManager,
|
AutomationTypes.verify_account: VerifyAccountManager,
|
||||||
|
AutomationTypes.remove_account: RemoveAccountManager,
|
||||||
AutomationTypes.gather_accounts: GatherAccountsManager,
|
AutomationTypes.gather_accounts: GatherAccountsManager,
|
||||||
AutomationTypes.verify_gateway_account: VerifyGatewayAccountManager,
|
AutomationTypes.verify_gateway_account: VerifyGatewayAccountManager,
|
||||||
# TODO 后期迁移到自动化策略中
|
# TODO 后期迁移到自动化策略中
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
- hosts: mongodb
|
- hosts: mongodb
|
||||||
gather_facts: no
|
gather_facts: no
|
||||||
vars:
|
vars:
|
||||||
ansible_python_interpreter: /usr/local/bin/python
|
ansible_python_interpreter: /opt/py3/bin/python
|
||||||
|
|
||||||
tasks:
|
tasks:
|
||||||
- name: Get info
|
- name: Get info
|
||||||
@@ -12,8 +12,8 @@
|
|||||||
login_port: "{{ jms_asset.port }}"
|
login_port: "{{ jms_asset.port }}"
|
||||||
login_database: "{{ jms_asset.spec_info.db_name }}"
|
login_database: "{{ jms_asset.spec_info.db_name }}"
|
||||||
ssl: "{{ jms_asset.spec_info.use_ssl }}"
|
ssl: "{{ jms_asset.spec_info.use_ssl }}"
|
||||||
ssl_ca_certs: "{{ jms_asset.secret_info.ca_cert }}"
|
ssl_ca_certs: "{{ jms_asset.secret_info.ca_cert | default('') }}"
|
||||||
ssl_certfile: "{{ jms_asset.secret_info.client_key }}"
|
ssl_certfile: "{{ jms_asset.secret_info.client_key | default('') }}"
|
||||||
connection_options:
|
connection_options:
|
||||||
- tlsAllowInvalidHostnames: "{{ jms_asset.spec_info.allow_invalid_cert}}"
|
- tlsAllowInvalidHostnames: "{{ jms_asset.spec_info.allow_invalid_cert}}"
|
||||||
filter: users
|
filter: users
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
- hosts: mysql
|
- hosts: mysql
|
||||||
gather_facts: no
|
gather_facts: no
|
||||||
vars:
|
vars:
|
||||||
ansible_python_interpreter: /usr/local/bin/python
|
ansible_python_interpreter: /opt/py3/bin/python
|
||||||
|
check_ssl: "{{ jms_asset.spec_info.use_ssl and not jms_asset.spec_info.allow_invalid_cert }}"
|
||||||
|
|
||||||
tasks:
|
tasks:
|
||||||
- name: Get info
|
- name: Get info
|
||||||
@@ -10,6 +11,10 @@
|
|||||||
login_password: "{{ jms_account.secret }}"
|
login_password: "{{ jms_account.secret }}"
|
||||||
login_host: "{{ jms_asset.address }}"
|
login_host: "{{ jms_asset.address }}"
|
||||||
login_port: "{{ jms_asset.port }}"
|
login_port: "{{ jms_asset.port }}"
|
||||||
|
check_hostname: "{{ check_ssl if check_ssl else omit }}"
|
||||||
|
ca_cert: "{{ jms_asset.secret_info.ca_cert | default(omit) if check_ssl else omit }}"
|
||||||
|
client_cert: "{{ jms_asset.secret_info.client_cert | default(omit) if check_ssl else omit }}"
|
||||||
|
client_key: "{{ jms_asset.secret_info.client_key | default(omit) if check_ssl else omit }}"
|
||||||
filter: users
|
filter: users
|
||||||
register: db_info
|
register: db_info
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
- hosts: oralce
|
- hosts: oralce
|
||||||
gather_facts: no
|
gather_facts: no
|
||||||
vars:
|
vars:
|
||||||
ansible_python_interpreter: /usr/local/bin/python
|
ansible_python_interpreter: /opt/py3/bin/python
|
||||||
|
|
||||||
tasks:
|
tasks:
|
||||||
- name: Get info
|
- name: Get info
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
- hosts: postgresql
|
- hosts: postgresql
|
||||||
gather_facts: no
|
gather_facts: no
|
||||||
vars:
|
vars:
|
||||||
ansible_python_interpreter: /usr/local/bin/python
|
ansible_python_interpreter: /opt/py3/bin/python
|
||||||
|
|
||||||
tasks:
|
tasks:
|
||||||
- name: Get info
|
- name: Get info
|
||||||
|
|||||||
@@ -1,9 +1,14 @@
|
|||||||
|
from collections import defaultdict
|
||||||
|
|
||||||
from accounts.const import AutomationTypes
|
from accounts.const import AutomationTypes
|
||||||
from accounts.models import GatheredAccount
|
from accounts.models import GatheredAccount
|
||||||
|
from assets.models import Asset
|
||||||
from common.utils import get_logger
|
from common.utils import get_logger
|
||||||
from orgs.utils import tmp_to_org
|
from orgs.utils import tmp_to_org
|
||||||
|
from users.models import User
|
||||||
from .filter import GatherAccountsFilter
|
from .filter import GatherAccountsFilter
|
||||||
from ..base.manager import AccountBasePlaybookManager
|
from ..base.manager import AccountBasePlaybookManager
|
||||||
|
from ...notifications import GatherAccountChangeMsg
|
||||||
|
|
||||||
logger = get_logger(__name__)
|
logger = get_logger(__name__)
|
||||||
|
|
||||||
@@ -12,6 +17,9 @@ class GatherAccountsManager(AccountBasePlaybookManager):
|
|||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
self.host_asset_mapper = {}
|
self.host_asset_mapper = {}
|
||||||
|
self.asset_account_info = {}
|
||||||
|
|
||||||
|
self.asset_username_mapper = defaultdict(set)
|
||||||
self.is_sync_account = self.execution.snapshot.get('is_sync_account')
|
self.is_sync_account = self.execution.snapshot.get('is_sync_account')
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@@ -26,10 +34,11 @@ class GatherAccountsManager(AccountBasePlaybookManager):
|
|||||||
def filter_success_result(self, tp, result):
|
def filter_success_result(self, tp, result):
|
||||||
result = GatherAccountsFilter(tp).run(self.method_id_meta_mapper, result)
|
result = GatherAccountsFilter(tp).run(self.method_id_meta_mapper, result)
|
||||||
return result
|
return result
|
||||||
@staticmethod
|
|
||||||
def generate_data(asset, result):
|
def generate_data(self, asset, result):
|
||||||
data = []
|
data = []
|
||||||
for username, info in result.items():
|
for username, info in result.items():
|
||||||
|
self.asset_username_mapper[str(asset.id)].add(username)
|
||||||
d = {'asset': asset, 'username': username, 'present': True}
|
d = {'asset': asset, 'username': username, 'present': True}
|
||||||
if info.get('date'):
|
if info.get('date'):
|
||||||
d['date_last_login'] = info['date']
|
d['date_last_login'] = info['date']
|
||||||
@@ -38,8 +47,21 @@ class GatherAccountsManager(AccountBasePlaybookManager):
|
|||||||
data.append(d)
|
data.append(d)
|
||||||
return data
|
return data
|
||||||
|
|
||||||
def update_or_create_accounts(self, asset, result):
|
def collect_asset_account_info(self, asset, result):
|
||||||
data = self.generate_data(asset, result)
|
data = self.generate_data(asset, result)
|
||||||
|
self.asset_account_info[asset] = data
|
||||||
|
|
||||||
|
def on_host_success(self, host, result):
|
||||||
|
info = result.get('debug', {}).get('res', {}).get('info', {})
|
||||||
|
asset = self.host_asset_mapper.get(host)
|
||||||
|
if asset and info:
|
||||||
|
result = self.filter_success_result(asset.type, info)
|
||||||
|
self.collect_asset_account_info(asset, result)
|
||||||
|
else:
|
||||||
|
logger.error(f'Not found {host} info')
|
||||||
|
|
||||||
|
def update_or_create_accounts(self):
|
||||||
|
for asset, data in self.asset_account_info.items():
|
||||||
with tmp_to_org(asset.org_id):
|
with tmp_to_org(asset.org_id):
|
||||||
gathered_accounts = []
|
gathered_accounts = []
|
||||||
GatheredAccount.objects.filter(asset=asset, present=True).update(present=False)
|
GatheredAccount.objects.filter(asset=asset, present=True).update(present=False)
|
||||||
@@ -50,14 +72,60 @@ class GatherAccountsManager(AccountBasePlaybookManager):
|
|||||||
)
|
)
|
||||||
gathered_accounts.append(gathered_account)
|
gathered_accounts.append(gathered_account)
|
||||||
if not self.is_sync_account:
|
if not self.is_sync_account:
|
||||||
return
|
continue
|
||||||
GatheredAccount.sync_accounts(gathered_accounts)
|
GatheredAccount.sync_accounts(gathered_accounts)
|
||||||
|
|
||||||
def on_host_success(self, host, result):
|
def run(self, *args, **kwargs):
|
||||||
info = result.get('debug', {}).get('res', {}).get('info', {})
|
super().run(*args, **kwargs)
|
||||||
asset = self.host_asset_mapper.get(host)
|
users, change_info = self.generate_send_users_and_change_info()
|
||||||
if asset and info:
|
self.update_or_create_accounts()
|
||||||
result = self.filter_success_result(asset.type, info)
|
self.send_email_if_need(users, change_info)
|
||||||
self.update_or_create_accounts(asset, result)
|
|
||||||
else:
|
def generate_send_users_and_change_info(self):
|
||||||
logger.error("Not found info".format(host))
|
recipients = self.execution.recipients
|
||||||
|
if not self.asset_username_mapper or not recipients:
|
||||||
|
return None, None
|
||||||
|
|
||||||
|
users = User.objects.filter(id__in=recipients)
|
||||||
|
if not users:
|
||||||
|
return users, None
|
||||||
|
|
||||||
|
asset_ids = self.asset_username_mapper.keys()
|
||||||
|
assets = Asset.objects.filter(id__in=asset_ids)
|
||||||
|
gather_accounts = GatheredAccount.objects.filter(asset_id__in=asset_ids, present=True)
|
||||||
|
asset_id_map = {str(asset.id): asset for asset in assets}
|
||||||
|
asset_id_username = list(assets.values_list('id', 'accounts__username'))
|
||||||
|
asset_id_username.extend(list(gather_accounts.values_list('asset_id', 'username')))
|
||||||
|
|
||||||
|
system_asset_username_mapper = defaultdict(set)
|
||||||
|
for asset_id, username in asset_id_username:
|
||||||
|
system_asset_username_mapper[str(asset_id)].add(username)
|
||||||
|
|
||||||
|
change_info = {}
|
||||||
|
for asset_id, usernames in self.asset_username_mapper.items():
|
||||||
|
system_usernames = system_asset_username_mapper.get(asset_id)
|
||||||
|
|
||||||
|
if not system_usernames:
|
||||||
|
continue
|
||||||
|
|
||||||
|
add_usernames = usernames - system_usernames
|
||||||
|
remove_usernames = system_usernames - usernames
|
||||||
|
k = f'{asset_id_map[asset_id]}[{asset_id}]'
|
||||||
|
|
||||||
|
if not add_usernames and not remove_usernames:
|
||||||
|
continue
|
||||||
|
|
||||||
|
change_info[k] = {
|
||||||
|
'add_usernames': ', '.join(add_usernames),
|
||||||
|
'remove_usernames': ', '.join(remove_usernames),
|
||||||
|
}
|
||||||
|
|
||||||
|
return users, change_info
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def send_email_if_need(users, change_info):
|
||||||
|
if not users or not change_info:
|
||||||
|
return
|
||||||
|
|
||||||
|
for user in users:
|
||||||
|
GatherAccountChangeMsg(user, change_info).publish_async()
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
- hosts: mongodb
|
- hosts: mongodb
|
||||||
gather_facts: no
|
gather_facts: no
|
||||||
vars:
|
vars:
|
||||||
ansible_python_interpreter: /usr/local/bin/python
|
ansible_python_interpreter: /opt/py3/bin/python
|
||||||
|
|
||||||
tasks:
|
tasks:
|
||||||
- name: Test MongoDB connection
|
- name: Test MongoDB connection
|
||||||
@@ -12,8 +12,8 @@
|
|||||||
login_port: "{{ jms_asset.port }}"
|
login_port: "{{ jms_asset.port }}"
|
||||||
login_database: "{{ jms_asset.spec_info.db_name }}"
|
login_database: "{{ jms_asset.spec_info.db_name }}"
|
||||||
ssl: "{{ jms_asset.spec_info.use_ssl }}"
|
ssl: "{{ jms_asset.spec_info.use_ssl }}"
|
||||||
ssl_ca_certs: "{{ jms_asset.secret_info.ca_cert }}"
|
ssl_ca_certs: "{{ jms_asset.secret_info.ca_cert | default('') }}"
|
||||||
ssl_certfile: "{{ jms_asset.secret_info.client_key }}"
|
ssl_certfile: "{{ jms_asset.secret_info.client_key | default('') }}"
|
||||||
connection_options:
|
connection_options:
|
||||||
- tlsAllowInvalidHostnames: "{{ jms_asset.spec_info.allow_invalid_cert}}"
|
- tlsAllowInvalidHostnames: "{{ jms_asset.spec_info.allow_invalid_cert}}"
|
||||||
register: db_info
|
register: db_info
|
||||||
@@ -31,8 +31,8 @@
|
|||||||
login_port: "{{ jms_asset.port }}"
|
login_port: "{{ jms_asset.port }}"
|
||||||
login_database: "{{ jms_asset.spec_info.db_name }}"
|
login_database: "{{ jms_asset.spec_info.db_name }}"
|
||||||
ssl: "{{ jms_asset.spec_info.use_ssl }}"
|
ssl: "{{ jms_asset.spec_info.use_ssl }}"
|
||||||
ssl_ca_certs: "{{ jms_asset.secret_info.ca_cert }}"
|
ssl_ca_certs: "{{ jms_asset.secret_info.ca_cert | default('') }}"
|
||||||
ssl_certfile: "{{ jms_asset.secret_info.client_key }}"
|
ssl_certfile: "{{ jms_asset.secret_info.client_key | default('') }}"
|
||||||
connection_options:
|
connection_options:
|
||||||
- tlsAllowInvalidHostnames: "{{ jms_asset.spec_info.allow_invalid_cert}}"
|
- tlsAllowInvalidHostnames: "{{ jms_asset.spec_info.allow_invalid_cert}}"
|
||||||
db: "{{ jms_asset.spec_info.db_name }}"
|
db: "{{ jms_asset.spec_info.db_name }}"
|
||||||
@@ -49,7 +49,7 @@
|
|||||||
login_port: "{{ jms_asset.port }}"
|
login_port: "{{ jms_asset.port }}"
|
||||||
login_database: "{{ jms_asset.spec_info.db_name }}"
|
login_database: "{{ jms_asset.spec_info.db_name }}"
|
||||||
ssl: "{{ jms_asset.spec_info.use_ssl }}"
|
ssl: "{{ jms_asset.spec_info.use_ssl }}"
|
||||||
ssl_ca_certs: "{{ jms_asset.secret_info.ca_cert }}"
|
ssl_ca_certs: "{{ jms_asset.secret_info.ca_cert | default('') }}"
|
||||||
ssl_certfile: "{{ jms_asset.secret_info.client_key }}"
|
ssl_certfile: "{{ jms_asset.secret_info.client_key | default('') }}"
|
||||||
connection_options:
|
connection_options:
|
||||||
- tlsAllowInvalidHostnames: "{{ jms_asset.spec_info.allow_invalid_cert}}"
|
- tlsAllowInvalidHostnames: "{{ jms_asset.spec_info.allow_invalid_cert}}"
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
- hosts: mysql
|
- hosts: mysql
|
||||||
gather_facts: no
|
gather_facts: no
|
||||||
vars:
|
vars:
|
||||||
ansible_python_interpreter: /usr/local/bin/python
|
ansible_python_interpreter: /opt/py3/bin/python
|
||||||
db_name: "{{ jms_asset.spec_info.db_name }}"
|
db_name: "{{ jms_asset.spec_info.db_name }}"
|
||||||
|
check_ssl: "{{ jms_asset.spec_info.use_ssl and not jms_asset.spec_info.allow_invalid_cert }}"
|
||||||
|
|
||||||
tasks:
|
tasks:
|
||||||
- name: Test MySQL connection
|
- name: Test MySQL connection
|
||||||
@@ -11,6 +12,10 @@
|
|||||||
login_password: "{{ jms_account.secret }}"
|
login_password: "{{ jms_account.secret }}"
|
||||||
login_host: "{{ jms_asset.address }}"
|
login_host: "{{ jms_asset.address }}"
|
||||||
login_port: "{{ jms_asset.port }}"
|
login_port: "{{ jms_asset.port }}"
|
||||||
|
check_hostname: "{{ check_ssl if check_ssl else omit }}"
|
||||||
|
ca_cert: "{{ jms_asset.secret_info.ca_cert | default(omit) if check_ssl else omit }}"
|
||||||
|
client_cert: "{{ jms_asset.secret_info.client_cert | default(omit) if check_ssl else omit }}"
|
||||||
|
client_key: "{{ jms_asset.secret_info.client_key | default(omit) if check_ssl else omit }}"
|
||||||
filter: version
|
filter: version
|
||||||
register: db_info
|
register: db_info
|
||||||
|
|
||||||
@@ -24,6 +29,10 @@
|
|||||||
login_password: "{{ jms_account.secret }}"
|
login_password: "{{ jms_account.secret }}"
|
||||||
login_host: "{{ jms_asset.address }}"
|
login_host: "{{ jms_asset.address }}"
|
||||||
login_port: "{{ jms_asset.port }}"
|
login_port: "{{ jms_asset.port }}"
|
||||||
|
check_hostname: "{{ check_ssl if check_ssl else omit }}"
|
||||||
|
ca_cert: "{{ jms_asset.secret_info.ca_cert | default(omit) if check_ssl else omit }}"
|
||||||
|
client_cert: "{{ jms_asset.secret_info.client_cert | default(omit) if check_ssl else omit }}"
|
||||||
|
client_key: "{{ jms_asset.secret_info.client_key | default(omit) if check_ssl else omit }}"
|
||||||
name: "{{ account.username }}"
|
name: "{{ account.username }}"
|
||||||
password: "{{ account.secret }}"
|
password: "{{ account.secret }}"
|
||||||
host: "%"
|
host: "%"
|
||||||
@@ -37,4 +46,8 @@
|
|||||||
login_password: "{{ account.secret }}"
|
login_password: "{{ account.secret }}"
|
||||||
login_host: "{{ jms_asset.address }}"
|
login_host: "{{ jms_asset.address }}"
|
||||||
login_port: "{{ jms_asset.port }}"
|
login_port: "{{ jms_asset.port }}"
|
||||||
|
check_hostname: "{{ check_ssl if check_ssl else omit }}"
|
||||||
|
ca_cert: "{{ jms_asset.secret_info.ca_cert | default(omit) if check_ssl else omit }}"
|
||||||
|
client_cert: "{{ jms_asset.secret_info.client_cert | default(omit) if check_ssl else omit }}"
|
||||||
|
client_key: "{{ jms_asset.secret_info.client_key | default(omit) if check_ssl else omit }}"
|
||||||
filter: version
|
filter: version
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
- hosts: oracle
|
- hosts: oracle
|
||||||
gather_facts: no
|
gather_facts: no
|
||||||
vars:
|
vars:
|
||||||
ansible_python_interpreter: /usr/local/bin/python
|
ansible_python_interpreter: /opt/py3/bin/python
|
||||||
|
|
||||||
tasks:
|
tasks:
|
||||||
- name: Test Oracle connection
|
- name: Test Oracle connection
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
- hosts: postgre
|
- hosts: postgre
|
||||||
gather_facts: no
|
gather_facts: no
|
||||||
vars:
|
vars:
|
||||||
ansible_python_interpreter: /usr/local/bin/python
|
ansible_python_interpreter: /opt/py3/bin/python
|
||||||
|
|
||||||
tasks:
|
tasks:
|
||||||
- name: Test PostgreSQL connection
|
- name: Test PostgreSQL connection
|
||||||
@@ -31,6 +31,7 @@
|
|||||||
role_attr_flags: LOGIN
|
role_attr_flags: LOGIN
|
||||||
ignore_errors: true
|
ignore_errors: true
|
||||||
when: result is succeeded
|
when: result is succeeded
|
||||||
|
register: change_info
|
||||||
|
|
||||||
- name: Verify password
|
- name: Verify password
|
||||||
community.postgresql.postgresql_ping:
|
community.postgresql.postgresql_ping:
|
||||||
@@ -42,3 +43,5 @@
|
|||||||
when:
|
when:
|
||||||
- result is succeeded
|
- result is succeeded
|
||||||
- change_info is succeeded
|
- change_info is succeeded
|
||||||
|
register: result
|
||||||
|
failed_when: not result.is_available
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
- hosts: sqlserver
|
- hosts: sqlserver
|
||||||
gather_facts: no
|
gather_facts: no
|
||||||
vars:
|
vars:
|
||||||
ansible_python_interpreter: /usr/local/bin/python
|
ansible_python_interpreter: /opt/py3/bin/python
|
||||||
|
|
||||||
tasks:
|
tasks:
|
||||||
- name: Test SQLServer connection
|
- name: Test SQLServer connection
|
||||||
@@ -40,7 +40,7 @@
|
|||||||
login_host: "{{ jms_asset.address }}"
|
login_host: "{{ jms_asset.address }}"
|
||||||
login_port: "{{ jms_asset.port }}"
|
login_port: "{{ jms_asset.port }}"
|
||||||
name: '{{ jms_asset.spec_info.db_name }}'
|
name: '{{ jms_asset.spec_info.db_name }}'
|
||||||
script: "ALTER LOGIN {{ account.username }} WITH PASSWORD = '{{ account.secret }}'; select @@version"
|
script: "ALTER LOGIN {{ account.username }} WITH PASSWORD = '{{ account.secret }}', DEFAULT_DATABASE = {{ jms_asset.spec_info.db_name }}; select @@version"
|
||||||
ignore_errors: true
|
ignore_errors: true
|
||||||
when: user_exist.query_results[0] | length != 0
|
when: user_exist.query_results[0] | length != 0
|
||||||
register: change_info
|
register: change_info
|
||||||
@@ -52,7 +52,7 @@
|
|||||||
login_host: "{{ jms_asset.address }}"
|
login_host: "{{ jms_asset.address }}"
|
||||||
login_port: "{{ jms_asset.port }}"
|
login_port: "{{ jms_asset.port }}"
|
||||||
name: '{{ jms_asset.spec_info.db_name }}'
|
name: '{{ jms_asset.spec_info.db_name }}'
|
||||||
script: "CREATE LOGIN {{ account.username }} WITH PASSWORD = '{{ account.secret }}'; select @@version"
|
script: "CREATE LOGIN [{{ account.username }}] WITH PASSWORD = '{{ account.secret }}'; CREATE USER [{{ account.username }}] FOR LOGIN [{{ account.username }}]; select @@version"
|
||||||
ignore_errors: true
|
ignore_errors: true
|
||||||
when: user_exist.query_results[0] | length == 0
|
when: user_exist.query_results[0] | length == 0
|
||||||
register: change_info
|
register: change_info
|
||||||
|
|||||||
@@ -80,7 +80,11 @@
|
|||||||
login_host: "{{ jms_asset.address }}"
|
login_host: "{{ jms_asset.address }}"
|
||||||
login_port: "{{ jms_asset.port }}"
|
login_port: "{{ jms_asset.port }}"
|
||||||
gateway_args: "{{ jms_asset.ansible_ssh_common_args | default('') }}"
|
gateway_args: "{{ jms_asset.ansible_ssh_common_args | default('') }}"
|
||||||
become: false
|
become: "{{ account.become.ansible_become | default(False) }}"
|
||||||
|
become_method: su
|
||||||
|
become_user: "{{ account.become.ansible_user | default('') }}"
|
||||||
|
become_password: "{{ account.become.ansible_password | default('') }}"
|
||||||
|
become_private_key_path: "{{ account.become.ansible_ssh_private_key_file | default(None) }}"
|
||||||
when: account.secret_type == "password"
|
when: account.secret_type == "password"
|
||||||
delegate_to: localhost
|
delegate_to: localhost
|
||||||
|
|
||||||
@@ -91,7 +95,6 @@
|
|||||||
login_user: "{{ account.username }}"
|
login_user: "{{ account.username }}"
|
||||||
login_private_key_path: "{{ account.private_key_path }}"
|
login_private_key_path: "{{ account.private_key_path }}"
|
||||||
gateway_args: "{{ jms_asset.ansible_ssh_common_args | default('') }}"
|
gateway_args: "{{ jms_asset.ansible_ssh_common_args | default('') }}"
|
||||||
become: false
|
|
||||||
when: account.secret_type == "ssh_key"
|
when: account.secret_type == "ssh_key"
|
||||||
delegate_to: localhost
|
delegate_to: localhost
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ params:
|
|||||||
type: str
|
type: str
|
||||||
label: 'Sudo'
|
label: 'Sudo'
|
||||||
default: '/bin/whoami'
|
default: '/bin/whoami'
|
||||||
help_text: '使用逗号分隔多个命令,如: /bin/whoami,/sbin/ifconfig'
|
help_text: "{{ 'Params sudo help text' | trans }}"
|
||||||
|
|
||||||
- name: shell
|
- name: shell
|
||||||
type: str
|
type: str
|
||||||
@@ -18,19 +18,44 @@ params:
|
|||||||
|
|
||||||
- name: home
|
- name: home
|
||||||
type: str
|
type: str
|
||||||
label: '家目录'
|
label: "{{ 'Params home label' | trans }}"
|
||||||
default: ''
|
default: ''
|
||||||
help_text: '默认家目录 /home/系统用户名: /home/username'
|
help_text: "{{ 'Params home help text' | trans }}"
|
||||||
|
|
||||||
- name: groups
|
- name: groups
|
||||||
type: str
|
type: str
|
||||||
label: '用户组'
|
label: "{{ 'Params groups label' | trans }}"
|
||||||
default: ''
|
default: ''
|
||||||
help_text: '请输入用户组,多个用户组使用逗号分隔(需填写已存在的用户组)'
|
help_text: "{{ 'Params groups help text' | trans }}"
|
||||||
|
|
||||||
i18n:
|
i18n:
|
||||||
Aix account push:
|
Aix account push:
|
||||||
zh: 使用 Ansible 模块 user 执行 Aix 账号推送 (DES)
|
zh: '使用 Ansible 模块 user 执行 Aix 账号推送 (DES)'
|
||||||
ja: Ansible user モジュールを使用して Aix アカウントをプッシュする (DES)
|
ja: 'Ansible user モジュールを使用して Aix アカウントをプッシュする (DES)'
|
||||||
en: Using Ansible module user to push account (DES)
|
en: 'Using Ansible module user to push account (DES)'
|
||||||
|
|
||||||
|
Params sudo help text:
|
||||||
|
zh: '使用逗号分隔多个命令,如: /bin/whoami,/sbin/ifconfig'
|
||||||
|
ja: 'コンマで区切って複数のコマンドを入力してください。例: /bin/whoami,/sbin/ifconfig'
|
||||||
|
en: 'Use commas to separate multiple commands, such as: /bin/whoami,/sbin/ifconfig'
|
||||||
|
|
||||||
|
Params home help text:
|
||||||
|
zh: '默认家目录 /home/{账号用户名}'
|
||||||
|
ja: 'デフォルトのホームディレクトリ /home/{アカウントユーザ名}'
|
||||||
|
en: 'Default home directory /home/{account username}'
|
||||||
|
|
||||||
|
Params groups help text:
|
||||||
|
zh: '请输入用户组,多个用户组使用逗号分隔(需填写已存在的用户组)'
|
||||||
|
ja: 'グループを入力してください。複数のグループはコンマで区切ってください(既存のグループを入力してください)'
|
||||||
|
en: 'Please enter the group. Multiple groups are separated by commas (please enter the existing group)'
|
||||||
|
|
||||||
|
Params home label:
|
||||||
|
zh: '家目录'
|
||||||
|
ja: 'ホームディレクトリ'
|
||||||
|
en: 'Home'
|
||||||
|
|
||||||
|
Params groups label:
|
||||||
|
zh: '用户组'
|
||||||
|
ja: 'グループ'
|
||||||
|
en: 'Groups'
|
||||||
|
|
||||||
|
|||||||
@@ -80,7 +80,11 @@
|
|||||||
login_host: "{{ jms_asset.address }}"
|
login_host: "{{ jms_asset.address }}"
|
||||||
login_port: "{{ jms_asset.port }}"
|
login_port: "{{ jms_asset.port }}"
|
||||||
gateway_args: "{{ jms_asset.ansible_ssh_common_args | default('') }}"
|
gateway_args: "{{ jms_asset.ansible_ssh_common_args | default('') }}"
|
||||||
become: false
|
become: "{{ account.become.ansible_become | default(False) }}"
|
||||||
|
become_method: su
|
||||||
|
become_user: "{{ account.become.ansible_user | default('') }}"
|
||||||
|
become_password: "{{ account.become.ansible_password | default('') }}"
|
||||||
|
become_private_key_path: "{{ account.become.ansible_ssh_private_key_file | default(None) }}"
|
||||||
when: account.secret_type == "password"
|
when: account.secret_type == "password"
|
||||||
delegate_to: localhost
|
delegate_to: localhost
|
||||||
|
|
||||||
@@ -91,7 +95,6 @@
|
|||||||
login_user: "{{ account.username }}"
|
login_user: "{{ account.username }}"
|
||||||
login_private_key_path: "{{ account.private_key_path }}"
|
login_private_key_path: "{{ account.private_key_path }}"
|
||||||
gateway_args: "{{ jms_asset.ansible_ssh_common_args | default('') }}"
|
gateway_args: "{{ jms_asset.ansible_ssh_common_args | default('') }}"
|
||||||
become: false
|
|
||||||
when: account.secret_type == "ssh_key"
|
when: account.secret_type == "ssh_key"
|
||||||
delegate_to: localhost
|
delegate_to: localhost
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ params:
|
|||||||
type: str
|
type: str
|
||||||
label: 'Sudo'
|
label: 'Sudo'
|
||||||
default: '/bin/whoami'
|
default: '/bin/whoami'
|
||||||
help_text: '使用逗号分隔多个命令,如: /bin/whoami,/sbin/ifconfig'
|
help_text: "{{ 'Params sudo help text' | trans }}"
|
||||||
|
|
||||||
- name: shell
|
- name: shell
|
||||||
type: str
|
type: str
|
||||||
@@ -20,18 +20,43 @@ params:
|
|||||||
|
|
||||||
- name: home
|
- name: home
|
||||||
type: str
|
type: str
|
||||||
label: '家目录'
|
label: "{{ 'Params home label' | trans }}"
|
||||||
default: ''
|
default: ''
|
||||||
help_text: '默认家目录 /home/系统用户名: /home/username'
|
help_text: "{{ 'Params home help text' | trans }}"
|
||||||
|
|
||||||
- name: groups
|
- name: groups
|
||||||
type: str
|
type: str
|
||||||
label: '用户组'
|
label: "{{ 'Params groups label' | trans }}"
|
||||||
default: ''
|
default: ''
|
||||||
help_text: '请输入用户组,多个用户组使用逗号分隔(需填写已存在的用户组)'
|
help_text: "{{ 'Params groups help text' | trans }}"
|
||||||
|
|
||||||
i18n:
|
i18n:
|
||||||
Posix account push:
|
Posix account push:
|
||||||
zh: 使用 Ansible 模块 user 执行账号推送 (sha512)
|
zh: '使用 Ansible 模块 user 执行账号推送 (sha512)'
|
||||||
ja: Ansible user モジュールを使用してアカウントをプッシュする (sha512)
|
ja: 'Ansible user モジュールを使用してアカウントをプッシュする (sha512)'
|
||||||
en: Using Ansible module user to push account (sha512)
|
en: 'Using Ansible module user to push account (sha512)'
|
||||||
|
|
||||||
|
Params sudo help text:
|
||||||
|
zh: '使用逗号分隔多个命令,如: /bin/whoami,/sbin/ifconfig'
|
||||||
|
ja: 'コンマで区切って複数のコマンドを入力してください。例: /bin/whoami,/sbin/ifconfig'
|
||||||
|
en: 'Use commas to separate multiple commands, such as: /bin/whoami,/sbin/ifconfig'
|
||||||
|
|
||||||
|
Params home help text:
|
||||||
|
zh: '默认家目录 /home/{账号用户名}'
|
||||||
|
ja: 'デフォルトのホームディレクトリ /home/{アカウントユーザ名}'
|
||||||
|
en: 'Default home directory /home/{account username}'
|
||||||
|
|
||||||
|
Params groups help text:
|
||||||
|
zh: '请输入用户组,多个用户组使用逗号分隔(需填写已存在的用户组)'
|
||||||
|
ja: 'グループを入力してください。複数のグループはコンマで区切ってください(既存のグループを入力してください)'
|
||||||
|
en: 'Please enter the group. Multiple groups are separated by commas (please enter the existing group)'
|
||||||
|
|
||||||
|
Params home label:
|
||||||
|
zh: '家目录'
|
||||||
|
ja: 'ホームディレクトリ'
|
||||||
|
en: 'Home'
|
||||||
|
|
||||||
|
Params groups label:
|
||||||
|
zh: '用户组'
|
||||||
|
ja: 'グループ'
|
||||||
|
en: 'Groups'
|
||||||
@@ -10,10 +10,15 @@ params:
|
|||||||
type: str
|
type: str
|
||||||
label: '用户组'
|
label: '用户组'
|
||||||
default: 'Users,Remote Desktop Users'
|
default: 'Users,Remote Desktop Users'
|
||||||
help_text: '请输入用户组,多个用户组使用逗号分隔(需填写已存在的用户组)'
|
help_text: "{{ 'Params groups help text' | trans }}"
|
||||||
|
|
||||||
i18n:
|
i18n:
|
||||||
Windows account push:
|
Windows account push:
|
||||||
zh: 使用 Ansible 模块 win_user 执行 Windows 账号推送
|
zh: '使用 Ansible 模块 win_user 执行 Windows 账号推送'
|
||||||
ja: Ansible win_user モジュールを使用して Windows アカウントをプッシュする
|
ja: 'Ansible win_user モジュールを使用して Windows アカウントをプッシュする'
|
||||||
en: Using Ansible module win_user to push account
|
en: 'Using Ansible module win_user to push account'
|
||||||
|
|
||||||
|
Params groups help text:
|
||||||
|
zh: '请输入用户组,多个用户组使用逗号分隔(需填写已存在的用户组)'
|
||||||
|
ja: 'グループを入力してください。複数のグループはコンマで区切ってください(既存のグループを入力してください)'
|
||||||
|
en: 'Please enter the group. Multiple groups are separated by commas (please enter the existing group)'
|
||||||
|
|||||||
@@ -0,0 +1,35 @@
|
|||||||
|
- hosts: demo
|
||||||
|
gather_facts: no
|
||||||
|
tasks:
|
||||||
|
- name: Test privileged account
|
||||||
|
ansible.windows.win_ping:
|
||||||
|
|
||||||
|
# - name: Print variables
|
||||||
|
# debug:
|
||||||
|
# msg: "Username: {{ account.username }}, Password: {{ account.secret }}"
|
||||||
|
|
||||||
|
- name: Push user password
|
||||||
|
ansible.windows.win_user:
|
||||||
|
fullname: "{{ account.username}}"
|
||||||
|
name: "{{ account.username }}"
|
||||||
|
password: "{{ account.secret }}"
|
||||||
|
password_never_expires: yes
|
||||||
|
groups: "{{ params.groups }}"
|
||||||
|
groups_action: add
|
||||||
|
update_password: always
|
||||||
|
ignore_errors: true
|
||||||
|
when: account.secret_type == "password"
|
||||||
|
|
||||||
|
- name: Refresh connection
|
||||||
|
ansible.builtin.meta: reset_connection
|
||||||
|
|
||||||
|
- name: Verify password (pyfreerdp)
|
||||||
|
rdp_ping:
|
||||||
|
login_host: "{{ jms_asset.address }}"
|
||||||
|
login_port: "{{ jms_asset.protocols | selectattr('name', 'equalto', 'rdp') | map(attribute='port') | first }}"
|
||||||
|
login_user: "{{ account.username }}"
|
||||||
|
login_password: "{{ account.secret }}"
|
||||||
|
login_secret_type: "{{ account.secret_type }}"
|
||||||
|
login_private_key_path: "{{ account.private_key_path }}"
|
||||||
|
when: account.secret_type == "password"
|
||||||
|
delegate_to: localhost
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
id: push_account_windows_rdp_verify
|
||||||
|
name: "{{ 'Windows account push rdp verify' | trans }}"
|
||||||
|
version: 1
|
||||||
|
method: push_account
|
||||||
|
category: host
|
||||||
|
type:
|
||||||
|
- windows
|
||||||
|
params:
|
||||||
|
- name: groups
|
||||||
|
type: str
|
||||||
|
label: '用户组'
|
||||||
|
default: 'Users,Remote Desktop Users'
|
||||||
|
help_text: "{{ 'Params groups help text' | trans }}"
|
||||||
|
|
||||||
|
i18n:
|
||||||
|
Windows account push rdp verify:
|
||||||
|
zh: '使用 Ansible 模块 win_user 执行 Windows 账号推送(最后使用 Python 模块 pyfreerdp 验证账号的可连接性)'
|
||||||
|
ja: 'Ansible モジュール win_user を使用して Windows アカウントのプッシュを実行します (最後に Python モジュール pyfreerdp を使用してアカウントの接続性を確認します)'
|
||||||
|
en: 'Use the Ansible module win_user to perform Windows account push (finally use the Python module pyfreerdp to verify the connectability of the account)'
|
||||||
|
|
||||||
|
Params groups help text:
|
||||||
|
zh: '请输入用户组,多个用户组使用逗号分隔(需填写已存在的用户组)'
|
||||||
|
ja: 'グループを入力してください。複数のグループはコンマで区切ってください(既存のグループを入力してください)'
|
||||||
|
en: 'Please enter the group. Multiple groups are separated by commas (please enter the existing group)'
|
||||||
@@ -1,7 +1,4 @@
|
|||||||
from copy import deepcopy
|
from accounts.const import AutomationTypes
|
||||||
|
|
||||||
from accounts.const import AutomationTypes, SecretType, Connectivity
|
|
||||||
from assets.const import HostTypes
|
|
||||||
from common.utils import get_logger
|
from common.utils import get_logger
|
||||||
from ..base.manager import AccountBasePlaybookManager
|
from ..base.manager import AccountBasePlaybookManager
|
||||||
from ..change_secret.manager import ChangeSecretManager
|
from ..change_secret.manager import ChangeSecretManager
|
||||||
@@ -10,83 +7,11 @@ logger = get_logger(__name__)
|
|||||||
|
|
||||||
|
|
||||||
class PushAccountManager(ChangeSecretManager, AccountBasePlaybookManager):
|
class PushAccountManager(ChangeSecretManager, AccountBasePlaybookManager):
|
||||||
ansible_account_prefer = ''
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def method_type(cls):
|
def method_type(cls):
|
||||||
return AutomationTypes.push_account
|
return AutomationTypes.push_account
|
||||||
|
|
||||||
def host_callback(self, host, asset=None, account=None, automation=None, path_dir=None, **kwargs):
|
|
||||||
host = super(ChangeSecretManager, self).host_callback(
|
|
||||||
host, asset=asset, account=account, automation=automation,
|
|
||||||
path_dir=path_dir, **kwargs
|
|
||||||
)
|
|
||||||
if host.get('error'):
|
|
||||||
return host
|
|
||||||
|
|
||||||
accounts = self.get_accounts(account)
|
|
||||||
inventory_hosts = []
|
|
||||||
if asset.type == HostTypes.WINDOWS and self.secret_type == SecretType.SSH_KEY:
|
|
||||||
msg = f'Windows {asset} does not support ssh key push'
|
|
||||||
print(msg)
|
|
||||||
return inventory_hosts
|
|
||||||
|
|
||||||
host['ssh_params'] = {}
|
|
||||||
for account in accounts:
|
|
||||||
h = deepcopy(host)
|
|
||||||
secret_type = account.secret_type
|
|
||||||
h['name'] += '(' + account.username + ')'
|
|
||||||
if self.secret_type is None:
|
|
||||||
new_secret = account.secret
|
|
||||||
else:
|
|
||||||
new_secret = self.get_secret(secret_type)
|
|
||||||
|
|
||||||
self.name_recorder_mapper[h['name']] = {
|
|
||||||
'account': account, 'new_secret': new_secret,
|
|
||||||
}
|
|
||||||
|
|
||||||
private_key_path = None
|
|
||||||
if secret_type == SecretType.SSH_KEY:
|
|
||||||
private_key_path = self.generate_private_key_path(new_secret, path_dir)
|
|
||||||
new_secret = self.generate_public_key(new_secret)
|
|
||||||
|
|
||||||
h['ssh_params'].update(self.get_ssh_params(account, new_secret, secret_type))
|
|
||||||
h['account'] = {
|
|
||||||
'name': account.name,
|
|
||||||
'username': account.username,
|
|
||||||
'secret_type': secret_type,
|
|
||||||
'secret': new_secret,
|
|
||||||
'private_key_path': private_key_path
|
|
||||||
}
|
|
||||||
if asset.platform.type == 'oracle':
|
|
||||||
h['account']['mode'] = 'sysdba' if account.privileged else None
|
|
||||||
inventory_hosts.append(h)
|
|
||||||
return inventory_hosts
|
|
||||||
|
|
||||||
def on_host_success(self, host, result):
|
|
||||||
account_info = self.name_recorder_mapper.get(host)
|
|
||||||
if not account_info:
|
|
||||||
return
|
|
||||||
|
|
||||||
account = account_info['account']
|
|
||||||
new_secret = account_info['new_secret']
|
|
||||||
if not account:
|
|
||||||
return
|
|
||||||
account.secret = new_secret
|
|
||||||
account.save(update_fields=['secret'])
|
|
||||||
account.set_connectivity(Connectivity.OK)
|
|
||||||
|
|
||||||
def on_host_error(self, host, error, result):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def on_runner_failed(self, runner, e):
|
|
||||||
logger.error("Pust account error: ", e)
|
|
||||||
|
|
||||||
def run(self, *args, **kwargs):
|
|
||||||
if self.secret_type and not self.check_secret():
|
|
||||||
return
|
|
||||||
super(ChangeSecretManager, self).run(*args, **kwargs)
|
|
||||||
|
|
||||||
# @classmethod
|
# @classmethod
|
||||||
# def trigger_by_asset_create(cls, asset):
|
# def trigger_by_asset_create(cls, asset):
|
||||||
# automations = PushAccountAutomation.objects.filter(
|
# automations = PushAccountAutomation.objects.filter(
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
- hosts: mongodb
|
||||||
|
gather_facts: no
|
||||||
|
vars:
|
||||||
|
ansible_python_interpreter: /opt/py3/bin/python
|
||||||
|
|
||||||
|
tasks:
|
||||||
|
- name: "Remove account"
|
||||||
|
mongodb_user:
|
||||||
|
login_user: "{{ jms_account.username }}"
|
||||||
|
login_password: "{{ jms_account.secret }}"
|
||||||
|
login_host: "{{ jms_asset.address }}"
|
||||||
|
login_port: "{{ jms_asset.port }}"
|
||||||
|
login_database: "{{ jms_asset.spec_info.db_name }}"
|
||||||
|
ssl: "{{ jms_asset.spec_info.use_ssl }}"
|
||||||
|
ssl_ca_certs: "{{ jms_asset.secret_info.ca_cert | default('') }}"
|
||||||
|
ssl_certfile: "{{ jms_asset.secret_info.client_key | default('') }}"
|
||||||
|
connection_options:
|
||||||
|
- tlsAllowInvalidHostnames: "{{ jms_asset.spec_info.allow_invalid_cert}}"
|
||||||
|
db: "{{ jms_asset.spec_info.db_name }}"
|
||||||
|
name: "{{ account.username }}"
|
||||||
|
state: absent
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
id: remove_account_mongodb
|
||||||
|
name: "{{ 'MongoDB account remove' | trans }}"
|
||||||
|
category: database
|
||||||
|
type:
|
||||||
|
- mongodb
|
||||||
|
method: remove_account
|
||||||
|
|
||||||
|
i18n:
|
||||||
|
MongoDB account remove:
|
||||||
|
zh: 使用 Ansible 模块 mongodb 删除账号
|
||||||
|
ja: Ansible モジュール mongodb を使用してアカウントを削除する
|
||||||
|
en: Delete account using Ansible module mongodb
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
- hosts: mysql
|
||||||
|
gather_facts: no
|
||||||
|
vars:
|
||||||
|
ansible_python_interpreter: /opt/py3/bin/python
|
||||||
|
|
||||||
|
tasks:
|
||||||
|
- name: "Remove account"
|
||||||
|
community.mysql.mysql_user:
|
||||||
|
login_user: "{{ jms_account.username }}"
|
||||||
|
login_password: "{{ jms_account.secret }}"
|
||||||
|
login_host: "{{ jms_asset.address }}"
|
||||||
|
login_port: "{{ jms_asset.port }}"
|
||||||
|
check_hostname: "{{ check_ssl if check_ssl else omit }}"
|
||||||
|
ca_cert: "{{ jms_asset.secret_info.ca_cert | default(omit) if check_ssl else omit }}"
|
||||||
|
client_cert: "{{ jms_asset.secret_info.client_cert | default(omit) if check_ssl else omit }}"
|
||||||
|
client_key: "{{ jms_asset.secret_info.client_key | default(omit) if check_ssl else omit }}"
|
||||||
|
name: "{{ account.username }}"
|
||||||
|
state: absent
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
id: remove_account_mysql
|
||||||
|
name: "{{ 'MySQL account remove' | trans }}"
|
||||||
|
category: database
|
||||||
|
type:
|
||||||
|
- mysql
|
||||||
|
- mariadb
|
||||||
|
method: remove_account
|
||||||
|
|
||||||
|
i18n:
|
||||||
|
MySQL account remove:
|
||||||
|
zh: 使用 Ansible 模块 mysql_user 删除账号
|
||||||
|
ja: Ansible モジュール mysql_user を使用してアカウントを削除します
|
||||||
|
en: Use the Ansible module mysql_user to delete the account
|
||||||
|
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
- hosts: oracle
|
||||||
|
gather_facts: no
|
||||||
|
vars:
|
||||||
|
ansible_python_interpreter: /opt/py3/bin/python
|
||||||
|
|
||||||
|
tasks:
|
||||||
|
- name: "Remove account"
|
||||||
|
oracle_user:
|
||||||
|
login_user: "{{ jms_account.username }}"
|
||||||
|
login_password: "{{ jms_account.secret }}"
|
||||||
|
login_host: "{{ jms_asset.address }}"
|
||||||
|
login_port: "{{ jms_asset.port }}"
|
||||||
|
login_database: "{{ jms_asset.spec_info.db_name }}"
|
||||||
|
mode: "{{ jms_account.mode }}"
|
||||||
|
name: "{{ account.username }}"
|
||||||
|
state: absent
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
id: remove_account_oracle
|
||||||
|
name: "{{ 'Oracle account remove' | trans }}"
|
||||||
|
category: database
|
||||||
|
type:
|
||||||
|
- oracle
|
||||||
|
method: remove_account
|
||||||
|
|
||||||
|
i18n:
|
||||||
|
Oracle account remove:
|
||||||
|
zh: 使用 Python 模块 oracledb 删除账号
|
||||||
|
ja: Python モジュール oracledb を使用してアカウントを検証する
|
||||||
|
en: Using Python module oracledb to verify account
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
- hosts: postgresql
|
||||||
|
gather_facts: no
|
||||||
|
vars:
|
||||||
|
ansible_python_interpreter: /opt/py3/bin/python
|
||||||
|
|
||||||
|
tasks:
|
||||||
|
- name: "Remove account"
|
||||||
|
community.postgresql.postgresql_user:
|
||||||
|
login_user: "{{ jms_account.username }}"
|
||||||
|
login_password: "{{ jms_account.secret }}"
|
||||||
|
login_host: "{{ jms_asset.address }}"
|
||||||
|
login_port: "{{ jms_asset.port }}"
|
||||||
|
db: "{{ jms_asset.spec_info.db_name }}"
|
||||||
|
name: "{{ account.username }}"
|
||||||
|
state: absent
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
id: remove_account_postgresql
|
||||||
|
name: "{{ 'PostgreSQL account remove' | trans }}"
|
||||||
|
category: database
|
||||||
|
type:
|
||||||
|
- postgresql
|
||||||
|
method: remove_account
|
||||||
|
|
||||||
|
i18n:
|
||||||
|
PostgreSQL account remove:
|
||||||
|
zh: 使用 Ansible 模块 postgresql_user 删除账号
|
||||||
|
ja: Ansible モジュール postgresql_user を使用してアカウントを削除します
|
||||||
|
en: Use the Ansible module postgresql_user to delete the account
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
- hosts: sqlserver
|
||||||
|
gather_facts: no
|
||||||
|
vars:
|
||||||
|
ansible_python_interpreter: /opt/py3/bin/python
|
||||||
|
|
||||||
|
tasks:
|
||||||
|
- name: "Remove account"
|
||||||
|
community.general.mssql_script:
|
||||||
|
login_user: "{{ jms_account.username }}"
|
||||||
|
login_password: "{{ jms_account.secret }}"
|
||||||
|
login_host: "{{ jms_asset.address }}"
|
||||||
|
login_port: "{{ jms_asset.port }}"
|
||||||
|
name: "{{ jms_asset.spec_info.db_name }}"
|
||||||
|
script: "DROP USER {{ account.username }}"
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
id: remove_account_sqlserver
|
||||||
|
name: "{{ 'SQLServer account remove' | trans }}"
|
||||||
|
category: database
|
||||||
|
type:
|
||||||
|
- sqlserver
|
||||||
|
method: remove_account
|
||||||
|
|
||||||
|
i18n:
|
||||||
|
SQLServer account remove:
|
||||||
|
zh: 使用 Ansible 模块 mssql 删除账号
|
||||||
|
ja: Ansible モジュール mssql を使用してアカウントを削除する
|
||||||
|
en: Use Ansible module mssql to delete account
|
||||||
26
apps/accounts/automations/remove_account/host/posix/main.yml
Normal file
26
apps/accounts/automations/remove_account/host/posix/main.yml
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
- hosts: demo
|
||||||
|
gather_facts: no
|
||||||
|
tasks:
|
||||||
|
- name: "Get user home directory path"
|
||||||
|
ansible.builtin.shell:
|
||||||
|
cmd: "getent passwd {{ account.username }} | cut -d: -f6"
|
||||||
|
register: user_home_dir
|
||||||
|
ignore_errors: yes
|
||||||
|
|
||||||
|
- name: "Check if user home directory exists"
|
||||||
|
ansible.builtin.stat:
|
||||||
|
path: "{{ user_home_dir.stdout }}"
|
||||||
|
register: home_dir
|
||||||
|
when: user_home_dir.stdout != ""
|
||||||
|
|
||||||
|
- name: "Rename user home directory if it exists"
|
||||||
|
ansible.builtin.command:
|
||||||
|
cmd: "mv {{ user_home_dir.stdout }} {{ user_home_dir.stdout }}.bak"
|
||||||
|
when: home_dir.stat | default(false) and user_home_dir.stdout != ""
|
||||||
|
|
||||||
|
- name: "Remove account"
|
||||||
|
ansible.builtin.user:
|
||||||
|
name: "{{ account.username }}"
|
||||||
|
state: absent
|
||||||
|
remove: "{{ home_dir.stat.exists }}"
|
||||||
|
when: home_dir.stat | default(false)
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
id: remove_account_posix
|
||||||
|
name: "{{ 'Posix account remove' | trans }}"
|
||||||
|
category: host
|
||||||
|
type:
|
||||||
|
- linux
|
||||||
|
- unix
|
||||||
|
method: remove_account
|
||||||
|
|
||||||
|
i18n:
|
||||||
|
Posix account remove:
|
||||||
|
zh: 使用 Ansible 模块 user 删除账号
|
||||||
|
ja: Ansible モジュール ユーザーを使用してアカウントを削除します
|
||||||
|
en: Use the Ansible module user to delete the account
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
- hosts: windows
|
||||||
|
gather_facts: no
|
||||||
|
tasks:
|
||||||
|
- name: "Remove account"
|
||||||
|
ansible.windows.win_user:
|
||||||
|
name: "{{ account.username }}"
|
||||||
|
state: absent
|
||||||
|
purge: yes
|
||||||
|
force: yes
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
id: remove_account_windows
|
||||||
|
name: "{{ 'Windows account remove' | trans }}"
|
||||||
|
version: 1
|
||||||
|
method: remove_account
|
||||||
|
category: host
|
||||||
|
type:
|
||||||
|
- windows
|
||||||
|
|
||||||
|
i18n:
|
||||||
|
Windows account remove:
|
||||||
|
zh: 使用 Ansible 模块 win_user 删除账号
|
||||||
|
ja: Ansible モジュール win_user を使用してアカウントを削除する
|
||||||
|
en: Use the Ansible module win_user to delete an account
|
||||||
67
apps/accounts/automations/remove_account/manager.py
Normal file
67
apps/accounts/automations/remove_account/manager.py
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
import os
|
||||||
|
from copy import deepcopy
|
||||||
|
|
||||||
|
from django.db.models import QuerySet
|
||||||
|
|
||||||
|
from accounts.const import AutomationTypes
|
||||||
|
from accounts.models import Account
|
||||||
|
from common.utils import get_logger
|
||||||
|
from ..base.manager import AccountBasePlaybookManager
|
||||||
|
|
||||||
|
logger = get_logger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class RemoveAccountManager(AccountBasePlaybookManager):
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
self.host_account_mapper = {}
|
||||||
|
|
||||||
|
def prepare_runtime_dir(self):
|
||||||
|
path = super().prepare_runtime_dir()
|
||||||
|
ansible_config_path = os.path.join(path, 'ansible.cfg')
|
||||||
|
|
||||||
|
with open(ansible_config_path, 'w') as f:
|
||||||
|
f.write('[ssh_connection]\n')
|
||||||
|
f.write('ssh_args = -o ControlMaster=no -o ControlPersist=no\n')
|
||||||
|
return path
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def method_type(cls):
|
||||||
|
return AutomationTypes.remove_account
|
||||||
|
|
||||||
|
def get_gather_accounts(self, privilege_account, gather_accounts: QuerySet):
|
||||||
|
gather_account_ids = self.execution.snapshot['gather_accounts']
|
||||||
|
gather_accounts = gather_accounts.filter(id__in=gather_account_ids)
|
||||||
|
gather_accounts = gather_accounts.exclude(
|
||||||
|
username__in=[privilege_account.username, 'root', 'Administrator']
|
||||||
|
)
|
||||||
|
return gather_accounts
|
||||||
|
|
||||||
|
def host_callback(self, host, asset=None, account=None, automation=None, path_dir=None, **kwargs):
|
||||||
|
if host.get('error'):
|
||||||
|
return host
|
||||||
|
|
||||||
|
gather_accounts = asset.gatheredaccount_set.all()
|
||||||
|
gather_accounts = self.get_gather_accounts(account, gather_accounts)
|
||||||
|
|
||||||
|
inventory_hosts = []
|
||||||
|
|
||||||
|
for gather_account in gather_accounts:
|
||||||
|
h = deepcopy(host)
|
||||||
|
h['name'] += '(' + gather_account.username + ')'
|
||||||
|
self.host_account_mapper[h['name']] = (asset, gather_account)
|
||||||
|
h['account'] = {'username': gather_account.username}
|
||||||
|
inventory_hosts.append(h)
|
||||||
|
return inventory_hosts
|
||||||
|
|
||||||
|
def on_host_success(self, host, result):
|
||||||
|
tuple_asset_gather_account = self.host_account_mapper.get(host)
|
||||||
|
if not tuple_asset_gather_account:
|
||||||
|
return
|
||||||
|
asset, gather_account = tuple_asset_gather_account
|
||||||
|
Account.objects.filter(
|
||||||
|
asset_id=asset.id,
|
||||||
|
username=gather_account.username
|
||||||
|
).delete()
|
||||||
|
gather_account.delete()
|
||||||
@@ -5,9 +5,10 @@ category:
|
|||||||
type:
|
type:
|
||||||
- windows
|
- windows
|
||||||
method: verify_account
|
method: verify_account
|
||||||
|
protocol: rdp
|
||||||
|
|
||||||
i18n:
|
i18n:
|
||||||
Windows rdp account verify:
|
Windows rdp account verify:
|
||||||
zh: 使用 Python 模块 pyfreerdp 验证账号
|
zh: '使用 Python 模块 pyfreerdp 验证账号'
|
||||||
ja: Python モジュール pyfreerdp を使用してアカウントを検証する
|
ja: 'Python モジュール pyfreerdp を使用してアカウントを検証する'
|
||||||
en: Using Python module pyfreerdp to verify account
|
en: 'Using Python module pyfreerdp to verify account'
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
gather_facts: no
|
gather_facts: no
|
||||||
vars:
|
vars:
|
||||||
ansible_connection: local
|
ansible_connection: local
|
||||||
|
ansible_shell_type: sh
|
||||||
ansible_become: false
|
ansible_become: false
|
||||||
|
|
||||||
tasks:
|
tasks:
|
||||||
@@ -13,8 +14,8 @@
|
|||||||
login_password: "{{ account.secret }}"
|
login_password: "{{ account.secret }}"
|
||||||
login_secret_type: "{{ account.secret_type }}"
|
login_secret_type: "{{ account.secret_type }}"
|
||||||
login_private_key_path: "{{ account.private_key_path }}"
|
login_private_key_path: "{{ account.private_key_path }}"
|
||||||
become: "{{ custom_become | default(False) }}"
|
become: "{{ account.become.ansible_become | default(False) }}"
|
||||||
become_method: "{{ custom_become_method | default('su') }}"
|
become_method: "{{ account.become.ansible_become_method | default('su') }}"
|
||||||
become_user: "{{ custom_become_user | default('') }}"
|
become_user: "{{ account.become.ansible_user | default('') }}"
|
||||||
become_password: "{{ custom_become_password | default('') }}"
|
become_password: "{{ account.become.ansible_password | default('') }}"
|
||||||
become_private_key_path: "{{ custom_become_private_key_path | default(None) }}"
|
become_private_key_path: "{{ account.become.ansible_ssh_private_key_file | default(None) }}"
|
||||||
|
|||||||
@@ -6,9 +6,10 @@ category:
|
|||||||
type:
|
type:
|
||||||
- all
|
- all
|
||||||
method: verify_account
|
method: verify_account
|
||||||
|
protocol: ssh
|
||||||
|
|
||||||
i18n:
|
i18n:
|
||||||
SSH account verify:
|
SSH account verify:
|
||||||
zh: 使用 Python 模块 paramiko 验证账号
|
zh: '使用 Python 模块 paramiko 验证账号'
|
||||||
ja: Python モジュール paramiko を使用してアカウントを検証する
|
ja: 'Python モジュール paramiko を使用してアカウントを検証する'
|
||||||
en: Using Python module paramiko to verify account
|
en: 'Using Python module paramiko to verify account'
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
- hosts: mongdb
|
- hosts: mongodb
|
||||||
gather_facts: no
|
gather_facts: no
|
||||||
vars:
|
vars:
|
||||||
ansible_python_interpreter: /usr/local/bin/python
|
ansible_python_interpreter: /opt/py3/bin/python
|
||||||
|
|
||||||
tasks:
|
tasks:
|
||||||
- name: Verify account
|
- name: Verify account
|
||||||
@@ -12,7 +12,7 @@
|
|||||||
login_port: "{{ jms_asset.port }}"
|
login_port: "{{ jms_asset.port }}"
|
||||||
login_database: "{{ jms_asset.spec_info.db_name }}"
|
login_database: "{{ jms_asset.spec_info.db_name }}"
|
||||||
ssl: "{{ jms_asset.spec_info.use_ssl }}"
|
ssl: "{{ jms_asset.spec_info.use_ssl }}"
|
||||||
ssl_ca_certs: "{{ jms_asset.secret_info.ca_cert }}"
|
ssl_ca_certs: "{{ jms_asset.secret_info.ca_cert | default('') }}"
|
||||||
ssl_certfile: "{{ jms_asset.secret_info.client_key }}"
|
ssl_certfile: "{{ jms_asset.secret_info.client_key | default('') }}"
|
||||||
connection_options:
|
connection_options:
|
||||||
- tlsAllowInvalidHostnames: "{{ jms_asset.spec_info.allow_invalid_cert }}"
|
- tlsAllowInvalidHostnames: "{{ jms_asset.spec_info.allow_invalid_cert }}"
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
- hosts: mysql
|
- hosts: mysql
|
||||||
gather_facts: no
|
gather_facts: no
|
||||||
vars:
|
vars:
|
||||||
ansible_python_interpreter: /usr/local/bin/python
|
ansible_python_interpreter: /opt/py3/bin/python
|
||||||
|
check_ssl: "{{ jms_asset.spec_info.use_ssl and not jms_asset.spec_info.allow_invalid_cert }}"
|
||||||
|
|
||||||
tasks:
|
tasks:
|
||||||
- name: Verify account
|
- name: Verify account
|
||||||
@@ -10,4 +11,8 @@
|
|||||||
login_password: "{{ account.secret }}"
|
login_password: "{{ account.secret }}"
|
||||||
login_host: "{{ jms_asset.address }}"
|
login_host: "{{ jms_asset.address }}"
|
||||||
login_port: "{{ jms_asset.port }}"
|
login_port: "{{ jms_asset.port }}"
|
||||||
|
check_hostname: "{{ check_ssl if check_ssl else omit }}"
|
||||||
|
ca_cert: "{{ jms_asset.secret_info.ca_cert | default(omit) if check_ssl else omit }}"
|
||||||
|
client_cert: "{{ jms_asset.secret_info.client_cert | default(omit) if check_ssl else omit }}"
|
||||||
|
client_key: "{{ jms_asset.secret_info.client_key | default(omit) if check_ssl else omit }}"
|
||||||
filter: version
|
filter: version
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
- hosts: oracle
|
- hosts: oracle
|
||||||
gather_facts: no
|
gather_facts: no
|
||||||
vars:
|
vars:
|
||||||
ansible_python_interpreter: /usr/local/bin/python
|
ansible_python_interpreter: /opt/py3/bin/python
|
||||||
|
|
||||||
tasks:
|
tasks:
|
||||||
- name: Verify account
|
- name: Verify account
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
- hosts: postgresql
|
- hosts: postgresql
|
||||||
gather_facts: no
|
gather_facts: no
|
||||||
vars:
|
vars:
|
||||||
ansible_python_interpreter: /usr/local/bin/python
|
ansible_python_interpreter: /opt/py3/bin/python
|
||||||
|
|
||||||
|
|
||||||
tasks:
|
tasks:
|
||||||
- name: Verify account
|
- name: Verify account
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
- hosts: sqlserver
|
- hosts: sqlserver
|
||||||
gather_facts: no
|
gather_facts: no
|
||||||
vars:
|
vars:
|
||||||
ansible_python_interpreter: /usr/local/bin/python
|
ansible_python_interpreter: /opt/py3/bin/python
|
||||||
|
|
||||||
tasks:
|
tasks:
|
||||||
- name: Verify account
|
- name: Verify account
|
||||||
|
|||||||
@@ -1,11 +1,23 @@
|
|||||||
- hosts: demo
|
- hosts: demo
|
||||||
gather_facts: no
|
gather_facts: no
|
||||||
tasks:
|
tasks:
|
||||||
- name: Verify account connectivity
|
- name: Verify account connectivity(Do not switch)
|
||||||
become: no
|
|
||||||
ansible.builtin.ping:
|
ansible.builtin.ping:
|
||||||
vars:
|
vars:
|
||||||
ansible_become: no
|
ansible_become: no
|
||||||
ansible_user: "{{ account.username }}"
|
ansible_user: "{{ account.username }}"
|
||||||
ansible_password: "{{ account.secret }}"
|
ansible_password: "{{ account.secret }}"
|
||||||
ansible_ssh_private_key_file: "{{ account.private_key_path }}"
|
ansible_ssh_private_key_file: "{{ account.private_key_path }}"
|
||||||
|
when: not account.become.ansible_become
|
||||||
|
|
||||||
|
- name: Verify account connectivity(Switch)
|
||||||
|
ansible.builtin.ping:
|
||||||
|
vars:
|
||||||
|
ansible_become: yes
|
||||||
|
ansible_user: "{{ account.become.ansible_user }}"
|
||||||
|
ansible_password: "{{ account.become.ansible_password }}"
|
||||||
|
ansible_ssh_private_key_file: "{{ account.become.ansible_ssh_private_key_file }}"
|
||||||
|
ansible_become_method: "{{ account.become.ansible_become_method }}"
|
||||||
|
ansible_become_user: "{{ account.become.ansible_become_user }}"
|
||||||
|
ansible_become_password: "{{ account.become.ansible_become_password }}"
|
||||||
|
when: account.become.ansible_become
|
||||||
|
|||||||
@@ -42,7 +42,6 @@ class VerifyAccountManager(AccountBasePlaybookManager):
|
|||||||
if host.get('error'):
|
if host.get('error'):
|
||||||
return host
|
return host
|
||||||
|
|
||||||
# host['ssh_args'] = '-o ControlMaster=no -o ControlPersist=no'
|
|
||||||
accounts = asset.accounts.all()
|
accounts = asset.accounts.all()
|
||||||
accounts = self.get_accounts(account, accounts)
|
accounts = self.get_accounts(account, accounts)
|
||||||
inventory_hosts = []
|
inventory_hosts = []
|
||||||
@@ -63,8 +62,9 @@ class VerifyAccountManager(AccountBasePlaybookManager):
|
|||||||
'name': account.name,
|
'name': account.name,
|
||||||
'username': account.username,
|
'username': account.username,
|
||||||
'secret_type': account.secret_type,
|
'secret_type': account.secret_type,
|
||||||
'secret': secret,
|
'secret': account.escape_jinja2_syntax(secret),
|
||||||
'private_key_path': private_key_path
|
'private_key_path': private_key_path,
|
||||||
|
'become': account.get_ansible_become_auth(),
|
||||||
}
|
}
|
||||||
if account.platform.type == 'oracle':
|
if account.platform.type == 'oracle':
|
||||||
h['account']['mode'] = 'sysdba' if account.privileged else None
|
h['account']['mode'] = 'sysdba' if account.privileged else None
|
||||||
|
|||||||
@@ -4,17 +4,19 @@ from django.utils.translation import gettext_lazy as _
|
|||||||
from assets.const import Connectivity
|
from assets.const import Connectivity
|
||||||
from common.db.fields import TreeChoices
|
from common.db.fields import TreeChoices
|
||||||
|
|
||||||
string_punctuation = '!#$%&()*+,-.:;<=>?@[]^_~'
|
|
||||||
DEFAULT_PASSWORD_LENGTH = 30
|
DEFAULT_PASSWORD_LENGTH = 30
|
||||||
DEFAULT_PASSWORD_RULES = {
|
DEFAULT_PASSWORD_RULES = {
|
||||||
'length': DEFAULT_PASSWORD_LENGTH,
|
'length': DEFAULT_PASSWORD_LENGTH,
|
||||||
'symbol_set': string_punctuation
|
'uppercase': True,
|
||||||
|
'lowercase': True,
|
||||||
|
'digit': True,
|
||||||
|
'symbol': True,
|
||||||
}
|
}
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
'AutomationTypes', 'SecretStrategy', 'SSHKeyStrategy', 'Connectivity',
|
'AutomationTypes', 'SecretStrategy', 'SSHKeyStrategy', 'Connectivity',
|
||||||
'DEFAULT_PASSWORD_LENGTH', 'DEFAULT_PASSWORD_RULES', 'TriggerChoice',
|
'DEFAULT_PASSWORD_LENGTH', 'DEFAULT_PASSWORD_RULES', 'TriggerChoice',
|
||||||
'PushAccountActionChoice',
|
'PushAccountActionChoice', 'AccountBackupType'
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
@@ -22,6 +24,7 @@ class AutomationTypes(models.TextChoices):
|
|||||||
push_account = 'push_account', _('Push account')
|
push_account = 'push_account', _('Push account')
|
||||||
change_secret = 'change_secret', _('Change secret')
|
change_secret = 'change_secret', _('Change secret')
|
||||||
verify_account = 'verify_account', _('Verify account')
|
verify_account = 'verify_account', _('Verify account')
|
||||||
|
remove_account = 'remove_account', _('Remove account')
|
||||||
gather_accounts = 'gather_accounts', _('Gather accounts')
|
gather_accounts = 'gather_accounts', _('Gather accounts')
|
||||||
verify_gateway_account = 'verify_gateway_account', _('Verify gateway account')
|
verify_gateway_account = 'verify_gateway_account', _('Verify gateway account')
|
||||||
|
|
||||||
@@ -41,8 +44,8 @@ class AutomationTypes(models.TextChoices):
|
|||||||
|
|
||||||
|
|
||||||
class SecretStrategy(models.TextChoices):
|
class SecretStrategy(models.TextChoices):
|
||||||
custom = 'specific', _('Specific password')
|
custom = 'specific', _('Specific secret')
|
||||||
random = 'random', _('Random')
|
random = 'random', _('Random generate')
|
||||||
|
|
||||||
|
|
||||||
class SSHKeyStrategy(models.TextChoices):
|
class SSHKeyStrategy(models.TextChoices):
|
||||||
@@ -93,3 +96,10 @@ class TriggerChoice(models.TextChoices, TreeChoices):
|
|||||||
class PushAccountActionChoice(models.TextChoices):
|
class PushAccountActionChoice(models.TextChoices):
|
||||||
create_and_push = 'create_and_push', _('Create and push')
|
create_and_push = 'create_and_push', _('Create and push')
|
||||||
only_create = 'only_create', _('Only create')
|
only_create = 'only_create', _('Only create')
|
||||||
|
|
||||||
|
|
||||||
|
class AccountBackupType(models.TextChoices):
|
||||||
|
"""Backup type"""
|
||||||
|
email = 'email', _('Email')
|
||||||
|
# 目前只支持sftp方式
|
||||||
|
object_storage = 'object_storage', _('SFTP')
|
||||||
|
|||||||
@@ -51,6 +51,7 @@ class AccountFilterSet(BaseFilterSet):
|
|||||||
|
|
||||||
class GatheredAccountFilterSet(BaseFilterSet):
|
class GatheredAccountFilterSet(BaseFilterSet):
|
||||||
node_id = drf_filters.CharFilter(method='filter_nodes')
|
node_id = drf_filters.CharFilter(method='filter_nodes')
|
||||||
|
asset_id = drf_filters.CharFilter(field_name='asset_id', lookup_expr='exact')
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def filter_nodes(queryset, name, value):
|
def filter_nodes(queryset, name, value):
|
||||||
@@ -58,4 +59,4 @@ class GatheredAccountFilterSet(BaseFilterSet):
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = GatheredAccount
|
model = GatheredAccount
|
||||||
fields = ['id', 'asset_id', 'username']
|
fields = ['id', 'username']
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ class Migration(migrations.Migration):
|
|||||||
'verbose_name': 'Automation execution',
|
'verbose_name': 'Automation execution',
|
||||||
'verbose_name_plural': 'Automation executions',
|
'verbose_name_plural': 'Automation executions',
|
||||||
'permissions': [('view_changesecretexecution', 'Can view change secret execution'),
|
'permissions': [('view_changesecretexecution', 'Can view change secret execution'),
|
||||||
('add_changesecretexection', 'Can add change secret execution'),
|
('add_changesecretexecution', 'Can add change secret execution'),
|
||||||
('view_gatheraccountsexecution', 'Can view gather accounts execution'),
|
('view_gatheraccountsexecution', 'Can view gather accounts execution'),
|
||||||
('add_gatheraccountsexecution', 'Can add gather accounts execution')],
|
('add_gatheraccountsexecution', 'Can add gather accounts execution')],
|
||||||
'proxy': True,
|
'proxy': True,
|
||||||
@@ -113,10 +113,10 @@ class Migration(migrations.Migration):
|
|||||||
('comment', models.TextField(blank=True, default='', verbose_name='Comment')),
|
('comment', models.TextField(blank=True, default='', verbose_name='Comment')),
|
||||||
('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)),
|
('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)),
|
||||||
('old_secret', common.db.fields.EncryptTextField(blank=True, null=True, verbose_name='Old secret')),
|
('old_secret', common.db.fields.EncryptTextField(blank=True, null=True, verbose_name='Old secret')),
|
||||||
('new_secret', common.db.fields.EncryptTextField(blank=True, null=True, verbose_name='Secret')),
|
('new_secret', common.db.fields.EncryptTextField(blank=True, null=True, verbose_name='New secret')),
|
||||||
('date_started', models.DateTimeField(blank=True, null=True, verbose_name='Date started')),
|
('date_started', models.DateTimeField(blank=True, null=True, verbose_name='Date started')),
|
||||||
('date_finished', models.DateTimeField(blank=True, null=True, verbose_name='Date finished')),
|
('date_finished', models.DateTimeField(blank=True, null=True, verbose_name='Date finished')),
|
||||||
('status', models.CharField(default='pending', max_length=16)),
|
('status', models.CharField(default='pending', max_length=16, verbose_name='Status')),
|
||||||
('error', models.TextField(blank=True, null=True, verbose_name='Error')),
|
('error', models.TextField(blank=True, null=True, verbose_name='Error')),
|
||||||
('account',
|
('account',
|
||||||
models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='accounts.account')),
|
models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='accounts.account')),
|
||||||
@@ -184,7 +184,7 @@ class Migration(migrations.Migration):
|
|||||||
migrations.AlterModelOptions(
|
migrations.AlterModelOptions(
|
||||||
name='automationexecution',
|
name='automationexecution',
|
||||||
options={'permissions': [('view_changesecretexecution', 'Can view change secret execution'),
|
options={'permissions': [('view_changesecretexecution', 'Can view change secret execution'),
|
||||||
('add_changesecretexection', 'Can add change secret execution'),
|
('add_changesecretexecution', 'Can add change secret execution'),
|
||||||
('view_gatheraccountsexecution', 'Can view gather accounts execution'),
|
('view_gatheraccountsexecution', 'Can view gather accounts execution'),
|
||||||
('add_gatheraccountsexecution', 'Can add gather accounts execution'),
|
('add_gatheraccountsexecution', 'Can add gather accounts execution'),
|
||||||
('view_pushaccountexecution', 'Can view push account execution'),
|
('view_pushaccountexecution', 'Can view push account execution'),
|
||||||
|
|||||||
@@ -13,11 +13,11 @@ class Migration(migrations.Migration):
|
|||||||
migrations.AlterField(
|
migrations.AlterField(
|
||||||
model_name='changesecretautomation',
|
model_name='changesecretautomation',
|
||||||
name='secret_strategy',
|
name='secret_strategy',
|
||||||
field=models.CharField(choices=[('specific', 'Specific password'), ('random', 'Random')], default='specific', max_length=16, verbose_name='Secret strategy'),
|
field=models.CharField(choices=[('specific', 'Specific secret'), ('random', 'Random generate')], default='specific', max_length=16, verbose_name='Secret strategy'),
|
||||||
),
|
),
|
||||||
migrations.AlterField(
|
migrations.AlterField(
|
||||||
model_name='pushaccountautomation',
|
model_name='pushaccountautomation',
|
||||||
name='secret_strategy',
|
name='secret_strategy',
|
||||||
field=models.CharField(choices=[('specific', 'Specific password'), ('random', 'Random')], default='specific', max_length=16, verbose_name='Secret strategy'),
|
field=models.CharField(choices=[('specific', 'Specific secret'), ('random', 'Random generate')], default='specific', max_length=16, verbose_name='Secret strategy'),
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ from django.db import migrations
|
|||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
('accounts', '0006_gatheredaccount'),
|
('accounts', '0006_gatheredaccount'),
|
||||||
]
|
]
|
||||||
@@ -12,6 +11,13 @@ class Migration(migrations.Migration):
|
|||||||
operations = [
|
operations = [
|
||||||
migrations.AlterModelOptions(
|
migrations.AlterModelOptions(
|
||||||
name='account',
|
name='account',
|
||||||
options={'permissions': [('view_accountsecret', 'Can view asset account secret'), ('view_historyaccount', 'Can view asset history account'), ('view_historyaccountsecret', 'Can view asset history account secret'), ('verify_account', 'Can verify account'), ('push_account', 'Can push account')], 'verbose_name': 'Account'},
|
options={'permissions': [
|
||||||
|
('view_accountsecret', 'Can view asset account secret'),
|
||||||
|
('view_historyaccount', 'Can view asset history account'),
|
||||||
|
('view_historyaccountsecret', 'Can view asset history account secret'),
|
||||||
|
('verify_account', 'Can verify account'),
|
||||||
|
('push_account', 'Can push account'),
|
||||||
|
('remove_account', 'Can remove account'),
|
||||||
|
], 'verbose_name': 'Account'},
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|||||||
34
apps/accounts/migrations/0015_auto_20230825_1120.py
Normal file
34
apps/accounts/migrations/0015_auto_20230825_1120.py
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
# Generated by Django 4.1.10 on 2023-08-25 03:19
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('assets', '0122_auto_20230803_1553'),
|
||||||
|
('accounts', '0014_virtualaccount'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='accounttemplate',
|
||||||
|
name='auto_push',
|
||||||
|
field=models.BooleanField(default=False, verbose_name='Auto push'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='accounttemplate',
|
||||||
|
name='platforms',
|
||||||
|
field=models.ManyToManyField(related_name='account_templates', to='assets.platform', verbose_name='Platforms', blank=True),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='accounttemplate',
|
||||||
|
name='push_params',
|
||||||
|
field=models.JSONField(default=dict, verbose_name='Push params'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='accounttemplate',
|
||||||
|
name='secret_strategy',
|
||||||
|
field=models.CharField(choices=[('specific', 'Specific secret'), ('random', 'Random generate')], default='specific', max_length=16, verbose_name='Secret strategy'),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
# Generated by Django 4.1.10 on 2023-09-18 08:09
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('accounts', '0015_auto_20230825_1120'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='accounttemplate',
|
||||||
|
name='password_rules',
|
||||||
|
field=models.JSONField(default=dict, verbose_name='Password rules'),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
# Generated by Django 4.1.10 on 2023-10-24 05:59
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
dependencies = [
|
||||||
|
('accounts', '0016_accounttemplate_password_rules'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterModelOptions(
|
||||||
|
name='automationexecution',
|
||||||
|
options={
|
||||||
|
'permissions': [
|
||||||
|
('view_changesecretexecution', 'Can view change secret execution'),
|
||||||
|
('add_changesecretexecution', 'Can add change secret execution'),
|
||||||
|
('view_gatheraccountsexecution', 'Can view gather accounts execution'),
|
||||||
|
('add_gatheraccountsexecution', 'Can add gather accounts execution'),
|
||||||
|
('view_pushaccountexecution', 'Can view push account execution'),
|
||||||
|
('add_pushaccountexecution', 'Can add push account execution')
|
||||||
|
],
|
||||||
|
'verbose_name': 'Automation execution', 'verbose_name_plural': 'Automation executions'},
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
# Generated by Django 4.1.10 on 2023-11-03 07:10
|
||||||
|
|
||||||
|
import common.db.fields
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('terminal', '0067_alter_replaystorage_type'),
|
||||||
|
('accounts', '0017_alter_automationexecution_options'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='accountbackupautomation',
|
||||||
|
name='backup_type',
|
||||||
|
field=models.CharField(choices=[('email', 'Email'), ('object_storage', 'Object Storage')], default='email', max_length=128),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='accountbackupautomation',
|
||||||
|
name='is_password_divided_by_email',
|
||||||
|
field=models.BooleanField(default=True),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='accountbackupautomation',
|
||||||
|
name='is_password_divided_by_obj_storage',
|
||||||
|
field=models.BooleanField(default=True),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='accountbackupautomation',
|
||||||
|
name='obj_recipients_part_one',
|
||||||
|
field=models.ManyToManyField(blank=True, related_name='obj_recipient_part_one_plans', to='terminal.replaystorage', verbose_name='Object Storage Recipient part one'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='accountbackupautomation',
|
||||||
|
name='obj_recipients_part_two',
|
||||||
|
field=models.ManyToManyField(blank=True, related_name='obj_recipient_part_two_plans', to='terminal.replaystorage', verbose_name='Object Storage Recipient part two'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='accountbackupautomation',
|
||||||
|
name='zip_encrypt_password',
|
||||||
|
field=common.db.fields.EncryptCharField(blank=True, max_length=4096, null=True, verbose_name='Zip Encrypt Password'),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
# Generated by Django 4.1.10 on 2023-10-31 06:12
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||||
|
('accounts', '0018_accountbackupautomation_backup_type_and_more'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='gatheraccountsautomation',
|
||||||
|
name='recipients',
|
||||||
|
field=models.ManyToManyField(blank=True, to=settings.AUTH_USER_MODEL, verbose_name='Recipient'),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
# Generated by Django 4.1.10 on 2023-11-16 02:13
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('accounts', '0019_gatheraccountsautomation_recipients'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='accountbackupautomation',
|
||||||
|
name='backup_type',
|
||||||
|
field=models.CharField(choices=[('email', 'Email'), ('object_storage', 'SFTP')], default='email', max_length=128, verbose_name='Backup Type'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='accountbackupautomation',
|
||||||
|
name='is_password_divided_by_email',
|
||||||
|
field=models.BooleanField(default=True, verbose_name='Is Password Divided'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='accountbackupautomation',
|
||||||
|
name='is_password_divided_by_obj_storage',
|
||||||
|
field=models.BooleanField(default=True, verbose_name='Is Password Divided'),
|
||||||
|
),
|
||||||
|
]
|
||||||
75
apps/accounts/mixins.py
Normal file
75
apps/accounts/mixins.py
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
from rest_framework.response import Response
|
||||||
|
from rest_framework import status
|
||||||
|
from django.utils import translation
|
||||||
|
from django.utils.translation import gettext_noop
|
||||||
|
|
||||||
|
from audits.const import ActionChoices
|
||||||
|
from common.views.mixins import RecordViewLogMixin
|
||||||
|
from common.utils import i18n_fmt
|
||||||
|
|
||||||
|
|
||||||
|
class AccountRecordViewLogMixin(RecordViewLogMixin):
|
||||||
|
get_object: callable
|
||||||
|
get_queryset: callable
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _filter_params(params):
|
||||||
|
new_params = {}
|
||||||
|
need_pop_params = ('format', 'order')
|
||||||
|
for key, value in params.items():
|
||||||
|
if key in need_pop_params:
|
||||||
|
continue
|
||||||
|
if isinstance(value, list):
|
||||||
|
value = list(filter(None, value))
|
||||||
|
if value:
|
||||||
|
new_params[key] = value
|
||||||
|
return new_params
|
||||||
|
|
||||||
|
def get_resource_display(self, request):
|
||||||
|
query_params = dict(request.query_params)
|
||||||
|
params = self._filter_params(query_params)
|
||||||
|
|
||||||
|
spm_filter = params.pop("spm", None)
|
||||||
|
|
||||||
|
if not params and not spm_filter:
|
||||||
|
display_message = gettext_noop("Export all")
|
||||||
|
elif spm_filter:
|
||||||
|
display_message = gettext_noop("Export only selected items")
|
||||||
|
else:
|
||||||
|
query = ",".join(
|
||||||
|
["%s=%s" % (key, value) for key, value in params.items()]
|
||||||
|
)
|
||||||
|
display_message = i18n_fmt(gettext_noop("Export filtered: %s"), query)
|
||||||
|
return display_message
|
||||||
|
|
||||||
|
@property
|
||||||
|
def detail_msg(self):
|
||||||
|
return i18n_fmt(
|
||||||
|
gettext_noop('User %s view/export secret'), self.request.user
|
||||||
|
)
|
||||||
|
|
||||||
|
def list(self, request, *args, **kwargs):
|
||||||
|
list_func = getattr(super(), 'list')
|
||||||
|
if not callable(list_func):
|
||||||
|
return Response(status=status.HTTP_405_METHOD_NOT_ALLOWED)
|
||||||
|
response = list_func(request, *args, **kwargs)
|
||||||
|
with translation.override('en'):
|
||||||
|
resource_display = self.get_resource_display(request)
|
||||||
|
ids = [q.id for q in self.get_queryset()]
|
||||||
|
self.record_logs(
|
||||||
|
ids, ActionChoices.view, self.detail_msg, resource_display=resource_display
|
||||||
|
)
|
||||||
|
return response
|
||||||
|
|
||||||
|
def retrieve(self, request, *args, **kwargs):
|
||||||
|
retrieve_func = getattr(super(), 'retrieve')
|
||||||
|
if not callable(retrieve_func):
|
||||||
|
return Response(status=status.HTTP_405_METHOD_NOT_ALLOWED)
|
||||||
|
response = retrieve_func(request, *args, **kwargs)
|
||||||
|
with translation.override('en'):
|
||||||
|
resource = self.get_object()
|
||||||
|
self.record_logs(
|
||||||
|
[resource.id], ActionChoices.view, self.detail_msg, resource=resource
|
||||||
|
)
|
||||||
|
return response
|
||||||
|
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
from .account import *
|
from .account import * # noqa
|
||||||
from .automations import *
|
from .base import * # noqa
|
||||||
from .base import *
|
from .automations import * # noqa
|
||||||
from .template import *
|
from .template import * # noqa
|
||||||
from .virtual import *
|
from .virtual import * # noqa
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ from simple_history.models import HistoricalRecords
|
|||||||
|
|
||||||
from assets.models.base import AbsConnectivity
|
from assets.models.base import AbsConnectivity
|
||||||
from common.utils import lazyproperty
|
from common.utils import lazyproperty
|
||||||
|
from labels.mixins import LabeledMixin
|
||||||
from .base import BaseAccount
|
from .base import BaseAccount
|
||||||
from .mixins import VaultModelMixin
|
from .mixins import VaultModelMixin
|
||||||
from ..const import Source
|
from ..const import Source
|
||||||
@@ -42,7 +43,7 @@ class AccountHistoricalRecords(HistoricalRecords):
|
|||||||
return super().create_history_model(model, inherited)
|
return super().create_history_model(model, inherited)
|
||||||
|
|
||||||
|
|
||||||
class Account(AbsConnectivity, BaseAccount):
|
class Account(AbsConnectivity, LabeledMixin, BaseAccount):
|
||||||
asset = models.ForeignKey(
|
asset = models.ForeignKey(
|
||||||
'assets.Asset', related_name='accounts',
|
'assets.Asset', related_name='accounts',
|
||||||
on_delete=models.CASCADE, verbose_name=_('Asset')
|
on_delete=models.CASCADE, verbose_name=_('Asset')
|
||||||
@@ -68,10 +69,15 @@ class Account(AbsConnectivity, BaseAccount):
|
|||||||
('view_historyaccountsecret', _('Can view asset history account secret')),
|
('view_historyaccountsecret', _('Can view asset history account secret')),
|
||||||
('verify_account', _('Can verify account')),
|
('verify_account', _('Can verify account')),
|
||||||
('push_account', _('Can push account')),
|
('push_account', _('Can push account')),
|
||||||
|
('remove_account', _('Can remove account')),
|
||||||
]
|
]
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return '{}'.format(self.username)
|
if self.asset_id:
|
||||||
|
host = self.asset.name
|
||||||
|
else:
|
||||||
|
host = 'Dynamic'
|
||||||
|
return '{}({})'.format(self.name, host)
|
||||||
|
|
||||||
@lazyproperty
|
@lazyproperty
|
||||||
def platform(self):
|
def platform(self):
|
||||||
@@ -95,6 +101,48 @@ class Account(AbsConnectivity, BaseAccount):
|
|||||||
""" 排除自己和以自己为 su-from 的账号 """
|
""" 排除自己和以自己为 su-from 的账号 """
|
||||||
return self.asset.accounts.exclude(id=self.id).exclude(su_from=self)
|
return self.asset.accounts.exclude(id=self.id).exclude(su_from=self)
|
||||||
|
|
||||||
|
def make_account_ansible_vars(self, su_from):
|
||||||
|
var = {
|
||||||
|
'ansible_user': su_from.username,
|
||||||
|
}
|
||||||
|
if not su_from.secret:
|
||||||
|
return var
|
||||||
|
var['ansible_password'] = self.escape_jinja2_syntax(su_from.secret)
|
||||||
|
var['ansible_ssh_private_key_file'] = su_from.private_key_path
|
||||||
|
return var
|
||||||
|
|
||||||
|
def get_ansible_become_auth(self):
|
||||||
|
su_from = self.su_from
|
||||||
|
platform = self.platform
|
||||||
|
auth = {'ansible_become': False}
|
||||||
|
if not (platform.su_enabled and su_from):
|
||||||
|
return auth
|
||||||
|
|
||||||
|
auth.update(self.make_account_ansible_vars(su_from))
|
||||||
|
become_method = platform.su_method if platform.su_method else 'sudo'
|
||||||
|
password = su_from.secret if become_method == 'sudo' else self.secret
|
||||||
|
auth['ansible_become'] = True
|
||||||
|
auth['ansible_become_method'] = become_method
|
||||||
|
auth['ansible_become_user'] = self.username
|
||||||
|
auth['ansible_become_password'] = self.escape_jinja2_syntax(password)
|
||||||
|
return auth
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def escape_jinja2_syntax(value):
|
||||||
|
if not isinstance(value, str):
|
||||||
|
return value
|
||||||
|
|
||||||
|
def escape(v):
|
||||||
|
v = v.replace('{{', '__TEMP_OPEN_BRACES__') \
|
||||||
|
.replace('}}', '__TEMP_CLOSE_BRACES__')
|
||||||
|
|
||||||
|
v = v.replace('__TEMP_OPEN_BRACES__', '{{ "{{" }}') \
|
||||||
|
.replace('__TEMP_CLOSE_BRACES__', '{{ "}}" }}')
|
||||||
|
|
||||||
|
return v.replace('{%', '{{ "{%" }}').replace('%}', '{{ "%}" }}')
|
||||||
|
|
||||||
|
return escape(value)
|
||||||
|
|
||||||
|
|
||||||
def replace_history_model_with_mixin():
|
def replace_history_model_with_mixin():
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -8,10 +8,11 @@ from django.db import models
|
|||||||
from django.db.models import F
|
from django.db.models import F
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
|
from accounts.const.automation import AccountBackupType
|
||||||
from common.const.choices import Trigger
|
from common.const.choices import Trigger
|
||||||
|
from common.db import fields
|
||||||
from common.db.encoder import ModelJSONFieldEncoder
|
from common.db.encoder import ModelJSONFieldEncoder
|
||||||
from common.utils import get_logger
|
from common.utils import get_logger, lazyproperty
|
||||||
from common.utils import lazyproperty
|
|
||||||
from ops.mixin import PeriodTaskModelMixin
|
from ops.mixin import PeriodTaskModelMixin
|
||||||
from orgs.mixins.models import OrgModelMixin, JMSOrgBaseModel
|
from orgs.mixins.models import OrgModelMixin, JMSOrgBaseModel
|
||||||
|
|
||||||
@@ -22,6 +23,10 @@ logger = get_logger(__file__)
|
|||||||
|
|
||||||
class AccountBackupAutomation(PeriodTaskModelMixin, JMSOrgBaseModel):
|
class AccountBackupAutomation(PeriodTaskModelMixin, JMSOrgBaseModel):
|
||||||
types = models.JSONField(default=list)
|
types = models.JSONField(default=list)
|
||||||
|
backup_type = models.CharField(max_length=128, choices=AccountBackupType.choices,
|
||||||
|
default=AccountBackupType.email.value, verbose_name=_('Backup Type'))
|
||||||
|
is_password_divided_by_email = models.BooleanField(default=True, verbose_name=_('Is Password Divided'))
|
||||||
|
is_password_divided_by_obj_storage = models.BooleanField(default=True, verbose_name=_('Is Password Divided'))
|
||||||
recipients_part_one = models.ManyToManyField(
|
recipients_part_one = models.ManyToManyField(
|
||||||
'users.User', related_name='recipient_part_one_plans', blank=True,
|
'users.User', related_name='recipient_part_one_plans', blank=True,
|
||||||
verbose_name=_("Recipient part one")
|
verbose_name=_("Recipient part one")
|
||||||
@@ -30,6 +35,16 @@ class AccountBackupAutomation(PeriodTaskModelMixin, JMSOrgBaseModel):
|
|||||||
'users.User', related_name='recipient_part_two_plans', blank=True,
|
'users.User', related_name='recipient_part_two_plans', blank=True,
|
||||||
verbose_name=_("Recipient part two")
|
verbose_name=_("Recipient part two")
|
||||||
)
|
)
|
||||||
|
obj_recipients_part_one = models.ManyToManyField(
|
||||||
|
'terminal.ReplayStorage', related_name='obj_recipient_part_one_plans', blank=True,
|
||||||
|
verbose_name=_("Object Storage Recipient part one")
|
||||||
|
)
|
||||||
|
obj_recipients_part_two = models.ManyToManyField(
|
||||||
|
'terminal.ReplayStorage', related_name='obj_recipient_part_two_plans', blank=True,
|
||||||
|
verbose_name=_("Object Storage Recipient part two")
|
||||||
|
)
|
||||||
|
zip_encrypt_password = fields.EncryptCharField(max_length=4096, blank=True, null=True,
|
||||||
|
verbose_name=_('Zip Encrypt Password'))
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f'{self.name}({self.org_id})'
|
return f'{self.name}({self.org_id})'
|
||||||
@@ -49,6 +64,7 @@ class AccountBackupAutomation(PeriodTaskModelMixin, JMSOrgBaseModel):
|
|||||||
|
|
||||||
def to_attr_json(self):
|
def to_attr_json(self):
|
||||||
return {
|
return {
|
||||||
|
'id': self.id,
|
||||||
'name': self.name,
|
'name': self.name,
|
||||||
'is_periodic': self.is_periodic,
|
'is_periodic': self.is_periodic,
|
||||||
'interval': self.interval,
|
'interval': self.interval,
|
||||||
@@ -56,6 +72,10 @@ class AccountBackupAutomation(PeriodTaskModelMixin, JMSOrgBaseModel):
|
|||||||
'org_id': self.org_id,
|
'org_id': self.org_id,
|
||||||
'created_by': self.created_by,
|
'created_by': self.created_by,
|
||||||
'types': self.types,
|
'types': self.types,
|
||||||
|
'backup_type': self.backup_type,
|
||||||
|
'is_password_divided_by_email': self.is_password_divided_by_email,
|
||||||
|
'is_password_divided_by_obj_storage': self.is_password_divided_by_obj_storage,
|
||||||
|
'zip_encrypt_password': self.zip_encrypt_password,
|
||||||
'recipients_part_one': {
|
'recipients_part_one': {
|
||||||
str(user.id): (str(user), bool(user.secret_key))
|
str(user.id): (str(user), bool(user.secret_key))
|
||||||
for user in self.recipients_part_one.all()
|
for user in self.recipients_part_one.all()
|
||||||
@@ -63,7 +83,15 @@ class AccountBackupAutomation(PeriodTaskModelMixin, JMSOrgBaseModel):
|
|||||||
'recipients_part_two': {
|
'recipients_part_two': {
|
||||||
str(user.id): (str(user), bool(user.secret_key))
|
str(user.id): (str(user), bool(user.secret_key))
|
||||||
for user in self.recipients_part_two.all()
|
for user in self.recipients_part_two.all()
|
||||||
}
|
},
|
||||||
|
'obj_recipients_part_one': {
|
||||||
|
str(obj_storage.id): (str(obj_storage.name), str(obj_storage.type))
|
||||||
|
for obj_storage in self.obj_recipients_part_one.all()
|
||||||
|
},
|
||||||
|
'obj_recipients_part_two': {
|
||||||
|
str(obj_storage.id): (str(obj_storage.name), str(obj_storage.type))
|
||||||
|
for obj_storage in self.obj_recipients_part_two.all()
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
|||||||
@@ -1,12 +1,15 @@
|
|||||||
|
from django.db import models
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
|
from accounts.const import SSHKeyStrategy
|
||||||
|
from accounts.models import Account, SecretWithRandomMixin
|
||||||
from accounts.tasks import execute_account_automation_task
|
from accounts.tasks import execute_account_automation_task
|
||||||
from assets.models.automations import (
|
from assets.models.automations import (
|
||||||
BaseAutomation as AssetBaseAutomation,
|
BaseAutomation as AssetBaseAutomation,
|
||||||
AutomationExecution as AssetAutomationExecution
|
AutomationExecution as AssetAutomationExecution
|
||||||
)
|
)
|
||||||
|
|
||||||
__all__ = ['AccountBaseAutomation', 'AutomationExecution']
|
__all__ = ['AccountBaseAutomation', 'AutomationExecution', 'ChangeSecretMixin']
|
||||||
|
|
||||||
|
|
||||||
class AccountBaseAutomation(AssetBaseAutomation):
|
class AccountBaseAutomation(AssetBaseAutomation):
|
||||||
@@ -30,7 +33,7 @@ class AutomationExecution(AssetAutomationExecution):
|
|||||||
verbose_name_plural = _("Automation executions")
|
verbose_name_plural = _("Automation executions")
|
||||||
permissions = [
|
permissions = [
|
||||||
('view_changesecretexecution', _('Can view change secret execution')),
|
('view_changesecretexecution', _('Can view change secret execution')),
|
||||||
('add_changesecretexection', _('Can add change secret execution')),
|
('add_changesecretexecution', _('Can add change secret execution')),
|
||||||
|
|
||||||
('view_gatheraccountsexecution', _('Can view gather accounts execution')),
|
('view_gatheraccountsexecution', _('Can view gather accounts execution')),
|
||||||
('add_gatheraccountsexecution', _('Can add gather accounts execution')),
|
('add_gatheraccountsexecution', _('Can add gather accounts execution')),
|
||||||
@@ -43,3 +46,40 @@ class AutomationExecution(AssetAutomationExecution):
|
|||||||
from accounts.automations.endpoint import ExecutionManager
|
from accounts.automations.endpoint import ExecutionManager
|
||||||
manager = ExecutionManager(execution=self)
|
manager = ExecutionManager(execution=self)
|
||||||
return manager.run()
|
return manager.run()
|
||||||
|
|
||||||
|
|
||||||
|
class ChangeSecretMixin(SecretWithRandomMixin):
|
||||||
|
ssh_key_change_strategy = models.CharField(
|
||||||
|
choices=SSHKeyStrategy.choices, max_length=16,
|
||||||
|
default=SSHKeyStrategy.add, verbose_name=_('SSH key change strategy')
|
||||||
|
)
|
||||||
|
get_all_assets: callable # get all assets
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
abstract = True
|
||||||
|
|
||||||
|
def create_nonlocal_accounts(self, usernames, asset):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def get_account_ids(self):
|
||||||
|
usernames = self.accounts
|
||||||
|
accounts = Account.objects.none()
|
||||||
|
for asset in self.get_all_assets():
|
||||||
|
self.create_nonlocal_accounts(usernames, asset)
|
||||||
|
accounts = accounts | asset.accounts.all()
|
||||||
|
account_ids = accounts.filter(
|
||||||
|
username__in=usernames, secret_type=self.secret_type
|
||||||
|
).values_list('id', flat=True)
|
||||||
|
return [str(_id) for _id in account_ids]
|
||||||
|
|
||||||
|
def to_attr_json(self):
|
||||||
|
attr_json = super().to_attr_json()
|
||||||
|
attr_json.update({
|
||||||
|
'secret': self.secret,
|
||||||
|
'secret_type': self.secret_type,
|
||||||
|
'accounts': self.get_account_ids(),
|
||||||
|
'password_rules': self.password_rules,
|
||||||
|
'secret_strategy': self.secret_strategy,
|
||||||
|
'ssh_key_change_strategy': self.ssh_key_change_strategy,
|
||||||
|
})
|
||||||
|
return attr_json
|
||||||
|
|||||||
@@ -2,62 +2,13 @@ from django.db import models
|
|||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
from accounts.const import (
|
from accounts.const import (
|
||||||
AutomationTypes, SecretType, SecretStrategy, SSHKeyStrategy
|
AutomationTypes
|
||||||
)
|
)
|
||||||
from accounts.models import Account
|
|
||||||
from common.db import fields
|
from common.db import fields
|
||||||
from common.db.models import JMSBaseModel
|
from common.db.models import JMSBaseModel
|
||||||
from .base import AccountBaseAutomation
|
from .base import AccountBaseAutomation, ChangeSecretMixin
|
||||||
|
|
||||||
__all__ = ['ChangeSecretAutomation', 'ChangeSecretRecord', 'ChangeSecretMixin']
|
__all__ = ['ChangeSecretAutomation', 'ChangeSecretRecord', ]
|
||||||
|
|
||||||
|
|
||||||
class ChangeSecretMixin(models.Model):
|
|
||||||
secret_type = models.CharField(
|
|
||||||
choices=SecretType.choices, max_length=16,
|
|
||||||
default=SecretType.PASSWORD, verbose_name=_('Secret type')
|
|
||||||
)
|
|
||||||
secret = fields.EncryptTextField(blank=True, null=True, verbose_name=_('Secret'))
|
|
||||||
secret_strategy = models.CharField(
|
|
||||||
choices=SecretStrategy.choices, max_length=16,
|
|
||||||
default=SecretStrategy.custom, verbose_name=_('Secret strategy')
|
|
||||||
)
|
|
||||||
password_rules = models.JSONField(default=dict, verbose_name=_('Password rules'))
|
|
||||||
ssh_key_change_strategy = models.CharField(
|
|
||||||
choices=SSHKeyStrategy.choices, max_length=16,
|
|
||||||
default=SSHKeyStrategy.add, verbose_name=_('SSH key change strategy')
|
|
||||||
)
|
|
||||||
|
|
||||||
get_all_assets: callable # get all assets
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
abstract = True
|
|
||||||
|
|
||||||
def create_nonlocal_accounts(self, usernames, asset):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def get_account_ids(self):
|
|
||||||
usernames = self.accounts
|
|
||||||
accounts = Account.objects.none()
|
|
||||||
for asset in self.get_all_assets():
|
|
||||||
self.create_nonlocal_accounts(usernames, asset)
|
|
||||||
accounts = accounts | asset.accounts.all()
|
|
||||||
account_ids = accounts.filter(
|
|
||||||
username__in=usernames, secret_type=self.secret_type
|
|
||||||
).values_list('id', flat=True)
|
|
||||||
return [str(_id) for _id in account_ids]
|
|
||||||
|
|
||||||
def to_attr_json(self):
|
|
||||||
attr_json = super().to_attr_json()
|
|
||||||
attr_json.update({
|
|
||||||
'secret': self.secret,
|
|
||||||
'secret_type': self.secret_type,
|
|
||||||
'accounts': self.get_account_ids(),
|
|
||||||
'password_rules': self.password_rules,
|
|
||||||
'secret_strategy': self.secret_strategy,
|
|
||||||
'ssh_key_change_strategy': self.ssh_key_change_strategy,
|
|
||||||
})
|
|
||||||
return attr_json
|
|
||||||
|
|
||||||
|
|
||||||
class ChangeSecretAutomation(ChangeSecretMixin, AccountBaseAutomation):
|
class ChangeSecretAutomation(ChangeSecretMixin, AccountBaseAutomation):
|
||||||
@@ -89,7 +40,7 @@ class ChangeSecretRecord(JMSBaseModel):
|
|||||||
new_secret = fields.EncryptTextField(blank=True, null=True, verbose_name=_('New secret'))
|
new_secret = fields.EncryptTextField(blank=True, null=True, verbose_name=_('New secret'))
|
||||||
date_started = models.DateTimeField(blank=True, null=True, verbose_name=_('Date started'))
|
date_started = models.DateTimeField(blank=True, null=True, verbose_name=_('Date started'))
|
||||||
date_finished = models.DateTimeField(blank=True, null=True, verbose_name=_('Date finished'))
|
date_finished = models.DateTimeField(blank=True, null=True, verbose_name=_('Date finished'))
|
||||||
status = models.CharField(max_length=16, default='pending')
|
status = models.CharField(max_length=16, default='pending', verbose_name=_('Status'))
|
||||||
error = models.TextField(blank=True, null=True, verbose_name=_('Error'))
|
error = models.TextField(blank=True, null=True, verbose_name=_('Error'))
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
@@ -98,9 +49,3 @@ class ChangeSecretRecord(JMSBaseModel):
|
|||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.account.__str__()
|
return self.account.__str__()
|
||||||
|
|
||||||
@property
|
|
||||||
def timedelta(self):
|
|
||||||
if self.date_started and self.date_finished:
|
|
||||||
return self.date_finished - self.date_started
|
|
||||||
return None
|
|
||||||
|
|||||||
@@ -55,11 +55,15 @@ class GatherAccountsAutomation(AccountBaseAutomation):
|
|||||||
is_sync_account = models.BooleanField(
|
is_sync_account = models.BooleanField(
|
||||||
default=False, blank=True, verbose_name=_("Is sync account")
|
default=False, blank=True, verbose_name=_("Is sync account")
|
||||||
)
|
)
|
||||||
|
recipients = models.ManyToManyField('users.User', verbose_name=_("Recipient"), blank=True)
|
||||||
|
|
||||||
def to_attr_json(self):
|
def to_attr_json(self):
|
||||||
attr_json = super().to_attr_json()
|
attr_json = super().to_attr_json()
|
||||||
attr_json.update({
|
attr_json.update({
|
||||||
'is_sync_account': self.is_sync_account,
|
'is_sync_account': self.is_sync_account,
|
||||||
|
'recipients': [
|
||||||
|
str(recipient.id) for recipient in self.recipients.all()
|
||||||
|
]
|
||||||
})
|
})
|
||||||
return attr_json
|
return attr_json
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
|
from django.conf import settings
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
from accounts.const import AutomationTypes
|
from accounts.const import AutomationTypes
|
||||||
from accounts.models import Account
|
from accounts.models import Account
|
||||||
from jumpserver.utils import has_valid_xpack_license
|
|
||||||
from .base import AccountBaseAutomation
|
from .base import AccountBaseAutomation
|
||||||
from .change_secret import ChangeSecretMixin
|
from .change_secret import ChangeSecretMixin
|
||||||
|
|
||||||
@@ -17,9 +17,9 @@ class PushAccountAutomation(ChangeSecretMixin, AccountBaseAutomation):
|
|||||||
|
|
||||||
def create_nonlocal_accounts(self, usernames, asset):
|
def create_nonlocal_accounts(self, usernames, asset):
|
||||||
secret_type = self.secret_type
|
secret_type = self.secret_type
|
||||||
account_usernames = asset.accounts.filter(secret_type=self.secret_type).values_list(
|
account_usernames = asset.accounts \
|
||||||
'username', flat=True
|
.filter(secret_type=self.secret_type) \
|
||||||
)
|
.values_list('username', flat=True)
|
||||||
create_usernames = set(usernames) - set(account_usernames)
|
create_usernames = set(usernames) - set(account_usernames)
|
||||||
create_account_objs = [
|
create_account_objs = [
|
||||||
Account(
|
Account(
|
||||||
@@ -30,9 +30,6 @@ class PushAccountAutomation(ChangeSecretMixin, AccountBaseAutomation):
|
|||||||
]
|
]
|
||||||
Account.objects.bulk_create(create_account_objs)
|
Account.objects.bulk_create(create_account_objs)
|
||||||
|
|
||||||
def set_period_schedule(self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def dynamic_username(self):
|
def dynamic_username(self):
|
||||||
return self.username == '@USER'
|
return self.username == '@USER'
|
||||||
@@ -44,7 +41,7 @@ class PushAccountAutomation(ChangeSecretMixin, AccountBaseAutomation):
|
|||||||
|
|
||||||
def save(self, *args, **kwargs):
|
def save(self, *args, **kwargs):
|
||||||
self.type = AutomationTypes.push_account
|
self.type = AutomationTypes.push_account
|
||||||
if not has_valid_xpack_license():
|
if not settings.XPACK_LICENSE_IS_VALID:
|
||||||
self.is_periodic = False
|
self.is_periodic = False
|
||||||
super().save(*args, **kwargs)
|
super().save(*args, **kwargs)
|
||||||
|
|
||||||
|
|||||||
@@ -8,12 +8,14 @@ from django.conf import settings
|
|||||||
from django.db import models
|
from django.db import models
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
from accounts.const import SecretType
|
from accounts.const import SecretType, SecretStrategy
|
||||||
|
from accounts.models.mixins import VaultModelMixin, VaultManagerMixin, VaultQuerySetMixin
|
||||||
|
from accounts.utils import SecretGenerator
|
||||||
|
from common.db import fields
|
||||||
from common.utils import (
|
from common.utils import (
|
||||||
ssh_key_string_to_obj, ssh_key_gen, get_logger,
|
ssh_key_string_to_obj, ssh_key_gen, get_logger,
|
||||||
random_string, lazyproperty, parse_ssh_public_key_str, is_openssh_format_key
|
random_string, lazyproperty, parse_ssh_public_key_str, is_openssh_format_key
|
||||||
)
|
)
|
||||||
from accounts.models.mixins import VaultModelMixin, VaultManagerMixin, VaultQuerySetMixin
|
|
||||||
from orgs.mixins.models import JMSOrgBaseModel, OrgManager
|
from orgs.mixins.models import JMSOrgBaseModel, OrgManager
|
||||||
|
|
||||||
logger = get_logger(__file__)
|
logger = get_logger(__file__)
|
||||||
@@ -29,6 +31,35 @@ class BaseAccountManager(VaultManagerMixin, OrgManager):
|
|||||||
return self.get_queryset().active()
|
return self.get_queryset().active()
|
||||||
|
|
||||||
|
|
||||||
|
class SecretWithRandomMixin(models.Model):
|
||||||
|
secret_type = models.CharField(
|
||||||
|
choices=SecretType.choices, max_length=16,
|
||||||
|
default=SecretType.PASSWORD, verbose_name=_('Secret type')
|
||||||
|
)
|
||||||
|
secret = fields.EncryptTextField(blank=True, null=True, verbose_name=_('Secret'))
|
||||||
|
secret_strategy = models.CharField(
|
||||||
|
choices=SecretStrategy.choices, max_length=16,
|
||||||
|
default=SecretStrategy.custom, verbose_name=_('Secret strategy')
|
||||||
|
)
|
||||||
|
password_rules = models.JSONField(default=dict, verbose_name=_('Password rules'))
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
abstract = True
|
||||||
|
|
||||||
|
@lazyproperty
|
||||||
|
def secret_generator(self):
|
||||||
|
return SecretGenerator(
|
||||||
|
self.secret_strategy, self.secret_type,
|
||||||
|
self.password_rules,
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_secret(self):
|
||||||
|
if self.secret_strategy == 'random':
|
||||||
|
return self.secret_generator.get_secret()
|
||||||
|
else:
|
||||||
|
return self.secret
|
||||||
|
|
||||||
|
|
||||||
class BaseAccount(VaultModelMixin, JMSOrgBaseModel):
|
class BaseAccount(VaultModelMixin, JMSOrgBaseModel):
|
||||||
name = models.CharField(max_length=128, verbose_name=_("Name"))
|
name = models.CharField(max_length=128, verbose_name=_("Name"))
|
||||||
username = models.CharField(max_length=128, blank=True, verbose_name=_('Username'), db_index=True)
|
username = models.CharField(max_length=128, blank=True, verbose_name=_('Username'), db_index=True)
|
||||||
|
|||||||
@@ -37,8 +37,9 @@ class VaultManagerMixin(models.Manager):
|
|||||||
post_save.send(obj.__class__, instance=obj, created=True)
|
post_save.send(obj.__class__, instance=obj, created=True)
|
||||||
return objs
|
return objs
|
||||||
|
|
||||||
def bulk_update(self, objs, batch_size=None, ignore_conflicts=False):
|
def bulk_update(self, objs, fields, batch_size=None):
|
||||||
objs = super().bulk_update(objs, batch_size=batch_size, ignore_conflicts=ignore_conflicts)
|
fields = ["_secret" if field == "secret" else field for field in fields]
|
||||||
|
super().bulk_update(objs, fields, batch_size=batch_size)
|
||||||
for obj in objs:
|
for obj in objs:
|
||||||
post_save.send(obj.__class__, instance=obj, created=False)
|
post_save.send(obj.__class__, instance=obj, created=False)
|
||||||
return objs
|
return objs
|
||||||
|
|||||||
@@ -3,17 +3,24 @@ from django.db.models import Count, Q
|
|||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
|
from labels.mixins import LabeledMixin
|
||||||
from .account import Account
|
from .account import Account
|
||||||
from .base import BaseAccount
|
from .base import BaseAccount, SecretWithRandomMixin
|
||||||
|
|
||||||
__all__ = ['AccountTemplate', ]
|
__all__ = ['AccountTemplate', ]
|
||||||
|
|
||||||
|
|
||||||
class AccountTemplate(BaseAccount):
|
class AccountTemplate(LabeledMixin, BaseAccount, SecretWithRandomMixin):
|
||||||
su_from = models.ForeignKey(
|
su_from = models.ForeignKey(
|
||||||
'self', related_name='su_to', null=True,
|
'self', related_name='su_to', null=True,
|
||||||
on_delete=models.SET_NULL, verbose_name=_("Su from")
|
on_delete=models.SET_NULL, verbose_name=_("Su from")
|
||||||
)
|
)
|
||||||
|
auto_push = models.BooleanField(default=False, verbose_name=_('Auto push'))
|
||||||
|
platforms = models.ManyToManyField(
|
||||||
|
'assets.Platform', related_name='account_templates',
|
||||||
|
verbose_name=_('Platforms'), blank=True,
|
||||||
|
)
|
||||||
|
push_params = models.JSONField(default=dict, verbose_name=_('Push params'))
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = _('Account template')
|
verbose_name = _('Account template')
|
||||||
@@ -25,15 +32,15 @@ class AccountTemplate(BaseAccount):
|
|||||||
('change_accounttemplatesecret', _('Can change asset account template secret')),
|
('change_accounttemplatesecret', _('Can change asset account template secret')),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f'{self.name}({self.username})'
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_su_from_account_templates(cls, pk=None):
|
def get_su_from_account_templates(cls, pk=None):
|
||||||
if pk is None:
|
if pk is None:
|
||||||
return cls.objects.all()
|
return cls.objects.all()
|
||||||
return cls.objects.exclude(Q(id=pk) | Q(su_from_id=pk))
|
return cls.objects.exclude(Q(id=pk) | Q(su_from_id=pk))
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return f'{self.name}({self.username})'
|
|
||||||
|
|
||||||
def get_su_from_account(self, asset):
|
def get_su_from_account(self, asset):
|
||||||
su_from = self.su_from
|
su_from = self.su_from
|
||||||
if su_from and asset.platform.su_enabled:
|
if su_from and asset.platform.su_enabled:
|
||||||
@@ -43,8 +50,7 @@ class AccountTemplate(BaseAccount):
|
|||||||
).first()
|
).first()
|
||||||
return account
|
return account
|
||||||
|
|
||||||
@staticmethod
|
def bulk_update_accounts(self, accounts):
|
||||||
def bulk_update_accounts(accounts, data):
|
|
||||||
history_model = Account.history.model
|
history_model = Account.history.model
|
||||||
account_ids = accounts.values_list('id', flat=True)
|
account_ids = accounts.values_list('id', flat=True)
|
||||||
history_accounts = history_model.objects.filter(id__in=account_ids)
|
history_accounts = history_model.objects.filter(id__in=account_ids)
|
||||||
@@ -57,8 +63,7 @@ class AccountTemplate(BaseAccount):
|
|||||||
for account in accounts:
|
for account in accounts:
|
||||||
account_id = str(account.id)
|
account_id = str(account.id)
|
||||||
account.version = account_id_count_map.get(account_id) + 1
|
account.version = account_id_count_map.get(account_id) + 1
|
||||||
for k, v in data.items():
|
account.secret = self.get_secret()
|
||||||
setattr(account, k, v)
|
|
||||||
Account.objects.bulk_update(accounts, ['version', 'secret'])
|
Account.objects.bulk_update(accounts, ['version', 'secret'])
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@@ -80,7 +85,5 @@ class AccountTemplate(BaseAccount):
|
|||||||
|
|
||||||
def bulk_sync_account_secret(self, accounts, user_id):
|
def bulk_sync_account_secret(self, accounts, user_id):
|
||||||
""" 批量同步账号密码 """
|
""" 批量同步账号密码 """
|
||||||
if not accounts:
|
self.bulk_update_accounts(accounts)
|
||||||
return
|
|
||||||
self.bulk_update_accounts(accounts, {'secret': self.secret})
|
|
||||||
self.bulk_create_history_accounts(accounts, user_id)
|
self.bulk_create_history_accounts(accounts, user_id)
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
|
from django.template.loader import render_to_string
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
from common.tasks import send_mail_attachment_async
|
from common.tasks import send_mail_attachment_async, upload_backup_to_obj_storage
|
||||||
|
from notifications.notifications import UserMessage
|
||||||
|
from terminal.models.component.storage import ReplayStorage
|
||||||
from users.models import User
|
from users.models import User
|
||||||
|
|
||||||
|
|
||||||
@@ -20,8 +23,8 @@ class AccountBackupExecutionTaskMsg(object):
|
|||||||
else:
|
else:
|
||||||
return _("{} - The account backup passage task has been completed: "
|
return _("{} - The account backup passage task has been completed: "
|
||||||
"the encryption password has not been set - "
|
"the encryption password has not been set - "
|
||||||
"please go to personal information -> file encryption password "
|
"please go to personal information -> Basic file encryption password for preference settings"
|
||||||
"to set the encryption password").format(name)
|
).format(name)
|
||||||
|
|
||||||
def publish(self, attachment_list=None):
|
def publish(self, attachment_list=None):
|
||||||
send_mail_attachment_async(
|
send_mail_attachment_async(
|
||||||
@@ -29,6 +32,25 @@ class AccountBackupExecutionTaskMsg(object):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class AccountBackupByObjStorageExecutionTaskMsg(object):
|
||||||
|
subject = _('Notification of account backup route task results')
|
||||||
|
|
||||||
|
def __init__(self, name: str, obj_storage: ReplayStorage):
|
||||||
|
self.name = name
|
||||||
|
self.obj_storage = obj_storage
|
||||||
|
|
||||||
|
@property
|
||||||
|
def message(self):
|
||||||
|
name = self.name
|
||||||
|
return _('{} - The account backup passage task has been completed.'
|
||||||
|
' See the attachment for details').format(name)
|
||||||
|
|
||||||
|
def publish(self, attachment_list=None):
|
||||||
|
upload_backup_to_obj_storage(
|
||||||
|
self.obj_storage, attachment_list
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class ChangeSecretExecutionTaskMsg(object):
|
class ChangeSecretExecutionTaskMsg(object):
|
||||||
subject = _('Notification of implementation result of encryption change plan')
|
subject = _('Notification of implementation result of encryption change plan')
|
||||||
|
|
||||||
@@ -51,3 +73,25 @@ class ChangeSecretExecutionTaskMsg(object):
|
|||||||
send_mail_attachment_async(
|
send_mail_attachment_async(
|
||||||
self.subject, self.message, [self.user.email], attachments
|
self.subject, self.message, [self.user.email], attachments
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class GatherAccountChangeMsg(UserMessage):
|
||||||
|
subject = _('Gather account change information')
|
||||||
|
|
||||||
|
def __init__(self, user, change_info: dict):
|
||||||
|
self.change_info = change_info
|
||||||
|
super().__init__(user)
|
||||||
|
|
||||||
|
def get_html_msg(self) -> dict:
|
||||||
|
context = {'change_info': self.change_info}
|
||||||
|
message = render_to_string('accounts/asset_account_change_info.html', context)
|
||||||
|
|
||||||
|
return {
|
||||||
|
'subject': str(self.subject),
|
||||||
|
'message': message
|
||||||
|
}
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def gen_test_msg(cls):
|
||||||
|
user = User.objects.first()
|
||||||
|
return cls(user, {})
|
||||||
|
|||||||
19
apps/accounts/permissions.py
Normal file
19
apps/accounts/permissions.py
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
from rest_framework import permissions
|
||||||
|
|
||||||
|
|
||||||
|
def check_permissions(request):
|
||||||
|
act = request.data.get('action')
|
||||||
|
if act == 'push':
|
||||||
|
code = 'accounts.push_account'
|
||||||
|
elif act == 'remove':
|
||||||
|
code = 'accounts.remove_account'
|
||||||
|
else:
|
||||||
|
code = 'accounts.verify_account'
|
||||||
|
return request.user.has_perm(code)
|
||||||
|
|
||||||
|
|
||||||
|
class AccountTaskActionPermission(permissions.IsAuthenticated):
|
||||||
|
|
||||||
|
def has_permission(self, request, view):
|
||||||
|
return super().has_permission(request, view) \
|
||||||
|
and check_permissions(request)
|
||||||
@@ -2,6 +2,7 @@ import uuid
|
|||||||
from copy import deepcopy
|
from copy import deepcopy
|
||||||
|
|
||||||
from django.db import IntegrityError
|
from django.db import IntegrityError
|
||||||
|
from django.db import transaction
|
||||||
from django.db.models import Q
|
from django.db.models import Q
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
@@ -9,7 +10,7 @@ from rest_framework.generics import get_object_or_404
|
|||||||
from rest_framework.validators import UniqueTogetherValidator
|
from rest_framework.validators import UniqueTogetherValidator
|
||||||
|
|
||||||
from accounts.const import SecretType, Source, AccountInvalidPolicy
|
from accounts.const import SecretType, Source, AccountInvalidPolicy
|
||||||
from accounts.models import Account, AccountTemplate
|
from accounts.models import Account, AccountTemplate, GatheredAccount
|
||||||
from accounts.tasks import push_accounts_to_assets_task
|
from accounts.tasks import push_accounts_to_assets_task
|
||||||
from assets.const import Category, AllTypes
|
from assets.const import Category, AllTypes
|
||||||
from assets.models import Asset
|
from assets.models import Asset
|
||||||
@@ -65,6 +66,9 @@ class AccountCreateUpdateSerializerMixin(serializers.Serializer):
|
|||||||
name = initial_data.get('name')
|
name = initial_data.get('name')
|
||||||
if name is not None:
|
if name is not None:
|
||||||
return
|
return
|
||||||
|
request = self.context.get('request')
|
||||||
|
if request and request.method == 'PATCH':
|
||||||
|
return
|
||||||
if not name:
|
if not name:
|
||||||
name = initial_data.get('username')
|
name = initial_data.get('username')
|
||||||
if self.instance and self.instance.name == name:
|
if self.instance and self.instance.name == name:
|
||||||
@@ -73,6 +77,23 @@ class AccountCreateUpdateSerializerMixin(serializers.Serializer):
|
|||||||
name = name + '_' + uuid.uuid4().hex[:4]
|
name = name + '_' + uuid.uuid4().hex[:4]
|
||||||
initial_data['name'] = name
|
initial_data['name'] = name
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_template_attr_for_account(template):
|
||||||
|
# Set initial data from template
|
||||||
|
field_names = [
|
||||||
|
'name', 'username', 'secret',
|
||||||
|
'secret_type', 'privileged', 'is_active'
|
||||||
|
]
|
||||||
|
|
||||||
|
attrs = {}
|
||||||
|
for name in field_names:
|
||||||
|
value = getattr(template, name, None)
|
||||||
|
if value is None:
|
||||||
|
continue
|
||||||
|
attrs[name] = value
|
||||||
|
attrs['secret'] = template.get_secret()
|
||||||
|
return attrs
|
||||||
|
|
||||||
def from_template_if_need(self, initial_data):
|
def from_template_if_need(self, initial_data):
|
||||||
if isinstance(initial_data, str):
|
if isinstance(initial_data, str):
|
||||||
return
|
return
|
||||||
@@ -89,20 +110,7 @@ class AccountCreateUpdateSerializerMixin(serializers.Serializer):
|
|||||||
raise serializers.ValidationError({'template': 'Template not found'})
|
raise serializers.ValidationError({'template': 'Template not found'})
|
||||||
|
|
||||||
self._template = template
|
self._template = template
|
||||||
# Set initial data from template
|
attrs = self.get_template_attr_for_account(template)
|
||||||
ignore_fields = ['id', 'date_created', 'date_updated', 'su_from', 'org_id']
|
|
||||||
field_names = [
|
|
||||||
field.name for field in template._meta.fields
|
|
||||||
if field.name not in ignore_fields
|
|
||||||
]
|
|
||||||
field_names = [name if name != '_secret' else 'secret' for name in field_names]
|
|
||||||
|
|
||||||
attrs = {}
|
|
||||||
for name in field_names:
|
|
||||||
value = getattr(template, name, None)
|
|
||||||
if value is None:
|
|
||||||
continue
|
|
||||||
attrs[name] = value
|
|
||||||
initial_data.update(attrs)
|
initial_data.update(attrs)
|
||||||
initial_data.update({
|
initial_data.update({
|
||||||
'source': Source.TEMPLATE,
|
'source': Source.TEMPLATE,
|
||||||
@@ -114,10 +122,13 @@ class AccountCreateUpdateSerializerMixin(serializers.Serializer):
|
|||||||
asset = get_object_or_404(Asset, pk=asset_id)
|
asset = get_object_or_404(Asset, pk=asset_id)
|
||||||
initial_data['su_from'] = template.get_su_from_account(asset)
|
initial_data['su_from'] = template.get_su_from_account(asset)
|
||||||
|
|
||||||
@staticmethod
|
def push_account_if_need(self, instance, push_now, params, stat):
|
||||||
def push_account_if_need(instance, push_now, params, stat):
|
|
||||||
if not push_now or stat not in ['created', 'updated']:
|
if not push_now or stat not in ['created', 'updated']:
|
||||||
return
|
return
|
||||||
|
transaction.on_commit(lambda: self.start_push(instance, params))
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def start_push(instance, params):
|
||||||
push_accounts_to_assets_task.delay([str(instance.id)], params)
|
push_accounts_to_assets_task.delay([str(instance.id)], params)
|
||||||
|
|
||||||
def get_validators(self):
|
def get_validators(self):
|
||||||
@@ -230,7 +241,7 @@ class AccountSerializer(AccountCreateUpdateSerializerMixin, BaseAccountSerialize
|
|||||||
queryset = queryset.prefetch_related(
|
queryset = queryset.prefetch_related(
|
||||||
'asset', 'asset__platform',
|
'asset', 'asset__platform',
|
||||||
'asset__platform__automation'
|
'asset__platform__automation'
|
||||||
)
|
).prefetch_related('labels', 'labels__label')
|
||||||
return queryset
|
return queryset
|
||||||
|
|
||||||
|
|
||||||
@@ -447,11 +458,15 @@ class AccountTaskSerializer(serializers.Serializer):
|
|||||||
('test', 'test'),
|
('test', 'test'),
|
||||||
('verify', 'verify'),
|
('verify', 'verify'),
|
||||||
('push', 'push'),
|
('push', 'push'),
|
||||||
|
('remove', 'remove'),
|
||||||
)
|
)
|
||||||
action = serializers.ChoiceField(choices=ACTION_CHOICES, write_only=True)
|
action = serializers.ChoiceField(choices=ACTION_CHOICES, write_only=True)
|
||||||
accounts = serializers.PrimaryKeyRelatedField(
|
accounts = serializers.PrimaryKeyRelatedField(
|
||||||
queryset=Account.objects, required=False, allow_empty=True, many=True
|
queryset=Account.objects, required=False, allow_empty=True, many=True
|
||||||
)
|
)
|
||||||
|
gather_accounts = serializers.PrimaryKeyRelatedField(
|
||||||
|
queryset=GatheredAccount.objects, required=False, allow_empty=True, many=True
|
||||||
|
)
|
||||||
task = serializers.CharField(read_only=True)
|
task = serializers.CharField(read_only=True)
|
||||||
params = serializers.JSONField(
|
params = serializers.JSONField(
|
||||||
decoder=None, encoder=None, required=False,
|
decoder=None, encoder=None, required=False,
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ from rest_framework import serializers
|
|||||||
|
|
||||||
from accounts.models import AccountBackupAutomation, AccountBackupExecution
|
from accounts.models import AccountBackupAutomation, AccountBackupExecution
|
||||||
from common.const.choices import Trigger
|
from common.const.choices import Trigger
|
||||||
from common.serializers.fields import LabeledChoiceField
|
from common.serializers.fields import LabeledChoiceField, EncryptedField
|
||||||
from common.utils import get_logger
|
from common.utils import get_logger
|
||||||
from ops.mixin import PeriodTaskSerializerMixin
|
from ops.mixin import PeriodTaskSerializerMixin
|
||||||
from orgs.mixins.serializers import BulkOrgResourceModelSerializer
|
from orgs.mixins.serializers import BulkOrgResourceModelSerializer
|
||||||
@@ -16,6 +16,11 @@ __all__ = ['AccountBackupSerializer', 'AccountBackupPlanExecutionSerializer']
|
|||||||
|
|
||||||
|
|
||||||
class AccountBackupSerializer(PeriodTaskSerializerMixin, BulkOrgResourceModelSerializer):
|
class AccountBackupSerializer(PeriodTaskSerializerMixin, BulkOrgResourceModelSerializer):
|
||||||
|
zip_encrypt_password = EncryptedField(
|
||||||
|
label=_('Zip Encrypt Password'), required=False, max_length=40960, allow_blank=True,
|
||||||
|
allow_null=True, write_only=True,
|
||||||
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = AccountBackupAutomation
|
model = AccountBackupAutomation
|
||||||
read_only_fields = [
|
read_only_fields = [
|
||||||
@@ -24,7 +29,9 @@ class AccountBackupSerializer(PeriodTaskSerializerMixin, BulkOrgResourceModelSer
|
|||||||
]
|
]
|
||||||
fields = read_only_fields + [
|
fields = read_only_fields + [
|
||||||
'id', 'name', 'is_periodic', 'interval', 'crontab',
|
'id', 'name', 'is_periodic', 'interval', 'crontab',
|
||||||
'comment', 'types', 'recipients_part_one', 'recipients_part_two'
|
'comment', 'types', 'recipients_part_one', 'recipients_part_two', 'backup_type',
|
||||||
|
'is_password_divided_by_email', 'is_password_divided_by_obj_storage', 'obj_recipients_part_one',
|
||||||
|
'obj_recipients_part_two', 'zip_encrypt_password'
|
||||||
]
|
]
|
||||||
extra_kwargs = {
|
extra_kwargs = {
|
||||||
'name': {'required': True},
|
'name': {'required': True},
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ from rest_framework import serializers
|
|||||||
from accounts.const import SecretType
|
from accounts.const import SecretType
|
||||||
from accounts.models import BaseAccount
|
from accounts.models import BaseAccount
|
||||||
from accounts.utils import validate_password_for_ansible, validate_ssh_key
|
from accounts.utils import validate_password_for_ansible, validate_ssh_key
|
||||||
|
from common.serializers import ResourceLabelsMixin
|
||||||
from common.serializers.fields import EncryptedField, LabeledChoiceField
|
from common.serializers.fields import EncryptedField, LabeledChoiceField
|
||||||
from orgs.mixins.serializers import BulkOrgResourceModelSerializer
|
from orgs.mixins.serializers import BulkOrgResourceModelSerializer
|
||||||
|
|
||||||
@@ -60,8 +61,7 @@ class AuthValidateMixin(serializers.Serializer):
|
|||||||
return super().update(instance, validated_data)
|
return super().update(instance, validated_data)
|
||||||
|
|
||||||
|
|
||||||
class BaseAccountSerializer(AuthValidateMixin, BulkOrgResourceModelSerializer):
|
class BaseAccountSerializer(AuthValidateMixin, ResourceLabelsMixin, BulkOrgResourceModelSerializer):
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = BaseAccount
|
model = BaseAccount
|
||||||
fields_mini = ['id', 'name', 'username']
|
fields_mini = ['id', 'name', 'username']
|
||||||
@@ -70,7 +70,7 @@ class BaseAccountSerializer(AuthValidateMixin, BulkOrgResourceModelSerializer):
|
|||||||
'privileged', 'is_active', 'spec_info',
|
'privileged', 'is_active', 'spec_info',
|
||||||
]
|
]
|
||||||
fields_other = ['created_by', 'date_created', 'date_updated', 'comment']
|
fields_other = ['created_by', 'date_created', 'date_updated', 'comment']
|
||||||
fields = fields_small + fields_other
|
fields = fields_small + fields_other + ['labels']
|
||||||
read_only_fields = [
|
read_only_fields = [
|
||||||
'spec_info', 'date_verified', 'created_by', 'date_created',
|
'spec_info', 'date_verified', 'created_by', 'date_created',
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -1,16 +1,27 @@
|
|||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
|
|
||||||
from accounts.models import AccountTemplate, Account
|
from accounts.const import SecretStrategy, SecretType
|
||||||
|
from accounts.models import AccountTemplate
|
||||||
|
from accounts.utils import SecretGenerator
|
||||||
from common.serializers import SecretReadableMixin
|
from common.serializers import SecretReadableMixin
|
||||||
from common.serializers.fields import ObjectRelatedField
|
from common.serializers.fields import ObjectRelatedField
|
||||||
from .base import BaseAccountSerializer
|
from .base import BaseAccountSerializer
|
||||||
|
|
||||||
|
|
||||||
class AccountTemplateSerializer(BaseAccountSerializer):
|
class PasswordRulesSerializer(serializers.Serializer):
|
||||||
is_sync_account = serializers.BooleanField(default=False, write_only=True)
|
length = serializers.IntegerField(min_value=8, max_value=30, default=16, label=_('Password length'))
|
||||||
_is_sync_account = False
|
lowercase = serializers.BooleanField(default=True, label=_('Lowercase'))
|
||||||
|
uppercase = serializers.BooleanField(default=True, label=_('Uppercase'))
|
||||||
|
digit = serializers.BooleanField(default=True, label=_('Digit'))
|
||||||
|
symbol = serializers.BooleanField(default=True, label=_('Special symbol'))
|
||||||
|
exclude_symbols = serializers.CharField(
|
||||||
|
default='', allow_blank=True, max_length=16, label=_('Exclude symbol')
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class AccountTemplateSerializer(BaseAccountSerializer):
|
||||||
|
password_rules = PasswordRulesSerializer(required=False, label=_('Password rules'))
|
||||||
su_from = ObjectRelatedField(
|
su_from = ObjectRelatedField(
|
||||||
required=False, queryset=AccountTemplate.objects, allow_null=True,
|
required=False, queryset=AccountTemplate.objects, allow_null=True,
|
||||||
allow_empty=True, label=_('Su from'), attrs=('id', 'name', 'username')
|
allow_empty=True, label=_('Su from'), attrs=('id', 'name', 'username')
|
||||||
@@ -18,36 +29,38 @@ class AccountTemplateSerializer(BaseAccountSerializer):
|
|||||||
|
|
||||||
class Meta(BaseAccountSerializer.Meta):
|
class Meta(BaseAccountSerializer.Meta):
|
||||||
model = AccountTemplate
|
model = AccountTemplate
|
||||||
fields = BaseAccountSerializer.Meta.fields + ['is_sync_account', 'su_from']
|
fields = BaseAccountSerializer.Meta.fields + [
|
||||||
|
'secret_strategy', 'password_rules',
|
||||||
def sync_accounts_secret(self, instance, diff):
|
'auto_push', 'push_params', 'platforms',
|
||||||
if not self._is_sync_account or 'secret' not in diff:
|
'su_from'
|
||||||
return
|
]
|
||||||
query_data = {
|
extra_kwargs = {
|
||||||
'source_id': instance.id,
|
'secret_strategy': {'help_text': _('Secret generation strategy for account creation')},
|
||||||
'username': instance.username,
|
'auto_push': {'help_text': _('Whether to automatically push the account to the asset')},
|
||||||
'secret_type': instance.secret_type
|
'platforms': {
|
||||||
|
'help_text': _(
|
||||||
|
'Associated platform, you can configure push parameters. '
|
||||||
|
'If not associated, default parameters will be used'
|
||||||
|
),
|
||||||
|
'required': False
|
||||||
|
},
|
||||||
}
|
}
|
||||||
accounts = Account.objects.filter(**query_data)
|
|
||||||
instance.bulk_sync_account_secret(accounts, self.context['request'].user.id)
|
@staticmethod
|
||||||
|
def generate_secret(attrs):
|
||||||
|
secret_type = attrs.get('secret_type', SecretType.PASSWORD)
|
||||||
|
secret_strategy = attrs.get('secret_strategy', SecretStrategy.custom)
|
||||||
|
password_rules = attrs.get('password_rules')
|
||||||
|
if secret_strategy != SecretStrategy.random:
|
||||||
|
return
|
||||||
|
generator = SecretGenerator(secret_strategy, secret_type, password_rules)
|
||||||
|
attrs['secret'] = generator.get_secret()
|
||||||
|
|
||||||
def validate(self, attrs):
|
def validate(self, attrs):
|
||||||
self._is_sync_account = attrs.pop('is_sync_account', None)
|
|
||||||
attrs = super().validate(attrs)
|
attrs = super().validate(attrs)
|
||||||
|
self.generate_secret(attrs)
|
||||||
return attrs
|
return attrs
|
||||||
|
|
||||||
def update(self, instance, validated_data):
|
|
||||||
diff = {
|
|
||||||
k: v for k, v in validated_data.items()
|
|
||||||
if getattr(instance, k, None) != v
|
|
||||||
}
|
|
||||||
instance = super().update(instance, validated_data)
|
|
||||||
if {'username', 'secret_type'} & set(diff.keys()):
|
|
||||||
Account.objects.filter(source_id=instance.id).update(source_id=None)
|
|
||||||
else:
|
|
||||||
self.sync_accounts_secret(instance, diff)
|
|
||||||
return instance
|
|
||||||
|
|
||||||
|
|
||||||
class AccountTemplateSecretSerializer(SecretReadableMixin, AccountTemplateSerializer):
|
class AccountTemplateSecretSerializer(SecretReadableMixin, AccountTemplateSerializer):
|
||||||
class Meta(AccountTemplateSerializer.Meta):
|
class Meta(AccountTemplateSerializer.Meta):
|
||||||
|
|||||||
@@ -19,8 +19,12 @@ class VirtualAccountSerializer(serializers.ModelSerializer):
|
|||||||
'comment': {'label': _('Comment')},
|
'comment': {'label': _('Comment')},
|
||||||
'name': {'label': _('Name')},
|
'name': {'label': _('Name')},
|
||||||
'username': {'label': _('Username')},
|
'username': {'label': _('Username')},
|
||||||
'secret_from_login': {'help_text': _('Current only support login from AD/LDAP. Secret priority: '
|
'secret_from_login': {
|
||||||
'Same account in asset secret > Login secret > Manual input')
|
'help_text': _(
|
||||||
|
'Current only support login from AD/LDAP. Secret priority: '
|
||||||
|
'Same account in asset secret > Login secret > Manual input. <br/ >'
|
||||||
|
'For security, please set config CACHE_LOGIN_PASSWORD_ENABLED to true'
|
||||||
|
)
|
||||||
},
|
},
|
||||||
'alias': {'required': False},
|
'alias': {'required': False},
|
||||||
}
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user