mirror of
https://github.com/jumpserver/jumpserver.git
synced 2025-12-16 17:12:53 +00:00
Compare commits
1830 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4d37dca0de | ||
|
|
8730fa8dee | ||
|
|
9a5a775652 | ||
|
|
8304ae9070 | ||
|
|
9533861e24 | ||
|
|
abbfbcde83 | ||
|
|
046a9d41bf | ||
|
|
363bb20da7 | ||
|
|
2b7c8b9c07 | ||
|
|
db040bbd06 | ||
|
|
a761ec9aa1 | ||
|
|
c0ffe45ce9 | ||
|
|
404d58a9c9 | ||
|
|
f64eab7a15 | ||
|
|
46f94fd138 | ||
|
|
2f1c0090b7 | ||
|
|
b0d6a09276 | ||
|
|
d8db76cc7b | ||
|
|
b35a55ed54 | ||
|
|
dc5ecfcc4b | ||
|
|
2ca4002624 | ||
|
|
543dde57ab | ||
|
|
c088437fe5 | ||
|
|
e721ec147c | ||
|
|
5d18d6dee0 | ||
|
|
ecfd338428 | ||
|
|
4b28b079dc | ||
|
|
c1c3236a30 | ||
|
|
4b19750581 | ||
|
|
eafb5ecfb3 | ||
|
|
583486e26e | ||
|
|
8198620a2e | ||
|
|
c0b301d52b | ||
|
|
7791d6222a | ||
|
|
b740d9d42f | ||
|
|
48d0187604 | ||
|
|
6217018427 | ||
|
|
923f40e523 | ||
|
|
1f1fe2084b | ||
|
|
b8b1a6ac9c | ||
|
|
35f88722af | ||
|
|
7e6d2749ae | ||
|
|
be57b101ff | ||
|
|
41c8cb6307 | ||
|
|
3a7ae01ede | ||
|
|
053d640e4c | ||
|
|
d17ca4f6a7 | ||
|
|
f3acc28ded | ||
|
|
5a14bb13d0 | ||
|
|
2956f2e4b7 | ||
|
|
e983ac3cbc | ||
|
|
fab156dc5f | ||
|
|
f6f897317e | ||
|
|
a0441cd6ea | ||
|
|
e9abd1e72d | ||
|
|
9fcb4ecba0 | ||
|
|
4b637ad86e | ||
|
|
829f867962 | ||
|
|
7f965b55f4 | ||
|
|
0e0be618e5 | ||
|
|
9577af3221 | ||
|
|
a6b7cc9d1b | ||
|
|
7a9a71197a | ||
|
|
3cd68ba0a9 | ||
|
|
02bdd0f07d | ||
|
|
98cf6f82b7 | ||
|
|
27fd5d51b9 | ||
|
|
095ca91e30 | ||
|
|
d05514962a | ||
|
|
c4066a03fa | ||
|
|
a7d4c4ca2a | ||
|
|
5b0f8f63a3 | ||
|
|
c4bcae68bf | ||
|
|
29ca50f97e | ||
|
|
49aaf8d53e | ||
|
|
931e15173b | ||
|
|
4018a59b2e | ||
|
|
88905bd28d | ||
|
|
abad98a190 | ||
|
|
7419139b29 | ||
|
|
a1fd3b1ecb | ||
|
|
8a8a7f9947 | ||
|
|
f9e6fc98fb | ||
|
|
0dd015bcba | ||
|
|
d1ea31c9a4 | ||
|
|
e2bf56e624 | ||
|
|
26040a5560 | ||
|
|
54726f0a2d | ||
|
|
7fd88b95f9 | ||
|
|
4f271d6405 | ||
|
|
fe17a8c3a0 | ||
|
|
ee5e97e860 | ||
|
|
dddfc66efd | ||
|
|
d005bd804f | ||
|
|
08de04fdbc | ||
|
|
9ed7c41514 | ||
|
|
1a81b76a46 | ||
|
|
cf99a7a031 | ||
|
|
64551b13a1 | ||
|
|
c715300416 | ||
|
|
d9031ae02b | ||
|
|
0d2ba5c518 | ||
|
|
817957dbac | ||
|
|
3796af78a6 | ||
|
|
1191e4ab2d | ||
|
|
1c6fcc5826 | ||
|
|
4728f95634 | ||
|
|
013502186b | ||
|
|
a6d040cd34 | ||
|
|
398758baa6 | ||
|
|
e29bddd89e | ||
|
|
e35c915ee3 | ||
|
|
de2dd583d0 | ||
|
|
43f1d7eeae | ||
|
|
9bb63e0933 | ||
|
|
c9e03fd5d8 | ||
|
|
7a147242c9 | ||
|
|
392c261a96 | ||
|
|
2bbccae0f5 | ||
|
|
606fa9bfbc | ||
|
|
96e7b165dd | ||
|
|
148413d280 | ||
|
|
a46a81d477 | ||
|
|
ff0f9eb6eb | ||
|
|
d8dfaf0868 | ||
|
|
3267c8074b | ||
|
|
7b14d680b2 | ||
|
|
0980808bb7 | ||
|
|
0519f15bbf | ||
|
|
f6742eb4c6 | ||
|
|
f8d11013fc | ||
|
|
7875777ed1 | ||
|
|
0ca81a8f30 | ||
|
|
09accbd922 | ||
|
|
945204c45b | ||
|
|
2d62dc0657 | ||
|
|
fa61688c28 | ||
|
|
801edc7cc9 | ||
|
|
d0617a0ea4 | ||
|
|
1191ed1793 | ||
|
|
4036420d0e | ||
|
|
35a1655905 | ||
|
|
d4dc31aefa | ||
|
|
04ec34364f | ||
|
|
01b8c1f7a8 | ||
|
|
77598a0f23 | ||
|
|
eafb074fda | ||
|
|
d4d903f5c6 | ||
|
|
c9c55b5fcb | ||
|
|
25987545db | ||
|
|
f7313bfcc1 | ||
|
|
d2f7376f78 | ||
|
|
6db56eb2aa | ||
|
|
442290703a | ||
|
|
e491a724ed | ||
|
|
230924baac | ||
|
|
0ae2f04f28 | ||
|
|
68a490d305 | ||
|
|
6abfeee683 | ||
|
|
1a03f7b265 | ||
|
|
2dae2b3789 | ||
|
|
bdbbebab76 | ||
|
|
33170887f4 | ||
|
|
88302c8846 | ||
|
|
4068b5c76a | ||
|
|
9966ad4c71 | ||
|
|
9cfe974c52 | ||
|
|
d9a9f890f5 | ||
|
|
e2904ab042 | ||
|
|
f92c557235 | ||
|
|
cfadbc164c | ||
|
|
374a102bc4 | ||
|
|
84e1411c22 | ||
|
|
e28bf170d1 | ||
|
|
7c9e3a1362 | ||
|
|
fba80342a5 | ||
|
|
5eeff0aabf | ||
|
|
5b4de02fff | ||
|
|
b6a5854fa2 | ||
|
|
9771d3c817 | ||
|
|
b33a0cf0b1 | ||
|
|
f9fa6ad9c1 | ||
|
|
4b2db2b6a1 | ||
|
|
822b353a40 | ||
|
|
2908d4ee5f | ||
|
|
482c4ced0c | ||
|
|
b2a5e457a9 | ||
|
|
343c3607fa | ||
|
|
f03263eedf | ||
|
|
98d7ecbf3e | ||
|
|
477ccda8ca | ||
|
|
fcdc2b9510 | ||
|
|
1ee57cfda0 | ||
|
|
804bd289a4 | ||
|
|
86273865c8 | ||
|
|
5142f0340c | ||
|
|
7c80c52d02 | ||
|
|
eb30b61ca9 | ||
|
|
dd5a272cdf | ||
|
|
5b27acf4ef | ||
|
|
1a41a7450e | ||
|
|
e1b501c7d4 | ||
|
|
b660bfb7ff | ||
|
|
5724912480 | ||
|
|
11b3bafd5a | ||
|
|
9f90838df1 | ||
|
|
b01916001e | ||
|
|
c96ae1022b | ||
|
|
8f11167db0 | ||
|
|
a53397b76f | ||
|
|
8f13224454 | ||
|
|
8f4dd25e69 | ||
|
|
9c8762e3a0 | ||
|
|
a8cf788122 | ||
|
|
7355a4f152 | ||
|
|
2cf80e6615 | ||
|
|
9a18ed631c | ||
|
|
1e16f1cb9f | ||
|
|
35b8b080ab | ||
|
|
4219d54db3 | ||
|
|
c3620254b3 | ||
|
|
d30de0b6a0 | ||
|
|
af91b6faeb | ||
|
|
49b84b019d | ||
|
|
a0ee520572 | ||
|
|
972afe0bfe | ||
|
|
e47e9b0a11 | ||
|
|
87e54d8823 | ||
|
|
a73c8d8285 | ||
|
|
b0dd8d044d | ||
|
|
7c55c42582 | ||
|
|
cc1fcd2b98 | ||
|
|
8434d8d5ba | ||
|
|
044fd238b8 | ||
|
|
be096a1319 | ||
|
|
6fa14833b3 | ||
|
|
1f32ab274c | ||
|
|
6720ecc6e0 | ||
|
|
b0f86e43a6 | ||
|
|
9b0c81333f | ||
|
|
05fc966444 | ||
|
|
b87650038f | ||
|
|
d4f69a7ff8 | ||
|
|
0e1e26c29c | ||
|
|
1b8cdbc4dd | ||
|
|
2a781c228f | ||
|
|
35d6b0f16a | ||
|
|
ca8987fef6 | ||
|
|
b385133071 | ||
|
|
aa78a03efa | ||
|
|
31f8a19392 | ||
|
|
7a528b499a | ||
|
|
1c6ce422cf | ||
|
|
f9cf2ea2e5 | ||
|
|
575b3a617f | ||
|
|
b7362d3f51 | ||
|
|
6ee3860124 | ||
|
|
7e111da529 | ||
|
|
578458f734 | ||
|
|
bd56697d6d | ||
|
|
aad824d127 | ||
|
|
63f828da0b | ||
|
|
7c211b3fb6 | ||
|
|
3881edd2ba | ||
|
|
b882b12d04 | ||
|
|
addd2e7d1c | ||
|
|
ad6d2e1cd7 | ||
|
|
5f07271afa | ||
|
|
efdcd4c708 | ||
|
|
b62763bca3 | ||
|
|
e95da730f2 | ||
|
|
43fa3f420a | ||
|
|
0311446384 | ||
|
|
f7030e4fee | ||
|
|
fce8cc375f | ||
|
|
920199c6df | ||
|
|
d09eb3c4fa | ||
|
|
6e8affcdd6 | ||
|
|
0b3a7bb020 | ||
|
|
647736f4e3 | ||
|
|
cbc09d84df | ||
|
|
4c957dd03b | ||
|
|
d34b65890f | ||
|
|
b53968ac00 | ||
|
|
f2ccb15101 | ||
|
|
db5bf046fc | ||
|
|
59c87483e6 | ||
|
|
26420b78f8 | ||
|
|
e47bdc093e | ||
|
|
3dde80a60a | ||
|
|
e373a79d63 | ||
|
|
744a5cd0e3 | ||
|
|
37ca4a46ee | ||
|
|
0dc9214f98 | ||
|
|
513508654b | ||
|
|
ef2b12fa0f | ||
|
|
4e719ecacd | ||
|
|
755a124b50 | ||
|
|
d6888776e7 | ||
|
|
29e233e715 | ||
|
|
99c3696d96 | ||
|
|
ed6de83e8c | ||
|
|
134f1a440c | ||
|
|
7da82242fe | ||
|
|
2fd50d2425 | ||
|
|
41a3e89248 | ||
|
|
b125297c37 | ||
|
|
24255b69ee | ||
|
|
3bb51b39c4 | ||
|
|
b54da7d3b3 | ||
|
|
534af0abf0 | ||
|
|
8b0073333b | ||
|
|
d8af2274f4 | ||
|
|
3dd828d703 | ||
|
|
fa6b4a5b63 | ||
|
|
8bd86c77f9 | ||
|
|
3828e89cf8 | ||
|
|
e531b040ef | ||
|
|
3eee84a34e | ||
|
|
ab29df5991 | ||
|
|
b042f00688 | ||
|
|
5beebaf51c | ||
|
|
50f075cc7e | ||
|
|
e997236159 | ||
|
|
c8b1d892e3 | ||
|
|
9cb9e7328b | ||
|
|
85129da942 | ||
|
|
1cb00b1db4 | ||
|
|
c3798bfa95 | ||
|
|
1d280599ae | ||
|
|
ee8d7cdcac | ||
|
|
1b4114fd5f | ||
|
|
3c6c476f2e | ||
|
|
f19e3fedbd | ||
|
|
542e64278f | ||
|
|
cd76294e81 | ||
|
|
4f9158b2ad | ||
|
|
e319f20296 | ||
|
|
b00f3a851c | ||
|
|
ab529fd22c | ||
|
|
c2784c44ad | ||
|
|
512e727ac6 | ||
|
|
2dd0154967 | ||
|
|
f55869a449 | ||
|
|
b6f3c23787 | ||
|
|
6982ab1efc | ||
|
|
db4d841bb0 | ||
|
|
ef91ebb468 | ||
|
|
6264319c51 | ||
|
|
1417abecfb | ||
|
|
bd548b3fe2 | ||
|
|
94cef9ea6e | ||
|
|
a338613b5a | ||
|
|
0d833a966c | ||
|
|
76b6489636 | ||
|
|
763fe778d5 | ||
|
|
cf1dc79c68 | ||
|
|
7973239424 | ||
|
|
1baacd0b2c | ||
|
|
054d385ffc | ||
|
|
50d3a4906a | ||
|
|
c8b7008d42 | ||
|
|
e94520a3fd | ||
|
|
55e8e34226 | ||
|
|
8755ece633 | ||
|
|
c545e2a3aa | ||
|
|
1068662ab1 | ||
|
|
75141741a1 | ||
|
|
9da507bb62 | ||
|
|
160293365a | ||
|
|
7a19007aba | ||
|
|
f866b93f96 | ||
|
|
b9e64747ac | ||
|
|
25a473dc99 | ||
|
|
e3bf015aa9 | ||
|
|
6d3d4a08af | ||
|
|
9554de4ea6 | ||
|
|
6157ff7b7d | ||
|
|
774fd176fd | ||
|
|
b489db8054 | ||
|
|
6b9fa6e01f | ||
|
|
9b59954393 | ||
|
|
ecaf19563f | ||
|
|
c431e96eaf | ||
|
|
d86f241450 | ||
|
|
3252db31fe | ||
|
|
dac118dd26 | ||
|
|
181eb621c0 | ||
|
|
828582333d | ||
|
|
657f7f822b | ||
|
|
93627e4f9d | ||
|
|
2adb2519fa | ||
|
|
56373e362b | ||
|
|
32ec48ac14 | ||
|
|
b3a0d81740 | ||
|
|
2b160fbbc2 | ||
|
|
60fcf5fcd3 | ||
|
|
7c2e50435d | ||
|
|
57f91d0973 | ||
|
|
49c033e003 | ||
|
|
6476a8fee8 | ||
|
|
c10db2ab0f | ||
|
|
647beffc01 | ||
|
|
ac0c6ef3d5 | ||
|
|
e13741827d | ||
|
|
29caf0154e | ||
|
|
fbdcc437e6 | ||
|
|
b38e5df1aa | ||
|
|
0a39ba0a75 | ||
|
|
c56e1bdbbe | ||
|
|
6b00ba271f | ||
|
|
bddb1de2f8 | ||
|
|
32ae77c42d | ||
|
|
3b1701b1aa | ||
|
|
3b9bcc719e | ||
|
|
8e6aa4524d | ||
|
|
cea63e6083 | ||
|
|
5d2d8ca487 | ||
|
|
81146f44f7 | ||
|
|
9adaa27f6c | ||
|
|
01c565f93f | ||
|
|
cb97afffab | ||
|
|
1b55bf1670 | ||
|
|
b1c68165bb | ||
|
|
5d3e633e83 | ||
|
|
c863bf63b1 | ||
|
|
c71a6ae4ba | ||
|
|
38e3d9de8b | ||
|
|
0c73acd4b9 | ||
|
|
581a5c73a6 | ||
|
|
e1ed1d7c4c | ||
|
|
805e7d1d5f | ||
|
|
1957c2983b | ||
|
|
6b1ceae6c5 | ||
|
|
2a5c41dfaf | ||
|
|
7a38c9136e | ||
|
|
9a3fdf76fc | ||
|
|
136db61011 | ||
|
|
0d338f80c5 | ||
|
|
bd3909ad27 | ||
|
|
96399f8315 | ||
|
|
4e90d17484 | ||
|
|
13de75c41f | ||
|
|
a77ebc5fee | ||
|
|
99ce82a6a0 | ||
|
|
ec95d25704 | ||
|
|
7c6e83d124 | ||
|
|
ad5e88f1e3 | ||
|
|
b1e958d806 | ||
|
|
8506ae9edd | ||
|
|
ceb2a9bb17 | ||
|
|
8d83c953d3 | ||
|
|
9825f9fbd2 | ||
|
|
41b2ce06a8 | ||
|
|
920cfdac5c | ||
|
|
8abf7876cc | ||
|
|
2e625f2c33 | ||
|
|
88037b2038 | ||
|
|
457021040a | ||
|
|
4887b21d35 | ||
|
|
03a66fd563 | ||
|
|
ef656a8dfd | ||
|
|
5e45129e32 | ||
|
|
ea64b01da6 | ||
|
|
c3b863c2be | ||
|
|
6a7896b712 | ||
|
|
83c1f8e4d3 | ||
|
|
9d3fdd37a3 | ||
|
|
419195895e | ||
|
|
c92188887d | ||
|
|
dcfc4e6e7b | ||
|
|
836adab5d0 | ||
|
|
e93227a53c | ||
|
|
d6f6bb9c1b | ||
|
|
85825165fc | ||
|
|
66047c7926 | ||
|
|
456bcd2d3f | ||
|
|
259f68a806 | ||
|
|
4e6231ab19 | ||
|
|
d7bbfdcce6 | ||
|
|
a0cc9e5db5 | ||
|
|
ea6cd853de | ||
|
|
53a388a7e0 | ||
|
|
13b1938efb | ||
|
|
6677985e4a | ||
|
|
cfa1034161 | ||
|
|
815973fb63 | ||
|
|
92d369aaca | ||
|
|
281a2d9679 | ||
|
|
e9f4615caa | ||
|
|
c0d2efa72a | ||
|
|
247f4d5c19 | ||
|
|
29c29b17d4 | ||
|
|
5608f7d20d | ||
|
|
aa8ae36255 | ||
|
|
2292e6f2eb | ||
|
|
bf82a1c721 | ||
|
|
8ef84bbc03 | ||
|
|
e36d51cc0b | ||
|
|
5c1d0238e1 | ||
|
|
c6befe4c4b | ||
|
|
5a57c296a1 | ||
|
|
34ddfd24be | ||
|
|
39051ef0fd | ||
|
|
ddd813241c | ||
|
|
60f7cbef9a | ||
|
|
4adc981a21 | ||
|
|
c42913c15e | ||
|
|
bb6d60b46d | ||
|
|
afe7f03c16 | ||
|
|
ba8d3be9a6 | ||
|
|
d14d8869ac | ||
|
|
2f7391efc3 | ||
|
|
75fa96b29c | ||
|
|
c56ab9bc1e | ||
|
|
443e492fd4 | ||
|
|
b8c223d525 | ||
|
|
a509afe24b | ||
|
|
9654add528 | ||
|
|
d0a9409078 | ||
|
|
5836583490 | ||
|
|
57d689bee6 | ||
|
|
8a3fb6bd4d | ||
|
|
78bd3f581a | ||
|
|
d07c476507 | ||
|
|
50d196eda4 | ||
|
|
823d9af91d | ||
|
|
3731123369 | ||
|
|
1a68c4b44a | ||
|
|
0f79006b59 | ||
|
|
c95ad5a31c | ||
|
|
e25a96d359 | ||
|
|
04284adc87 | ||
|
|
02fc045370 | ||
|
|
7ee7d50f22 | ||
|
|
3d015398c3 | ||
|
|
da8b328f80 | ||
|
|
82a6702c90 | ||
|
|
ad267bcd35 | ||
|
|
15dc922bca | ||
|
|
22405d46d6 | ||
|
|
35b0741068 | ||
|
|
d7b8174fd0 | ||
|
|
43cfb11bca | ||
|
|
f955cebaa0 | ||
|
|
5d7ec054e6 | ||
|
|
6088a38eed | ||
|
|
e1a84e76bb | ||
|
|
19f9179e7f | ||
|
|
aa4a8d5b42 | ||
|
|
10ba31086c | ||
|
|
fa8312bc65 | ||
|
|
512e727dd4 | ||
|
|
a529609275 | ||
|
|
a8973330fe | ||
|
|
d42acc3848 | ||
|
|
912cefbc85 | ||
|
|
2bb475d0ce | ||
|
|
22788ff2da | ||
|
|
5594b25ae0 | ||
|
|
4733d89807 | ||
|
|
c718fe1a9d | ||
|
|
237b4a82c9 | ||
|
|
76e0cbb8ac | ||
|
|
b3a670d380 | ||
|
|
db243d050e | ||
|
|
cd2648291e | ||
|
|
4a49bde1f0 | ||
|
|
d9754496d0 | ||
|
|
6753b5fd19 | ||
|
|
aeb320ba30 | ||
|
|
e712e8ccfc | ||
|
|
1d6f827296 | ||
|
|
772c9b385c | ||
|
|
f5053728e7 | ||
|
|
f67fd29499 | ||
|
|
138ea35620 | ||
|
|
bf56549f01 | ||
|
|
908181af64 | ||
|
|
7b4d3c44f8 | ||
|
|
b7a6454d65 | ||
|
|
6d81fa7fdf | ||
|
|
0e8833cce3 | ||
|
|
24d9e65532 | ||
|
|
bca9bdf619 | ||
|
|
cd39e20808 | ||
|
|
9c8680d3f4 | ||
|
|
dd84ca8f85 | ||
|
|
96c1f689c0 | ||
|
|
84855bfd7e | ||
|
|
40c5a218a9 | ||
|
|
8e87972a76 | ||
|
|
3faee9b80c | ||
|
|
5a1389a187 | ||
|
|
565c2f493c | ||
|
|
8d48593fc4 | ||
|
|
b50c96fcd6 | ||
|
|
85700a2a26 | ||
|
|
66615b7dd3 | ||
|
|
2c1a1fa31e | ||
|
|
bbc442b56e | ||
|
|
1ca579f4f0 | ||
|
|
9e3b23179c | ||
|
|
9fd861d047 | ||
|
|
4abfcb27d1 | ||
|
|
3463761693 | ||
|
|
c311adc1da | ||
|
|
ee258707c8 | ||
|
|
17d96669fe | ||
|
|
3fade107d5 | ||
|
|
f91ec6fa6a | ||
|
|
dfff41e9d6 | ||
|
|
478e81b8fa | ||
|
|
9b14f2aa1f | ||
|
|
18e648af6e | ||
|
|
45bd69585a | ||
|
|
42a0cde450 | ||
|
|
a9ef21ea3f | ||
|
|
13d24a12db | ||
|
|
2bd09f246d | ||
|
|
23c81cf5eb | ||
|
|
e95284335e | ||
|
|
1c7f82e65a | ||
|
|
dfde50c768 | ||
|
|
8bfbebf29e | ||
|
|
8157f9891f | ||
|
|
ad95adc833 | ||
|
|
f7e55c9b89 | ||
|
|
11b125655d | ||
|
|
c6628a1959 | ||
|
|
165d030c8e | ||
|
|
9be77cf58f | ||
|
|
887724bad4 | ||
|
|
ae7dbbedcc | ||
|
|
407a77f61b | ||
|
|
e06f9a03d6 | ||
|
|
07edbea54e | ||
|
|
856e501a15 | ||
|
|
8cf900f9de | ||
|
|
a54605ac79 | ||
|
|
e4ac73896f | ||
|
|
b283d88781 | ||
|
|
92790d711e | ||
|
|
2977323800 | ||
|
|
5b548d8d57 | ||
|
|
4a520e9e10 | ||
|
|
afdf777386 | ||
|
|
cd2af0dcf7 | ||
|
|
44f29e166c | ||
|
|
f42113afb9 | ||
|
|
523468f7af | ||
|
|
9385d04812 | ||
|
|
ff126f3459 | ||
|
|
2ee435a8ec | ||
|
|
f3a827b76b | ||
|
|
50ceca9f06 | ||
|
|
8a5e86dfa7 | ||
|
|
6ffae48ab2 | ||
|
|
66cd6e95a8 | ||
|
|
b28aec527f | ||
|
|
9ff78c8569 | ||
|
|
d6718d7b78 | ||
|
|
32966b260a | ||
|
|
6c59888d77 | ||
|
|
1c1d839b82 | ||
|
|
7d295cc675 | ||
|
|
75496cbe91 | ||
|
|
496903dfb2 | ||
|
|
11f6a029de | ||
|
|
e40c66c7ed | ||
|
|
2a33337963 | ||
|
|
bd1a768743 | ||
|
|
0a0312695b | ||
|
|
0c0ec098ae | ||
|
|
37ad7b32e4 | ||
|
|
2640963938 | ||
|
|
6bc9181c25 | ||
|
|
d8379195e6 | ||
|
|
9195c658a0 | ||
|
|
3fb261b5c8 | ||
|
|
aa16c3d3a1 | ||
|
|
7be6cf2b73 | ||
|
|
3608b025e5 | ||
|
|
60738da053 | ||
|
|
507ad10389 | ||
|
|
68244b2b37 | ||
|
|
948e9ecb4b | ||
|
|
67bc16238c | ||
|
|
7ad4d9116a | ||
|
|
db88f6c9b4 | ||
|
|
8b7f60d43e | ||
|
|
9439035b86 | ||
|
|
2b220d3753 | ||
|
|
cd1f6a9137 | ||
|
|
7973d066a3 | ||
|
|
440a7ae9cc | ||
|
|
ad65097a8f | ||
|
|
1b05f56598 | ||
|
|
3468f8cd40 | ||
|
|
5c81e974cd | ||
|
|
b638cf7417 | ||
|
|
1db1961cc0 | ||
|
|
811afdcf1a | ||
|
|
1f87ce2a47 | ||
|
|
8213e38e6a | ||
|
|
263bcbb566 | ||
|
|
050ddc88f2 | ||
|
|
40a4efc992 | ||
|
|
38e8791d9f | ||
|
|
15d4fafbdb | ||
|
|
deb8474c1b | ||
|
|
12740ead08 | ||
|
|
6322559bd7 | ||
|
|
6c5eb00fb6 | ||
|
|
dad2f8eb65 | ||
|
|
c8679f48f5 | ||
|
|
510dc1eaf2 | ||
|
|
a313753757 | ||
|
|
48b037ac26 | ||
|
|
53f106b30d | ||
|
|
dfd133cf5a | ||
|
|
0d27bfcfa9 | ||
|
|
cdfb11549e | ||
|
|
ba6660216c | ||
|
|
3536af2051 | ||
|
|
21bb0a8162 | ||
|
|
d718398791 | ||
|
|
0d825927e1 | ||
|
|
0b65e3ffda | ||
|
|
91a1da57e9 | ||
|
|
4e8d7df005 | ||
|
|
5d1829b998 | ||
|
|
75df845024 | ||
|
|
c103253867 | ||
|
|
f95cbd6977 | ||
|
|
f16ec02c40 | ||
|
|
0ea2339ad5 | ||
|
|
81da9e018a | ||
|
|
8ebdd59e00 | ||
|
|
c4e30737a4 | ||
|
|
f127aca5f8 | ||
|
|
7333c8e094 | ||
|
|
a1e9382275 | ||
|
|
097a6c5c5f | ||
|
|
4e023057cc | ||
|
|
7f90fccc4f | ||
|
|
4034e2152c | ||
|
|
e8d6c6b711 | ||
|
|
43215d27c5 | ||
|
|
e20db96331 | ||
|
|
564ad40b99 | ||
|
|
32ef4c79da | ||
|
|
af4f6ebb26 | ||
|
|
33b688b021 | ||
|
|
b179770dbf | ||
|
|
e7f92ec0d7 | ||
|
|
79449a8a02 | ||
|
|
4ebcba81e0 | ||
|
|
5616d31888 | ||
|
|
f259509ef8 | ||
|
|
82977f9023 | ||
|
|
4a5205c5ac | ||
|
|
714b4ef7f4 | ||
|
|
df091f0ee1 | ||
|
|
7037cf56ec | ||
|
|
f683d195e4 | ||
|
|
5ab55b823c | ||
|
|
0f2c769e8d | ||
|
|
1d53f292ae | ||
|
|
606d2c8933 | ||
|
|
a15335cac9 | ||
|
|
f33cf07859 | ||
|
|
bce55421ce | ||
|
|
c3449cd6bc | ||
|
|
4e903ce19b | ||
|
|
90826b358c | ||
|
|
7d46aa9892 | ||
|
|
49d2bd93b7 | ||
|
|
9f103a88d6 | ||
|
|
ce33bdc370 | ||
|
|
cdf1f81c8a | ||
|
|
79edff5fca | ||
|
|
1518f792d6 | ||
|
|
a534c496d0 | ||
|
|
a11097fb5a | ||
|
|
d4c1f93ef6 | ||
|
|
9168e92669 | ||
|
|
a7316bc7c1 | ||
|
|
bfd030d70f | ||
|
|
da0c017c4f | ||
|
|
5ffc0a9665 | ||
|
|
10e9026ec7 | ||
|
|
7c4c0b5924 | ||
|
|
42c3008ec9 | ||
|
|
2f6d743cf0 | ||
|
|
e8faaeb8fb | ||
|
|
09f802b00d | ||
|
|
a644b84bb1 | ||
|
|
b6f48111e3 | ||
|
|
0ea675f8d6 | ||
|
|
3a6e4e7fb6 | ||
|
|
e42a98ff95 | ||
|
|
8fe511cec6 | ||
|
|
ffb3cd13cb | ||
|
|
77caa5536f | ||
|
|
b1abf8a339 | ||
|
|
89d20c8a4d | ||
|
|
d66f923c0c | ||
|
|
d3c14428a1 | ||
|
|
c104f85b18 | ||
|
|
755d8124ac | ||
|
|
a029cc8ed5 | ||
|
|
111dfa8c29 | ||
|
|
52c905832b | ||
|
|
5f892c3afe | ||
|
|
313202fe41 | ||
|
|
af1adc3baa | ||
|
|
be214c84d1 | ||
|
|
082614e7b0 | ||
|
|
94ee3169dc | ||
|
|
83835747c5 | ||
|
|
2a7b48c83d | ||
|
|
a9068496d9 | ||
|
|
8bad88e798 | ||
|
|
92b6286feb | ||
|
|
bce776bb63 | ||
|
|
9f45eeeb1f | ||
|
|
dc39cbf037 | ||
|
|
60110982f1 | ||
|
|
259204bfe2 | ||
|
|
c55e9679db | ||
|
|
c05a3c315a | ||
|
|
dbdf586f5b | ||
|
|
b1bd4db3e9 | ||
|
|
7806a13db5 | ||
|
|
928f564109 | ||
|
|
328f718fe8 | ||
|
|
cb4402c610 | ||
|
|
fbc4cb9046 | ||
|
|
94567b86f0 | ||
|
|
38175d6b57 | ||
|
|
8aa707427f | ||
|
|
7408ed0f03 | ||
|
|
5135186961 | ||
|
|
5be399616b | ||
|
|
7d64b8419f | ||
|
|
fad9249810 | ||
|
|
46a23afbec | ||
|
|
8c4add241d | ||
|
|
feee92daee | ||
|
|
42054c7989 | ||
|
|
bb4fbc3a1c | ||
|
|
9b20b67039 | ||
|
|
2acc84dc69 | ||
|
|
d7916a62f0 | ||
|
|
3383d0f314 | ||
|
|
c9858b5a84 | ||
|
|
25e21b185f | ||
|
|
da27e1b93c | ||
|
|
720231f692 | ||
|
|
95f29a584e | ||
|
|
50cbb75b96 | ||
|
|
67277dd622 | ||
|
|
82e7f020ea | ||
|
|
d418647774 | ||
|
|
6b5d4a4810 | ||
|
|
2cc67634a4 | ||
|
|
99b24cad00 | ||
|
|
52922088a9 | ||
|
|
ef7329a721 | ||
|
|
ad0bc82539 | ||
|
|
1ecf8534f6 | ||
|
|
94286caec4 | ||
|
|
d4c8425218 | ||
|
|
59f9a4f369 | ||
|
|
64125051df | ||
|
|
660572a0ea | ||
|
|
c0273dc698 | ||
|
|
2782d4b5f1 | ||
|
|
9dbdd6ac60 | ||
|
|
f20b9e01ab | ||
|
|
d4f9e30306 | ||
|
|
8cf8a3701b | ||
|
|
1b221d1cb6 | ||
|
|
fbf42ebbf9 | ||
|
|
a0c4eae04c | ||
|
|
d1c293940a | ||
|
|
6f2d04a029 | ||
|
|
29dbc2e4d4 | ||
|
|
e8d717d174 | ||
|
|
138a3a2f46 | ||
|
|
cade2cfa13 | ||
|
|
ac988a76b4 | ||
|
|
5a9815481a | ||
|
|
f8953441e3 | ||
|
|
5b41eddacc | ||
|
|
bfbddfdead | ||
|
|
3cf526fdf3 | ||
|
|
f6a4ee54d0 | ||
|
|
5755d281d7 | ||
|
|
1569524583 | ||
|
|
7ba876eb0a | ||
|
|
a31ea77b3c | ||
|
|
44445a9482 | ||
|
|
b8449a6efa | ||
|
|
ccf6b00084 | ||
|
|
4423f842e0 | ||
|
|
a432af1a6d | ||
|
|
7660e3228e | ||
|
|
482f5613e4 | ||
|
|
3cfb46f798 | ||
|
|
f987515b89 | ||
|
|
f0d1279a42 | ||
|
|
140118c9c6 | ||
|
|
637b9b1b15 | ||
|
|
969069dde0 | ||
|
|
84a71c8b3a | ||
|
|
f3bd727c32 | ||
|
|
2ac87e4ad6 | ||
|
|
3740a4ad6f | ||
|
|
3bc8db7c3d | ||
|
|
f3d19ad9f4 | ||
|
|
d2396afdd5 | ||
|
|
43f9c07838 | ||
|
|
6052306c04 | ||
|
|
6a12bc39e9 | ||
|
|
3f67b40975 | ||
|
|
0adc854721 | ||
|
|
ab76745a9f | ||
|
|
574639d5e1 | ||
|
|
fa5d9d3df4 | ||
|
|
0c31925131 | ||
|
|
94b5d8b9e9 | ||
|
|
bffc9f4b1d | ||
|
|
6b5d18222e | ||
|
|
2b05fd5276 | ||
|
|
3e46d72ba3 | ||
|
|
6502adb772 | ||
|
|
a8112c86e3 | ||
|
|
8911c9c649 | ||
|
|
3b70b4cf9e | ||
|
|
1e0ea3905e | ||
|
|
2afabd65f9 | ||
|
|
85cb80cbfe | ||
|
|
79f8480ae4 | ||
|
|
dec502e025 | ||
|
|
c7b5cc7d89 | ||
|
|
bc76ce50e1 | ||
|
|
be90bf6b28 | ||
|
|
dfa68d1ca8 | ||
|
|
0237edf6c1 | ||
|
|
6a87221c2a | ||
|
|
f0e87ef3f8 | ||
|
|
cd19a276c9 | ||
|
|
5ea4bba676 | ||
|
|
8500f186f6 | ||
|
|
8c93d419fe | ||
|
|
2530827d07 | ||
|
|
8e54c446bc | ||
|
|
3456e9ac5b | ||
|
|
689f858f97 | ||
|
|
93eebd7876 | ||
|
|
82cc21ef59 | ||
|
|
e61f9efbf2 | ||
|
|
97f60a61e0 | ||
|
|
45bac09dc7 | ||
|
|
989a970a7c | ||
|
|
0296df0480 | ||
|
|
9776d35140 | ||
|
|
0aeea414f5 | ||
|
|
9817154234 | ||
|
|
39ae14877b | ||
|
|
9c238a9147 | ||
|
|
42d7e983e4 | ||
|
|
611d0b71e8 | ||
|
|
d78d55091c | ||
|
|
3b8aab8c25 | ||
|
|
2f16bdc4be | ||
|
|
22d70eb416 | ||
|
|
afa1ba4f6b | ||
|
|
39d3e5477c | ||
|
|
08ac8b0857 | ||
|
|
255817f5c6 | ||
|
|
19b196eb1f | ||
|
|
40db6485dd | ||
|
|
b23e99885e | ||
|
|
22fbbb92da | ||
|
|
99c94166bb | ||
|
|
169254a1c7 | ||
|
|
bda6037b2a | ||
|
|
1cf0b15528 | ||
|
|
ff3865d1a7 | ||
|
|
10435788bc | ||
|
|
02750e56d9 | ||
|
|
a1d53cba44 | ||
|
|
d499b94e04 | ||
|
|
b921ca8c9d | ||
|
|
7a6468530f | ||
|
|
29b38632e2 | ||
|
|
56193f833f | ||
|
|
02893c2a2b | ||
|
|
4470b68de9 | ||
|
|
d3d89b0853 | ||
|
|
681cecc52b | ||
|
|
2c8b977001 | ||
|
|
4827fcf243 | ||
|
|
3336a4526b | ||
|
|
bca0863952 | ||
|
|
9140ed6969 | ||
|
|
24e7597c67 | ||
|
|
833dd654b2 | ||
|
|
ae74154071 | ||
|
|
fb1631c1c7 | ||
|
|
1c6832b9b2 | ||
|
|
77d06037bb | ||
|
|
136e62b97d | ||
|
|
24c36087dd | ||
|
|
73f9d721fe | ||
|
|
792f8b2d1f | ||
|
|
6871d194a8 | ||
|
|
12c26e4551 | ||
|
|
bf1a29fac2 | ||
|
|
47ceaf967c | ||
|
|
3426f650fa | ||
|
|
00c5b3c0a2 | ||
|
|
f224dc241e | ||
|
|
f6effb3c40 | ||
|
|
6bbdcc060d | ||
|
|
14411d8c86 | ||
|
|
cca2bfee4e | ||
|
|
c6cc68601b | ||
|
|
06f33e4bdc | ||
|
|
616b38158a | ||
|
|
c22f88ae42 | ||
|
|
3aeadc2f03 | ||
|
|
f0cbd77310 | ||
|
|
3bf401f029 | ||
|
|
0b8b74b7a4 | ||
|
|
e1bd0ee3d7 | ||
|
|
f11852c60d | ||
|
|
4b0d95ed0c | ||
|
|
8b870678df | ||
|
|
470a088a9f | ||
|
|
fedb146025 | ||
|
|
695a5eb470 | ||
|
|
ccd4f3ada4 | ||
|
|
ae7a562b85 | ||
|
|
be6d8566da | ||
|
|
f6e4d909ff | ||
|
|
6c0299b05a | ||
|
|
f264bf03ff | ||
|
|
02c2ee8c54 | ||
|
|
d71374ca8a | ||
|
|
0589f7fe33 | ||
|
|
a5e8792092 | ||
|
|
15acfe84b0 | ||
|
|
08b483140c | ||
|
|
cf1e048328 | ||
|
|
fb02095568 | ||
|
|
d5675ce498 | ||
|
|
ccbb860de1 | ||
|
|
5e104a3dd2 | ||
|
|
51890c94cc | ||
|
|
06259a2d63 | ||
|
|
a6228f145d | ||
|
|
b6ab3df038 | ||
|
|
e9f591b33b | ||
|
|
90d4914280 | ||
|
|
80a506e99f | ||
|
|
d8a891a7d7 | ||
|
|
d71c41e384 | ||
|
|
bb27ff7f8a | ||
|
|
0671e56d65 | ||
|
|
d04ac09e82 | ||
|
|
73a4ce0943 | ||
|
|
902fac61e9 | ||
|
|
dcd7f9f7e6 | ||
|
|
80035e7cb6 | ||
|
|
e2d14f5e4b | ||
|
|
cae9f03892 | ||
|
|
a27cc22596 | ||
|
|
72362274ce | ||
|
|
cfb1d306a3 | ||
|
|
bffcd6107c | ||
|
|
e5cb99d682 | ||
|
|
056e0c816b | ||
|
|
cbd812ab5f | ||
|
|
ea67312877 | ||
|
|
d0117b5a91 | ||
|
|
afe3777895 | ||
|
|
e45676edc4 | ||
|
|
60e4b19d07 | ||
|
|
86d76c53d6 | ||
|
|
b50f1a662d | ||
|
|
b3e4c10bc2 | ||
|
|
327cdc8604 | ||
|
|
ba11e646d6 | ||
|
|
6de524c797 | ||
|
|
2e067a7950 | ||
|
|
a3658136e2 | ||
|
|
4108415894 | ||
|
|
ae2fdff9a7 | ||
|
|
6f37cc4d01 | ||
|
|
003dd49ed6 | ||
|
|
46d57f02e7 | ||
|
|
30915a93e5 | ||
|
|
b9422c096e | ||
|
|
b3e73605b0 | ||
|
|
c64480dc33 | ||
|
|
4a9b1aff96 | ||
|
|
542e94ec9c | ||
|
|
9341558f61 | ||
|
|
6c89349194 | ||
|
|
670eac49b6 | ||
|
|
a7a099f290 | ||
|
|
5157514c62 | ||
|
|
533d2ab98a | ||
|
|
6ea13b2c0d | ||
|
|
40730b741d | ||
|
|
e57512f4fe | ||
|
|
348f67f4a4 | ||
|
|
83bdf07600 | ||
|
|
dfe4eddbbc | ||
|
|
1caed59f76 | ||
|
|
6db4e88a2c | ||
|
|
786cb23f98 | ||
|
|
133daeb664 | ||
|
|
518ae3fa09 | ||
|
|
18707d365b | ||
|
|
a4a8d1ecf0 | ||
|
|
7ba24293d1 | ||
|
|
f10114c9ed | ||
|
|
cf31cbfb07 | ||
|
|
f0ffa2408d | ||
|
|
b557e264bc | ||
|
|
88a08a74f7 | ||
|
|
457d2b2359 | ||
|
|
c9e12a3027 | ||
|
|
82aa4a65ab | ||
|
|
d46237f1bf | ||
|
|
1744f94910 | ||
|
|
8ebc99339b | ||
|
|
e71e335f5c | ||
|
|
e308812429 | ||
|
|
2328ef0b0c | ||
|
|
000c5770f2 | ||
|
|
9e1a3598ab | ||
|
|
7268f60343 | ||
|
|
c8b274031f | ||
|
|
7517e77af9 | ||
|
|
889cdca3b0 | ||
|
|
4cfd1bc047 | ||
|
|
fc0891ceee | ||
|
|
cea16fc41f | ||
|
|
4b7c0b8437 | ||
|
|
10394dbb1c | ||
|
|
859bb91fc7 | ||
|
|
09432b01a7 | ||
|
|
d7f8ba58ad | ||
|
|
0fd0d33704 | ||
|
|
ad0f489834 | ||
|
|
b1fa870de7 | ||
|
|
7c5e2ae8ea | ||
|
|
c0e4065a45 | ||
|
|
f660c38d80 | ||
|
|
35448eea9f | ||
|
|
edf0630cef | ||
|
|
430f45a3ec | ||
|
|
71b6fd5326 | ||
|
|
c4342567ba | ||
|
|
d4e53be7ce | ||
|
|
d4721e90d5 | ||
|
|
bb6c6c8f6a | ||
|
|
251db733b2 | ||
|
|
753ab77c46 | ||
|
|
ba127c506d | ||
|
|
d799725b8a | ||
|
|
c21ca70158 | ||
|
|
135fb7c6f9 | ||
|
|
f592f19b08 | ||
|
|
dce68cd011 | ||
|
|
d7b1903fb7 | ||
|
|
6e506e3146 | ||
|
|
58d30e7f85 | ||
|
|
9d80aed468 | ||
|
|
2062778ab8 | ||
|
|
eaca296bd0 | ||
|
|
1051c6af04 | ||
|
|
96f92f0908 | ||
|
|
314e4301f3 | ||
|
|
aa69353474 | ||
|
|
b284bb60f5 | ||
|
|
f99396ec50 | ||
|
|
d1f31f078b | ||
|
|
be80663436 | ||
|
|
1ae363d6bd | ||
|
|
31b0d345ad | ||
|
|
cabda0a32f | ||
|
|
886cf6ed1f | ||
|
|
f606dd8920 | ||
|
|
973df0360c | ||
|
|
f9f1d96674 | ||
|
|
0edad24d5d | ||
|
|
74dd6e97a2 | ||
|
|
8cb74976e1 | ||
|
|
46fde2f1aa | ||
|
|
279109c9a6 | ||
|
|
8c7ba4a497 | ||
|
|
9cc048267b | ||
|
|
78d0e3f485 | ||
|
|
8aefacd7ed | ||
|
|
ef8db68db1 | ||
|
|
00256f86df | ||
|
|
77569c554b | ||
|
|
7897462e32 | ||
|
|
aee11827c4 | ||
|
|
a6bf592046 | ||
|
|
1dea424104 | ||
|
|
1f5554d945 | ||
|
|
0303408be8 | ||
|
|
f5802ace02 | ||
|
|
8bde45d9dc | ||
|
|
e8bbc44647 | ||
|
|
34aa48d18c | ||
|
|
1f1c1a9157 | ||
|
|
7aa6613e69 | ||
|
|
503034299e | ||
|
|
6c9d271ae1 | ||
|
|
6ff852e225 | ||
|
|
0c74e92bfb | ||
|
|
3853d0bcc6 | ||
|
|
cd0348cca1 | ||
|
|
ce94348d45 | ||
|
|
f74f8b7d8c | ||
|
|
dc79346bdc | ||
|
|
37a0d831da | ||
|
|
e509568fe5 | ||
|
|
baa75dc735 | ||
|
|
8a9f0436b8 | ||
|
|
2c2c3eb21a | ||
|
|
18681d1f50 | ||
|
|
86ef984c02 | ||
|
|
e4d8ce097a | ||
|
|
ae68241812 | ||
|
|
e2a3c360ea | ||
|
|
9968617758 | ||
|
|
13d4177531 | ||
|
|
1cec27ed70 | ||
|
|
641e75a905 | ||
|
|
f0dfff0625 | ||
|
|
a2d6e41816 | ||
|
|
6cd3672604 | ||
|
|
3c3c1499b7 | ||
|
|
e29e51121d | ||
|
|
fabee37e9e | ||
|
|
2994ea6f68 | ||
|
|
fdaec3c959 | ||
|
|
fcb4c6a972 | ||
|
|
513974bbed | ||
|
|
644eada8a1 | ||
|
|
000a3038e1 | ||
|
|
9c8635b230 | ||
|
|
e428eb351b | ||
|
|
1275087f19 | ||
|
|
311c01242b | ||
|
|
bab5b67c52 | ||
|
|
00d6effd69 | ||
|
|
3eb0b768a6 | ||
|
|
c06c68d5da | ||
|
|
6dcc74a388 | ||
|
|
2b15fc5e8b | ||
|
|
df655f304a | ||
|
|
25223719cb | ||
|
|
814dbeb749 | ||
|
|
630bb56601 | ||
|
|
496b72aaee | ||
|
|
b57e943990 | ||
|
|
b4c1dd2944 | ||
|
|
9ede3670a7 | ||
|
|
2a29cd0e70 | ||
|
|
15ac81a422 | ||
|
|
eb5a53b91b | ||
|
|
4dd72b109f | ||
|
|
2fcbfe9f21 | ||
|
|
e80a0e41ba | ||
|
|
7cdba3ef38 | ||
|
|
2d6e815b3d | ||
|
|
38642024be | ||
|
|
257ee205ac | ||
|
|
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 |
@@ -8,3 +8,4 @@ celerybeat.pid
|
|||||||
.vagrant/
|
.vagrant/
|
||||||
apps/xpack/.git
|
apps/xpack/.git
|
||||||
.history/
|
.history/
|
||||||
|
.idea
|
||||||
12
.github/ISSUE_TEMPLATE/----.md
vendored
12
.github/ISSUE_TEMPLATE/----.md
vendored
@@ -1,12 +0,0 @@
|
|||||||
---
|
|
||||||
name: 需求建议
|
|
||||||
about: 提出针对本项目的想法和建议
|
|
||||||
title: "[Feature] "
|
|
||||||
labels: 类型:需求
|
|
||||||
assignees:
|
|
||||||
- ibuler
|
|
||||||
- baijiangjie
|
|
||||||
- wojiushixiaobai
|
|
||||||
---
|
|
||||||
|
|
||||||
**请描述您的需求或者改进建议.**
|
|
||||||
72
.github/ISSUE_TEMPLATE/1_bug_report.yml
vendored
Normal file
72
.github/ISSUE_TEMPLATE/1_bug_report.yml
vendored
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
name: '🐛 Bug Report'
|
||||||
|
description: 'Report an Bug'
|
||||||
|
title: '[Bug] '
|
||||||
|
labels: ['🐛 Bug']
|
||||||
|
assignees:
|
||||||
|
- baijiangjie
|
||||||
|
body:
|
||||||
|
- type: input
|
||||||
|
attributes:
|
||||||
|
label: 'Product Version'
|
||||||
|
description: The versions prior to v2.28 (inclusive) are no longer supported.
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: checkboxes
|
||||||
|
attributes:
|
||||||
|
label: 'Product Edition'
|
||||||
|
options:
|
||||||
|
- label: 'Community Edition'
|
||||||
|
- label: 'Enterprise Edition'
|
||||||
|
- label: 'Enterprise Trial Edition'
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: checkboxes
|
||||||
|
attributes:
|
||||||
|
label: 'Installation Method'
|
||||||
|
options:
|
||||||
|
- label: 'Online Installation (One-click command installation)'
|
||||||
|
- label: 'Offline Package Installation'
|
||||||
|
- label: 'All-in-One'
|
||||||
|
- label: '1Panel'
|
||||||
|
- label: 'Kubernetes'
|
||||||
|
- label: 'Source Code'
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
attributes:
|
||||||
|
label: 'Environment Information'
|
||||||
|
description: Please provide a clear and concise description outlining your environment information.
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
attributes:
|
||||||
|
label: '🐛 Bug Description'
|
||||||
|
description:
|
||||||
|
Please provide a clear and concise description of the defect. If the issue is complex, please provide detailed explanations. <br/>
|
||||||
|
Unclear descriptions will not be processed. Please ensure you provide enough detail and information to support replicating and fixing the defect.
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
attributes:
|
||||||
|
label: 'Recurrence Steps'
|
||||||
|
description: Please provide a clear and concise description outlining how to reproduce the issue.
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
attributes:
|
||||||
|
label: 'Expected Behavior'
|
||||||
|
description: Please provide a clear and concise description of what you expect to happen.
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
attributes:
|
||||||
|
label: 'Additional Information'
|
||||||
|
description: Please add any additional background information about the issue here.
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
attributes:
|
||||||
|
label: 'Attempted Solutions'
|
||||||
|
description: If you have already attempted to solve the issue, please list the solutions you have tried here.
|
||||||
60
.github/ISSUE_TEMPLATE/2_question.yml
vendored
Normal file
60
.github/ISSUE_TEMPLATE/2_question.yml
vendored
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
name: '🤔 Question'
|
||||||
|
description: 'Pose a question'
|
||||||
|
title: '[Question] '
|
||||||
|
labels: ['🤔 Question']
|
||||||
|
assignees:
|
||||||
|
- baijiangjie
|
||||||
|
body:
|
||||||
|
- type: input
|
||||||
|
attributes:
|
||||||
|
label: 'Product Version'
|
||||||
|
description: The versions prior to v2.28 (inclusive) are no longer supported.
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: checkboxes
|
||||||
|
attributes:
|
||||||
|
label: 'Product Edition'
|
||||||
|
options:
|
||||||
|
- label: 'Community Edition'
|
||||||
|
- label: 'Enterprise Edition'
|
||||||
|
- label: 'Enterprise Trial Edition'
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: checkboxes
|
||||||
|
attributes:
|
||||||
|
label: 'Installation Method'
|
||||||
|
options:
|
||||||
|
- label: 'Online Installation (One-click command installation)'
|
||||||
|
- label: 'Offline Package Installation'
|
||||||
|
- label: 'All-in-One'
|
||||||
|
- label: '1Panel'
|
||||||
|
- label: 'Kubernetes'
|
||||||
|
- label: 'Source Code'
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
attributes:
|
||||||
|
label: 'Environment Information'
|
||||||
|
description: Please provide a clear and concise description outlining your environment information.
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
attributes:
|
||||||
|
label: '🤔 Question Description'
|
||||||
|
description: |
|
||||||
|
Please provide a clear and concise description of the defect. If the issue is complex, please provide detailed explanations. <br/>
|
||||||
|
Unclear descriptions will not be processed.
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
attributes:
|
||||||
|
label: 'Expected Behavior'
|
||||||
|
description: Please provide a clear and concise description of what you expect to happen.
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
attributes:
|
||||||
|
label: 'Additional Information'
|
||||||
|
description: Please add any additional background information about the issue here.
|
||||||
56
.github/ISSUE_TEMPLATE/3_feature_request.yml
vendored
Normal file
56
.github/ISSUE_TEMPLATE/3_feature_request.yml
vendored
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
name: '⭐️ Feature Request'
|
||||||
|
description: 'Suggest an idea'
|
||||||
|
title: '[Feature] '
|
||||||
|
labels: ['⭐️ Feature Request']
|
||||||
|
assignees:
|
||||||
|
- baijiangjie
|
||||||
|
- ibuler
|
||||||
|
body:
|
||||||
|
- type: input
|
||||||
|
attributes:
|
||||||
|
label: 'Product Version'
|
||||||
|
description: The versions prior to v2.28 (inclusive) are no longer supported.
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: checkboxes
|
||||||
|
attributes:
|
||||||
|
label: 'Product Edition'
|
||||||
|
options:
|
||||||
|
- label: 'Community Edition'
|
||||||
|
- label: 'Enterprise Edition'
|
||||||
|
- label: 'Enterprise Trial Edition'
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: checkboxes
|
||||||
|
attributes:
|
||||||
|
label: 'Installation Method'
|
||||||
|
options:
|
||||||
|
- label: 'Online Installation (One-click command installation)'
|
||||||
|
- label: 'Offline Package Installation'
|
||||||
|
- label: 'All-in-One'
|
||||||
|
- label: '1Panel'
|
||||||
|
- label: 'Kubernetes'
|
||||||
|
- label: 'Source Code'
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
attributes:
|
||||||
|
label: '⭐️ Feature Description'
|
||||||
|
description: |
|
||||||
|
Please add a clear and concise description of the problem you aim to solve with this feature request.<br/>
|
||||||
|
Unclear descriptions will not be processed.
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
attributes:
|
||||||
|
label: 'Proposed Solution'
|
||||||
|
description: Please provide a clear and concise description of the solution you desire.
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
attributes:
|
||||||
|
label: 'Additional Information'
|
||||||
|
description: Please add any additional background information about the issue here.
|
||||||
72
.github/ISSUE_TEMPLATE/4_bug_report_cn.yml
vendored
Normal file
72
.github/ISSUE_TEMPLATE/4_bug_report_cn.yml
vendored
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
name: '🐛 反馈缺陷'
|
||||||
|
description: '反馈一个缺陷'
|
||||||
|
title: '[Bug] '
|
||||||
|
labels: ['🐛 Bug']
|
||||||
|
assignees:
|
||||||
|
- baijiangjie
|
||||||
|
body:
|
||||||
|
- type: input
|
||||||
|
attributes:
|
||||||
|
label: '产品版本'
|
||||||
|
description: 不再支持 v2.28(含)之前的版本。
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: checkboxes
|
||||||
|
attributes:
|
||||||
|
label: '版本类型'
|
||||||
|
options:
|
||||||
|
- label: '社区版'
|
||||||
|
- label: '企业版'
|
||||||
|
- label: '企业试用版'
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: checkboxes
|
||||||
|
attributes:
|
||||||
|
label: '安装方式'
|
||||||
|
options:
|
||||||
|
- label: '在线安装 (一键命令安装)'
|
||||||
|
- label: '离线包安装'
|
||||||
|
- label: 'All-in-One'
|
||||||
|
- label: '1Panel'
|
||||||
|
- label: 'Kubernetes'
|
||||||
|
- label: '源码安装'
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
attributes:
|
||||||
|
label: '环境信息'
|
||||||
|
description: 请提供一个清晰且简洁的描述,说明你的环境信息。
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
attributes:
|
||||||
|
label: '🐛 缺陷描述'
|
||||||
|
description: |
|
||||||
|
请提供一个清晰且简洁的缺陷描述,如果问题比较复杂,也请详细说明。<br/>
|
||||||
|
针对不清晰的描述信息将不予处理。请确保提供足够的细节和信息,以支持对缺陷进行复现和修复。
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
attributes:
|
||||||
|
label: '复现步骤'
|
||||||
|
description: 请提供一个清晰且简洁的描述,说明如何复现问题。
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
attributes:
|
||||||
|
label: '期望结果'
|
||||||
|
description: 请提供一个清晰且简洁的描述,说明你期望发生什么。
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
attributes:
|
||||||
|
label: '补充信息'
|
||||||
|
description: 在这里添加关于问题的任何其他背景信息。
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
attributes:
|
||||||
|
label: '尝试过的解决方案'
|
||||||
|
description: 如果你已经尝试解决问题,请在此列出你尝试过的解决方案。
|
||||||
61
.github/ISSUE_TEMPLATE/5_question_cn.yml
vendored
Normal file
61
.github/ISSUE_TEMPLATE/5_question_cn.yml
vendored
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
name: '🤔 问题咨询'
|
||||||
|
description: '提出一个问题'
|
||||||
|
title: '[Question] '
|
||||||
|
labels: ['🤔 Question']
|
||||||
|
assignees:
|
||||||
|
- baijiangjie
|
||||||
|
body:
|
||||||
|
- type: input
|
||||||
|
attributes:
|
||||||
|
label: '产品版本'
|
||||||
|
description: 不再支持 v2.28(含)之前的版本。
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: checkboxes
|
||||||
|
attributes:
|
||||||
|
label: '版本类型'
|
||||||
|
options:
|
||||||
|
- label: '社区版'
|
||||||
|
- label: '企业版'
|
||||||
|
- label: '企业试用版'
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: checkboxes
|
||||||
|
attributes:
|
||||||
|
label: '安装方式'
|
||||||
|
options:
|
||||||
|
- label: '在线安装 (一键命令安装)'
|
||||||
|
- label: '离线包安装'
|
||||||
|
- label: 'All-in-One'
|
||||||
|
- label: '1Panel'
|
||||||
|
- label: 'Kubernetes'
|
||||||
|
- label: '源码安装'
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
attributes:
|
||||||
|
label: '环境信息'
|
||||||
|
description: 请在此详细描述你的环境信息,如操作系统、浏览器和部署架构等。
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
attributes:
|
||||||
|
label: '🤔 问题描述'
|
||||||
|
description: |
|
||||||
|
请提供一个清晰且简洁的问题描述,如果问题比较复杂,也请详细说明。<br/>
|
||||||
|
针对不清晰的描述信息将不予处理。
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
attributes:
|
||||||
|
label: '期望结果'
|
||||||
|
description: 请提供一个清晰且简洁的描述,说明你期望发生什么。
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
attributes:
|
||||||
|
label: '补充信息'
|
||||||
|
description: 在这里添加关于问题的任何其他背景信息。
|
||||||
|
|
||||||
56
.github/ISSUE_TEMPLATE/6_feature_request_cn.yml
vendored
Normal file
56
.github/ISSUE_TEMPLATE/6_feature_request_cn.yml
vendored
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
name: '⭐️ 功能需求'
|
||||||
|
description: '提出需求或建议'
|
||||||
|
title: '[Feature] '
|
||||||
|
labels: ['⭐️ Feature Request']
|
||||||
|
assignees:
|
||||||
|
- baijiangjie
|
||||||
|
- ibuler
|
||||||
|
body:
|
||||||
|
- type: input
|
||||||
|
attributes:
|
||||||
|
label: '产品版本'
|
||||||
|
description: 不再支持 v2.28(含)之前的版本。
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: checkboxes
|
||||||
|
attributes:
|
||||||
|
label: '版本类型'
|
||||||
|
options:
|
||||||
|
- label: '社区版'
|
||||||
|
- label: '企业版'
|
||||||
|
- label: '企业试用版'
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: checkboxes
|
||||||
|
attributes:
|
||||||
|
label: '安装方式'
|
||||||
|
options:
|
||||||
|
- label: '在线安装 (一键命令安装)'
|
||||||
|
- label: '离线包安装'
|
||||||
|
- label: 'All-in-One'
|
||||||
|
- label: '1Panel'
|
||||||
|
- label: 'Kubernetes'
|
||||||
|
- label: '源码安装'
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
attributes:
|
||||||
|
label: '⭐️ 需求描述'
|
||||||
|
description: |
|
||||||
|
请添加一个清晰且简洁的问题描述,阐述你希望通过这个功能需求解决的问题。<br/>
|
||||||
|
针对不清晰的描述信息将不予处理。
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
attributes:
|
||||||
|
label: '解决方案'
|
||||||
|
description: 请清晰且简洁地描述你想要的解决方案。
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
attributes:
|
||||||
|
label: '补充信息'
|
||||||
|
description: 在这里添加关于问题的任何其他背景信息。
|
||||||
24
.github/ISSUE_TEMPLATE/bug---.md
vendored
24
.github/ISSUE_TEMPLATE/bug---.md
vendored
@@ -1,24 +0,0 @@
|
|||||||
---
|
|
||||||
name: Bug 提交
|
|
||||||
about: 提交产品缺陷帮助我们更好的改进
|
|
||||||
title: "[Bug] "
|
|
||||||
labels: 类型:bug
|
|
||||||
assignees:
|
|
||||||
- wojiushixiaobai
|
|
||||||
- baijiangjie
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
**JumpServer 版本( v2.28 之前的版本不再支持 )**
|
|
||||||
|
|
||||||
|
|
||||||
**浏览器版本**
|
|
||||||
|
|
||||||
|
|
||||||
**Bug 描述**
|
|
||||||
|
|
||||||
|
|
||||||
**Bug 重现步骤(有截图更好)**
|
|
||||||
1.
|
|
||||||
2.
|
|
||||||
3.
|
|
||||||
12
.github/ISSUE_TEMPLATE/question.md
vendored
12
.github/ISSUE_TEMPLATE/question.md
vendored
@@ -1,12 +0,0 @@
|
|||||||
---
|
|
||||||
name: 问题咨询
|
|
||||||
about: 提出针对本项目安装部署、使用及其他方面的相关问题
|
|
||||||
title: "[Question] "
|
|
||||||
labels: 类型:提问
|
|
||||||
assignees:
|
|
||||||
- wojiushixiaobai
|
|
||||||
- baijiangjie
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
**请描述您的问题.**
|
|
||||||
72
.github/workflows/build-base-image.yml
vendored
Normal file
72
.github/workflows/build-base-image.yml
vendored
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
name: Build and Push Base Image
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
branches:
|
||||||
|
- 'dev'
|
||||||
|
- 'v*'
|
||||||
|
paths:
|
||||||
|
- poetry.lock
|
||||||
|
- pyproject.toml
|
||||||
|
- Dockerfile-base
|
||||||
|
- package.json
|
||||||
|
- go.mod
|
||||||
|
- yarn.lock
|
||||||
|
- pom.xml
|
||||||
|
- install_deps.sh
|
||||||
|
- utils/clean_site_packages.sh
|
||||||
|
types:
|
||||||
|
- opened
|
||||||
|
- synchronize
|
||||||
|
- reopened
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build-and-push:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
ref: ${{ github.event.pull_request.head.ref }}
|
||||||
|
|
||||||
|
- name: Set up QEMU
|
||||||
|
uses: docker/setup-qemu-action@v3
|
||||||
|
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v3
|
||||||
|
|
||||||
|
- name: Login to DockerHub
|
||||||
|
uses: docker/login-action@v2
|
||||||
|
with:
|
||||||
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: Extract date
|
||||||
|
id: vars
|
||||||
|
run: echo "IMAGE_TAG=$(date +'%Y%m%d_%H%M%S')" >> $GITHUB_ENV
|
||||||
|
|
||||||
|
- name: Extract repository name
|
||||||
|
id: repo
|
||||||
|
run: echo "REPO=$(basename ${{ github.repository }})" >> $GITHUB_ENV
|
||||||
|
|
||||||
|
- name: Build and push multi-arch image
|
||||||
|
uses: docker/build-push-action@v6
|
||||||
|
with:
|
||||||
|
platforms: linux/amd64,linux/arm64
|
||||||
|
push: true
|
||||||
|
file: Dockerfile-base
|
||||||
|
tags: jumpserver/core-base:${{ env.IMAGE_TAG }}
|
||||||
|
|
||||||
|
- name: Update Dockerfile
|
||||||
|
run: |
|
||||||
|
sed -i 's|-base:.* AS stage-build|-base:${{ env.IMAGE_TAG }} AS stage-build|' Dockerfile
|
||||||
|
|
||||||
|
- name: Commit changes
|
||||||
|
run: |
|
||||||
|
git config --global user.name 'github-actions[bot]'
|
||||||
|
git config --global user.email 'github-actions[bot]@users.noreply.github.com'
|
||||||
|
git add Dockerfile
|
||||||
|
git commit -m "perf: Update Dockerfile with new base image tag"
|
||||||
|
git push origin ${{ github.event.pull_request.head.ref }}
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
31
.github/workflows/check-compilemessages.yml
vendored
Normal file
31
.github/workflows/check-compilemessages.yml
vendored
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
name: Check I18n files CompileMessages
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
branches:
|
||||||
|
- 'dev'
|
||||||
|
paths:
|
||||||
|
- 'apps/i18n/core/**/*.po'
|
||||||
|
types:
|
||||||
|
- opened
|
||||||
|
- synchronize
|
||||||
|
- reopened
|
||||||
|
jobs:
|
||||||
|
compile-messages-check:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v3
|
||||||
|
|
||||||
|
- name: Build and check compilemessages
|
||||||
|
uses: docker/build-push-action@v6
|
||||||
|
with:
|
||||||
|
platforms: linux/amd64
|
||||||
|
push: false
|
||||||
|
file: Dockerfile
|
||||||
|
target: stage-build
|
||||||
|
tags: jumpserver/core:stage-build
|
||||||
24
.github/workflows/discord-release.yml
vendored
Normal file
24
.github/workflows/discord-release.yml
vendored
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
name: Publish Release to Discord
|
||||||
|
|
||||||
|
on:
|
||||||
|
release:
|
||||||
|
types: [published]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
send_discord_notification:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
if: startsWith(github.event.release.tag_name, 'v4.')
|
||||||
|
steps:
|
||||||
|
- name: Send release notification to Discord
|
||||||
|
env:
|
||||||
|
WEBHOOK_URL: ${{ secrets.DISCORD_CHANGELOG_WEBHOOK }}
|
||||||
|
run: |
|
||||||
|
# 获取标签名称和 release body
|
||||||
|
TAG_NAME="${{ github.event.release.tag_name }}"
|
||||||
|
RELEASE_BODY="${{ github.event.release.body }}"
|
||||||
|
|
||||||
|
# 使用 jq 构建 JSON 数据,以确保安全传递
|
||||||
|
JSON_PAYLOAD=$(jq -n --arg tag "# JumpServer $TAG_NAME Released! 🚀" --arg body "$RELEASE_BODY" '{content: "\($tag)\n\($body)"}')
|
||||||
|
|
||||||
|
# 使用 curl 发送 JSON 数据
|
||||||
|
curl -X POST -H "Content-Type: application/json" -d "$JSON_PAYLOAD" "$WEBHOOK_URL"
|
||||||
24
.github/workflows/docs-release.yml
vendored
Normal file
24
.github/workflows/docs-release.yml
vendored
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
name: Auto update docs changelog
|
||||||
|
|
||||||
|
on:
|
||||||
|
release:
|
||||||
|
types: [published]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
update_docs_changelog:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
if: startsWith(github.event.release.tag_name, 'v4.')
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
- name: Update docs changelog
|
||||||
|
env:
|
||||||
|
TAG_NAME: ${{ github.event.release.tag_name }}
|
||||||
|
DOCS_TOKEN: ${{ secrets.DOCS_TOKEN }}
|
||||||
|
run: |
|
||||||
|
git config --global user.name 'BaiJiangJie'
|
||||||
|
git config --global user.email 'jiangjie.bai@fit2cloud.com'
|
||||||
|
|
||||||
|
git clone https://$DOCS_TOKEN@github.com/jumpservice/documentation.git
|
||||||
|
cd documentation/utils
|
||||||
|
bash update_changelog.sh
|
||||||
4
.github/workflows/issue-close-require.yml
vendored
4
.github/workflows/issue-close-require.yml
vendored
@@ -12,7 +12,9 @@ jobs:
|
|||||||
uses: actions-cool/issues-helper@v2
|
uses: actions-cool/issues-helper@v2
|
||||||
with:
|
with:
|
||||||
actions: 'close-issues'
|
actions: 'close-issues'
|
||||||
labels: '状态:待反馈'
|
labels: '⏳ Pending feedback'
|
||||||
inactive-day: 30
|
inactive-day: 30
|
||||||
body: |
|
body: |
|
||||||
|
You haven't provided feedback for over 30 days.
|
||||||
|
We will close this issue. If you have any further needs, you can reopen it or submit a new issue.
|
||||||
您超过 30 天未反馈信息,我们将关闭该 issue,如有需求您可以重新打开或者提交新的 issue。
|
您超过 30 天未反馈信息,我们将关闭该 issue,如有需求您可以重新打开或者提交新的 issue。
|
||||||
|
|||||||
2
.github/workflows/issue-close.yml
vendored
2
.github/workflows/issue-close.yml
vendored
@@ -13,4 +13,4 @@ jobs:
|
|||||||
if: ${{ !github.event.issue.pull_request }}
|
if: ${{ !github.event.issue.pull_request }}
|
||||||
with:
|
with:
|
||||||
actions: 'remove-labels'
|
actions: 'remove-labels'
|
||||||
labels: '状态:待处理,状态:待反馈'
|
labels: '🔔 Pending processing,⏳ Pending feedback'
|
||||||
8
.github/workflows/issue-comment.yml
vendored
8
.github/workflows/issue-comment.yml
vendored
@@ -13,13 +13,13 @@ jobs:
|
|||||||
uses: actions-cool/issues-helper@v2
|
uses: actions-cool/issues-helper@v2
|
||||||
with:
|
with:
|
||||||
actions: 'add-labels'
|
actions: 'add-labels'
|
||||||
labels: '状态:待处理'
|
labels: '🔔 Pending processing'
|
||||||
|
|
||||||
- name: Remove require reply label
|
- name: Remove require reply label
|
||||||
uses: actions-cool/issues-helper@v2
|
uses: actions-cool/issues-helper@v2
|
||||||
with:
|
with:
|
||||||
actions: 'remove-labels'
|
actions: 'remove-labels'
|
||||||
labels: '状态:待反馈'
|
labels: '⏳ Pending feedback'
|
||||||
|
|
||||||
add-label-if-is-member:
|
add-label-if-is-member:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
@@ -55,11 +55,11 @@ jobs:
|
|||||||
uses: actions-cool/issues-helper@v2
|
uses: actions-cool/issues-helper@v2
|
||||||
with:
|
with:
|
||||||
actions: 'add-labels'
|
actions: 'add-labels'
|
||||||
labels: '状态:待反馈'
|
labels: '⏳ Pending feedback'
|
||||||
|
|
||||||
- name: Remove require handle label
|
- name: Remove require handle label
|
||||||
if: contains(steps.member_names.outputs.data, github.event.comment.user.login)
|
if: contains(steps.member_names.outputs.data, github.event.comment.user.login)
|
||||||
uses: actions-cool/issues-helper@v2
|
uses: actions-cool/issues-helper@v2
|
||||||
with:
|
with:
|
||||||
actions: 'remove-labels'
|
actions: 'remove-labels'
|
||||||
labels: '状态:待处理'
|
labels: '🔔 Pending processing'
|
||||||
|
|||||||
2
.github/workflows/issue-open.yml
vendored
2
.github/workflows/issue-open.yml
vendored
@@ -13,4 +13,4 @@ jobs:
|
|||||||
if: ${{ !github.event.issue.pull_request }}
|
if: ${{ !github.event.issue.pull_request }}
|
||||||
with:
|
with:
|
||||||
actions: 'add-labels'
|
actions: 'add-labels'
|
||||||
labels: '状态:待处理'
|
labels: '🔔 Pending processing'
|
||||||
36
.github/workflows/jms-build-test.yml
vendored
36
.github/workflows/jms-build-test.yml
vendored
@@ -1,36 +0,0 @@
|
|||||||
name: "Run Build Test"
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- pr@*
|
|
||||||
- repr@*
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v3
|
|
||||||
|
|
||||||
- uses: docker/setup-qemu-action@v2
|
|
||||||
|
|
||||||
- uses: docker/setup-buildx-action@v2
|
|
||||||
|
|
||||||
- uses: docker/build-push-action@v3
|
|
||||||
with:
|
|
||||||
context: .
|
|
||||||
push: false
|
|
||||||
tags: jumpserver/core:test
|
|
||||||
file: Dockerfile
|
|
||||||
build-args: |
|
|
||||||
APT_MIRROR=http://deb.debian.org
|
|
||||||
PIP_MIRROR=https://pypi.org/simple
|
|
||||||
PIP_JMS_MIRROR=https://pypi.org/simple
|
|
||||||
cache-from: type=gha
|
|
||||||
cache-to: type=gha,mode=max
|
|
||||||
|
|
||||||
- uses: LouisBrunner/checks-action@v1.5.0
|
|
||||||
if: always()
|
|
||||||
with:
|
|
||||||
token: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
name: Check Build
|
|
||||||
conclusion: ${{ job.status }}
|
|
||||||
63
.github/workflows/jms-build-test.yml.disabled
vendored
Normal file
63
.github/workflows/jms-build-test.yml.disabled
vendored
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
name: "Run Build Test"
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
paths:
|
||||||
|
- 'Dockerfile'
|
||||||
|
- 'Dockerfile*'
|
||||||
|
- 'Dockerfile-*'
|
||||||
|
- 'pyproject.toml'
|
||||||
|
- 'poetry.lock'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
component: [core]
|
||||||
|
version: [v4]
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- uses: docker/setup-buildx-action@v3
|
||||||
|
|
||||||
|
- name: Login to GitHub Container Registry
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
registry: ghcr.io
|
||||||
|
username: ${{ github.repository_owner }}
|
||||||
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: Prepare Build
|
||||||
|
run: |
|
||||||
|
sed -i 's@^FROM registry.fit2cloud.com/jumpserver@FROM ghcr.io/jumpserver@g' Dockerfile-ee
|
||||||
|
|
||||||
|
- name: Build CE Image
|
||||||
|
uses: docker/build-push-action@v5
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
push: true
|
||||||
|
file: Dockerfile
|
||||||
|
tags: ghcr.io/jumpserver/${{ matrix.component }}:${{ matrix.version }}-ce
|
||||||
|
platforms: linux/amd64
|
||||||
|
build-args: |
|
||||||
|
VERSION=${{ matrix.version }}
|
||||||
|
APT_MIRROR=http://deb.debian.org
|
||||||
|
PIP_MIRROR=https://pypi.org/simple
|
||||||
|
outputs: type=image,oci-mediatypes=true,compression=zstd,compression-level=3,force-compression=true
|
||||||
|
cache-from: type=gha
|
||||||
|
cache-to: type=gha,mode=max
|
||||||
|
|
||||||
|
- name: Build EE Image
|
||||||
|
uses: docker/build-push-action@v5
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
push: false
|
||||||
|
file: Dockerfile-ee
|
||||||
|
tags: ghcr.io/jumpserver/${{ matrix.component }}:${{ matrix.version }}
|
||||||
|
platforms: linux/amd64
|
||||||
|
build-args: |
|
||||||
|
VERSION=${{ matrix.version }}
|
||||||
|
APT_MIRROR=http://deb.debian.org
|
||||||
|
PIP_MIRROR=https://pypi.org/simple
|
||||||
|
outputs: type=image,oci-mediatypes=true,compression=zstd,compression-level=3,force-compression=true
|
||||||
|
cache-from: type=gha
|
||||||
|
cache-to: type=gha,mode=max
|
||||||
@@ -10,3 +10,4 @@ jobs:
|
|||||||
- uses: jumpserver/action-generic-handler@master
|
- uses: jumpserver/action-generic-handler@master
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.PRIVATE_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.PRIVATE_TOKEN }}
|
||||||
|
I18N_TOKEN: ${{ secrets.I18N_TOKEN }}
|
||||||
|
|||||||
28
.github/workflows/llm-code-review.yml
vendored
Normal file
28
.github/workflows/llm-code-review.yml
vendored
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
name: LLM Code Review
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
pull-requests: write
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
types: [opened, reopened, synchronize]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
llm-code-review:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: fit2cloud/LLM-CodeReview-Action@main
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.FIT2CLOUDRD_LLM_CODE_REVIEW_TOKEN }}
|
||||||
|
OPENAI_API_KEY: ${{ secrets.ALIYUN_LLM_API_KEY }}
|
||||||
|
LANGUAGE: English
|
||||||
|
OPENAI_API_ENDPOINT: https://dashscope.aliyuncs.com/compatible-mode/v1
|
||||||
|
MODEL: qwen2-1.5b-instruct
|
||||||
|
PROMPT: "Please check the following code differences for any irregularities, potential issues, or optimization suggestions, and provide your answers in English."
|
||||||
|
top_p: 1
|
||||||
|
temperature: 1
|
||||||
|
# max_tokens: 10000
|
||||||
|
MAX_PATCH_LENGTH: 10000
|
||||||
|
IGNORE_PATTERNS: "/node_modules,*.md,/dist,/.github"
|
||||||
|
FILE_PATTERNS: "*.java,*.go,*.py,*.vue,*.ts,*.js,*.css,*.scss,*.html"
|
||||||
40
.github/workflows/translate-readme.yml
vendored
Normal file
40
.github/workflows/translate-readme.yml
vendored
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
name: Translate README
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
target_langs:
|
||||||
|
description: "Target Languages"
|
||||||
|
required: false
|
||||||
|
default: "zh-hans,zh-hant,ja,pt-br"
|
||||||
|
gen_dir_path:
|
||||||
|
description: "Generate Dir Name"
|
||||||
|
required: false
|
||||||
|
default: "readmes/"
|
||||||
|
push_branch:
|
||||||
|
description: "Push Branch"
|
||||||
|
required: false
|
||||||
|
default: "pr@dev@translate_readme"
|
||||||
|
prompt:
|
||||||
|
description: "AI Translate Prompt"
|
||||||
|
required: false
|
||||||
|
default: ""
|
||||||
|
|
||||||
|
gpt_mode:
|
||||||
|
description: "GPT Mode"
|
||||||
|
required: false
|
||||||
|
default: "gpt-4o-mini"
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Auto Translate
|
||||||
|
uses: jumpserver-dev/action-translate-readme@main
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.PRIVATE_TOKEN }}
|
||||||
|
OPENAI_API_KEY: ${{ secrets.GPT_API_TOKEN }}
|
||||||
|
GPT_MODE: ${{ github.event.inputs.gpt_mode }}
|
||||||
|
TARGET_LANGUAGES: ${{ github.event.inputs.target_langs }}
|
||||||
|
PUSH_BRANCH: ${{ github.event.inputs.push_branch }}
|
||||||
|
GEN_DIR_PATH: ${{ github.event.inputs.gen_dir_path }}
|
||||||
|
PROMPT: ${{ github.event.inputs.prompt }}
|
||||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -43,3 +43,6 @@ releashe
|
|||||||
data/*
|
data/*
|
||||||
test.py
|
test.py
|
||||||
.history/
|
.history/
|
||||||
|
.test/
|
||||||
|
*.mo
|
||||||
|
apps.iml
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
[settings]
|
[settings]
|
||||||
line_length=120
|
line_length=120
|
||||||
known_first_party=common,users,assets,perms,authentication,jumpserver,notification,ops,orgs,rbac,settings,terminal,tickets
|
known_first_party=common,users,assets,perms,authentication,jumpserver,notification,ops,orgs,rbac,settings,terminal,tickets
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,10 @@
|
|||||||
# Contributing
|
# Contributing
|
||||||
|
|
||||||
|
As a contributor, you should agree that:
|
||||||
|
|
||||||
|
- The producer can adjust the open-source agreement to be more strict or relaxed as deemed necessary.
|
||||||
|
- Your contributed code may be used for commercial purposes, including but not limited to its cloud business operations.
|
||||||
|
|
||||||
## Create pull request
|
## Create pull request
|
||||||
PR are always welcome, even if they only contain small fixes like typos or a few lines of code. If there will be a significant effort, please document it as an issue and get a discussion going before starting to work on it.
|
PR are always welcome, even if they only contain small fixes like typos or a few lines of code. If there will be a significant effort, please document it as an issue and get a discussion going before starting to work on it.
|
||||||
|
|
||||||
|
|||||||
88
Dockerfile
88
Dockerfile
@@ -1,84 +1,68 @@
|
|||||||
FROM python:3.11-slim-bullseye as stage-build
|
FROM jumpserver/core-base:20241210_070105 AS stage-build
|
||||||
ARG TARGETARCH
|
|
||||||
|
|
||||||
ARG VERSION
|
ARG VERSION
|
||||||
ENV VERSION=$VERSION
|
|
||||||
|
|
||||||
WORKDIR /opt/jumpserver
|
WORKDIR /opt/jumpserver
|
||||||
|
|
||||||
ADD . .
|
ADD . .
|
||||||
RUN cd utils && bash -ixeu build.sh
|
|
||||||
|
RUN echo > /opt/jumpserver/config.yml \
|
||||||
|
&& \
|
||||||
|
if [ -n "${VERSION}" ]; then \
|
||||||
|
sed -i "s@VERSION = .*@VERSION = '${VERSION}'@g" apps/jumpserver/const.py; \
|
||||||
|
fi
|
||||||
|
|
||||||
|
RUN set -ex \
|
||||||
|
&& export SECRET_KEY=$(head -c100 < /dev/urandom | base64 | tr -dc A-Za-z0-9 | head -c 48) \
|
||||||
|
&& . /opt/py3/bin/activate \
|
||||||
|
&& cd apps \
|
||||||
|
&& python manage.py compilemessages
|
||||||
|
|
||||||
|
|
||||||
FROM python:3.11-slim-bullseye
|
FROM python:3.11-slim-bullseye
|
||||||
ARG TARGETARCH
|
ENV LANG=en_US.UTF-8 \
|
||||||
|
PATH=/opt/py3/bin:$PATH
|
||||||
ARG BUILD_DEPENDENCIES=" \
|
|
||||||
g++ \
|
|
||||||
make \
|
|
||||||
pkg-config"
|
|
||||||
|
|
||||||
ARG DEPENDENCIES=" \
|
ARG DEPENDENCIES=" \
|
||||||
freetds-dev \
|
|
||||||
libpq-dev \
|
|
||||||
libffi-dev \
|
|
||||||
libjpeg-dev \
|
|
||||||
libkrb5-dev \
|
|
||||||
libldap2-dev \
|
libldap2-dev \
|
||||||
libsasl2-dev \
|
libx11-dev"
|
||||||
libssl-dev \
|
|
||||||
libxml2-dev \
|
|
||||||
libxmlsec1-dev \
|
|
||||||
libxmlsec1-openssl \
|
|
||||||
freerdp2-dev \
|
|
||||||
libaio-dev"
|
|
||||||
|
|
||||||
ARG TOOLS=" \
|
ARG TOOLS=" \
|
||||||
|
cron \
|
||||||
ca-certificates \
|
ca-certificates \
|
||||||
curl \
|
|
||||||
default-libmysqlclient-dev \
|
default-libmysqlclient-dev \
|
||||||
default-mysql-client \
|
|
||||||
locales \
|
|
||||||
nmap \
|
|
||||||
openssh-client \
|
openssh-client \
|
||||||
sshpass \
|
sshpass \
|
||||||
telnet \
|
bubblewrap"
|
||||||
vim \
|
|
||||||
wget"
|
|
||||||
|
|
||||||
ARG APT_MIRROR=http://mirrors.ustc.edu.cn
|
ARG APT_MIRROR=http://deb.debian.org
|
||||||
|
|
||||||
RUN --mount=type=cache,target=/var/cache/apt,sharing=locked,id=core \
|
RUN set -ex \
|
||||||
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 \
|
|
||||||
&& ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime \
|
&& ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime \
|
||||||
&& apt-get update \
|
&& apt-get update > /dev/null \
|
||||||
&& 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/ \
|
&& 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 "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 "no" | dpkg-reconfigure dash \
|
||||||
&& echo "zh_CN.UTF-8" | dpkg-reconfigure locales \
|
&& apt-get clean all \
|
||||||
&& sed -i "s@# export @export @g" ~/.bashrc \
|
&& rm -rf /var/lib/apt/lists/* \
|
||||||
&& sed -i "s@# alias @alias @g" ~/.bashrc \
|
&& echo "0 3 * * * root find /tmp -type f -mtime +1 -size +1M -exec rm -f {} \; && date > /tmp/clean.log" > /etc/cron.d/cleanup_tmp \
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
&& chmod 0644 /etc/cron.d/cleanup_tmp
|
||||||
|
|
||||||
|
COPY --from=stage-build /opt /opt
|
||||||
|
COPY --from=stage-build /usr/local/bin /usr/local/bin
|
||||||
|
COPY --from=stage-build /opt/jumpserver/apps/libs/ansible/ansible.cfg /etc/ansible/
|
||||||
|
|
||||||
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
|
|
||||||
RUN --mount=type=cache,target=/root/.cache \
|
|
||||||
set -ex \
|
|
||||||
&& echo > /opt/jumpserver/config.yml \
|
|
||||||
&& pip install poetry -i ${PIP_MIRROR} \
|
|
||||||
&& poetry config virtualenvs.create false \
|
|
||||||
&& poetry install --only=main
|
|
||||||
|
|
||||||
VOLUME /opt/jumpserver/data
|
VOLUME /opt/jumpserver/data
|
||||||
VOLUME /opt/jumpserver/logs
|
|
||||||
|
|
||||||
ENV LANG=zh_CN.UTF-8
|
ENTRYPOINT ["./entrypoint.sh"]
|
||||||
|
|
||||||
EXPOSE 8080
|
EXPOSE 8080
|
||||||
|
|
||||||
ENTRYPOINT ["./entrypoint.sh"]
|
STOPSIGNAL SIGQUIT
|
||||||
|
|
||||||
|
CMD ["start", "all"]
|
||||||
|
|||||||
60
Dockerfile-base
Normal file
60
Dockerfile-base
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
FROM python:3.11-slim-bullseye
|
||||||
|
ARG TARGETARCH
|
||||||
|
|
||||||
|
# Install APT dependencies
|
||||||
|
ARG DEPENDENCIES=" \
|
||||||
|
ca-certificates \
|
||||||
|
wget \
|
||||||
|
g++ \
|
||||||
|
make \
|
||||||
|
pkg-config \
|
||||||
|
default-libmysqlclient-dev \
|
||||||
|
freetds-dev \
|
||||||
|
gettext \
|
||||||
|
libkrb5-dev \
|
||||||
|
libldap2-dev \
|
||||||
|
libsasl2-dev"
|
||||||
|
|
||||||
|
ARG APT_MIRROR=http://deb.debian.org
|
||||||
|
|
||||||
|
RUN --mount=type=cache,target=/var/cache/apt,sharing=locked,id=core \
|
||||||
|
--mount=type=cache,target=/var/lib/apt,sharing=locked,id=core \
|
||||||
|
set -ex \
|
||||||
|
&& rm -f /etc/apt/apt.conf.d/docker-clean \
|
||||||
|
&& echo 'Binary::apt::APT::Keep-Downloaded-Packages "true";' > /etc/apt/apt.conf.d/keep-cache \
|
||||||
|
&& sed -i "s@http://.*.debian.org@${APT_MIRROR}@g" /etc/apt/sources.list \
|
||||||
|
&& apt-get update > /dev/null \
|
||||||
|
&& apt-get -y install --no-install-recommends ${DEPENDENCIES} \
|
||||||
|
&& echo "no" | dpkg-reconfigure dash
|
||||||
|
|
||||||
|
# Install bin tools
|
||||||
|
ARG CHECK_VERSION=v1.0.4
|
||||||
|
RUN set -ex \
|
||||||
|
&& wget https://github.com/jumpserver-dev/healthcheck/releases/download/${CHECK_VERSION}/check-${CHECK_VERSION}-linux-${TARGETARCH}.tar.gz \
|
||||||
|
&& tar -xf check-${CHECK_VERSION}-linux-${TARGETARCH}.tar.gz \
|
||||||
|
&& mv check /usr/local/bin/ \
|
||||||
|
&& chown root:root /usr/local/bin/check \
|
||||||
|
&& chmod 755 /usr/local/bin/check \
|
||||||
|
&& rm -f check-${CHECK_VERSION}-linux-${TARGETARCH}.tar.gz
|
||||||
|
|
||||||
|
# Install Python dependencies
|
||||||
|
WORKDIR /opt/jumpserver
|
||||||
|
|
||||||
|
ARG PIP_MIRROR=https://pypi.org/simple
|
||||||
|
ENV POETRY_PYPI_MIRROR_URL=${PIP_MIRROR}
|
||||||
|
ENV ANSIBLE_COLLECTIONS_PATHS=/opt/py3/lib/python3.11/site-packages/ansible_collections
|
||||||
|
|
||||||
|
RUN --mount=type=cache,target=/root/.cache \
|
||||||
|
--mount=type=bind,source=poetry.lock,target=poetry.lock \
|
||||||
|
--mount=type=bind,source=pyproject.toml,target=pyproject.toml \
|
||||||
|
--mount=type=bind,source=utils/clean_site_packages.sh,target=clean_site_packages.sh \
|
||||||
|
--mount=type=bind,source=requirements/collections.yml,target=collections.yml \
|
||||||
|
set -ex \
|
||||||
|
&& python3 -m venv /opt/py3 \
|
||||||
|
&& pip install poetry poetry-plugin-pypi-mirror -i ${PIP_MIRROR} \
|
||||||
|
&& . /opt/py3/bin/activate \
|
||||||
|
&& poetry config virtualenvs.create false \
|
||||||
|
&& poetry install --no-cache --only main \
|
||||||
|
&& ansible-galaxy collection install -r collections.yml --force --ignore-certs \
|
||||||
|
&& bash clean_site_packages.sh \
|
||||||
|
&& poetry cache clear pypi --all
|
||||||
@@ -1,9 +1,34 @@
|
|||||||
ARG VERSION
|
ARG VERSION=dev
|
||||||
FROM registry.fit2cloud.com/jumpserver/xpack:${VERSION} as build-xpack
|
|
||||||
FROM jumpserver/core:${VERSION}
|
FROM registry.fit2cloud.com/jumpserver/xpack:${VERSION} AS build-xpack
|
||||||
|
FROM jumpserver/core:${VERSION}-ce
|
||||||
|
|
||||||
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 \
|
ARG TOOLS=" \
|
||||||
set -ex \
|
g++ \
|
||||||
&& poetry install --only=xpack
|
curl \
|
||||||
|
iputils-ping \
|
||||||
|
netcat-openbsd \
|
||||||
|
nmap \
|
||||||
|
telnet \
|
||||||
|
vim \
|
||||||
|
wget"
|
||||||
|
|
||||||
|
RUN set -ex \
|
||||||
|
&& apt-get update \
|
||||||
|
&& apt-get -y install --no-install-recommends ${TOOLS} \
|
||||||
|
&& apt-get clean all \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
WORKDIR /opt/jumpserver
|
||||||
|
|
||||||
|
ARG PIP_MIRROR=https://pypi.org/simple
|
||||||
|
ENV POETRY_PYPI_MIRROR_URL=${PIP_MIRROR}
|
||||||
|
COPY poetry.lock pyproject.toml ./
|
||||||
|
RUN set -ex \
|
||||||
|
&& . /opt/py3/bin/activate \
|
||||||
|
&& pip install poetry poetry-plugin-pypi-mirror -i ${PIP_MIRROR} \
|
||||||
|
&& poetry install --only xpack \
|
||||||
|
&& poetry cache clear pypi --all
|
||||||
|
|
||||||
|
|||||||
218
README.md
218
README.md
@@ -1,125 +1,119 @@
|
|||||||
<p align="center">
|
<div align="center">
|
||||||
<a href="https://jumpserver.org"><img src="https://download.jumpserver.org/images/jumpserver-logo.svg" alt="JumpServer" width="300" /></a>
|
<a name="readme-top"></a>
|
||||||
</p>
|
<a href="https://jumpserver.org/index-en.html"><img src="https://download.jumpserver.org/images/jumpserver-logo.svg" alt="JumpServer" width="300" /></a>
|
||||||
<h3 align="center">广受欢迎的开源堡垒机</h3>
|
|
||||||
|
## An open-source PAM tool (Bastion Host)
|
||||||
|
|
||||||
<p align="center">
|
[![][license-shield]][license-link]
|
||||||
<a href="https://www.gnu.org/licenses/gpl-3.0.html"><img src="https://img.shields.io/github/license/jumpserver/jumpserver" alt="License: GPLv3"></a>
|
[![][discord-shield]][discord-link]
|
||||||
<a href="https://hub.docker.com/u/jumpserver"><img src="https://img.shields.io/docker/pulls/jumpserver/jms_all.svg" alt="Docker pulls"></a>
|
[![][docker-shield]][docker-link]
|
||||||
<a href="https://github.com/jumpserver/jumpserver/releases/latest"><img src="https://img.shields.io/github/v/release/jumpserver/jumpserver" alt="Latest release"></a>
|
[![][github-release-shield]][github-release-link]
|
||||||
<a href="https://github.com/jumpserver/jumpserver"><img src="https://img.shields.io/github/stars/jumpserver/jumpserver?color=%231890FF&style=flat-square" alt="Stars"></a>
|
[![][github-stars-shield]][github-stars-link]
|
||||||
</p>
|
|
||||||
|
[English](/README.md) · [中文(简体)](/readmes/README.zh-hans.md) · [中文(繁體)](/readmes/README.zh-hant.md) · [日本語](/readmes/README.ja.md) · [Português (Brasil)](/readmes/README.pt-br.md)
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<br/>
|
||||||
|
|
||||||
|
## What is JumpServer?
|
||||||
|
|
||||||
|
JumpServer is an open-source Privileged Access Management (PAM) tool that provides DevOps and IT teams with on-demand and secure access to SSH, RDP, Kubernetes, Database and RemoteApp endpoints through a web browser.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## Quickstart
|
||||||
|
|
||||||
|
Prepare a clean Linux Server ( 64 bit, >= 4c8g )
|
||||||
|
|
||||||
|
```sh
|
||||||
|
curl -sSL https://github.com/jumpserver/jumpserver/releases/latest/download/quick_start.sh | bash
|
||||||
|
```
|
||||||
|
|
||||||
|
Access JumpServer in your browser at `http://your-jumpserver-ip/`
|
||||||
|
- Username: `admin`
|
||||||
|
- Password: `ChangeMe`
|
||||||
|
|
||||||
|
[](https://www.youtube.com/watch?v=UlGYRbKrpgY "JumpServer Quickstart")
|
||||||
|
|
||||||
|
## Screenshots
|
||||||
|
|
||||||
|
<table style="border-collapse: collapse; border: 1px solid black;">
|
||||||
|
<tr>
|
||||||
|
<td style="padding: 5px;background-color:#fff;"><img src= "https://github.com/jumpserver/jumpserver/assets/32935519/99fabe5b-0475-4a53-9116-4c370a1426c4" alt="JumpServer Console" /></td>
|
||||||
|
<td style="padding: 5px;background-color:#fff;"><img src= "https://github.com/jumpserver/jumpserver/assets/32935519/a424d731-1c70-4108-a7d8-5bbf387dda9a" alt="JumpServer Audits" /></td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td style="padding: 5px;background-color:#fff;"><img src= "https://github.com/jumpserver/jumpserver/assets/32935519/393d2c27-a2d0-4dea-882d-00ed509e00c9" alt="JumpServer Workbench" /></td>
|
||||||
|
<td style="padding: 5px;background-color:#fff;"><img src= "https://github.com/jumpserver/jumpserver/assets/32935519/3a2611cd-8902-49b8-b82b-2a6dac851f3e" alt="JumpServer Settings" /></td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td style="padding: 5px;background-color:#fff;"><img src= "https://github.com/jumpserver/jumpserver/assets/32935519/1e236093-31f7-4563-8eb1-e36d865f1568" alt="JumpServer SSH" /></td>
|
||||||
|
<td style="padding: 5px;background-color:#fff;"><img src= "https://github.com/jumpserver/jumpserver/assets/32935519/69373a82-f7ab-41e8-b763-bbad2ba52167" alt="JumpServer RDP" /></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td style="padding: 5px;background-color:#fff;"><img src= "https://github.com/jumpserver/jumpserver/assets/32935519/5bed98c6-cbe8-4073-9597-d53c69dc3957" alt="JumpServer K8s" /></td>
|
||||||
|
<td style="padding: 5px;background-color:#fff;"><img src= "https://github.com/jumpserver/jumpserver/assets/32935519/b80ad654-548f-42bc-ba3d-c1cfdf1b46d6" alt="JumpServer DB" /></td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
## Components
|
||||||
|
|
||||||
|
JumpServer consists of multiple key components, which collectively form the functional framework of JumpServer, providing users with comprehensive capabilities for operations management and security control.
|
||||||
|
|
||||||
|
| Project | Status | Description |
|
||||||
|
|--------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------|
|
||||||
|
| [Lina](https://github.com/jumpserver/lina) | <a href="https://github.com/jumpserver/lina/releases"><img alt="Lina release" src="https://img.shields.io/github/release/jumpserver/lina.svg" /></a> | JumpServer Web UI |
|
||||||
|
| [Luna](https://github.com/jumpserver/luna) | <a href="https://github.com/jumpserver/luna/releases"><img alt="Luna release" src="https://img.shields.io/github/release/jumpserver/luna.svg" /></a> | JumpServer Web Terminal |
|
||||||
|
| [KoKo](https://github.com/jumpserver/koko) | <a href="https://github.com/jumpserver/koko/releases"><img alt="Koko release" src="https://img.shields.io/github/release/jumpserver/koko.svg" /></a> | JumpServer Character Protocol Connector |
|
||||||
|
| [Lion](https://github.com/jumpserver/lion) | <a href="https://github.com/jumpserver/lion/releases"><img alt="Lion release" src="https://img.shields.io/github/release/jumpserver/lion.svg" /></a> | JumpServer Graphical Protocol Connector |
|
||||||
|
| [Chen](https://github.com/jumpserver/chen) | <a href="https://github.com/jumpserver/chen/releases"><img alt="Chen release" src="https://img.shields.io/github/release/jumpserver/chen.svg" /> | JumpServer Web DB |
|
||||||
|
| [Razor](https://github.com/jumpserver/razor) | <img alt="Chen" src="https://img.shields.io/badge/release-private-red" /> | JumpServer EE RDP Proxy Connector |
|
||||||
|
| [Tinker](https://github.com/jumpserver/tinker) | <img alt="Tinker" src="https://img.shields.io/badge/release-private-red" /> | JumpServer EE Remote Application Connector (Windows) |
|
||||||
|
| [Panda](https://github.com/jumpserver/Panda) | <img alt="Panda" src="https://img.shields.io/badge/release-private-red" /> | JumpServer EE Remote Application Connector (Linux) |
|
||||||
|
| [Magnus](https://github.com/jumpserver/magnus) | <img alt="Magnus" src="https://img.shields.io/badge/release-private-red" /> | JumpServer EE Database Proxy Connector |
|
||||||
|
| [Nec](https://github.com/jumpserver/nec) | <img alt="Nec" src="https://img.shields.io/badge/release-private-red" /> | JumpServer EE VNC Proxy Connector |
|
||||||
|
| [Facelive](https://github.com/jumpserver/facelive) | <img alt="Facelive" src="https://img.shields.io/badge/release-private-red" /> | JumpServer EE Facial Recognition |
|
||||||
|
|
||||||
|
|
||||||
<p align="center">
|
## Contributing
|
||||||
JumpServer <a href="https://github.com/jumpserver/jumpserver/releases/tag/v3.0.0">v3.0</a> 正式发布。
|
|
||||||
<br>
|
|
||||||
9 年时间,倾情投入,用心做好一款开源堡垒机。
|
|
||||||
</p>
|
|
||||||
|
|
||||||
------------------------------
|
Welcome to submit PR to contribute. Please refer to [CONTRIBUTING.md][contributing-link] for guidelines.
|
||||||
JumpServer 是广受欢迎的开源堡垒机,是符合 4A 规范的专业运维安全审计系统。
|
|
||||||
|
|
||||||
JumpServer 堡垒机帮助企业以更安全的方式管控和登录各种类型的资产,包括:
|
## Security
|
||||||
|
|
||||||
- **SSH**: Linux / Unix / 网络设备 等;
|
JumpServer is a mission critical product. Please refer to the Basic Security Recommendations for installation and deployment. If you encounter any security-related issues, please contact us directly:
|
||||||
- **Windows**: Web 方式连接 / 原生 RDP 连接;
|
|
||||||
- **数据库**: MySQL / MariaDB / PostgreSQL / Oracle / SQLServer / ClickHouse 等;
|
|
||||||
- **NoSQL**: Redis / MongoDB 等;
|
|
||||||
- **GPT**: ChatGPT 等;
|
|
||||||
- **云服务**: Kubernetes / VMware vSphere 等;
|
|
||||||
- **Web 站点**: 各类系统的 Web 管理后台;
|
|
||||||
- **应用**: 通过 Remote App 连接各类应用。
|
|
||||||
|
|
||||||
## 产品特色
|
- Email: support@fit2cloud.com
|
||||||
|
|
||||||
- **开源**: 零门槛,线上快速获取和安装;
|
## License
|
||||||
- **无插件**: 仅需浏览器,极致的 Web Terminal 使用体验;
|
|
||||||
- **分布式**: 支持分布式部署和横向扩展,轻松支持大规模并发访问;
|
|
||||||
- **多云支持**: 一套系统,同时管理不同云上面的资产;
|
|
||||||
- **多租户**: 一套系统,多个子公司或部门同时使用;
|
|
||||||
- **云端存储**: 审计录像云端存储,永不丢失;
|
|
||||||
|
|
||||||
## UI 展示
|
Copyright (c) 2014-2025 FIT2CLOUD, All rights reserved.
|
||||||
|
|
||||||

|
Licensed under The GNU General Public License version 3 (GPLv3) (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at
|
||||||
|
|
||||||
## 在线体验
|
|
||||||
|
|
||||||
- 环境地址:<https://demo.jumpserver.org/>
|
|
||||||
|
|
||||||
| :warning: 注意 |
|
|
||||||
|:-----------------------------|
|
|
||||||
| 该环境仅作体验目的使用,我们会定时清理、重置数据! |
|
|
||||||
| 请勿修改体验环境用户的密码! |
|
|
||||||
| 请勿在环境中添加业务生产环境地址、用户名密码等敏感信息! |
|
|
||||||
|
|
||||||
## 快速开始
|
|
||||||
|
|
||||||
- [快速入门](https://docs.jumpserver.org/zh/v3/quick_start/)
|
|
||||||
- [产品文档](https://docs.jumpserver.org)
|
|
||||||
- [在线学习](https://edu.fit2cloud.com/page/2635362)
|
|
||||||
- [知识库](https://kb.fit2cloud.com/categories/jumpserver)
|
|
||||||
|
|
||||||
## 案例研究
|
|
||||||
|
|
||||||
- [腾讯海外游戏:基于JumpServer构建游戏安全运营能力](https://blog.fit2cloud.com/?p=3704)
|
|
||||||
- [万华化学:通过JumpServer管理全球化分布式IT资产,并且实现与云管平台的联动](https://blog.fit2cloud.com/?p=3504)
|
|
||||||
- [雪花啤酒:JumpServer堡垒机使用体会](https://blog.fit2cloud.com/?p=3412)
|
|
||||||
- [顺丰科技:JumpServer 堡垒机护航顺丰科技超大规模资产安全运维](https://blog.fit2cloud.com/?p=1147)
|
|
||||||
- [沐瞳游戏:通过JumpServer管控多项目分布式资产](https://blog.fit2cloud.com/?p=3213)
|
|
||||||
- [携程:JumpServer 堡垒机部署与运营实战](https://blog.fit2cloud.com/?p=851)
|
|
||||||
- [大智慧:JumpServer 堡垒机让“大智慧”的混合 IT 运维更智慧](https://blog.fit2cloud.com/?p=882)
|
|
||||||
- [小红书:JumpServer 堡垒机大规模资产跨版本迁移之路](https://blog.fit2cloud.com/?p=516)
|
|
||||||
- [中手游:JumpServer堡垒机助力中手游提升多云环境下安全运维能力](https://blog.fit2cloud.com/?p=732)
|
|
||||||
- [中通快递:JumpServer主机安全运维实践](https://blog.fit2cloud.com/?p=708)
|
|
||||||
- [东方明珠:JumpServer高效管控异构化、分布式云端资产](https://blog.fit2cloud.com/?p=687)
|
|
||||||
- [江苏农信:JumpServer堡垒机助力行业云安全运维](https://blog.fit2cloud.com/?p=666)
|
|
||||||
|
|
||||||
## 社区交流
|
|
||||||
|
|
||||||
如果您在使用过程中有任何疑问或对建议,欢迎提交 [GitHub Issue](https://github.com/jumpserver/jumpserver/issues/new/choose)。
|
|
||||||
|
|
||||||
您也可以到我们的 [社区论坛](https://bbs.fit2cloud.com/c/js/5) 当中进行交流沟通。
|
|
||||||
|
|
||||||
### 参与贡献
|
|
||||||
|
|
||||||
欢迎提交 PR 参与贡献。 参考 [CONTRIBUTING.md](https://github.com/jumpserver/jumpserver/blob/dev/CONTRIBUTING.md)
|
|
||||||
|
|
||||||
## 组件项目
|
|
||||||
|
|
||||||
| 项目 | 状态 | 描述 |
|
|
||||||
|--------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------|
|
|
||||||
| [Lina](https://github.com/jumpserver/lina) | <a href="https://github.com/jumpserver/lina/releases"><img alt="Lina release" src="https://img.shields.io/github/release/jumpserver/lina.svg" /></a> | JumpServer Web UI 项目 |
|
|
||||||
| [Luna](https://github.com/jumpserver/luna) | <a href="https://github.com/jumpserver/luna/releases"><img alt="Luna release" src="https://img.shields.io/github/release/jumpserver/luna.svg" /></a> | JumpServer Web Terminal 项目 |
|
|
||||||
| [KoKo](https://github.com/jumpserver/koko) | <a href="https://github.com/jumpserver/koko/releases"><img alt="Koko release" src="https://img.shields.io/github/release/jumpserver/koko.svg" /></a> | JumpServer 字符协议 Connector 项目 |
|
|
||||||
| [Lion](https://github.com/jumpserver/lion-release) | <a href="https://github.com/jumpserver/lion-release/releases"><img alt="Lion release" src="https://img.shields.io/github/release/jumpserver/lion-release.svg" /></a> | JumpServer 图形协议 Connector 项目,依赖 [Apache Guacamole](https://guacamole.apache.org/) |
|
|
||||||
| [Razor](https://github.com/jumpserver/razor) | <img alt="Chen" src="https://img.shields.io/badge/release-私有发布-red" /> | JumpServer RDP 代理 Connector 项目 |
|
|
||||||
| [Tinker](https://github.com/jumpserver/tinker) | <img alt="Tinker" src="https://img.shields.io/badge/release-私有发布-red" /> | JumpServer 远程应用 Connector 项目 |
|
|
||||||
| [Magnus](https://github.com/jumpserver/magnus-release) | <a href="https://github.com/jumpserver/magnus-release/releases"><img alt="Magnus release" src="https://img.shields.io/github/release/jumpserver/magnus-release.svg" /> | JumpServer 数据库代理 Connector 项目 |
|
|
||||||
| [Chen](https://github.com/jumpserver/chen-release) | <a href="https://github.com/jumpserver/chen-release/releases"><img alt="Chen release" src="https://img.shields.io/github/release/jumpserver/chen-release.svg" /> | JumpServer Web DB 项目,替代原来的 OmniDB |
|
|
||||||
| [Kael](https://github.com/jumpserver/kael) | <a href="https://github.com/jumpserver/kael/releases"><img alt="Kael release" src="https://img.shields.io/github/release/jumpserver/kael.svg" /> | JumpServer 连接 GPT 资产的组件项目 |
|
|
||||||
| [Wisp](https://github.com/jumpserver/wisp) | <a href="https://github.com/jumpserver/wisp/releases"><img alt="Magnus release" src="https://img.shields.io/github/release/jumpserver/wisp.svg" /> | JumpServer 各系统终端组件和 Core Api 通信的组件项目 |
|
|
||||||
| [Clients](https://github.com/jumpserver/clients) | <a href="https://github.com/jumpserver/clients/releases"><img alt="Clients release" src="https://img.shields.io/github/release/jumpserver/clients.svg" /> | JumpServer 客户端 项目 |
|
|
||||||
| [Installer](https://github.com/jumpserver/installer) | <a href="https://github.com/jumpserver/installer/releases"><img alt="Installer release" src="https://img.shields.io/github/release/jumpserver/installer.svg" /> | JumpServer 安装包 项目 |
|
|
||||||
|
|
||||||
## 安全说明
|
|
||||||
|
|
||||||
JumpServer是一款安全产品,请参考 [基本安全建议](https://docs.jumpserver.org/zh/master/install/install_security/)
|
|
||||||
进行安装部署。如果您发现安全相关问题,请直接联系我们:
|
|
||||||
|
|
||||||
- 邮箱:support@fit2cloud.com
|
|
||||||
- 电话:400-052-0755
|
|
||||||
|
|
||||||
## License & Copyright
|
|
||||||
|
|
||||||
Copyright (c) 2014-2023 飞致云 FIT2CLOUD, All rights reserved.
|
|
||||||
|
|
||||||
Licensed under The GNU General Public License version 3 (GPLv3) (the "License"); you may not use this file except in
|
|
||||||
compliance with the License. You may obtain a copy of the License at
|
|
||||||
|
|
||||||
https://www.gnu.org/licenses/gpl-3.0.html
|
https://www.gnu.org/licenses/gpl-3.0.html
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "
|
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an " AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
|
||||||
AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific
|
|
||||||
language governing permissions and limitations under the License.
|
<!-- JumpServer official link -->
|
||||||
|
[docs-link]: https://jumpserver.com/docs
|
||||||
|
[discord-link]: https://discord.com/invite/W6vYXmAQG2
|
||||||
|
[contributing-link]: https://github.com/jumpserver/jumpserver/blob/dev/CONTRIBUTING.md
|
||||||
|
|
||||||
|
<!-- JumpServer Other link-->
|
||||||
|
[license-link]: https://www.gnu.org/licenses/gpl-3.0.html
|
||||||
|
[docker-link]: https://hub.docker.com/u/jumpserver
|
||||||
|
[github-release-link]: https://github.com/jumpserver/jumpserver/releases/latest
|
||||||
|
[github-stars-link]: https://github.com/jumpserver/jumpserver
|
||||||
|
[github-issues-link]: https://github.com/jumpserver/jumpserver/issues
|
||||||
|
|
||||||
|
<!-- Shield link-->
|
||||||
|
[github-release-shield]: https://img.shields.io/github/v/release/jumpserver/jumpserver
|
||||||
|
[github-stars-shield]: https://img.shields.io/github/stars/jumpserver/jumpserver?color=%231890FF&style=flat-square
|
||||||
|
[docker-shield]: https://img.shields.io/docker/pulls/jumpserver/jms_all.svg
|
||||||
|
[license-shield]: https://img.shields.io/github/license/jumpserver/jumpserver
|
||||||
|
[discord-shield]: https://img.shields.io/discord/1194233267294052363?style=flat&logo=discord&logoColor=%23f5f5f5&labelColor=%235462eb&color=%235462eb
|
||||||
|
|
||||||
|
<!-- Image link -->
|
||||||
|
|||||||
121
README.zh-CN.md
Normal file
121
README.zh-CN.md
Normal file
@@ -0,0 +1,121 @@
|
|||||||
|
<p align="center">
|
||||||
|
<a href="https://jumpserver.org"><img src="https://download.jumpserver.org/images/jumpserver-logo.svg" alt="JumpServer" width="300" /></a>
|
||||||
|
</p>
|
||||||
|
<h3 align="center">广受欢迎的开源堡垒机</h3>
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
<a href="https://www.gnu.org/licenses/gpl-3.0.html"><img src="https://img.shields.io/github/license/jumpserver/jumpserver" alt="License: GPLv3"></a>
|
||||||
|
<a href="https://hub.docker.com/u/jumpserver"><img src="https://img.shields.io/docker/pulls/jumpserver/jms_all.svg" alt="Docker pulls"></a>
|
||||||
|
<a href="https://github.com/jumpserver/jumpserver/releases/latest"><img src="https://img.shields.io/github/v/release/jumpserver/jumpserver" alt="Latest release"></a>
|
||||||
|
<a href="https://github.com/jumpserver/jumpserver"><img src="https://img.shields.io/github/stars/jumpserver/jumpserver?color=%231890FF&style=flat-square" alt="Stars"></a>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
10 年时间,倾情投入,用心做好一款开源堡垒机。
|
||||||
|
</p>
|
||||||
|
|
||||||
|
------------------------------
|
||||||
|
## JumpServer 是什么?
|
||||||
|
|
||||||
|
JumpServer 是广受欢迎的开源堡垒机,是符合 4A 规范的专业运维安全审计系统。JumpServer 堡垒机帮助企业以更安全的方式管控和登录各种类型的资产,包括:
|
||||||
|
|
||||||
|
- **SSH**: Linux / Unix / 网络设备 等;
|
||||||
|
- **Windows**: Web 方式连接 / 原生 RDP 连接;
|
||||||
|
- **数据库**: MySQL / MariaDB / PostgreSQL / Oracle / SQLServer / ClickHouse 等;
|
||||||
|
- **NoSQL**: Redis / MongoDB 等;
|
||||||
|
- **GPT**: ChatGPT 等;
|
||||||
|
- **云服务**: Kubernetes / VMware vSphere 等;
|
||||||
|
- **Web 站点**: 各类系统的 Web 管理后台;
|
||||||
|
- **应用**: 通过 Remote App 连接各类应用。
|
||||||
|
|
||||||
|
## 产品特色
|
||||||
|
|
||||||
|
- **开源**: 零门槛,线上快速获取和安装;
|
||||||
|
- **无插件**: 仅需浏览器,极致的 Web Terminal 使用体验;
|
||||||
|
- **分布式**: 支持分布式部署和横向扩展,轻松支持大规模并发访问;
|
||||||
|
- **多云支持**: 一套系统,同时管理不同云上面的资产;
|
||||||
|
- **多租户**: 一套系统,多个子公司或部门同时使用;
|
||||||
|
- **云端存储**: 审计录像云端存储,永不丢失;
|
||||||
|
|
||||||
|
## 快速开始
|
||||||
|
|
||||||
|
- [快速入门](https://docs.jumpserver.org/zh/v3/quick_start/)
|
||||||
|
- [产品文档](https://docs.jumpserver.org)
|
||||||
|
- [在线学习](https://edu.fit2cloud.com/page/2635362)
|
||||||
|
- [知识库](https://kb.fit2cloud.com/categories/jumpserver)
|
||||||
|
|
||||||
|
## UI 展示
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## 在线体验
|
||||||
|
|
||||||
|
- 环境地址:<https://demo.jumpserver.org/>
|
||||||
|
|
||||||
|
| :warning: 注意 |
|
||||||
|
|:-----------------------------|
|
||||||
|
| 该环境仅作体验目的使用,我们会定时清理、重置数据! |
|
||||||
|
| 请勿修改体验环境用户的密码! |
|
||||||
|
| 请勿在环境中添加业务生产环境地址、用户名密码等敏感信息! |
|
||||||
|
|
||||||
|
## 案例研究
|
||||||
|
|
||||||
|
- [腾讯音乐娱乐集团:基于JumpServer的安全运维审计解决方案](https://blog.fit2cloud.com/?p=a04cdf0d-6704-4d18-9b40-9180baecd0e2)
|
||||||
|
- [腾讯海外游戏:基于JumpServer构建游戏安全运营能力](https://blog.fit2cloud.com/?p=3704)
|
||||||
|
- [万华化学:通过JumpServer管理全球化分布式IT资产,并且实现与云管平台的联动](https://blog.fit2cloud.com/?p=3504)
|
||||||
|
- [雪花啤酒:JumpServer堡垒机使用体会](https://blog.fit2cloud.com/?p=3412)
|
||||||
|
- [顺丰科技:JumpServer 堡垒机护航顺丰科技超大规模资产安全运维](https://blog.fit2cloud.com/?p=1147)
|
||||||
|
- [沐瞳游戏:通过JumpServer管控多项目分布式资产](https://blog.fit2cloud.com/?p=3213)
|
||||||
|
- [携程:JumpServer 堡垒机部署与运营实战](https://blog.fit2cloud.com/?p=851)
|
||||||
|
- [大智慧:JumpServer 堡垒机让“大智慧”的混合 IT 运维更智慧](https://blog.fit2cloud.com/?p=882)
|
||||||
|
- [小红书:JumpServer 堡垒机大规模资产跨版本迁移之路](https://blog.fit2cloud.com/?p=516)
|
||||||
|
- [中手游:JumpServer堡垒机助力中手游提升多云环境下安全运维能力](https://blog.fit2cloud.com/?p=732)
|
||||||
|
- [中通快递:JumpServer主机安全运维实践](https://blog.fit2cloud.com/?p=708)
|
||||||
|
- [东方明珠:JumpServer高效管控异构化、分布式云端资产](https://blog.fit2cloud.com/?p=687)
|
||||||
|
- [江苏农信:JumpServer堡垒机助力行业云安全运维](https://blog.fit2cloud.com/?p=666)
|
||||||
|
|
||||||
|
## 社区交流
|
||||||
|
|
||||||
|
如果您在使用过程中有任何疑问或对建议,欢迎提交 [GitHub Issue](https://github.com/jumpserver/jumpserver/issues/new/choose)。
|
||||||
|
|
||||||
|
您也可以到我们的 [社区论坛](https://bbs.fit2cloud.com/c/js/5) 当中进行交流沟通。
|
||||||
|
|
||||||
|
## 参与贡献
|
||||||
|
|
||||||
|
欢迎提交 PR 参与贡献。 参考 [CONTRIBUTING.md](https://github.com/jumpserver/jumpserver/blob/dev/CONTRIBUTING.md)
|
||||||
|
|
||||||
|
## 组件项目
|
||||||
|
|
||||||
|
|
||||||
|
| Project | Status | Description |
|
||||||
|
|--------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------|
|
||||||
|
| [Lina](https://github.com/jumpserver/lina) | <a href="https://github.com/jumpserver/lina/releases"><img alt="Lina release" src="https://img.shields.io/github/release/jumpserver/lina.svg" /></a> | JumpServer Web UI |
|
||||||
|
| [Luna](https://github.com/jumpserver/luna) | <a href="https://github.com/jumpserver/luna/releases"><img alt="Luna release" src="https://img.shields.io/github/release/jumpserver/luna.svg" /></a> | JumpServer Web Terminal |
|
||||||
|
| [KoKo](https://github.com/jumpserver/koko) | <a href="https://github.com/jumpserver/koko/releases"><img alt="Koko release" src="https://img.shields.io/github/release/jumpserver/koko.svg" /></a> | JumpServer Character Protocol Connector |
|
||||||
|
| [Lion](https://github.com/jumpserver/lion) | <a href="https://github.com/jumpserver/lion/releases"><img alt="Lion release" src="https://img.shields.io/github/release/jumpserver/lion.svg" /></a> | JumpServer Graphical Protocol Connector |
|
||||||
|
| [Chen](https://github.com/jumpserver/chen) | <a href="https://github.com/jumpserver/chen/releases"><img alt="Chen release" src="https://img.shields.io/github/release/jumpserver/chen.svg" /> | JumpServer Web DB |
|
||||||
|
| [Razor](https://github.com/jumpserver/razor) | <img alt="Chen" src="https://img.shields.io/badge/release-private-red" /> | JumpServer EE RDP Proxy Connector |
|
||||||
|
| [Tinker](https://github.com/jumpserver/tinker) | <img alt="Tinker" src="https://img.shields.io/badge/release-private-red" /> | JumpServer EE Remote Application Connector (Windows) |
|
||||||
|
| [Panda](https://github.com/jumpserver/Panda) | <img alt="Panda" src="https://img.shields.io/badge/release-private-red" /> | JumpServer EE Remote Application Connector (Linux) |
|
||||||
|
| [Magnus](https://github.com/jumpserver/magnus) | <img alt="Magnus" src="https://img.shields.io/badge/release-private-red" /> | JumpServer EE Database Proxy Connector |
|
||||||
|
## 安全说明
|
||||||
|
|
||||||
|
JumpServer是一款安全产品,请参考 [基本安全建议](https://docs.jumpserver.org/zh/master/install/install_security/)
|
||||||
|
进行安装部署。如果您发现安全相关问题,请直接联系我们:
|
||||||
|
|
||||||
|
- 邮箱:support@fit2cloud.com
|
||||||
|
- 电话:400-052-0755
|
||||||
|
|
||||||
|
## License & Copyright
|
||||||
|
|
||||||
|
Copyright (c) 2014-2024 飞致云, All rights reserved.
|
||||||
|
|
||||||
|
Licensed under The GNU General Public License version 3 (GPLv3) (the "License"); you may not use this file except in
|
||||||
|
compliance with the License. You may obtain a copy of the License at
|
||||||
|
|
||||||
|
https://www.gnu.org/licenses/gpl-3.0.html
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "
|
||||||
|
AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific
|
||||||
|
language governing permissions and limitations under the License.
|
||||||
94
README_EN.md
94
README_EN.md
@@ -1,94 +0,0 @@
|
|||||||
<p align="center"><a href="https://jumpserver.org"><img src="https://download.jumpserver.org/images/jumpserver-logo.svg" alt="JumpServer" width="300" /></a></p>
|
|
||||||
<h3 align="center">Open Source Bastion Host</h3>
|
|
||||||
|
|
||||||
<p align="center">
|
|
||||||
<a href="https://www.gnu.org/licenses/gpl-3.0.html"><img src="https://img.shields.io/github/license/jumpserver/jumpserver" alt="License: GPLv3"></a>
|
|
||||||
<a href="https://shields.io/github/downloads/jumpserver/jumpserver/total"><img src="https://shields.io/github/downloads/jumpserver/jumpserver/total" alt=" release"></a>
|
|
||||||
<a href="https://hub.docker.com/u/jumpserver"><img src="https://img.shields.io/docker/pulls/jumpserver/jms_all.svg" alt="Codacy"></a>
|
|
||||||
<a href="https://github.com/jumpserver/jumpserver"><img src="https://img.shields.io/github/stars/jumpserver/jumpserver?color=%231890FF&style=flat-square" alt="Stars"></a>
|
|
||||||
</p>
|
|
||||||
|
|
||||||
JumpServer is the world's first open-source Bastion Host and is licensed under the GPLv3. It is a 4A-compliant professional operation and maintenance security audit system.
|
|
||||||
|
|
||||||
JumpServer uses Python / Django for development, follows Web 2.0 specifications, and is equipped with an industry-leading Web Terminal solution that provides a beautiful user interface and great user experience
|
|
||||||
|
|
||||||
JumpServer adopts a distributed architecture to support multi-branch deployment across multiple cross-regional areas. The central node provides APIs, and login nodes are deployed in each branch. It can be scaled horizontally without concurrency restrictions.
|
|
||||||
|
|
||||||
Change the world by taking every little step
|
|
||||||
|
|
||||||
----
|
|
||||||
### Advantages
|
|
||||||
|
|
||||||
- Open Source: huge transparency and free to access with quick installation process.
|
|
||||||
- Distributed: support large-scale concurrent access with ease.
|
|
||||||
- No Plugin required: all you need is a browser, the ultimate Web Terminal experience.
|
|
||||||
- Multi-Cloud supported: a unified system to manage assets on different clouds at the same time
|
|
||||||
- Cloud storage: audit records are stored in the cloud. Data lost no more!
|
|
||||||
- Multi-Tenant system: multiple subsidiary companies or departments access the same system simultaneously.
|
|
||||||
- Many applications supported: link to databases, windows remote applications, and Kubernetes cluster, etc.
|
|
||||||
|
|
||||||
|
|
||||||
### JumpServer Component Projects
|
|
||||||
- [Lina](https://github.com/jumpserver/lina) JumpServer Web UI
|
|
||||||
- [Luna](https://github.com/jumpserver/luna) JumpServer Web Terminal
|
|
||||||
- [KoKo](https://github.com/jumpserver/koko) JumpServer Character protocaol Connector, replace original Python Version [Coco](https://github.com/jumpserver/coco)
|
|
||||||
- [Lion](https://github.com/jumpserver/lion-release) JumpServer Graphics protocol Connector,rely on [Apache Guacamole](https://guacamole.apache.org/)
|
|
||||||
|
|
||||||
### Contribution
|
|
||||||
If you have any good ideas or helping us to fix bugs, please submit a Pull Request and accept our thanks :)
|
|
||||||
|
|
||||||
Thanks to the following contributors for making JumpServer better everyday!
|
|
||||||
|
|
||||||
<a href="https://github.com/jumpserver/jumpserver/graphs/contributors">
|
|
||||||
<img src="https://contrib.rocks/image?repo=jumpserver/jumpserver" />
|
|
||||||
</a>
|
|
||||||
|
|
||||||
<a href="https://github.com/jumpserver/koko/graphs/contributors">
|
|
||||||
<img src="https://contrib.rocks/image?repo=jumpserver/koko" />
|
|
||||||
</a>
|
|
||||||
|
|
||||||
<a href="https://github.com/jumpserver/lina/graphs/contributors">
|
|
||||||
<img src="https://contrib.rocks/image?repo=jumpserver/lina" />
|
|
||||||
</a>
|
|
||||||
|
|
||||||
<a href="https://github.com/jumpserver/luna/graphs/contributors">
|
|
||||||
<img src="https://contrib.rocks/image?repo=jumpserver/luna" />
|
|
||||||
</a>
|
|
||||||
|
|
||||||
### Thanks to
|
|
||||||
- [Apache Guacamole](https://guacamole.apache.org/) Web page connection RDP, SSH, VNC protocol equipment. JumpServer graphical connection dependent.
|
|
||||||
- [OmniDB](https://omnidb.org/) Web page connection to databases. JumpServer Web database dependent.
|
|
||||||
|
|
||||||
|
|
||||||
### JumpServer Enterprise Version
|
|
||||||
- [Apply for it](https://jinshuju.net/f/kyOYpi)
|
|
||||||
|
|
||||||
### Case Study
|
|
||||||
|
|
||||||
- [JumpServer 堡垒机护航顺丰科技超大规模资产安全运维](https://blog.fit2cloud.com/?p=1147);
|
|
||||||
- [JumpServer 堡垒机让“大智慧”的混合 IT 运维更智慧](https://blog.fit2cloud.com/?p=882);
|
|
||||||
- [携程 JumpServer 堡垒机部署与运营实战](https://blog.fit2cloud.com/?p=851);
|
|
||||||
- [小红书的JumpServer堡垒机大规模资产跨版本迁移之路](https://blog.fit2cloud.com/?p=516);
|
|
||||||
- [JumpServer堡垒机助力中手游提升多云环境下安全运维能力](https://blog.fit2cloud.com/?p=732);
|
|
||||||
- [中通快递:JumpServer主机安全运维实践](https://blog.fit2cloud.com/?p=708);
|
|
||||||
- [东方明珠:JumpServer高效管控异构化、分布式云端资产](https://blog.fit2cloud.com/?p=687);
|
|
||||||
- [江苏农信:JumpServer堡垒机助力行业云安全运维](https://blog.fit2cloud.com/?p=666)。
|
|
||||||
|
|
||||||
### For safety instructions
|
|
||||||
|
|
||||||
JumpServer is a security product. Please refer to [Basic Security Recommendations](https://docs.jumpserver.org/zh/master/install/install_security/) for deployment and installation.
|
|
||||||
|
|
||||||
If you find a security problem, please contact us directly:
|
|
||||||
|
|
||||||
- ibuler@fit2cloud.com
|
|
||||||
- support@fit2cloud.com
|
|
||||||
- 400-052-0755
|
|
||||||
|
|
||||||
### License & Copyright
|
|
||||||
Copyright (c) 2014-2022 FIT2CLOUD Tech, Inc., All rights reserved.
|
|
||||||
|
|
||||||
Licensed under The GNU General Public License version 3 (GPLv3) (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at
|
|
||||||
|
|
||||||
https://www.gnu.org/licenses/gpl-3.0.htmll
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
|
|
||||||
@@ -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,13 @@
|
|||||||
|
from django.db.models import Q
|
||||||
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.models import Account
|
||||||
from assets.exceptions import NotSupportedTemporarilyError
|
from accounts.permissions import AccountTaskActionPermission
|
||||||
|
from accounts.tasks import (
|
||||||
|
remove_accounts_task, verify_accounts_connectivity_task, push_accounts_to_assets_task
|
||||||
|
)
|
||||||
|
from authentication.permissions import UserConfirmation, ConfirmType
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
'AccountsTaskCreateAPI',
|
'AccountsTaskCreateAPI',
|
||||||
@@ -12,38 +16,48 @@ __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()
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_account_ids(data, action):
|
||||||
|
account_type = 'gather_accounts' if action == 'remove' else 'accounts'
|
||||||
|
accounts = data.get(account_type, [])
|
||||||
|
account_ids = [str(a.id) for a in accounts]
|
||||||
|
|
||||||
|
if action == 'remove':
|
||||||
|
return account_ids
|
||||||
|
|
||||||
|
assets = data.get('assets', [])
|
||||||
|
asset_ids = [str(a.id) for a in assets]
|
||||||
|
ids = Account.objects.filter(
|
||||||
|
Q(id__in=account_ids) | Q(asset_id__in=asset_ids)
|
||||||
|
).distinct().values_list('id', flat=True)
|
||||||
|
return [str(_id) for _id in ids]
|
||||||
|
|
||||||
def perform_create(self, serializer):
|
def perform_create(self, serializer):
|
||||||
data = serializer.validated_data
|
data = serializer.validated_data
|
||||||
accounts = data.get('accounts', [])
|
action = data['action']
|
||||||
params = data.get('params')
|
ids = self.get_account_ids(data, action)
|
||||||
account_ids = [str(a.id) for a in accounts]
|
|
||||||
|
|
||||||
if data['action'] == 'push':
|
if action == 'push':
|
||||||
task = push_accounts_to_assets_task.delay(account_ids, params)
|
task = push_accounts_to_assets_task.delay(ids, data.get('params'))
|
||||||
|
elif action == 'remove':
|
||||||
|
task = remove_accounts_task.delay(ids)
|
||||||
|
elif action == 'verify':
|
||||||
|
task = verify_accounts_connectivity_task.delay(ids)
|
||||||
else:
|
else:
|
||||||
account = accounts[0]
|
raise ValueError(f"Invalid action: {action}")
|
||||||
asset = account.asset
|
|
||||||
if not asset.auto_config['ansible_enabled'] or \
|
|
||||||
not asset.auto_config['ping_enabled']:
|
|
||||||
raise NotSupportedTemporarilyError()
|
|
||||||
task = verify_accounts_connectivity_task.delay(account_ids)
|
|
||||||
|
|
||||||
data = getattr(serializer, '_data', {})
|
data = getattr(serializer, '_data', {})
|
||||||
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,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,9 +18,8 @@ __all__ = [
|
|||||||
|
|
||||||
class AccountBackupPlanViewSet(OrgBulkModelViewSet):
|
class AccountBackupPlanViewSet(OrgBulkModelViewSet):
|
||||||
model = AccountBackupAutomation
|
model = AccountBackupAutomation
|
||||||
filter_fields = ('name',)
|
filterset_fields = ('name',)
|
||||||
search_fields = filter_fields
|
search_fields = filterset_fields
|
||||||
ordering = ('name',)
|
|
||||||
serializer_class = serializers.AccountBackupSerializer
|
serializer_class = serializers.AccountBackupSerializer
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -20,8 +20,8 @@ __all__ = [
|
|||||||
class AutomationAssetsListApi(generics.ListAPIView):
|
class AutomationAssetsListApi(generics.ListAPIView):
|
||||||
model = BaseAutomation
|
model = BaseAutomation
|
||||||
serializer_class = serializers.AutomationAssetsSerializer
|
serializer_class = serializers.AutomationAssetsSerializer
|
||||||
filter_fields = ("name", "address")
|
filterset_fields = ("name", "address")
|
||||||
search_fields = filter_fields
|
search_fields = filterset_fields
|
||||||
|
|
||||||
def get_object(self):
|
def get_object(self):
|
||||||
pk = self.kwargs.get('pk')
|
pk = self.kwargs.get('pk')
|
||||||
|
|||||||
@@ -1,13 +1,17 @@
|
|||||||
# -*- 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.filters import ChangeSecretRecordFilterSet
|
||||||
from common.utils import get_object_or_none
|
from accounts.models import ChangeSecretAutomation, ChangeSecretRecord
|
||||||
|
from accounts.tasks import execute_automation_record_task
|
||||||
|
from authentication.permissions import UserConfirmation, ConfirmType
|
||||||
from orgs.mixins.api import OrgBulkModelViewSet, OrgGenericViewSet
|
from orgs.mixins.api import OrgBulkModelViewSet, OrgGenericViewSet
|
||||||
|
from rbac.permissions import RBACPermission
|
||||||
from .base import (
|
from .base import (
|
||||||
AutomationAssetsListApi, AutomationRemoveAssetApi, AutomationAddAssetApi,
|
AutomationAssetsListApi, AutomationRemoveAssetApi, AutomationAddAssetApi,
|
||||||
AutomationNodeAddRemoveApi, AutomationExecutionViewSet
|
AutomationNodeAddRemoveApi, AutomationExecutionViewSet
|
||||||
@@ -23,28 +27,53 @@ __all__ = [
|
|||||||
|
|
||||||
class ChangeSecretAutomationViewSet(OrgBulkModelViewSet):
|
class ChangeSecretAutomationViewSet(OrgBulkModelViewSet):
|
||||||
model = ChangeSecretAutomation
|
model = ChangeSecretAutomation
|
||||||
filter_fields = ('name', 'secret_type', 'secret_strategy')
|
filterset_fields = ('name', 'secret_type', 'secret_strategy')
|
||||||
search_fields = filter_fields
|
search_fields = filterset_fields
|
||||||
serializer_class = serializers.ChangeSecretAutomationSerializer
|
serializer_class = serializers.ChangeSecretAutomationSerializer
|
||||||
|
|
||||||
|
|
||||||
class ChangeSecretRecordViewSet(mixins.ListModelMixin, OrgGenericViewSet):
|
class ChangeSecretRecordViewSet(mixins.ListModelMixin, OrgGenericViewSet):
|
||||||
serializer_class = serializers.ChangeSecretRecordSerializer
|
filterset_class = ChangeSecretRecordFilterSet
|
||||||
filter_fields = ['asset', 'execution_id']
|
search_fields = ('asset__address',)
|
||||||
search_fields = ['asset__hostname']
|
tp = AutomationTypes.change_secret
|
||||||
|
serializer_classes = {
|
||||||
|
'default': serializers.ChangeSecretRecordSerializer,
|
||||||
|
'secret': serializers.ChangeSecretRecordViewSecretSerializer,
|
||||||
|
}
|
||||||
|
rbac_perms = {
|
||||||
|
'execute': 'accounts.add_changesecretexecution',
|
||||||
|
'secret': 'accounts.view_changesecretrecord',
|
||||||
|
}
|
||||||
|
|
||||||
|
def get_permissions(self):
|
||||||
|
if self.action == 'secret':
|
||||||
|
self.permission_classes = [
|
||||||
|
RBACPermission,
|
||||||
|
UserConfirmation.require(ConfirmType.MFA)
|
||||||
|
]
|
||||||
|
return super().get_permissions()
|
||||||
|
|
||||||
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_ids = request.data.get('record_ids')
|
||||||
execution = get_object_or_none(AutomationExecution, pk=eid)
|
records = self.get_queryset().filter(id__in=record_ids)
|
||||||
if execution:
|
execution_count = records.values_list('execution_id', flat=True).distinct().count()
|
||||||
queryset = queryset.filter(execution=execution)
|
if execution_count != 1:
|
||||||
return queryset
|
return Response(
|
||||||
|
{'detail': 'Only one execution is allowed to execute'},
|
||||||
|
status=status.HTTP_400_BAD_REQUEST
|
||||||
|
)
|
||||||
|
task = execute_automation_record_task.delay(record_ids, self.tp)
|
||||||
|
return Response({'task': task.id}, status=status.HTTP_200_OK)
|
||||||
|
|
||||||
|
@action(methods=['get'], detail=True, url_path='secret')
|
||||||
|
def secret(self, request, *args, **kwargs):
|
||||||
|
instance = self.get_object()
|
||||||
|
serializer = self.get_serializer(instance)
|
||||||
|
return Response(serializer.data)
|
||||||
|
|
||||||
|
|
||||||
class ChangSecretExecutionViewSet(AutomationExecutionViewSet):
|
class ChangSecretExecutionViewSet(AutomationExecutionViewSet):
|
||||||
|
|||||||
@@ -20,8 +20,8 @@ __all__ = [
|
|||||||
|
|
||||||
class GatherAccountsAutomationViewSet(OrgBulkModelViewSet):
|
class GatherAccountsAutomationViewSet(OrgBulkModelViewSet):
|
||||||
model = GatherAccountsAutomation
|
model = GatherAccountsAutomation
|
||||||
filter_fields = ('name',)
|
filterset_fields = ('name',)
|
||||||
search_fields = filter_fields
|
search_fields = filterset_fields
|
||||||
serializer_class = serializers.GatherAccountAutomationSerializer
|
serializer_class = serializers.GatherAccountAutomationSerializer
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -20,8 +20,8 @@ __all__ = [
|
|||||||
|
|
||||||
class PushAccountAutomationViewSet(OrgBulkModelViewSet):
|
class PushAccountAutomationViewSet(OrgBulkModelViewSet):
|
||||||
model = PushAccountAutomation
|
model = PushAccountAutomation
|
||||||
filter_fields = ('name', 'secret_type', 'secret_strategy')
|
filterset_fields = ('name', 'secret_type', 'secret_strategy')
|
||||||
search_fields = filter_fields
|
search_fields = filterset_fields
|
||||||
serializer_class = serializers.PushAccountAutomationSerializer
|
serializer_class = serializers.PushAccountAutomationSerializer
|
||||||
|
|
||||||
|
|
||||||
@@ -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(
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ from django.apps import AppConfig
|
|||||||
class AccountsConfig(AppConfig):
|
class AccountsConfig(AppConfig):
|
||||||
default_auto_field = 'django.db.models.BigAutoField'
|
default_auto_field = 'django.db.models.BigAutoField'
|
||||||
name = 'accounts'
|
name = 'accounts'
|
||||||
|
verbose_name = 'App Accounts'
|
||||||
|
|
||||||
def ready(self):
|
def ready(self):
|
||||||
from . import signal_handlers # noqa
|
from . import signal_handlers # noqa
|
||||||
|
|||||||
@@ -3,17 +3,25 @@ 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 django.utils.translation import gettext_lazy as _
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
|
from xlsxwriter import Workbook
|
||||||
|
|
||||||
from accounts.notifications import AccountBackupExecutionTaskMsg
|
from accounts.const 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')
|
||||||
|
split_help_text = _('The account key will be split into two parts and sent')
|
||||||
|
|
||||||
|
class RecipientsNotFound(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
class BaseAccountHandler:
|
class BaseAccountHandler:
|
||||||
@@ -67,7 +75,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
|
||||||
|
|
||||||
@@ -108,8 +116,8 @@ class AssetAccountHandler(BaseAccountHandler):
|
|||||||
data = AccountSecretSerializer(_accounts, many=True).data
|
data = AccountSecretSerializer(_accounts, many=True).data
|
||||||
cls.handler_secret(data, section)
|
cls.handler_secret(data, section)
|
||||||
data_map.update(cls.add_rows(data, header_fields, sheet_name))
|
data_map.update(cls.add_rows(data, header_fields, sheet_name))
|
||||||
|
number_of_backup_accounts = _('Number of backup accounts')
|
||||||
print('\n\033[33m- 共备份 {} 条账号\033[0m'.format(accounts.count()))
|
print('\n\033[33m- {}: {}\033[0m'.format(number_of_backup_accounts, accounts.count()))
|
||||||
return data_map
|
return data_map
|
||||||
|
|
||||||
|
|
||||||
@@ -120,9 +128,10 @@ class AccountBackupHandler:
|
|||||||
self.is_frozen = False # 任务状态冻结标志
|
self.is_frozen = False # 任务状态冻结标志
|
||||||
|
|
||||||
def create_excel(self, section='complete'):
|
def create_excel(self, section='complete'):
|
||||||
|
hint = _('Generating asset or application related backup information files')
|
||||||
print(
|
print(
|
||||||
'\n'
|
'\n'
|
||||||
'\033[32m>>> 正在生成资产或应用相关备份信息文件\033[0m'
|
f'\033[32m>>> {hint}\033[0m'
|
||||||
''
|
''
|
||||||
)
|
)
|
||||||
# Print task start date
|
# Print task start date
|
||||||
@@ -137,13 +146,16 @@ 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_index, row_data in enumerate(data):
|
||||||
ws.append(row)
|
for col_index, col_data in enumerate(row_data):
|
||||||
wb.save(filename)
|
ws.write_string(row_index, col_index, 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))
|
time_cost = _('Time cost')
|
||||||
|
file_created = _('Backup file creation completed')
|
||||||
|
print('{}: {} {}s'.format(file_created, time_cost, timedelta))
|
||||||
return files
|
return files
|
||||||
|
|
||||||
def send_backup_mail(self, files, recipients):
|
def send_backup_mail(self, files, recipients):
|
||||||
@@ -152,7 +164,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'
|
f'\033[32m>>> {_("Start sending backup emails")}\033[0m'
|
||||||
''
|
''
|
||||||
)
|
)
|
||||||
plan_name = self.plan_name
|
plan_name = self.plan_name
|
||||||
@@ -160,12 +172,37 @@ class AccountBackupHandler:
|
|||||||
if not user.secret_key:
|
if not user.secret_key:
|
||||||
attachment_list = []
|
attachment_list = []
|
||||||
else:
|
else:
|
||||||
password = user.secret_key.encode('utf8')
|
attachment = os.path.join(PATH, f'{plan_name}-{local_now_filename()}-{time.time()}.zip')
|
||||||
attachment = os.path.join(PATH, f'{plan_name}-{local_now_display()}-{time.time()}.zip')
|
encrypt_and_compress_zip_file(attachment, user.secret_key, 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)
|
||||||
print('邮件已发送至{}({})'.format(user, user.email))
|
email_sent_to = _('Email sent to')
|
||||||
|
print('{} {}({})'.format(email_sent_to, user, user.email))
|
||||||
|
for file in files:
|
||||||
|
os.remove(file)
|
||||||
|
|
||||||
|
def send_backup_obj_storage(self, files, recipients, password):
|
||||||
|
if not files:
|
||||||
|
return
|
||||||
|
recipients = ReplayStorage.objects.filter(id__in=list(recipients))
|
||||||
|
print(
|
||||||
|
'\n'
|
||||||
|
'\033[32m>>> 📃 ---> sftp \033[0m'
|
||||||
|
''
|
||||||
|
)
|
||||||
|
plan_name = self.plan_name
|
||||||
|
encrypt_file = _('Encrypting files using encryption password')
|
||||||
|
for rec in recipients:
|
||||||
|
attachment = os.path.join(PATH, f'{plan_name}-{local_now_filename()}-{time.time()}.zip')
|
||||||
|
if password:
|
||||||
|
print(f'\033[32m>>> {encrypt_file}\033[0m')
|
||||||
|
encrypt_and_compress_zip_file(attachment, password, files)
|
||||||
|
else:
|
||||||
|
zip_files(attachment, files)
|
||||||
|
attachment_list = attachment
|
||||||
|
AccountBackupByObjStorageExecutionTaskMsg(plan_name, rec).publish(attachment_list)
|
||||||
|
file_sent_to = _('The backup file will be sent to')
|
||||||
|
print('{}: {}({})'.format(file_sent_to, rec.name, rec.id))
|
||||||
for file in files:
|
for file in files:
|
||||||
os.remove(file)
|
os.remove(file)
|
||||||
|
|
||||||
@@ -173,41 +210,27 @@ class AccountBackupHandler:
|
|||||||
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('已完成对任务状态的更新')
|
finish = _('Finish')
|
||||||
|
print(f'\n{finish}\n')
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def step_finished(is_success):
|
def step_finished(is_success):
|
||||||
if is_success:
|
if is_success:
|
||||||
print('任务执行成功')
|
print(_('Success'))
|
||||||
else:
|
else:
|
||||||
print('任务执行失败')
|
print(_('Failed'))
|
||||||
|
|
||||||
def _run(self):
|
def _run(self):
|
||||||
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('下面打印发生异常的 Traceback 信息 : ')
|
|
||||||
print(e)
|
print(e)
|
||||||
error = str(e)
|
error = str(e)
|
||||||
else:
|
else:
|
||||||
@@ -217,16 +240,68 @@ 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', [])
|
||||||
|
no_assigned_sftp_server = _('The backup task has no assigned sftp server')
|
||||||
|
if not obj_recipients_part_one and not obj_recipients_part_two:
|
||||||
|
print(
|
||||||
|
'\n'
|
||||||
|
f'\033[31m>>> {no_assigned_sftp_server}\033[0m'
|
||||||
|
''
|
||||||
|
)
|
||||||
|
raise RecipientsNotFound('Not Found Recipients')
|
||||||
|
if obj_recipients_part_one and obj_recipients_part_two:
|
||||||
|
print(f'\033[32m>>> {split_help_text}\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):
|
||||||
|
|
||||||
|
warn_text = _('The backup task has no assigned recipient')
|
||||||
|
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'
|
||||||
|
f'\033[31m>>> {warn_text}\033[0m'
|
||||||
|
''
|
||||||
|
)
|
||||||
|
raise RecipientsNotFound('Not Found Recipients')
|
||||||
|
if recipients_part_one and recipients_part_two:
|
||||||
|
print(f'\033[32m>>> {split_help_text}\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()))
|
plan_start = _('Plan start')
|
||||||
|
plan_end = _('Plan end')
|
||||||
|
time_cost = _('Time cost')
|
||||||
|
error = _('An exception occurred during task execution')
|
||||||
|
print('{}: {}'.format(plan_start, local_now_display()))
|
||||||
time_start = time.time()
|
time_start = time.time()
|
||||||
try:
|
try:
|
||||||
self._run()
|
self._run()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print('任务运行出现异常')
|
print(error)
|
||||||
print('下面显示异常 Traceback 信息: ')
|
|
||||||
print(e)
|
print(e)
|
||||||
finally:
|
finally:
|
||||||
print('\n任务结束: {}'.format(local_now_display()))
|
print('\n{}: {}'.format(plan_end, local_now_display()))
|
||||||
timedelta = round((time.time() - time_start), 2)
|
timedelta = round((time.time() - time_start), 2)
|
||||||
print('用时: {}'.format(timedelta))
|
print('{}: {}s'.format(time_cost, timedelta))
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
import time
|
import time
|
||||||
|
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
from common.utils.timezone import local_now_display
|
from common.utils.timezone import local_now_display
|
||||||
from .handlers import AccountBackupHandler
|
from .handlers import AccountBackupHandler
|
||||||
@@ -19,7 +20,8 @@ class AccountBackupManager:
|
|||||||
|
|
||||||
def do_run(self):
|
def do_run(self):
|
||||||
execution = self.execution
|
execution = self.execution
|
||||||
print('\n\033[33m# 账号备份计划正在执行\033[0m')
|
account_backup_execution_being_executed = _('The account backup plan is being executed')
|
||||||
|
print(f'\n\033[33m# {account_backup_execution_being_executed}\033[0m')
|
||||||
handler = AccountBackupHandler(execution)
|
handler = AccountBackupHandler(execution)
|
||||||
handler.run()
|
handler.run()
|
||||||
|
|
||||||
@@ -32,9 +34,11 @@ class AccountBackupManager:
|
|||||||
self.date_end = timezone.now()
|
self.date_end = timezone.now()
|
||||||
|
|
||||||
print('\n\n' + '-' * 80)
|
print('\n\n' + '-' * 80)
|
||||||
print('计划执行结束 {}\n'.format(local_now_display()))
|
plan_execution_end = _('Plan execution end')
|
||||||
|
print('{} {}\n'.format(plan_execution_end, local_now_display()))
|
||||||
self.timedelta = self.time_end - self.time_start
|
self.timedelta = self.time_end - self.time_start
|
||||||
print('用时: {}s'.format(self.timedelta))
|
time_cost = _('Time cost')
|
||||||
|
print('{}: {}s'.format(time_cost, self.timedelta))
|
||||||
self.execution.timedelta = self.timedelta
|
self.execution.timedelta = self.timedelta
|
||||||
self.execution.save()
|
self.execution.save()
|
||||||
|
|
||||||
|
|||||||
@@ -13,12 +13,15 @@
|
|||||||
login_password: "{{ jms_account.secret }}"
|
login_password: "{{ jms_account.secret }}"
|
||||||
login_secret_type: "{{ jms_account.secret_type }}"
|
login_secret_type: "{{ jms_account.secret_type }}"
|
||||||
login_private_key_path: "{{ jms_account.private_key_path }}"
|
login_private_key_path: "{{ jms_account.private_key_path }}"
|
||||||
become: "{{ custom_become | default(False) }}"
|
become: "{{ jms_custom_become | default(False) }}"
|
||||||
become_method: "{{ custom_become_method | default('su') }}"
|
become_method: "{{ jms_custom_become_method | default('su') }}"
|
||||||
become_user: "{{ custom_become_user | default('') }}"
|
become_user: "{{ jms_custom_become_user | default('') }}"
|
||||||
become_password: "{{ custom_become_password | default('') }}"
|
become_password: "{{ jms_custom_become_password | default('') }}"
|
||||||
become_private_key_path: "{{ custom_become_private_key_path | default(None) }}"
|
become_private_key_path: "{{ jms_custom_become_private_key_path | default(None) }}"
|
||||||
|
old_ssh_version: "{{ jms_asset.old_ssh_version | default(False) }}"
|
||||||
|
gateway_args: "{{ jms_asset.ansible_ssh_common_args | default(None) }}"
|
||||||
register: ping_info
|
register: ping_info
|
||||||
|
delegate_to: localhost
|
||||||
|
|
||||||
- name: Change asset password (paramiko)
|
- name: Change asset password (paramiko)
|
||||||
custom_command:
|
custom_command:
|
||||||
@@ -28,11 +31,11 @@
|
|||||||
login_port: "{{ jms_asset.port }}"
|
login_port: "{{ jms_asset.port }}"
|
||||||
login_secret_type: "{{ jms_account.secret_type }}"
|
login_secret_type: "{{ jms_account.secret_type }}"
|
||||||
login_private_key_path: "{{ jms_account.private_key_path }}"
|
login_private_key_path: "{{ jms_account.private_key_path }}"
|
||||||
become: "{{ custom_become | default(False) }}"
|
become: "{{ jms_custom_become | default(False) }}"
|
||||||
become_method: "{{ custom_become_method | default('su') }}"
|
become_method: "{{ jms_custom_become_method | default('su') }}"
|
||||||
become_user: "{{ custom_become_user | default('') }}"
|
become_user: "{{ jms_custom_become_user | default('') }}"
|
||||||
become_password: "{{ custom_become_password | default('') }}"
|
become_password: "{{ jms_custom_become_password | default('') }}"
|
||||||
become_private_key_path: "{{ custom_become_private_key_path | default(None) }}"
|
become_private_key_path: "{{ jms_custom_become_private_key_path | default(None) }}"
|
||||||
name: "{{ account.username }}"
|
name: "{{ account.username }}"
|
||||||
password: "{{ account.secret }}"
|
password: "{{ account.secret }}"
|
||||||
commands: "{{ params.commands }}"
|
commands: "{{ params.commands }}"
|
||||||
@@ -40,6 +43,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 +51,11 @@
|
|||||||
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) }}"
|
||||||
|
old_ssh_version: "{{ jms_asset.old_ssh_version | default(False) }}"
|
||||||
|
gateway_args: "{{ jms_asset.ansible_ssh_common_args | default(None) }}"
|
||||||
|
delegate_to: localhost
|
||||||
|
|||||||
@@ -6,15 +6,27 @@ category:
|
|||||||
type:
|
type:
|
||||||
- all
|
- all
|
||||||
method: change_secret
|
method: change_secret
|
||||||
|
protocol: ssh
|
||||||
|
priority: 50
|
||||||
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,12 @@
|
|||||||
- 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 }}"
|
||||||
|
ca_cert: "{{ jms_asset.secret_info.ca_cert | default('') }}"
|
||||||
|
ssl_cert: "{{ jms_asset.secret_info.client_cert | default('') }}"
|
||||||
|
ssl_key: "{{ jms_asset.secret_info.client_key | default('') }}"
|
||||||
|
|
||||||
tasks:
|
tasks:
|
||||||
- name: Test MySQL connection
|
- name: Test MySQL connection
|
||||||
@@ -11,6 +15,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: "{{ ca_cert if check_ssl and ca_cert | length > 0 else omit }}"
|
||||||
|
client_cert: "{{ ssl_cert if check_ssl and ssl_cert | length > 0 else omit }}"
|
||||||
|
client_key: "{{ ssl_key if check_ssl and ssl_key | length > 0 else omit }}"
|
||||||
filter: version
|
filter: version
|
||||||
register: db_info
|
register: db_info
|
||||||
|
|
||||||
@@ -24,6 +32,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: "{{ ca_cert if check_ssl and ca_cert | length > 0 else omit }}"
|
||||||
|
client_cert: "{{ ssl_cert if check_ssl and ssl_cert | length > 0 else omit }}"
|
||||||
|
client_key: "{{ ssl_key if check_ssl and ssl_key | length > 0 else omit }}"
|
||||||
name: "{{ account.username }}"
|
name: "{{ account.username }}"
|
||||||
password: "{{ account.secret }}"
|
password: "{{ account.secret }}"
|
||||||
host: "%"
|
host: "%"
|
||||||
@@ -37,4 +49,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: "{{ ca_cert if check_ssl and ca_cert | length > 0 else omit }}"
|
||||||
|
client_cert: "{{ ssl_cert if check_ssl and ssl_cert | length > 0 else omit }}"
|
||||||
|
client_key: "{{ ssl_key if check_ssl and ssl_key | length > 0 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
|
||||||
@@ -39,3 +39,4 @@
|
|||||||
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 }}"
|
||||||
|
mode: "{{ account.mode }}"
|
||||||
|
|||||||
@@ -1,7 +1,11 @@
|
|||||||
- 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
|
||||||
|
check_ssl: "{{ jms_asset.spec_info.use_ssl }}"
|
||||||
|
ca_cert: "{{ jms_asset.secret_info.ca_cert | default('') }}"
|
||||||
|
ssl_cert: "{{ jms_asset.secret_info.client_cert | default('') }}"
|
||||||
|
ssl_key: "{{ jms_asset.secret_info.client_key | default('') }}"
|
||||||
|
|
||||||
tasks:
|
tasks:
|
||||||
- name: Test PostgreSQL connection
|
- name: Test PostgreSQL connection
|
||||||
@@ -11,6 +15,10 @@
|
|||||||
login_host: "{{ jms_asset.address }}"
|
login_host: "{{ jms_asset.address }}"
|
||||||
login_port: "{{ jms_asset.port }}"
|
login_port: "{{ jms_asset.port }}"
|
||||||
login_db: "{{ jms_asset.spec_info.db_name }}"
|
login_db: "{{ jms_asset.spec_info.db_name }}"
|
||||||
|
ca_cert: "{{ ca_cert if check_ssl and ca_cert | length > 0 else omit }}"
|
||||||
|
ssl_cert: "{{ ssl_cert if check_ssl and ssl_cert | length > 0 else omit }}"
|
||||||
|
ssl_key: "{{ ssl_key if check_ssl and ssl_key | length > 0 else omit }}"
|
||||||
|
ssl_mode: "{{ jms_asset.spec_info.pg_ssl_mode }}"
|
||||||
register: result
|
register: result
|
||||||
failed_when: not result.is_available
|
failed_when: not result.is_available
|
||||||
|
|
||||||
@@ -28,6 +36,10 @@
|
|||||||
db: "{{ jms_asset.spec_info.db_name }}"
|
db: "{{ jms_asset.spec_info.db_name }}"
|
||||||
name: "{{ account.username }}"
|
name: "{{ account.username }}"
|
||||||
password: "{{ account.secret }}"
|
password: "{{ account.secret }}"
|
||||||
|
ca_cert: "{{ ca_cert if check_ssl and ca_cert | length > 0 else omit }}"
|
||||||
|
ssl_cert: "{{ ssl_cert if check_ssl and ssl_cert | length > 0 else omit }}"
|
||||||
|
ssl_key: "{{ ssl_key if check_ssl and ssl_key | length > 0 else omit }}"
|
||||||
|
ssl_mode: "{{ jms_asset.spec_info.pg_ssl_mode }}"
|
||||||
role_attr_flags: LOGIN
|
role_attr_flags: LOGIN
|
||||||
ignore_errors: true
|
ignore_errors: true
|
||||||
when: result is succeeded
|
when: result is succeeded
|
||||||
@@ -39,3 +51,7 @@
|
|||||||
login_host: "{{ jms_asset.address }}"
|
login_host: "{{ jms_asset.address }}"
|
||||||
login_port: "{{ jms_asset.port }}"
|
login_port: "{{ jms_asset.port }}"
|
||||||
db: "{{ jms_asset.spec_info.db_name }}"
|
db: "{{ jms_asset.spec_info.db_name }}"
|
||||||
|
ca_cert: "{{ ca_cert if check_ssl and ca_cert | length > 0 else omit }}"
|
||||||
|
ssl_cert: "{{ ssl_cert if check_ssl and ssl_cert | length > 0 else omit }}"
|
||||||
|
ssl_key: "{{ ssl_key if check_ssl and ssl_key | length > 0 else omit }}"
|
||||||
|
ssl_mode: "{{ jms_asset.spec_info.pg_ssl_mode }}"
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -14,51 +14,15 @@
|
|||||||
- name: "Add {{ account.username }} user"
|
- name: "Add {{ account.username }} user"
|
||||||
ansible.builtin.user:
|
ansible.builtin.user:
|
||||||
name: "{{ account.username }}"
|
name: "{{ account.username }}"
|
||||||
shell: "{{ params.shell }}"
|
uid: "{{ params.uid | int if params.uid | length > 0 else omit }}"
|
||||||
home: "{{ params.home | default('/home/' + account.username, true) }}"
|
shell: "{{ params.shell if params.shell | length > 0 else omit }}"
|
||||||
groups: "{{ params.groups }}"
|
home: "{{ params.home if params.home | length > 0 else '/home/' + account.username }}"
|
||||||
|
groups: "{{ params.groups if params.groups | length > 0 else omit }}"
|
||||||
|
append: yes
|
||||||
expires: -1
|
expires: -1
|
||||||
state: present
|
state: present
|
||||||
when: user_info.failed
|
when: user_info.failed
|
||||||
|
|
||||||
- name: "Add {{ account.username }} group"
|
|
||||||
ansible.builtin.group:
|
|
||||||
name: "{{ account.username }}"
|
|
||||||
state: present
|
|
||||||
when: user_info.failed
|
|
||||||
|
|
||||||
- name: "Add {{ account.username }} user to group"
|
|
||||||
ansible.builtin.user:
|
|
||||||
name: "{{ account.username }}"
|
|
||||||
groups: "{{ params.groups }}"
|
|
||||||
when:
|
|
||||||
- user_info.failed
|
|
||||||
- params.groups
|
|
||||||
|
|
||||||
- name: "Change {{ account.username }} password"
|
|
||||||
ansible.builtin.user:
|
|
||||||
name: "{{ account.username }}"
|
|
||||||
password: "{{ account.secret | password_hash('des') }}"
|
|
||||||
update_password: always
|
|
||||||
ignore_errors: true
|
|
||||||
when: account.secret_type == "password"
|
|
||||||
|
|
||||||
- name: remove jumpserver ssh key
|
|
||||||
ansible.builtin.lineinfile:
|
|
||||||
dest: "{{ ssh_params.dest }}"
|
|
||||||
regexp: "{{ ssh_params.regexp }}"
|
|
||||||
state: absent
|
|
||||||
when:
|
|
||||||
- account.secret_type == "ssh_key"
|
|
||||||
- ssh_params.strategy == "set_jms"
|
|
||||||
|
|
||||||
- name: "Change {{ account.username }} SSH key"
|
|
||||||
ansible.builtin.authorized_key:
|
|
||||||
user: "{{ account.username }}"
|
|
||||||
key: "{{ account.secret }}"
|
|
||||||
exclusive: "{{ ssh_params.exclusive }}"
|
|
||||||
when: account.secret_type == "ssh_key"
|
|
||||||
|
|
||||||
- name: "Set {{ account.username }} sudo setting"
|
- name: "Set {{ account.username }} sudo setting"
|
||||||
ansible.builtin.lineinfile:
|
ansible.builtin.lineinfile:
|
||||||
dest: /etc/sudoers
|
dest: /etc/sudoers
|
||||||
@@ -67,9 +31,59 @@
|
|||||||
line: "{{ account.username + ' ALL=(ALL) NOPASSWD: ' + params.sudo }}"
|
line: "{{ account.username + ' ALL=(ALL) NOPASSWD: ' + params.sudo }}"
|
||||||
validate: visudo -cf %s
|
validate: visudo -cf %s
|
||||||
when:
|
when:
|
||||||
- user_info.failed
|
- user_info.failed or params.modify_sudo
|
||||||
- params.sudo
|
- params.sudo
|
||||||
|
|
||||||
|
- name: "Change {{ account.username }} password"
|
||||||
|
ansible.builtin.user:
|
||||||
|
name: "{{ account.username }}"
|
||||||
|
password: "{{ account.secret | password_hash('des') }}"
|
||||||
|
update_password: always
|
||||||
|
ignore_errors: true
|
||||||
|
when: account.secret_type == "password"
|
||||||
|
|
||||||
|
- name: "Get home directory for {{ account.username }}"
|
||||||
|
ansible.builtin.shell: "getent passwd {{ account.username }} | cut -d: -f6"
|
||||||
|
register: home_dir
|
||||||
|
when: account.secret_type == "ssh_key"
|
||||||
|
ignore_errors: yes
|
||||||
|
|
||||||
|
- name: "Check if home directory exists for {{ account.username }}"
|
||||||
|
ansible.builtin.stat:
|
||||||
|
path: "{{ home_dir.stdout.strip() }}"
|
||||||
|
register: home_dir_stat
|
||||||
|
when: account.secret_type == "ssh_key"
|
||||||
|
ignore_errors: yes
|
||||||
|
|
||||||
|
- name: "Ensure {{ account.username }} home directory exists"
|
||||||
|
ansible.builtin.file:
|
||||||
|
path: "{{ home_dir.stdout.strip() }}"
|
||||||
|
state: directory
|
||||||
|
owner: "{{ account.username }}"
|
||||||
|
group: "{{ account.username }}"
|
||||||
|
mode: '0750'
|
||||||
|
when:
|
||||||
|
- account.secret_type == "ssh_key"
|
||||||
|
- home_dir_stat.stat.exists == false
|
||||||
|
ignore_errors: yes
|
||||||
|
|
||||||
|
- name: Remove jumpserver ssh key
|
||||||
|
ansible.builtin.lineinfile:
|
||||||
|
dest: "{{ home_dir.stdout.strip() }}/.ssh/authorized_keys"
|
||||||
|
regexp: "{{ ssh_params.regexp }}"
|
||||||
|
state: absent
|
||||||
|
when:
|
||||||
|
- account.secret_type == "ssh_key"
|
||||||
|
- ssh_params.strategy == "set_jms"
|
||||||
|
ignore_errors: yes
|
||||||
|
|
||||||
|
- name: "Change {{ account.username }} SSH key"
|
||||||
|
ansible.builtin.authorized_key:
|
||||||
|
user: "{{ account.username }}"
|
||||||
|
key: "{{ account.secret }}"
|
||||||
|
exclusive: "{{ ssh_params.exclusive }}"
|
||||||
|
when: account.secret_type == "ssh_key"
|
||||||
|
|
||||||
- name: Refresh connection
|
- name: Refresh connection
|
||||||
ansible.builtin.meta: reset_connection
|
ansible.builtin.meta: reset_connection
|
||||||
|
|
||||||
@@ -79,8 +93,13 @@
|
|||||||
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 }}"
|
||||||
gateway_args: "{{ jms_asset.ansible_ssh_common_args | default('') }}"
|
gateway_args: "{{ jms_asset.ansible_ssh_common_args | default(None) }}"
|
||||||
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) }}"
|
||||||
|
old_ssh_version: "{{ jms_asset.old_ssh_version | default(False) }}"
|
||||||
when: account.secret_type == "password"
|
when: account.secret_type == "password"
|
||||||
delegate_to: localhost
|
delegate_to: localhost
|
||||||
|
|
||||||
@@ -90,7 +109,7 @@
|
|||||||
login_port: "{{ jms_asset.port }}"
|
login_port: "{{ jms_asset.port }}"
|
||||||
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(None) }}"
|
||||||
become: false
|
old_ssh_version: "{{ jms_asset.old_ssh_version | default(False) }}"
|
||||||
when: account.secret_type == "ssh_key"
|
when: account.secret_type == "ssh_key"
|
||||||
delegate_to: localhost
|
delegate_to: localhost
|
||||||
|
|||||||
@@ -5,6 +5,12 @@ type:
|
|||||||
- AIX
|
- AIX
|
||||||
method: change_secret
|
method: change_secret
|
||||||
params:
|
params:
|
||||||
|
- name: modify_sudo
|
||||||
|
type: bool
|
||||||
|
label: "{{ 'Modify sudo label' | trans }}"
|
||||||
|
default: False
|
||||||
|
help_text: "{{ 'Modify params sudo help text' | trans }}"
|
||||||
|
|
||||||
- name: sudo
|
- name: sudo
|
||||||
type: str
|
type: str
|
||||||
label: 'Sudo'
|
label: 'Sudo'
|
||||||
@@ -28,12 +34,23 @@ params:
|
|||||||
default: ''
|
default: ''
|
||||||
help_text: "{{ 'Params groups help text' | trans }}"
|
help_text: "{{ 'Params groups help text' | trans }}"
|
||||||
|
|
||||||
|
- name: uid
|
||||||
|
type: str
|
||||||
|
label: "{{ 'Params uid label' | trans }}"
|
||||||
|
default: ''
|
||||||
|
help_text: "{{ 'Params uid help text' | trans }}"
|
||||||
|
|
||||||
i18n:
|
i18n:
|
||||||
AIX account change secret:
|
AIX account change secret:
|
||||||
zh: '使用 Ansible 模块 user 执行账号改密 (DES)'
|
zh: '使用 Ansible 模块 user 执行账号改密 (DES)'
|
||||||
ja: 'Ansible user モジュールを使用してアカウントのパスワード変更 (DES)'
|
ja: 'Ansible user モジュールを使用してアカウントのパスワード変更 (DES)'
|
||||||
en: 'Using Ansible module user to change account secret (DES)'
|
en: 'Using Ansible module user to change account secret (DES)'
|
||||||
|
|
||||||
|
Modify params sudo help text:
|
||||||
|
zh: '如果用户存在,可以修改sudo权限'
|
||||||
|
ja: 'ユーザーが存在する場合、sudo権限を変更できます'
|
||||||
|
en: 'If the user exists, sudo permissions can be modified'
|
||||||
|
|
||||||
Params sudo help text:
|
Params sudo help text:
|
||||||
zh: '使用逗号分隔多个命令,如: /bin/whoami,/sbin/ifconfig'
|
zh: '使用逗号分隔多个命令,如: /bin/whoami,/sbin/ifconfig'
|
||||||
ja: 'コンマで区切って複数のコマンドを入力してください。例: /bin/whoami,/sbin/ifconfig'
|
ja: 'コンマで区切って複数のコマンドを入力してください。例: /bin/whoami,/sbin/ifconfig'
|
||||||
@@ -49,6 +66,16 @@ i18n:
|
|||||||
ja: 'グループを入力してください。複数のグループはコンマで区切ってください(既存のグループを入力してください)'
|
ja: 'グループを入力してください。複数のグループはコンマで区切ってください(既存のグループを入力してください)'
|
||||||
en: 'Please enter the group. Multiple groups are separated by commas (please enter the existing group)'
|
en: 'Please enter the group. Multiple groups are separated by commas (please enter the existing group)'
|
||||||
|
|
||||||
|
Params uid help text:
|
||||||
|
zh: '请输入用户ID'
|
||||||
|
ja: 'ユーザーIDを入力してください'
|
||||||
|
en: 'Please enter the user ID'
|
||||||
|
|
||||||
|
Modify sudo label:
|
||||||
|
zh: '修改 sudo 权限'
|
||||||
|
ja: 'sudo 権限を変更'
|
||||||
|
en: 'Modify sudo'
|
||||||
|
|
||||||
Params home label:
|
Params home label:
|
||||||
zh: '家目录'
|
zh: '家目录'
|
||||||
ja: 'ホームディレクトリ'
|
ja: 'ホームディレクトリ'
|
||||||
@@ -59,3 +86,7 @@ i18n:
|
|||||||
ja: 'グループ'
|
ja: 'グループ'
|
||||||
en: 'Groups'
|
en: 'Groups'
|
||||||
|
|
||||||
|
Params uid label:
|
||||||
|
zh: '用户ID'
|
||||||
|
ja: 'ユーザーID'
|
||||||
|
en: 'User ID'
|
||||||
|
|||||||
@@ -14,51 +14,15 @@
|
|||||||
- name: "Add {{ account.username }} user"
|
- name: "Add {{ account.username }} user"
|
||||||
ansible.builtin.user:
|
ansible.builtin.user:
|
||||||
name: "{{ account.username }}"
|
name: "{{ account.username }}"
|
||||||
shell: "{{ params.shell }}"
|
uid: "{{ params.uid | int if params.uid | length > 0 else omit }}"
|
||||||
home: "{{ params.home | default('/home/' + account.username, true) }}"
|
shell: "{{ params.shell if params.shell | length > 0 else omit }}"
|
||||||
groups: "{{ params.groups }}"
|
home: "{{ params.home if params.home | length > 0 else '/home/' + account.username }}"
|
||||||
|
groups: "{{ params.groups if params.groups | length > 0 else omit }}"
|
||||||
|
append: yes
|
||||||
expires: -1
|
expires: -1
|
||||||
state: present
|
state: present
|
||||||
when: user_info.failed
|
when: user_info.failed
|
||||||
|
|
||||||
- name: "Add {{ account.username }} group"
|
|
||||||
ansible.builtin.group:
|
|
||||||
name: "{{ account.username }}"
|
|
||||||
state: present
|
|
||||||
when: user_info.failed
|
|
||||||
|
|
||||||
- name: "Add {{ account.username }} user to group"
|
|
||||||
ansible.builtin.user:
|
|
||||||
name: "{{ account.username }}"
|
|
||||||
groups: "{{ params.groups }}"
|
|
||||||
when:
|
|
||||||
- user_info.failed
|
|
||||||
- params.groups
|
|
||||||
|
|
||||||
- name: "Change {{ account.username }} password"
|
|
||||||
ansible.builtin.user:
|
|
||||||
name: "{{ account.username }}"
|
|
||||||
password: "{{ account.secret | password_hash('sha512') }}"
|
|
||||||
update_password: always
|
|
||||||
ignore_errors: true
|
|
||||||
when: account.secret_type == "password"
|
|
||||||
|
|
||||||
- name: remove jumpserver ssh key
|
|
||||||
ansible.builtin.lineinfile:
|
|
||||||
dest: "{{ ssh_params.dest }}"
|
|
||||||
regexp: "{{ ssh_params.regexp }}"
|
|
||||||
state: absent
|
|
||||||
when:
|
|
||||||
- account.secret_type == "ssh_key"
|
|
||||||
- ssh_params.strategy == "set_jms"
|
|
||||||
|
|
||||||
- name: "Change {{ account.username }} SSH key"
|
|
||||||
ansible.builtin.authorized_key:
|
|
||||||
user: "{{ account.username }}"
|
|
||||||
key: "{{ account.secret }}"
|
|
||||||
exclusive: "{{ ssh_params.exclusive }}"
|
|
||||||
when: account.secret_type == "ssh_key"
|
|
||||||
|
|
||||||
- name: "Set {{ account.username }} sudo setting"
|
- name: "Set {{ account.username }} sudo setting"
|
||||||
ansible.builtin.lineinfile:
|
ansible.builtin.lineinfile:
|
||||||
dest: /etc/sudoers
|
dest: /etc/sudoers
|
||||||
@@ -67,9 +31,59 @@
|
|||||||
line: "{{ account.username + ' ALL=(ALL) NOPASSWD: ' + params.sudo }}"
|
line: "{{ account.username + ' ALL=(ALL) NOPASSWD: ' + params.sudo }}"
|
||||||
validate: visudo -cf %s
|
validate: visudo -cf %s
|
||||||
when:
|
when:
|
||||||
- user_info.failed
|
- user_info.failed or params.modify_sudo
|
||||||
- params.sudo
|
- params.sudo
|
||||||
|
|
||||||
|
- name: "Change {{ account.username }} password"
|
||||||
|
ansible.builtin.user:
|
||||||
|
name: "{{ account.username }}"
|
||||||
|
password: "{{ account.secret | password_hash('sha512') }}"
|
||||||
|
update_password: always
|
||||||
|
ignore_errors: true
|
||||||
|
when: account.secret_type == "password"
|
||||||
|
|
||||||
|
- name: "Get home directory for {{ account.username }}"
|
||||||
|
ansible.builtin.shell: "getent passwd {{ account.username }} | cut -d: -f6"
|
||||||
|
register: home_dir
|
||||||
|
when: account.secret_type == "ssh_key"
|
||||||
|
ignore_errors: yes
|
||||||
|
|
||||||
|
- name: "Check if home directory exists for {{ account.username }}"
|
||||||
|
ansible.builtin.stat:
|
||||||
|
path: "{{ home_dir.stdout.strip() }}"
|
||||||
|
register: home_dir_stat
|
||||||
|
when: account.secret_type == "ssh_key"
|
||||||
|
ignore_errors: yes
|
||||||
|
|
||||||
|
- name: "Ensure {{ account.username }} home directory exists"
|
||||||
|
ansible.builtin.file:
|
||||||
|
path: "{{ home_dir.stdout.strip() }}"
|
||||||
|
state: directory
|
||||||
|
owner: "{{ account.username }}"
|
||||||
|
group: "{{ account.username }}"
|
||||||
|
mode: '0750'
|
||||||
|
when:
|
||||||
|
- account.secret_type == "ssh_key"
|
||||||
|
- home_dir_stat.stat.exists == false
|
||||||
|
ignore_errors: yes
|
||||||
|
|
||||||
|
- name: Remove jumpserver ssh key
|
||||||
|
ansible.builtin.lineinfile:
|
||||||
|
dest: "{{ home_dir.stdout.strip() }}/.ssh/authorized_keys"
|
||||||
|
regexp: "{{ ssh_params.regexp }}"
|
||||||
|
state: absent
|
||||||
|
when:
|
||||||
|
- account.secret_type == "ssh_key"
|
||||||
|
- ssh_params.strategy == "set_jms"
|
||||||
|
ignore_errors: yes
|
||||||
|
|
||||||
|
- name: "Change {{ account.username }} SSH key"
|
||||||
|
ansible.builtin.authorized_key:
|
||||||
|
user: "{{ account.username }}"
|
||||||
|
key: "{{ account.secret }}"
|
||||||
|
exclusive: "{{ ssh_params.exclusive }}"
|
||||||
|
when: account.secret_type == "ssh_key"
|
||||||
|
|
||||||
- name: Refresh connection
|
- name: Refresh connection
|
||||||
ansible.builtin.meta: reset_connection
|
ansible.builtin.meta: reset_connection
|
||||||
|
|
||||||
@@ -79,8 +93,13 @@
|
|||||||
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 }}"
|
||||||
gateway_args: "{{ jms_asset.ansible_ssh_common_args | default('') }}"
|
gateway_args: "{{ jms_asset.ansible_ssh_common_args | default(None) }}"
|
||||||
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) }}"
|
||||||
|
old_ssh_version: "{{ jms_asset.old_ssh_version | default(False) }}"
|
||||||
when: account.secret_type == "password"
|
when: account.secret_type == "password"
|
||||||
delegate_to: localhost
|
delegate_to: localhost
|
||||||
|
|
||||||
@@ -90,7 +109,7 @@
|
|||||||
login_port: "{{ jms_asset.port }}"
|
login_port: "{{ jms_asset.port }}"
|
||||||
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(None) }}"
|
||||||
become: false
|
old_ssh_version: "{{ jms_asset.old_ssh_version | default(False) }}"
|
||||||
when: account.secret_type == "ssh_key"
|
when: account.secret_type == "ssh_key"
|
||||||
delegate_to: localhost
|
delegate_to: localhost
|
||||||
|
|||||||
@@ -6,6 +6,12 @@ type:
|
|||||||
- linux
|
- linux
|
||||||
method: change_secret
|
method: change_secret
|
||||||
params:
|
params:
|
||||||
|
- name: modify_sudo
|
||||||
|
type: bool
|
||||||
|
label: "{{ 'Modify sudo label' | trans }}"
|
||||||
|
default: False
|
||||||
|
help_text: "{{ 'Modify params sudo help text' | trans }}"
|
||||||
|
|
||||||
- name: sudo
|
- name: sudo
|
||||||
type: str
|
type: str
|
||||||
label: 'Sudo'
|
label: 'Sudo'
|
||||||
@@ -30,12 +36,23 @@ params:
|
|||||||
default: ''
|
default: ''
|
||||||
help_text: "{{ 'Params groups help text' | trans }}"
|
help_text: "{{ 'Params groups help text' | trans }}"
|
||||||
|
|
||||||
|
- name: uid
|
||||||
|
type: str
|
||||||
|
label: "{{ 'Params uid label' | trans }}"
|
||||||
|
default: ''
|
||||||
|
help_text: "{{ 'Params uid help text' | trans }}"
|
||||||
|
|
||||||
i18n:
|
i18n:
|
||||||
Posix account change secret:
|
Posix account change secret:
|
||||||
zh: '使用 Ansible 模块 user 执行账号改密 (SHA512)'
|
zh: '使用 Ansible 模块 user 执行账号改密 (SHA512)'
|
||||||
ja: 'Ansible user モジュールを使用して アカウントのパスワード変更 (SHA512)'
|
ja: 'Ansible user モジュールを使用して アカウントのパスワード変更 (SHA512)'
|
||||||
en: 'Using Ansible module user to change account secret (SHA512)'
|
en: 'Using Ansible module user to change account secret (SHA512)'
|
||||||
|
|
||||||
|
Modify params sudo help text:
|
||||||
|
zh: '如果用户存在,可以修改sudo权限'
|
||||||
|
ja: 'ユーザーが存在する場合、sudo権限を変更できます'
|
||||||
|
en: 'If the user exists, sudo permissions can be modified'
|
||||||
|
|
||||||
Params sudo help text:
|
Params sudo help text:
|
||||||
zh: '使用逗号分隔多个命令,如: /bin/whoami,/sbin/ifconfig'
|
zh: '使用逗号分隔多个命令,如: /bin/whoami,/sbin/ifconfig'
|
||||||
ja: 'コンマで区切って複数のコマンドを入力してください。例: /bin/whoami,/sbin/ifconfig'
|
ja: 'コンマで区切って複数のコマンドを入力してください。例: /bin/whoami,/sbin/ifconfig'
|
||||||
@@ -51,6 +68,16 @@ i18n:
|
|||||||
ja: 'グループを入力してください。複数のグループはコンマで区切ってください(既存のグループを入力してください)'
|
ja: 'グループを入力してください。複数のグループはコンマで区切ってください(既存のグループを入力してください)'
|
||||||
en: 'Please enter the group. Multiple groups are separated by commas (please enter the existing group)'
|
en: 'Please enter the group. Multiple groups are separated by commas (please enter the existing group)'
|
||||||
|
|
||||||
|
Params uid help text:
|
||||||
|
zh: '请输入用户ID'
|
||||||
|
ja: 'ユーザーIDを入力してください'
|
||||||
|
en: 'Please enter the user ID'
|
||||||
|
|
||||||
|
Modify sudo label:
|
||||||
|
zh: '修改 sudo 权限'
|
||||||
|
ja: 'sudo 権限を変更'
|
||||||
|
en: 'Modify sudo'
|
||||||
|
|
||||||
Params home label:
|
Params home label:
|
||||||
zh: '家目录'
|
zh: '家目录'
|
||||||
ja: 'ホームディレクトリ'
|
ja: 'ホームディレクトリ'
|
||||||
@@ -61,3 +88,7 @@ i18n:
|
|||||||
ja: 'グループ'
|
ja: 'グループ'
|
||||||
en: 'Groups'
|
en: 'Groups'
|
||||||
|
|
||||||
|
Params uid label:
|
||||||
|
zh: '用户ID'
|
||||||
|
ja: 'ユーザーID'
|
||||||
|
en: 'User ID'
|
||||||
|
|||||||
@@ -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.origin_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 }}"
|
||||||
|
gateway_args: "{{ jms_gateway | default({}) }}"
|
||||||
|
when: account.secret_type == "password"
|
||||||
|
delegate_to: localhost
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
id: change_secret_windows_rdp_verify
|
||||||
|
name: "{{ 'Windows account change secret rdp verify' | trans }}"
|
||||||
|
version: 1
|
||||||
|
method: change_secret
|
||||||
|
category: host
|
||||||
|
type:
|
||||||
|
- windows
|
||||||
|
priority: 49
|
||||||
|
params:
|
||||||
|
- name: groups
|
||||||
|
type: str
|
||||||
|
label: '用户组'
|
||||||
|
default: 'Users,Remote Desktop Users'
|
||||||
|
help_text: "{{ 'Params groups help text' | trans }}"
|
||||||
|
|
||||||
|
|
||||||
|
i18n:
|
||||||
|
Windows account change secret rdp verify:
|
||||||
|
zh: '使用 Ansible 模块 win_user 执行 Windows 账号改密 RDP 协议测试最后的可连接性'
|
||||||
|
ja: 'Ansibleモジュールwin_userはWindowsアカウントの改密RDPプロトコルテストの最後の接続性を実行する'
|
||||||
|
en: 'Using the Ansible module win_user performs Windows account encryption RDP protocol testing for final connectivity'
|
||||||
|
|
||||||
|
Params groups help text:
|
||||||
|
zh: '请输入用户组,多个用户组使用逗号分隔(需填写已存在的用户组)'
|
||||||
|
ja: 'グループを入力してください。複数のグループはコンマで区切ってください(既存のグループを入力してください)'
|
||||||
|
en: 'Please enter the group. Multiple groups are separated by commas (please enter the existing group)'
|
||||||
|
|
||||||
@@ -1,20 +1,20 @@
|
|||||||
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 django.utils.translation import gettext_lazy as _
|
||||||
|
from xlsxwriter import Workbook
|
||||||
|
|
||||||
from accounts.const import AutomationTypes, SecretType, SSHKeyStrategy, SecretStrategy
|
from accounts.const import AutomationTypes, SecretType, SSHKeyStrategy, SecretStrategy, ChangeSecretRecordStatusChoice
|
||||||
from accounts.models import ChangeSecretRecord
|
from accounts.models import ChangeSecretRecord, BaseAccountQuerySet
|
||||||
from accounts.notifications import ChangeSecretExecutionTaskMsg
|
from accounts.notifications import ChangeSecretExecutionTaskMsg, ChangeSecretFailedMsg
|
||||||
from accounts.serializers import ChangeSecretRecordBackUpSerializer
|
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 +27,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_map = self.execution.snapshot.get('record_map', {})
|
||||||
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 +50,6 @@ 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)
|
|
||||||
kwargs['regexp'] = '.*{}$'.format(secret.split()[2].strip())
|
kwargs['regexp'] = '.*{}$'.format(secret.split()[2].strip())
|
||||||
return kwargs
|
return kwargs
|
||||||
|
|
||||||
@@ -66,10 +65,10 @@ class ChangeSecretManager(AccountBasePlaybookManager):
|
|||||||
else:
|
else:
|
||||||
return self.secret_generator(secret_type).get_secret()
|
return self.secret_generator(secret_type).get_secret()
|
||||||
|
|
||||||
def get_accounts(self, privilege_account):
|
def get_accounts(self, privilege_account) -> BaseAccountQuerySet | None:
|
||||||
if not privilege_account:
|
if not privilege_account:
|
||||||
print(f'not privilege account')
|
print('Not privilege account')
|
||||||
return []
|
return
|
||||||
|
|
||||||
asset = privilege_account.asset
|
asset = privilege_account.asset
|
||||||
accounts = asset.accounts.all()
|
accounts = asset.accounts.all()
|
||||||
@@ -95,34 +94,50 @@ class ChangeSecretManager(AccountBasePlaybookManager):
|
|||||||
return host
|
return host
|
||||||
|
|
||||||
accounts = self.get_accounts(account)
|
accounts = self.get_accounts(account)
|
||||||
|
error_msg = _("No pending accounts found")
|
||||||
if not accounts:
|
if not accounts:
|
||||||
print('没有发现待改密账号: %s 用户ID: %s 类型: %s' % (
|
print(f'{asset}: {error_msg}')
|
||||||
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
|
||||||
|
|
||||||
|
if asset.type == HostTypes.WINDOWS:
|
||||||
|
accounts = accounts.filter(secret_type=SecretType.PASSWORD)
|
||||||
|
|
||||||
host['ssh_params'] = {}
|
host['ssh_params'] = {}
|
||||||
for account in accounts:
|
for account in accounts:
|
||||||
h = deepcopy(host)
|
h = deepcopy(host)
|
||||||
secret_type = account.secret_type
|
secret_type = account.secret_type
|
||||||
h['name'] += '(' + account.username + ')'
|
h['name'] += '(' + account.username + ')'
|
||||||
new_secret = self.get_secret(secret_type)
|
if self.secret_type is None:
|
||||||
|
new_secret = account.secret
|
||||||
|
else:
|
||||||
|
new_secret = self.get_secret(secret_type)
|
||||||
|
|
||||||
|
if new_secret is None:
|
||||||
|
print(f'new_secret is None, account: {account}')
|
||||||
|
continue
|
||||||
|
|
||||||
|
asset_account_id = f'{asset.id}-{account.id}'
|
||||||
|
if asset_account_id not in self.record_map:
|
||||||
|
recorder = ChangeSecretRecord(
|
||||||
|
asset=asset, account=account, execution=self.execution,
|
||||||
|
old_secret=account.secret, new_secret=new_secret,
|
||||||
|
comment=f'{account.username}@{asset.address}'
|
||||||
|
)
|
||||||
|
records.append(recorder)
|
||||||
|
else:
|
||||||
|
record_id = self.record_map[asset_account_id]
|
||||||
|
try:
|
||||||
|
recorder = ChangeSecretRecord.objects.get(id=record_id)
|
||||||
|
except ChangeSecretRecord.DoesNotExist:
|
||||||
|
print(f"Record {record_id} not found")
|
||||||
|
continue
|
||||||
|
|
||||||
recorder = ChangeSecretRecord(
|
|
||||||
asset=asset, account=account, execution=self.execution,
|
|
||||||
old_secret=account.secret, new_secret=new_secret,
|
|
||||||
)
|
|
||||||
records.append(recorder)
|
|
||||||
self.name_recorder_mapper[h['name']] = recorder
|
self.name_recorder_mapper[h['name']] = recorder
|
||||||
|
|
||||||
private_key_path = None
|
private_key_path = None
|
||||||
@@ -135,42 +150,69 @@ 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
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def require_update_version(account, recorder):
|
||||||
|
return account.secret != recorder.new_secret
|
||||||
|
|
||||||
def on_host_success(self, host, result):
|
def on_host_success(self, host, result):
|
||||||
recorder = self.name_recorder_mapper.get(host)
|
recorder = self.name_recorder_mapper.get(host)
|
||||||
if not recorder:
|
if not recorder:
|
||||||
return
|
return
|
||||||
recorder.status = 'success'
|
recorder.status = ChangeSecretRecordStatusChoice.success.value
|
||||||
recorder.date_finished = timezone.now()
|
recorder.date_finished = timezone.now()
|
||||||
recorder.save()
|
|
||||||
account = recorder.account
|
account = recorder.account
|
||||||
if not account:
|
if not account:
|
||||||
print("Account not found, deleted ?")
|
print("Account not found, deleted ?")
|
||||||
return
|
return
|
||||||
|
|
||||||
|
version_update_required = self.require_update_version(account, recorder)
|
||||||
account.secret = recorder.new_secret
|
account.secret = recorder.new_secret
|
||||||
account.save(update_fields=['secret'])
|
account.date_updated = timezone.now()
|
||||||
|
|
||||||
|
max_retries = 3
|
||||||
|
retry_count = 0
|
||||||
|
|
||||||
|
while retry_count < max_retries:
|
||||||
|
try:
|
||||||
|
recorder.save()
|
||||||
|
account_update_fields = ['secret', 'date_updated']
|
||||||
|
if version_update_required:
|
||||||
|
account_update_fields.append('version')
|
||||||
|
account.save(update_fields=account_update_fields)
|
||||||
|
break
|
||||||
|
except Exception as e:
|
||||||
|
retry_count += 1
|
||||||
|
if retry_count == max_retries:
|
||||||
|
self.on_host_error(host, str(e), result)
|
||||||
|
else:
|
||||||
|
print(f'retry {retry_count} times for {host} recorder save error: {e}')
|
||||||
|
time.sleep(1)
|
||||||
|
|
||||||
def on_host_error(self, host, error, result):
|
def on_host_error(self, host, error, result):
|
||||||
recorder = self.name_recorder_mapper.get(host)
|
recorder = self.name_recorder_mapper.get(host)
|
||||||
if not recorder:
|
if not recorder:
|
||||||
return
|
return
|
||||||
recorder.status = 'failed'
|
recorder.status = ChangeSecretRecordStatusChoice.failed.value
|
||||||
recorder.date_finished = timezone.now()
|
recorder.date_finished = timezone.now()
|
||||||
recorder.error = error
|
recorder.error = error
|
||||||
recorder.save()
|
try:
|
||||||
|
recorder.save()
|
||||||
|
except Exception as e:
|
||||||
|
print(f"\033[31m Save {host} recorder error: {e} \033[0m\n")
|
||||||
|
|
||||||
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 \
|
||||||
@@ -179,35 +221,72 @@ class ChangeSecretManager(AccountBasePlaybookManager):
|
|||||||
return False
|
return False
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_summary(recorders):
|
||||||
|
total, succeed, failed = 0, 0, 0
|
||||||
|
for recorder in recorders:
|
||||||
|
if recorder.status == ChangeSecretRecordStatusChoice.success.value:
|
||||||
|
succeed += 1
|
||||||
|
else:
|
||||||
|
failed += 1
|
||||||
|
total += 1
|
||||||
|
|
||||||
|
summary = _('Success: %s, Failed: %s, Total: %s') % (succeed, failed, total)
|
||||||
|
return summary
|
||||||
|
|
||||||
def run(self, *args, **kwargs):
|
def run(self, *args, **kwargs):
|
||||||
if not self.check_secret():
|
if self.secret_type and not self.check_secret():
|
||||||
|
self.execution.status = 'success'
|
||||||
|
self.execution.date_finished = timezone.now()
|
||||||
|
self.execution.save()
|
||||||
return
|
return
|
||||||
super().run(*args, **kwargs)
|
super().run(*args, **kwargs)
|
||||||
recorders = self.name_recorder_mapper.values()
|
recorders = list(self.name_recorder_mapper.values())
|
||||||
recorders = list(recorders)
|
summary = self.get_summary(recorders)
|
||||||
self.send_recorder_mail(recorders)
|
print(summary, end='')
|
||||||
|
|
||||||
def send_recorder_mail(self, recorders):
|
if self.record_map:
|
||||||
recipients = self.execution.recipients
|
|
||||||
if not recorders or not recipients:
|
|
||||||
return
|
return
|
||||||
|
|
||||||
recipients = User.objects.filter(id__in=list(recipients.keys()))
|
failed_recorders = [
|
||||||
|
r for r in recorders
|
||||||
|
if r.status == ChangeSecretRecordStatusChoice.failed.value
|
||||||
|
]
|
||||||
|
|
||||||
|
recipients = self.execution.recipients
|
||||||
|
recipients = User.objects.filter(id__in=list(recipients.keys()))
|
||||||
|
if not recipients:
|
||||||
|
return
|
||||||
|
|
||||||
|
if failed_recorders:
|
||||||
|
name = self.execution.snapshot.get('name')
|
||||||
|
execution_id = str(self.execution.id)
|
||||||
|
_ids = [r.id for r in failed_recorders]
|
||||||
|
asset_account_errors = ChangeSecretRecord.objects.filter(
|
||||||
|
id__in=_ids).values_list('asset__name', 'account__username', 'error')
|
||||||
|
|
||||||
|
for user in recipients:
|
||||||
|
ChangeSecretFailedMsg(name, execution_id, user, asset_account_errors).publish()
|
||||||
|
|
||||||
|
if not recorders:
|
||||||
|
return
|
||||||
|
|
||||||
|
self.send_recorder_mail(recipients, recorders, summary)
|
||||||
|
|
||||||
|
def send_recorder_mail(self, recipients, recorders, summary):
|
||||||
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
|
||||||
|
|
||||||
for user in recipients:
|
for user in recipients:
|
||||||
attachments = []
|
attachments = []
|
||||||
if user.secret_key:
|
if user.secret_key:
|
||||||
password = user.secret_key.encode('utf8')
|
attachment = os.path.join(path, f'{name}-{local_now_filename()}-{time.time()}.zip')
|
||||||
attachment = os.path.join(path, f'{name}-{local_now_display()}-{time.time()}.zip')
|
encrypt_and_compress_zip_file(attachment, user.secret_key, [filename])
|
||||||
encrypt_and_compress_zip_file(attachment, password, [filename])
|
|
||||||
attachments = [attachment]
|
attachments = [attachment]
|
||||||
ChangeSecretExecutionTaskMsg(name, user).publish(attachments)
|
ChangeSecretExecutionTaskMsg(name, user, summary).publish(attachments)
|
||||||
os.remove(filename)
|
os.remove(filename)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@@ -222,8 +301,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_index, row_data in enumerate(rows):
|
||||||
ws.append(row)
|
for col_index, col_data in enumerate(row_data):
|
||||||
wb.save(filename)
|
ws.write_string(row_index, col_index, 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,11 @@
|
|||||||
- 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 }}"
|
||||||
|
ca_cert: "{{ jms_asset.secret_info.ca_cert | default('') }}"
|
||||||
|
ssl_cert: "{{ jms_asset.secret_info.client_cert | default('') }}"
|
||||||
|
ssl_key: "{{ jms_asset.secret_info.client_key | default('') }}"
|
||||||
|
|
||||||
tasks:
|
tasks:
|
||||||
- name: Get info
|
- name: Get info
|
||||||
@@ -10,6 +14,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: "{{ ca_cert if check_ssl and ca_cert | length > 0 else omit }}"
|
||||||
|
client_cert: "{{ ssl_cert if check_ssl and ssl_cert | length > 0 else omit }}"
|
||||||
|
client_key: "{{ ssl_key if check_ssl and ssl_key | length > 0 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,11 @@
|
|||||||
- 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
|
||||||
|
check_ssl: "{{ jms_asset.spec_info.use_ssl }}"
|
||||||
|
ca_cert: "{{ jms_asset.secret_info.ca_cert | default('') }}"
|
||||||
|
ssl_cert: "{{ jms_asset.secret_info.client_cert | default('') }}"
|
||||||
|
ssl_key: "{{ jms_asset.secret_info.client_key | default('') }}"
|
||||||
|
|
||||||
tasks:
|
tasks:
|
||||||
- name: Get info
|
- name: Get info
|
||||||
@@ -11,6 +15,10 @@
|
|||||||
login_host: "{{ jms_asset.address }}"
|
login_host: "{{ jms_asset.address }}"
|
||||||
login_port: "{{ jms_asset.port }}"
|
login_port: "{{ jms_asset.port }}"
|
||||||
login_db: "{{ jms_asset.spec_info.db_name }}"
|
login_db: "{{ jms_asset.spec_info.db_name }}"
|
||||||
|
ca_cert: "{{ ca_cert if check_ssl and ca_cert | length > 0 else omit }}"
|
||||||
|
ssl_cert: "{{ ssl_cert if check_ssl and ssl_cert | length > 0 else omit }}"
|
||||||
|
ssl_key: "{{ ssl_key if check_ssl and ssl_key | length > 0 else omit }}"
|
||||||
|
ssl_mode: "{{ jms_asset.spec_info.pg_ssl_mode }}"
|
||||||
filter: "roles"
|
filter: "roles"
|
||||||
register: db_info
|
register: db_info
|
||||||
|
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ class GatherAccountsFilter:
|
|||||||
def posix_filter(info):
|
def posix_filter(info):
|
||||||
username_pattern = re.compile(r'^(\S+)')
|
username_pattern = re.compile(r'^(\S+)')
|
||||||
ip_pattern = re.compile(r'(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})')
|
ip_pattern = re.compile(r'(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})')
|
||||||
login_time_pattern = re.compile(r'\w{3} \d{2} \d{2}:\d{2}:\d{2} \d{4}')
|
login_time_pattern = re.compile(r'\w{3} \w{3}\s+\d{1,2} \d{2}:\d{2}:\d{2} \d{4}')
|
||||||
result = {}
|
result = {}
|
||||||
for line in info:
|
for line in info:
|
||||||
usernames = username_pattern.findall(line)
|
usernames = username_pattern.findall(line)
|
||||||
@@ -46,7 +46,8 @@ class GatherAccountsFilter:
|
|||||||
result[username].update({'address': ip_addr})
|
result[username].update({'address': ip_addr})
|
||||||
login_times = login_time_pattern.findall(line)
|
login_times = login_time_pattern.findall(line)
|
||||||
if login_times:
|
if login_times:
|
||||||
date = timezone.datetime.strptime(f'{login_times[0]} +0800', '%b %d %H:%M:%S %Y %z')
|
datetime_str = login_times[0].split(' ', 1)[1] + " +0800"
|
||||||
|
date = timezone.datetime.strptime(datetime_str, '%b %d %H:%M:%S %Y %z')
|
||||||
result[username].update({'date': date})
|
result[username].update({'date': date})
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
- hosts: demo
|
- hosts: demo
|
||||||
gather_facts: no
|
gather_facts: no
|
||||||
tasks:
|
tasks:
|
||||||
- name: Gather posix account
|
- name: Gather windows account
|
||||||
ansible.builtin.win_shell: net user
|
ansible.builtin.win_shell: net user
|
||||||
register: result
|
register: result
|
||||||
|
ignore_errors: true
|
||||||
|
|
||||||
- name: Define info by set_fact
|
- name: Define info by set_fact
|
||||||
ansible.builtin.set_fact:
|
ansible.builtin.set_fact:
|
||||||
|
|||||||
@@ -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,26 +47,93 @@ 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)
|
||||||
with tmp_to_org(asset.org_id):
|
self.asset_account_info[asset] = data
|
||||||
gathered_accounts = []
|
|
||||||
GatheredAccount.objects.filter(asset=asset, present=True).update(present=False)
|
@staticmethod
|
||||||
for d in data:
|
def get_nested_info(data, *keys):
|
||||||
username = d['username']
|
for key in keys:
|
||||||
gathered_account, __ = GatheredAccount.objects.update_or_create(
|
data = data.get(key, {})
|
||||||
defaults=d, asset=asset, username=username,
|
if not data:
|
||||||
)
|
break
|
||||||
gathered_accounts.append(gathered_account)
|
return data
|
||||||
if not self.is_sync_account:
|
|
||||||
return
|
|
||||||
GatheredAccount.sync_accounts(gathered_accounts)
|
|
||||||
|
|
||||||
def on_host_success(self, host, result):
|
def on_host_success(self, host, result):
|
||||||
info = result.get('debug', {}).get('res', {}).get('info', {})
|
info = self.get_nested_info(result, 'debug', 'res', 'info')
|
||||||
asset = self.host_asset_mapper.get(host)
|
asset = self.host_asset_mapper.get(host)
|
||||||
if asset and info:
|
if asset and info:
|
||||||
result = self.filter_success_result(asset.type, info)
|
result = self.filter_success_result(asset.type, info)
|
||||||
self.update_or_create_accounts(asset, result)
|
self.collect_asset_account_info(asset, result)
|
||||||
else:
|
else:
|
||||||
logger.error("Not found info".format(host))
|
print(f'\033[31m Not found {host} info \033[0m\n')
|
||||||
|
|
||||||
|
def update_or_create_accounts(self):
|
||||||
|
for asset, data in self.asset_account_info.items():
|
||||||
|
with tmp_to_org(asset.org_id):
|
||||||
|
gathered_accounts = []
|
||||||
|
GatheredAccount.objects.filter(asset=asset, present=True).update(present=False)
|
||||||
|
for d in data:
|
||||||
|
username = d['username']
|
||||||
|
gathered_account, __ = GatheredAccount.objects.update_or_create(
|
||||||
|
defaults=d, asset=asset, username=username,
|
||||||
|
)
|
||||||
|
gathered_accounts.append(gathered_account)
|
||||||
|
if not self.is_sync_account:
|
||||||
|
continue
|
||||||
|
GatheredAccount.sync_accounts(gathered_accounts)
|
||||||
|
|
||||||
|
def run(self, *args, **kwargs):
|
||||||
|
super().run(*args, **kwargs)
|
||||||
|
users, change_info = self.generate_send_users_and_change_info()
|
||||||
|
self.update_or_create_accounts()
|
||||||
|
self.send_email_if_need(users, change_info)
|
||||||
|
|
||||||
|
def generate_send_users_and_change_info(self):
|
||||||
|
recipients = self.execution.recipients
|
||||||
|
if not self.asset_username_mapper or not recipients:
|
||||||
|
return None, None
|
||||||
|
|
||||||
|
users = User.objects.filter(id__in=recipients)
|
||||||
|
if not users.exists():
|
||||||
|
return users, None
|
||||||
|
|
||||||
|
asset_ids = self.asset_username_mapper.keys()
|
||||||
|
|
||||||
|
assets = Asset.objects.filter(id__in=asset_ids).prefetch_related('accounts')
|
||||||
|
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 = defaultdict(dict)
|
||||||
|
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
|
||||||
|
|
||||||
|
if not add_usernames and not remove_usernames:
|
||||||
|
continue
|
||||||
|
|
||||||
|
change_info[str(asset_id_map[asset_id])] = {
|
||||||
|
'add_usernames': add_usernames,
|
||||||
|
'remove_usernames': remove_usernames
|
||||||
|
}
|
||||||
|
|
||||||
|
return users, dict(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,12 @@
|
|||||||
- 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 }}"
|
||||||
|
ca_cert: "{{ jms_asset.secret_info.ca_cert | default('') }}"
|
||||||
|
ssl_cert: "{{ jms_asset.secret_info.client_cert | default('') }}"
|
||||||
|
ssl_key: "{{ jms_asset.secret_info.client_key | default('') }}"
|
||||||
|
|
||||||
tasks:
|
tasks:
|
||||||
- name: Test MySQL connection
|
- name: Test MySQL connection
|
||||||
@@ -11,6 +15,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: "{{ ca_cert if check_ssl and ca_cert | length > 0 else omit }}"
|
||||||
|
client_cert: "{{ ssl_cert if check_ssl and ssl_cert | length > 0 else omit }}"
|
||||||
|
client_key: "{{ ssl_key if check_ssl and ssl_key | length > 0 else omit }}"
|
||||||
filter: version
|
filter: version
|
||||||
register: db_info
|
register: db_info
|
||||||
|
|
||||||
@@ -24,6 +32,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: "{{ ca_cert if check_ssl and ca_cert | length > 0 else omit }}"
|
||||||
|
client_cert: "{{ ssl_cert if check_ssl and ssl_cert | length > 0 else omit }}"
|
||||||
|
client_key: "{{ ssl_key if check_ssl and ssl_key | length > 0 else omit }}"
|
||||||
name: "{{ account.username }}"
|
name: "{{ account.username }}"
|
||||||
password: "{{ account.secret }}"
|
password: "{{ account.secret }}"
|
||||||
host: "%"
|
host: "%"
|
||||||
@@ -37,4 +49,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: "{{ ca_cert if check_ssl and ca_cert | length > 0 else omit }}"
|
||||||
|
client_cert: "{{ ssl_cert if check_ssl and ssl_cert | length > 0 else omit }}"
|
||||||
|
client_key: "{{ ssl_key if check_ssl and ssl_key | length > 0 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
|
||||||
@@ -39,3 +39,4 @@
|
|||||||
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 }}"
|
||||||
|
mode: "{{ account.mode }}"
|
||||||
|
|||||||
@@ -1,7 +1,11 @@
|
|||||||
- 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
|
||||||
|
check_ssl: "{{ jms_asset.spec_info.use_ssl }}"
|
||||||
|
ca_cert: "{{ jms_asset.secret_info.ca_cert | default('') }}"
|
||||||
|
ssl_cert: "{{ jms_asset.secret_info.client_cert | default('') }}"
|
||||||
|
ssl_key: "{{ jms_asset.secret_info.client_key | default('') }}"
|
||||||
|
|
||||||
tasks:
|
tasks:
|
||||||
- name: Test PostgreSQL connection
|
- name: Test PostgreSQL connection
|
||||||
@@ -11,6 +15,10 @@
|
|||||||
login_host: "{{ jms_asset.address }}"
|
login_host: "{{ jms_asset.address }}"
|
||||||
login_port: "{{ jms_asset.port }}"
|
login_port: "{{ jms_asset.port }}"
|
||||||
login_db: "{{ jms_asset.spec_info.db_name }}"
|
login_db: "{{ jms_asset.spec_info.db_name }}"
|
||||||
|
ca_cert: "{{ ca_cert if check_ssl and ca_cert | length > 0 else omit }}"
|
||||||
|
ssl_cert: "{{ ssl_cert if check_ssl and ssl_cert | length > 0 else omit }}"
|
||||||
|
ssl_key: "{{ ssl_key if check_ssl and ssl_key | length > 0 else omit }}"
|
||||||
|
ssl_mode: "{{ jms_asset.spec_info.pg_ssl_mode }}"
|
||||||
register: result
|
register: result
|
||||||
failed_when: not result.is_available
|
failed_when: not result.is_available
|
||||||
|
|
||||||
@@ -28,9 +36,14 @@
|
|||||||
db: "{{ jms_asset.spec_info.db_name }}"
|
db: "{{ jms_asset.spec_info.db_name }}"
|
||||||
name: "{{ account.username }}"
|
name: "{{ account.username }}"
|
||||||
password: "{{ account.secret }}"
|
password: "{{ account.secret }}"
|
||||||
|
ca_cert: "{{ ca_cert if check_ssl and ca_cert | length > 0 else omit }}"
|
||||||
|
ssl_cert: "{{ ssl_cert if check_ssl and ssl_cert | length > 0 else omit }}"
|
||||||
|
ssl_key: "{{ ssl_key if check_ssl and ssl_key | length > 0 else omit }}"
|
||||||
|
ssl_mode: "{{ jms_asset.spec_info.pg_ssl_mode }}"
|
||||||
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:
|
||||||
@@ -39,6 +52,12 @@
|
|||||||
login_host: "{{ jms_asset.address }}"
|
login_host: "{{ jms_asset.address }}"
|
||||||
login_port: "{{ jms_asset.port }}"
|
login_port: "{{ jms_asset.port }}"
|
||||||
db: "{{ jms_asset.spec_info.db_name }}"
|
db: "{{ jms_asset.spec_info.db_name }}"
|
||||||
|
ca_cert: "{{ ca_cert if check_ssl and ca_cert | length > 0 else omit }}"
|
||||||
|
ssl_cert: "{{ ssl_cert if check_ssl and ssl_cert | length > 0 else omit }}"
|
||||||
|
ssl_key: "{{ ssl_key if check_ssl and ssl_key | length > 0 else omit }}"
|
||||||
|
ssl_mode: "{{ jms_asset.spec_info.pg_ssl_mode }}"
|
||||||
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
|
||||||
|
|||||||
@@ -14,51 +14,15 @@
|
|||||||
- name: "Add {{ account.username }} user"
|
- name: "Add {{ account.username }} user"
|
||||||
ansible.builtin.user:
|
ansible.builtin.user:
|
||||||
name: "{{ account.username }}"
|
name: "{{ account.username }}"
|
||||||
shell: "{{ params.shell }}"
|
uid: "{{ params.uid | int if params.uid | length > 0 else omit }}"
|
||||||
home: "{{ params.home | default('/home/' + account.username, true) }}"
|
shell: "{{ params.shell if params.shell | length > 0 else omit }}"
|
||||||
groups: "{{ params.groups }}"
|
home: "{{ params.home if params.home | length > 0 else '/home/' + account.username }}"
|
||||||
|
groups: "{{ params.groups if params.groups | length > 0 else omit }}"
|
||||||
|
append: yes
|
||||||
expires: -1
|
expires: -1
|
||||||
state: present
|
state: present
|
||||||
when: user_info.failed
|
when: user_info.failed
|
||||||
|
|
||||||
- name: "Add {{ account.username }} group"
|
|
||||||
ansible.builtin.group:
|
|
||||||
name: "{{ account.username }}"
|
|
||||||
state: present
|
|
||||||
when: user_info.failed
|
|
||||||
|
|
||||||
- name: "Add {{ account.username }} user to group"
|
|
||||||
ansible.builtin.user:
|
|
||||||
name: "{{ account.username }}"
|
|
||||||
groups: "{{ params.groups }}"
|
|
||||||
when:
|
|
||||||
- user_info.failed
|
|
||||||
- params.groups
|
|
||||||
|
|
||||||
- name: "Change {{ account.username }} password"
|
|
||||||
ansible.builtin.user:
|
|
||||||
name: "{{ account.username }}"
|
|
||||||
password: "{{ account.secret | password_hash('des') }}"
|
|
||||||
update_password: always
|
|
||||||
ignore_errors: true
|
|
||||||
when: account.secret_type == "password"
|
|
||||||
|
|
||||||
- name: remove jumpserver ssh key
|
|
||||||
ansible.builtin.lineinfile:
|
|
||||||
dest: "{{ ssh_params.dest }}"
|
|
||||||
regexp: "{{ ssh_params.regexp }}"
|
|
||||||
state: absent
|
|
||||||
when:
|
|
||||||
- account.secret_type == "ssh_key"
|
|
||||||
- ssh_params.strategy == "set_jms"
|
|
||||||
|
|
||||||
- name: "Change {{ account.username }} SSH key"
|
|
||||||
ansible.builtin.authorized_key:
|
|
||||||
user: "{{ account.username }}"
|
|
||||||
key: "{{ account.secret }}"
|
|
||||||
exclusive: "{{ ssh_params.exclusive }}"
|
|
||||||
when: account.secret_type == "ssh_key"
|
|
||||||
|
|
||||||
- name: "Set {{ account.username }} sudo setting"
|
- name: "Set {{ account.username }} sudo setting"
|
||||||
ansible.builtin.lineinfile:
|
ansible.builtin.lineinfile:
|
||||||
dest: /etc/sudoers
|
dest: /etc/sudoers
|
||||||
@@ -67,9 +31,59 @@
|
|||||||
line: "{{ account.username + ' ALL=(ALL) NOPASSWD: ' + params.sudo }}"
|
line: "{{ account.username + ' ALL=(ALL) NOPASSWD: ' + params.sudo }}"
|
||||||
validate: visudo -cf %s
|
validate: visudo -cf %s
|
||||||
when:
|
when:
|
||||||
- user_info.failed
|
- user_info.failed or params.modify_sudo
|
||||||
- params.sudo
|
- params.sudo
|
||||||
|
|
||||||
|
- name: "Change {{ account.username }} password"
|
||||||
|
ansible.builtin.user:
|
||||||
|
name: "{{ account.username }}"
|
||||||
|
password: "{{ account.secret | password_hash('des') }}"
|
||||||
|
update_password: always
|
||||||
|
ignore_errors: true
|
||||||
|
when: account.secret_type == "password"
|
||||||
|
|
||||||
|
- name: "Get home directory for {{ account.username }}"
|
||||||
|
ansible.builtin.shell: "getent passwd {{ account.username }} | cut -d: -f6"
|
||||||
|
register: home_dir
|
||||||
|
when: account.secret_type == "ssh_key"
|
||||||
|
ignore_errors: yes
|
||||||
|
|
||||||
|
- name: "Check if home directory exists for {{ account.username }}"
|
||||||
|
ansible.builtin.stat:
|
||||||
|
path: "{{ home_dir.stdout.strip() }}"
|
||||||
|
register: home_dir_stat
|
||||||
|
when: account.secret_type == "ssh_key"
|
||||||
|
ignore_errors: yes
|
||||||
|
|
||||||
|
- name: "Ensure {{ account.username }} home directory exists"
|
||||||
|
ansible.builtin.file:
|
||||||
|
path: "{{ home_dir.stdout.strip() }}"
|
||||||
|
state: directory
|
||||||
|
owner: "{{ account.username }}"
|
||||||
|
group: "{{ account.username }}"
|
||||||
|
mode: '0750'
|
||||||
|
when:
|
||||||
|
- account.secret_type == "ssh_key"
|
||||||
|
- home_dir_stat.stat.exists == false
|
||||||
|
ignore_errors: yes
|
||||||
|
|
||||||
|
- name: Remove jumpserver ssh key
|
||||||
|
ansible.builtin.lineinfile:
|
||||||
|
dest: "{{ home_dir.stdout.strip() }}/.ssh/authorized_keys"
|
||||||
|
regexp: "{{ ssh_params.regexp }}"
|
||||||
|
state: absent
|
||||||
|
when:
|
||||||
|
- account.secret_type == "ssh_key"
|
||||||
|
- ssh_params.strategy == "set_jms"
|
||||||
|
ignore_errors: yes
|
||||||
|
|
||||||
|
- name: "Change {{ account.username }} SSH key"
|
||||||
|
ansible.builtin.authorized_key:
|
||||||
|
user: "{{ account.username }}"
|
||||||
|
key: "{{ account.secret }}"
|
||||||
|
exclusive: "{{ ssh_params.exclusive }}"
|
||||||
|
when: account.secret_type == "ssh_key"
|
||||||
|
|
||||||
- name: Refresh connection
|
- name: Refresh connection
|
||||||
ansible.builtin.meta: reset_connection
|
ansible.builtin.meta: reset_connection
|
||||||
|
|
||||||
@@ -79,8 +93,13 @@
|
|||||||
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 }}"
|
||||||
gateway_args: "{{ jms_asset.ansible_ssh_common_args | default('') }}"
|
gateway_args: "{{ jms_asset.ansible_ssh_common_args | default(None) }}"
|
||||||
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) }}"
|
||||||
|
old_ssh_version: "{{ jms_asset.old_ssh_version | default(False) }}"
|
||||||
when: account.secret_type == "password"
|
when: account.secret_type == "password"
|
||||||
delegate_to: localhost
|
delegate_to: localhost
|
||||||
|
|
||||||
@@ -90,8 +109,8 @@
|
|||||||
login_port: "{{ jms_asset.port }}"
|
login_port: "{{ jms_asset.port }}"
|
||||||
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(None) }}"
|
||||||
become: false
|
old_ssh_version: "{{ jms_asset.old_ssh_version | default(False) }}"
|
||||||
when: account.secret_type == "ssh_key"
|
when: account.secret_type == "ssh_key"
|
||||||
delegate_to: localhost
|
delegate_to: localhost
|
||||||
|
|
||||||
|
|||||||
@@ -5,11 +5,17 @@ type:
|
|||||||
- AIX
|
- AIX
|
||||||
method: push_account
|
method: push_account
|
||||||
params:
|
params:
|
||||||
|
- name: modify_sudo
|
||||||
|
type: bool
|
||||||
|
label: "{{ 'Modify sudo label' | trans }}"
|
||||||
|
default: False
|
||||||
|
help_text: "{{ 'Modify params sudo help text' | trans }}"
|
||||||
|
|
||||||
- name: sudo
|
- name: sudo
|
||||||
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 +24,69 @@ 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 }}"
|
||||||
|
|
||||||
|
- name: uid
|
||||||
|
type: str
|
||||||
|
label: "{{ 'Params uid label' | trans }}"
|
||||||
|
default: ''
|
||||||
|
help_text: "{{ 'Params uid 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)'
|
||||||
|
|
||||||
|
Modify params sudo help text:
|
||||||
|
zh: '如果用户存在,可以修改sudo权限'
|
||||||
|
ja: 'ユーザーが存在する場合、sudo権限を変更できます'
|
||||||
|
en: 'If the user exists, sudo permissions can be modified'
|
||||||
|
|
||||||
|
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 uid help text:
|
||||||
|
zh: '请输入用户ID'
|
||||||
|
ja: 'ユーザーIDを入力してください'
|
||||||
|
en: 'Please enter the user ID'
|
||||||
|
|
||||||
|
Modify sudo label:
|
||||||
|
zh: '修改 sudo 权限'
|
||||||
|
ja: 'sudo 権限を変更'
|
||||||
|
en: 'Modify sudo'
|
||||||
|
|
||||||
|
Params home label:
|
||||||
|
zh: '家目录'
|
||||||
|
ja: 'ホームディレクトリ'
|
||||||
|
en: 'Home'
|
||||||
|
|
||||||
|
Params groups label:
|
||||||
|
zh: '用户组'
|
||||||
|
ja: 'グループ'
|
||||||
|
en: 'Groups'
|
||||||
|
|
||||||
|
Params uid label:
|
||||||
|
zh: '用户ID'
|
||||||
|
ja: 'ユーザーID'
|
||||||
|
en: 'User ID'
|
||||||
|
|||||||
@@ -14,51 +14,15 @@
|
|||||||
- name: "Add {{ account.username }} user"
|
- name: "Add {{ account.username }} user"
|
||||||
ansible.builtin.user:
|
ansible.builtin.user:
|
||||||
name: "{{ account.username }}"
|
name: "{{ account.username }}"
|
||||||
shell: "{{ params.shell }}"
|
uid: "{{ params.uid | int if params.uid | length > 0 else omit }}"
|
||||||
home: "{{ params.home | default('/home/' + account.username, true) }}"
|
shell: "{{ params.shell if params.shell | length > 0 else omit }}"
|
||||||
groups: "{{ params.groups }}"
|
home: "{{ params.home if params.home | length > 0 else '/home/' + account.username }}"
|
||||||
|
groups: "{{ params.groups if params.groups | length > 0 else omit }}"
|
||||||
|
append: yes
|
||||||
expires: -1
|
expires: -1
|
||||||
state: present
|
state: present
|
||||||
when: user_info.failed
|
when: user_info.failed
|
||||||
|
|
||||||
- name: "Add {{ account.username }} group"
|
|
||||||
ansible.builtin.group:
|
|
||||||
name: "{{ account.username }}"
|
|
||||||
state: present
|
|
||||||
when: user_info.failed
|
|
||||||
|
|
||||||
- name: "Add {{ account.username }} user to group"
|
|
||||||
ansible.builtin.user:
|
|
||||||
name: "{{ account.username }}"
|
|
||||||
groups: "{{ params.groups }}"
|
|
||||||
when:
|
|
||||||
- user_info.failed
|
|
||||||
- params.groups
|
|
||||||
|
|
||||||
- name: "Change {{ account.username }} password"
|
|
||||||
ansible.builtin.user:
|
|
||||||
name: "{{ account.username }}"
|
|
||||||
password: "{{ account.secret | password_hash('sha512') }}"
|
|
||||||
update_password: always
|
|
||||||
ignore_errors: true
|
|
||||||
when: account.secret_type == "password"
|
|
||||||
|
|
||||||
- name: remove jumpserver ssh key
|
|
||||||
ansible.builtin.lineinfile:
|
|
||||||
dest: "{{ ssh_params.dest }}"
|
|
||||||
regexp: "{{ ssh_params.regexp }}"
|
|
||||||
state: absent
|
|
||||||
when:
|
|
||||||
- account.secret_type == "ssh_key"
|
|
||||||
- ssh_params.strategy == "set_jms"
|
|
||||||
|
|
||||||
- name: "Change {{ account.username }} SSH key"
|
|
||||||
ansible.builtin.authorized_key:
|
|
||||||
user: "{{ account.username }}"
|
|
||||||
key: "{{ account.secret }}"
|
|
||||||
exclusive: "{{ ssh_params.exclusive }}"
|
|
||||||
when: account.secret_type == "ssh_key"
|
|
||||||
|
|
||||||
- name: "Set {{ account.username }} sudo setting"
|
- name: "Set {{ account.username }} sudo setting"
|
||||||
ansible.builtin.lineinfile:
|
ansible.builtin.lineinfile:
|
||||||
dest: /etc/sudoers
|
dest: /etc/sudoers
|
||||||
@@ -67,9 +31,59 @@
|
|||||||
line: "{{ account.username + ' ALL=(ALL) NOPASSWD: ' + params.sudo }}"
|
line: "{{ account.username + ' ALL=(ALL) NOPASSWD: ' + params.sudo }}"
|
||||||
validate: visudo -cf %s
|
validate: visudo -cf %s
|
||||||
when:
|
when:
|
||||||
- user_info.failed
|
- user_info.failed or params.modify_sudo
|
||||||
- params.sudo
|
- params.sudo
|
||||||
|
|
||||||
|
- name: "Change {{ account.username }} password"
|
||||||
|
ansible.builtin.user:
|
||||||
|
name: "{{ account.username }}"
|
||||||
|
password: "{{ account.secret | password_hash('sha512') }}"
|
||||||
|
update_password: always
|
||||||
|
ignore_errors: true
|
||||||
|
when: account.secret_type == "password"
|
||||||
|
|
||||||
|
- name: "Get home directory for {{ account.username }}"
|
||||||
|
ansible.builtin.shell: "getent passwd {{ account.username }} | cut -d: -f6"
|
||||||
|
register: home_dir
|
||||||
|
when: account.secret_type == "ssh_key"
|
||||||
|
ignore_errors: yes
|
||||||
|
|
||||||
|
- name: "Check if home directory exists for {{ account.username }}"
|
||||||
|
ansible.builtin.stat:
|
||||||
|
path: "{{ home_dir.stdout.strip() }}"
|
||||||
|
register: home_dir_stat
|
||||||
|
when: account.secret_type == "ssh_key"
|
||||||
|
ignore_errors: yes
|
||||||
|
|
||||||
|
- name: "Ensure {{ account.username }} home directory exists"
|
||||||
|
ansible.builtin.file:
|
||||||
|
path: "{{ home_dir.stdout.strip() }}"
|
||||||
|
state: directory
|
||||||
|
owner: "{{ account.username }}"
|
||||||
|
group: "{{ account.username }}"
|
||||||
|
mode: '0750'
|
||||||
|
when:
|
||||||
|
- account.secret_type == "ssh_key"
|
||||||
|
- home_dir_stat.stat.exists == false
|
||||||
|
ignore_errors: yes
|
||||||
|
|
||||||
|
- name: Remove jumpserver ssh key
|
||||||
|
ansible.builtin.lineinfile:
|
||||||
|
dest: "{{ home_dir.stdout.strip() }}/.ssh/authorized_keys"
|
||||||
|
regexp: "{{ ssh_params.regexp }}"
|
||||||
|
state: absent
|
||||||
|
when:
|
||||||
|
- account.secret_type == "ssh_key"
|
||||||
|
- ssh_params.strategy == "set_jms"
|
||||||
|
ignore_errors: yes
|
||||||
|
|
||||||
|
- name: "Change {{ account.username }} SSH key"
|
||||||
|
ansible.builtin.authorized_key:
|
||||||
|
user: "{{ account.username }}"
|
||||||
|
key: "{{ account.secret }}"
|
||||||
|
exclusive: "{{ ssh_params.exclusive }}"
|
||||||
|
when: account.secret_type == "ssh_key"
|
||||||
|
|
||||||
- name: Refresh connection
|
- name: Refresh connection
|
||||||
ansible.builtin.meta: reset_connection
|
ansible.builtin.meta: reset_connection
|
||||||
|
|
||||||
@@ -79,8 +93,13 @@
|
|||||||
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 }}"
|
||||||
gateway_args: "{{ jms_asset.ansible_ssh_common_args | default('') }}"
|
gateway_args: "{{ jms_asset.ansible_ssh_common_args | default(None) }}"
|
||||||
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) }}"
|
||||||
|
old_ssh_version: "{{ jms_asset.old_ssh_version | default(False) }}"
|
||||||
when: account.secret_type == "password"
|
when: account.secret_type == "password"
|
||||||
delegate_to: localhost
|
delegate_to: localhost
|
||||||
|
|
||||||
@@ -90,8 +109,8 @@
|
|||||||
login_port: "{{ jms_asset.port }}"
|
login_port: "{{ jms_asset.port }}"
|
||||||
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(None) }}"
|
||||||
become: false
|
old_ssh_version: "{{ jms_asset.old_ssh_version | default(False) }}"
|
||||||
when: account.secret_type == "ssh_key"
|
when: account.secret_type == "ssh_key"
|
||||||
delegate_to: localhost
|
delegate_to: localhost
|
||||||
|
|
||||||
|
|||||||
@@ -6,11 +6,17 @@ type:
|
|||||||
- linux
|
- linux
|
||||||
method: push_account
|
method: push_account
|
||||||
params:
|
params:
|
||||||
|
- name: modify_sudo
|
||||||
|
type: bool
|
||||||
|
label: "{{ 'Modify sudo label' | trans }}"
|
||||||
|
default: False
|
||||||
|
help_text: "{{ 'Modify params sudo help text' | trans }}"
|
||||||
|
|
||||||
- name: sudo
|
- name: sudo
|
||||||
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 +26,69 @@ 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 }}"
|
||||||
|
|
||||||
|
- name: uid
|
||||||
|
type: str
|
||||||
|
label: "{{ 'Params uid label' | trans }}"
|
||||||
|
default: ''
|
||||||
|
help_text: "{{ 'Params uid 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)'
|
||||||
|
|
||||||
|
Modify params sudo help text:
|
||||||
|
zh: '如果用户存在,可以修改sudo权限'
|
||||||
|
ja: 'ユーザーが存在する場合、sudo権限を変更できます'
|
||||||
|
en: 'If the user exists, sudo permissions can be modified'
|
||||||
|
|
||||||
|
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 uid help text:
|
||||||
|
zh: '请输入用户ID'
|
||||||
|
ja: 'ユーザーIDを入力してください'
|
||||||
|
en: 'Please enter the user ID'
|
||||||
|
|
||||||
|
Modify sudo label:
|
||||||
|
zh: '修改 sudo 权限'
|
||||||
|
ja: 'sudo 権限を変更'
|
||||||
|
en: 'Modify sudo'
|
||||||
|
|
||||||
|
Params home label:
|
||||||
|
zh: '家目录'
|
||||||
|
ja: 'ホームディレクトリ'
|
||||||
|
en: 'Home'
|
||||||
|
|
||||||
|
Params groups label:
|
||||||
|
zh: '用户组'
|
||||||
|
ja: 'グループ'
|
||||||
|
en: 'Groups'
|
||||||
|
|
||||||
|
Params uid label:
|
||||||
|
zh: '用户ID'
|
||||||
|
ja: 'ユーザーID'
|
||||||
|
en: 'User ID'
|
||||||
@@ -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.origin_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 }}"
|
||||||
|
gateway_args: "{{ jms_gateway | default({}) }}"
|
||||||
|
when: account.secret_type == "password"
|
||||||
|
delegate_to: localhost
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
id: push_account_windows_rdp_verify
|
||||||
|
name: "{{ 'Windows account push rdp verify' | trans }}"
|
||||||
|
version: 1
|
||||||
|
method: push_account
|
||||||
|
category: host
|
||||||
|
type:
|
||||||
|
- windows
|
||||||
|
priority: 49
|
||||||
|
params:
|
||||||
|
- name: groups
|
||||||
|
type: str
|
||||||
|
label: '用户组'
|
||||||
|
default: 'Users,Remote Desktop Users'
|
||||||
|
help_text: "{{ 'Params groups help text' | trans }}"
|
||||||
|
|
||||||
|
i18n:
|
||||||
|
Windows account push rdp verify:
|
||||||
|
zh: '使用 Ansible 模块 win_user 执行 Windows 账号推送(最后使用 Python 模块 pyfreerdp 验证账号的可连接性)'
|
||||||
|
ja: 'Ansible モジュール win_user を使用して Windows アカウントのプッシュを実行します (最後に Python モジュール pyfreerdp を使用してアカウントの接続性を確認します)'
|
||||||
|
en: 'Use the Ansible module win_user to perform Windows account push (finally use the Python module pyfreerdp to verify the connectability of the account)'
|
||||||
|
|
||||||
|
Params groups help text:
|
||||||
|
zh: '请输入用户组,多个用户组使用逗号分隔(需填写已存在的用户组)'
|
||||||
|
ja: 'グループを入力してください。複数のグループはコンマで区切ってください(既存のグループを入力してください)'
|
||||||
|
en: 'Please enter the group. Multiple groups are separated by commas (please enter the existing group)'
|
||||||
@@ -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,16 @@ logger = get_logger(__name__)
|
|||||||
|
|
||||||
|
|
||||||
class PushAccountManager(ChangeSecretManager, AccountBasePlaybookManager):
|
class PushAccountManager(ChangeSecretManager, AccountBasePlaybookManager):
|
||||||
ansible_account_prefer = ''
|
|
||||||
|
@staticmethod
|
||||||
|
def require_update_version(account, recorder):
|
||||||
|
account.skip_history_when_saving = True
|
||||||
|
return False
|
||||||
|
|
||||||
@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,22 @@
|
|||||||
|
- hosts: mysql
|
||||||
|
gather_facts: no
|
||||||
|
vars:
|
||||||
|
ansible_python_interpreter: /opt/py3/bin/python
|
||||||
|
check_ssl: "{{ jms_asset.spec_info.use_ssl and not jms_asset.spec_info.allow_invalid_cert }}"
|
||||||
|
ca_cert: "{{ jms_asset.secret_info.ca_cert | default('') }}"
|
||||||
|
ssl_cert: "{{ jms_asset.secret_info.client_cert | default('') }}"
|
||||||
|
ssl_key: "{{ jms_asset.secret_info.client_key | default('') }}"
|
||||||
|
|
||||||
|
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: "{{ ca_cert if check_ssl and ca_cert | length > 0 else omit }}"
|
||||||
|
client_cert: "{{ ssl_cert if check_ssl and ssl_cert | length > 0 else omit }}"
|
||||||
|
client_key: "{{ ssl_key if check_ssl and ssl_key | length > 0 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,23 @@
|
|||||||
|
- hosts: postgresql
|
||||||
|
gather_facts: no
|
||||||
|
vars:
|
||||||
|
ansible_python_interpreter: /opt/py3/bin/python
|
||||||
|
check_ssl: "{{ jms_asset.spec_info.use_ssl }}"
|
||||||
|
ca_cert: "{{ jms_asset.secret_info.ca_cert | default('') }}"
|
||||||
|
ssl_cert: "{{ jms_asset.secret_info.client_cert | default('') }}"
|
||||||
|
ssl_key: "{{ jms_asset.secret_info.client_key | default('') }}"
|
||||||
|
|
||||||
|
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 }}"
|
||||||
|
ca_cert: "{{ ca_cert if check_ssl and ca_cert | length > 0 else omit }}"
|
||||||
|
ssl_cert: "{{ ssl_cert if check_ssl and ssl_cert | length > 0 else omit }}"
|
||||||
|
ssl_key: "{{ ssl_key if check_ssl and ssl_key | length > 0 else omit }}"
|
||||||
|
ssl_mode: "{{ jms_asset.spec_info.pg_ssl_mode }}"
|
||||||
|
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
|
||||||
28
apps/accounts/automations/remove_account/host/posix/main.yml
Normal file
28
apps/accounts/automations/remove_account/host/posix/main.yml
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
- 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 != ""
|
||||||
|
ignore_errors: yes
|
||||||
|
|
||||||
|
- 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 != ""
|
||||||
|
ignore_errors: yes
|
||||||
|
|
||||||
|
- 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,7 @@
|
|||||||
|
- hosts: windows
|
||||||
|
gather_facts: no
|
||||||
|
tasks:
|
||||||
|
- name: "Remove account"
|
||||||
|
ansible.windows.win_user:
|
||||||
|
name: "{{ account.username }}"
|
||||||
|
state: absent
|
||||||
@@ -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
|
||||||
70
apps/accounts/automations/remove_account/manager.py
Normal file
70
apps/accounts/automations/remove_account/manager.py
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
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
|
||||||
|
try:
|
||||||
|
Account.objects.filter(
|
||||||
|
asset_id=asset.id,
|
||||||
|
username=gather_account.username
|
||||||
|
).delete()
|
||||||
|
gather_account.delete()
|
||||||
|
except Exception as e:
|
||||||
|
print(f'\033[31m Delete account {gather_account.username} failed: {e} \033[0m\n')
|
||||||
@@ -3,6 +3,7 @@
|
|||||||
vars:
|
vars:
|
||||||
ansible_shell_type: sh
|
ansible_shell_type: sh
|
||||||
ansible_connection: local
|
ansible_connection: local
|
||||||
|
ansible_python_interpreter: /opt/py3/bin/python
|
||||||
|
|
||||||
tasks:
|
tasks:
|
||||||
- name: Verify account (pyfreerdp)
|
- name: Verify account (pyfreerdp)
|
||||||
@@ -12,4 +13,3 @@
|
|||||||
login_user: "{{ account.username }}"
|
login_user: "{{ account.username }}"
|
||||||
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 }}"
|
|
||||||
|
|||||||
@@ -5,9 +5,11 @@ category:
|
|||||||
type:
|
type:
|
||||||
- windows
|
- windows
|
||||||
method: verify_account
|
method: verify_account
|
||||||
|
protocol: rdp
|
||||||
|
priority: 1
|
||||||
|
|
||||||
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,10 @@
|
|||||||
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) }}"
|
||||||
|
old_ssh_version: "{{ jms_asset.old_ssh_version | default(False) }}"
|
||||||
|
gateway_args: "{{ jms_asset.ansible_ssh_common_args | default(None) }}"
|
||||||
|
|||||||
@@ -6,9 +6,11 @@ category:
|
|||||||
type:
|
type:
|
||||||
- all
|
- all
|
||||||
method: verify_account
|
method: verify_account
|
||||||
|
protocol: ssh
|
||||||
|
priority: 50
|
||||||
|
|
||||||
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,11 @@
|
|||||||
- 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 }}"
|
||||||
|
ca_cert: "{{ jms_asset.secret_info.ca_cert | default('') }}"
|
||||||
|
ssl_cert: "{{ jms_asset.secret_info.client_cert | default('') }}"
|
||||||
|
ssl_key: "{{ jms_asset.secret_info.client_key | default('') }}"
|
||||||
|
|
||||||
tasks:
|
tasks:
|
||||||
- name: Verify account
|
- name: Verify account
|
||||||
@@ -10,4 +14,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: "{{ ca_cert if check_ssl and ca_cert | length > 0 else omit }}"
|
||||||
|
client_cert: "{{ ssl_cert if check_ssl and ssl_cert | length > 0 else omit }}"
|
||||||
|
client_key: "{{ ssl_key if check_ssl and ssl_key | length > 0 else omit }}"
|
||||||
filter: version
|
filter: version
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user