mirror of
https://github.com/jumpserver/jumpserver.git
synced 2025-12-17 09:32:35 +00:00
Compare commits
412 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d420d94d71 | ||
|
|
e90e61e8dd | ||
|
|
4c48204e16 | ||
|
|
bddcd8475d | ||
|
|
5f8d84df66 | ||
|
|
cee87ae4d7 | ||
|
|
79a2d4e039 | ||
|
|
4f5e360991 | ||
|
|
8e86173cb8 | ||
|
|
08bc3d14aa | ||
|
|
19b91a6c1f | ||
|
|
c50330e055 | ||
|
|
f5d9dedae1 | ||
|
|
ffb400d70d | ||
|
|
2291cfeaae | ||
|
|
400d37ffca | ||
|
|
14efd9afc1 | ||
|
|
cfca519158 | ||
|
|
23361fdba9 | ||
|
|
1b0d23fbf4 | ||
|
|
de4ef7d1b5 | ||
|
|
046342ceee | ||
|
|
47195e2c44 | ||
|
|
947c9e6216 | ||
|
|
e1af380ad5 | ||
|
|
9e8579d5b4 | ||
|
|
b8397e7db9 | ||
|
|
8ed8d6f01c | ||
|
|
ea607c6177 | ||
|
|
fa52e2bf5e | ||
|
|
02fc9a730b | ||
|
|
aa744c0fec | ||
|
|
02d0c7e4e7 | ||
|
|
0c34a41381 | ||
|
|
8ed3da85f2 | ||
|
|
de5b501ebf | ||
|
|
ea5a54f9c7 | ||
|
|
6338ecc6fe | ||
|
|
be17fe6c31 | ||
|
|
a18c97aec0 | ||
|
|
27c10fcae1 | ||
|
|
539babcc97 | ||
|
|
0436487bdb | ||
|
|
f466904a1c | ||
|
|
1d6bdc9b6b | ||
|
|
d965ac0781 | ||
|
|
6035241efb | ||
|
|
0771b804d1 | ||
|
|
a2c6e5f3fb | ||
|
|
c39041fe7b | ||
|
|
22588c52a9 | ||
|
|
daef154622 | ||
|
|
7b9c4b300d | ||
|
|
819853eae4 | ||
|
|
f686f9f107 | ||
|
|
8a89ee7ac0 | ||
|
|
696295cf0d | ||
|
|
d99a3455cd | ||
|
|
7f5b0618c6 | ||
|
|
0f1d9bc3eb | ||
|
|
8f6b8b5a11 | ||
|
|
4da0fadcc4 | ||
|
|
f504413d7f | ||
|
|
9b5803f2a2 | ||
|
|
d95e7c2e24 | ||
|
|
a1ded0c737 | ||
|
|
bedc83bd3a | ||
|
|
c9f3e4b28d | ||
|
|
05bbd22c44 | ||
|
|
d00ef2b051 | ||
|
|
efc538a569 | ||
|
|
c1de9151b8 | ||
|
|
2898d25bf8 | ||
|
|
68e2de81d8 | ||
|
|
dd5802316d | ||
|
|
6f1ab1e09a | ||
|
|
6096ccc30a | ||
|
|
ddbd142ea3 | ||
|
|
61d8328337 | ||
|
|
4caa704abe | ||
|
|
b75d69de5d | ||
|
|
10fa122e2f | ||
|
|
00ff1644cb | ||
|
|
2b51a7590e | ||
|
|
30d07820c7 | ||
|
|
c51ebd62df | ||
|
|
593e28d7fa | ||
|
|
89f1a1653d | ||
|
|
ad311c15ca | ||
|
|
b10623c970 | ||
|
|
7d17c1a450 | ||
|
|
100b1553b6 | ||
|
|
76af71bbbe | ||
|
|
9607ab5164 | ||
|
|
61078ee2ed | ||
|
|
6a720cde0a | ||
|
|
a2a5d5e08b | ||
|
|
9c2cc65ce8 | ||
|
|
ee3cdcd9e4 | ||
|
|
89492410aa | ||
|
|
b324c6cc8a | ||
|
|
6b189e6162 | ||
|
|
a07cab9ae7 | ||
|
|
751bd35349 | ||
|
|
d6aaf23abb | ||
|
|
f096014d03 | ||
|
|
7f03639c34 | ||
|
|
3963881226 | ||
|
|
fb279dbc39 | ||
|
|
785e4cc3e4 | ||
|
|
dd846d4183 | ||
|
|
9169f3546a | ||
|
|
7e2c0d0a2d | ||
|
|
66c60ef5be | ||
|
|
f095998096 | ||
|
|
d06e5d0001 | ||
|
|
c8f420f62d | ||
|
|
02550b38f8 | ||
|
|
50531d3b97 | ||
|
|
db7ad81103 | ||
|
|
d72ec653f4 | ||
|
|
7950718582 | ||
|
|
998321f090 | ||
|
|
1fa258da3e | ||
|
|
8dbe61100b | ||
|
|
d7f9f3b670 | ||
|
|
8b18f46613 | ||
|
|
eb49beaf46 | ||
|
|
3971fce561 | ||
|
|
2f81196874 | ||
|
|
411102ed85 | ||
|
|
125dc2adf5 | ||
|
|
6001175629 | ||
|
|
41e39c9614 | ||
|
|
19de79fadf | ||
|
|
6b7df10d50 | ||
|
|
ce269e315a | ||
|
|
dfc8654d96 | ||
|
|
ea07f9e56a | ||
|
|
bbbd011cc2 | ||
|
|
6962430e6a | ||
|
|
ca1b82330e | ||
|
|
f4bd06b970 | ||
|
|
d0bf5b46f6 | ||
|
|
3c707996e0 | ||
|
|
ac0a673818 | ||
|
|
1ed6c7e01d | ||
|
|
adcabf69ed | ||
|
|
0b92e43e20 | ||
|
|
9c1a6b8565 | ||
|
|
fc8d226005 | ||
|
|
f3955a47f6 | ||
|
|
0020fe7be0 | ||
|
|
cea56a2f7e | ||
|
|
e3cf6cc476 | ||
|
|
57fccc9baf | ||
|
|
fbcb0da349 | ||
|
|
877a053717 | ||
|
|
d293a03649 | ||
|
|
08e0c5fdf5 | ||
|
|
ac906a5d52 | ||
|
|
9ad8e53743 | ||
|
|
a67ee976b4 | ||
|
|
dfa12239d6 | ||
|
|
4737e2cf4a | ||
|
|
d3d8fcbbb3 | ||
|
|
a64aa89b3f | ||
|
|
a22f36a06a | ||
|
|
17fa139bc9 | ||
|
|
77bcb05d80 | ||
|
|
4e9012cc07 | ||
|
|
b3dce27309 | ||
|
|
bccf3a0340 | ||
|
|
358b3a1891 | ||
|
|
5a2f6bdfc9 | ||
|
|
768eb033eb | ||
|
|
d7d554daf5 | ||
|
|
780b1104de | ||
|
|
eeba0a4bfc | ||
|
|
b2ee8c8216 | ||
|
|
26edd2f040 | ||
|
|
270ed5e2f8 | ||
|
|
b2bff22387 | ||
|
|
1ca71f78ed | ||
|
|
fa24a8e2f3 | ||
|
|
b9c1a89f51 | ||
|
|
a2bbf11f9d | ||
|
|
1d084311c5 | ||
|
|
cb0fd937c8 | ||
|
|
13fc2aa73c | ||
|
|
5d9979ec03 | ||
|
|
e4f21b8a5f | ||
|
|
9403b76333 | ||
|
|
666df6ffef | ||
|
|
9cc3942b3d | ||
|
|
42852c368c | ||
|
|
4d4644dddd | ||
|
|
471411a1aa | ||
|
|
db12bc07e8 | ||
|
|
618ee0b2f9 | ||
|
|
39ba52e4de | ||
|
|
a8ef405939 | ||
|
|
09f7ddd28a | ||
|
|
da4337168f | ||
|
|
f13966e061 | ||
|
|
f4b5a302a1 | ||
|
|
dd955530f1 | ||
|
|
50b64f6cf5 | ||
|
|
a5b21f94c2 | ||
|
|
9e3e183f95 | ||
|
|
9ec3147b5f | ||
|
|
79fa134621 | ||
|
|
ef4132d2c5 | ||
|
|
b31a08ed8d | ||
|
|
cdd47f4bc6 | ||
|
|
269a5e9d52 | ||
|
|
dd0d1d3592 | ||
|
|
c06368d812 | ||
|
|
96ef56da67 | ||
|
|
0a1b379dcd | ||
|
|
54926f7c70 | ||
|
|
a48d0046a9 | ||
|
|
852435c7d5 | ||
|
|
b19d9c8754 | ||
|
|
e92c82568d | ||
|
|
c6e19a2989 | ||
|
|
58edf02179 | ||
|
|
3e9bafadec | ||
|
|
70af478f66 | ||
|
|
d7121296f2 | ||
|
|
a76b243226 | ||
|
|
5bd276b9ce | ||
|
|
abd4e87bc2 | ||
|
|
40d8a71bf8 | ||
|
|
aad804f1af | ||
|
|
ee15f2d3d7 | ||
|
|
7c31b4ee30 | ||
|
|
25e7249957 | ||
|
|
d10db0aa62 | ||
|
|
d87ece00bd | ||
|
|
fca3936a79 | ||
|
|
2c2334b618 | ||
|
|
9e31a5064b | ||
|
|
954f86f8a9 | ||
|
|
a3d32c901d | ||
|
|
ce5ddf7873 | ||
|
|
29ebdb03e7 | ||
|
|
53c3c90e2d | ||
|
|
4bcd47df64 | ||
|
|
d51323faef | ||
|
|
e8163167c5 | ||
|
|
e762a5d8ae | ||
|
|
dd85e2d74f | ||
|
|
96a66e555f | ||
|
|
120f0dd3ad | ||
|
|
de43df8370 | ||
|
|
459176550d | ||
|
|
4112ad21c3 | ||
|
|
df8baede43 | ||
|
|
5bd4a882cc | ||
|
|
370d944396 | ||
|
|
c056cde2b7 | ||
|
|
93c0f11a5f | ||
|
|
91ea738dcd | ||
|
|
0d3478c728 | ||
|
|
c271d3276a | ||
|
|
dfd1ececdb | ||
|
|
4683ae8c09 | ||
|
|
db3fca0409 | ||
|
|
9f4cb2e790 | ||
|
|
7e9d1fc945 | ||
|
|
af018ea262 | ||
|
|
71ccfe66ec | ||
|
|
a991a6c56c | ||
|
|
9a29cda210 | ||
|
|
aee20a6c05 | ||
|
|
499c52800e | ||
|
|
4a2f7d21f6 | ||
|
|
44d92b9dec | ||
|
|
1e9310bf0c | ||
|
|
1b750cf51d | ||
|
|
e9125d1228 | ||
|
|
c85df4cf42 | ||
|
|
09a5b63240 | ||
|
|
f9bc7ec4aa | ||
|
|
d59a293bb9 | ||
|
|
cb2b8bb70b | ||
|
|
86c81c42de | ||
|
|
5c2b54ad3b | ||
|
|
b79aaff4a0 | ||
|
|
3fd8e5755d | ||
|
|
3604ef4228 | ||
|
|
24272d3162 | ||
|
|
a99d22708c | ||
|
|
dc35a8c52b | ||
|
|
fc90ced2b0 | ||
|
|
7bfe8816a3 | ||
|
|
b4008338c6 | ||
|
|
6058f1bdc0 | ||
|
|
5708e57631 | ||
|
|
ba353271ad | ||
|
|
adfc22ae85 | ||
|
|
ef2ecb225a | ||
|
|
9574d03c12 | ||
|
|
00d3caf80c | ||
|
|
2333a29a56 | ||
|
|
b3c5674213 | ||
|
|
f372f1e417 | ||
|
|
a86378601a | ||
|
|
6a73cd6b77 | ||
|
|
3022ca983c | ||
|
|
8f8e781376 | ||
|
|
998505e999 | ||
|
|
1c95b67154 | ||
|
|
2837dcf40e | ||
|
|
271ec1bfe0 | ||
|
|
41e147d4b2 | ||
|
|
d2f1309900 | ||
|
|
0025b2483e | ||
|
|
a6d586efb4 | ||
|
|
f0c0ba3653 | ||
|
|
d6eb4bcbd2 | ||
|
|
bfd77aa1b0 | ||
|
|
cc57fcacce | ||
|
|
7d3b60232c | ||
|
|
10996f573a | ||
|
|
a7ca9ccfe9 | ||
|
|
c6f92a462f | ||
|
|
a341b55f43 | ||
|
|
3c68b880a7 | ||
|
|
42c35b0271 | ||
|
|
8d8f479da6 | ||
|
|
9d8c1bb317 | ||
|
|
ed117ceac3 | ||
|
|
1ac9d727ef | ||
|
|
a0bb25e558 | ||
|
|
51d6090fdc | ||
|
|
d402de012b | ||
|
|
2a183e34ac | ||
|
|
7d111b6efb | ||
|
|
0ba7ca6373 | ||
|
|
51e5733f1c | ||
|
|
3626bf8df6 | ||
|
|
312213f1c5 | ||
|
|
d285daa1c1 | ||
|
|
f4c29a262a | ||
|
|
b98ccf8b3d | ||
|
|
ef7886b25b | ||
|
|
89b42ce51b | ||
|
|
e5c93dc50f | ||
|
|
50d8389fff | ||
|
|
5edacf369b | ||
|
|
7a39552bb2 | ||
|
|
e61227d694 | ||
|
|
0901b95ce0 | ||
|
|
fd7e821f11 | ||
|
|
ac3415d95c | ||
|
|
b0b174bb2a | ||
|
|
3c568510cf | ||
|
|
a1ed59d116 | ||
|
|
7708812556 | ||
|
|
24a98eb747 | ||
|
|
60fd5a2e91 | ||
|
|
9932e7eadd | ||
|
|
73102fceb0 | ||
|
|
5e177b6ce5 | ||
|
|
38b121421f | ||
|
|
a6366a2dd4 | ||
|
|
fa21c83db3 | ||
|
|
f20a4beef3 | ||
|
|
cc2e42c77a | ||
|
|
bcb4e04200 | ||
|
|
848ea0cf3c | ||
|
|
20cc4ea320 | ||
|
|
5e7d474bb7 | ||
|
|
b72f8a7241 | ||
|
|
35e9c21ec5 | ||
|
|
dcd35310cd | ||
|
|
32a8e150da | ||
|
|
cabdc3ad42 | ||
|
|
653b996d84 | ||
|
|
2262b0ecb5 | ||
|
|
eccbf46300 | ||
|
|
440cd13fcc | ||
|
|
20c1f4a293 | ||
|
|
feb42961ef | ||
|
|
3eaed62186 | ||
|
|
a3f472137f | ||
|
|
d4bb501ef9 | ||
|
|
c4b25fbdbd | ||
|
|
197364d42d | ||
|
|
6eb9986c75 | ||
|
|
e40d65871b | ||
|
|
a236de1eff | ||
|
|
a261d69cd2 | ||
|
|
efb31d6f37 | ||
|
|
ebaa8d2637 | ||
|
|
bb27be0924 | ||
|
|
4e5ab5a605 | ||
|
|
7c850a8a1e | ||
|
|
1ec4cbdf38 | ||
|
|
a112d3c99d | ||
|
|
5a6e13721d | ||
|
|
90090a7fc7 | ||
|
|
338ab5c634 | ||
|
|
632627db11 | ||
|
|
20b7b794d8 | ||
|
|
19d29d6637 | ||
|
|
c824ae4478 | ||
|
|
3cdb81cf4a | ||
|
|
378eee0402 | ||
|
|
c991a73632 |
@@ -1,5 +1,4 @@
|
|||||||
.git
|
.git
|
||||||
logs/*
|
|
||||||
data/*
|
data/*
|
||||||
.github
|
.github
|
||||||
tmp/*
|
tmp/*
|
||||||
|
|||||||
3
.github/ISSUE_TEMPLATE/----.md
vendored
3
.github/ISSUE_TEMPLATE/----.md
vendored
@@ -6,8 +6,7 @@ labels: 类型:需求
|
|||||||
assignees:
|
assignees:
|
||||||
- ibuler
|
- ibuler
|
||||||
- baijiangjie
|
- baijiangjie
|
||||||
|
- wojiushixiaobai
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
**请描述您的需求或者改进建议.**
|
**请描述您的需求或者改进建议.**
|
||||||
|
|||||||
31
.github/workflows/issue-comment.yml
vendored
31
.github/workflows/issue-comment.yml
vendored
@@ -21,17 +21,44 @@ jobs:
|
|||||||
actions: 'remove-labels'
|
actions: 'remove-labels'
|
||||||
labels: '状态:待反馈'
|
labels: '状态:待反馈'
|
||||||
|
|
||||||
add-label-if-not-author:
|
add-label-if-is-member:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
if: (github.event.issue.user.id != github.event.comment.user.id) && !github.event.issue.pull_request && (github.event.issue.state == 'open')
|
|
||||||
steps:
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
|
||||||
|
- name: Get Organization name
|
||||||
|
id: org_name
|
||||||
|
run: echo "data=$(echo '${{ github.repository }}' | cut -d '/' -f 1)" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
|
- name: Get Organization public members
|
||||||
|
uses: octokit/request-action@v2.x
|
||||||
|
id: members
|
||||||
|
with:
|
||||||
|
route: GET /orgs/${{ steps.org_name.outputs.data }}/public_members
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: Process public members data
|
||||||
|
# 将 members 中的数据转化为 login 字段的拼接字符串
|
||||||
|
id: member_names
|
||||||
|
run: echo "data=$(echo '${{ steps.members.outputs.data }}' | jq '[.[].login] | join(",")')" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
|
|
||||||
|
- run: "echo members: '${{ steps.members.outputs.data }}'"
|
||||||
|
- run: "echo member names: '${{ steps.member_names.outputs.data }}'"
|
||||||
|
- run: "echo comment user: '${{ github.event.comment.user.login }}'"
|
||||||
|
- run: "echo contains? : '${{ contains(steps.member_names.outputs.data, github.event.comment.user.login) }}'"
|
||||||
|
|
||||||
- name: Add require replay label
|
- name: Add require replay label
|
||||||
|
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: 'add-labels'
|
actions: 'add-labels'
|
||||||
labels: '状态:待反馈'
|
labels: '状态:待反馈'
|
||||||
|
|
||||||
- name: Remove require handle label
|
- name: Remove require handle label
|
||||||
|
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'
|
||||||
|
|||||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -35,7 +35,6 @@ celerybeat-schedule.db
|
|||||||
docs/_build/
|
docs/_build/
|
||||||
xpack
|
xpack
|
||||||
xpack.bak
|
xpack.bak
|
||||||
logs/*
|
|
||||||
### Vagrant ###
|
### Vagrant ###
|
||||||
.vagrant/
|
.vagrant/
|
||||||
release/*
|
release/*
|
||||||
|
|||||||
33
Dockerfile
33
Dockerfile
@@ -1,4 +1,4 @@
|
|||||||
FROM python:3.9-slim as stage-build
|
FROM jumpserver/python:3.9-slim-buster as stage-build
|
||||||
ARG TARGETARCH
|
ARG TARGETARCH
|
||||||
|
|
||||||
ARG VERSION
|
ARG VERSION
|
||||||
@@ -8,7 +8,7 @@ WORKDIR /opt/jumpserver
|
|||||||
ADD . .
|
ADD . .
|
||||||
RUN cd utils && bash -ixeu build.sh
|
RUN cd utils && bash -ixeu build.sh
|
||||||
|
|
||||||
FROM python:3.9-slim
|
FROM jumpserver/python:3.9-slim-buster
|
||||||
ARG TARGETARCH
|
ARG TARGETARCH
|
||||||
MAINTAINER JumpServer Team <ibuler@qq.com>
|
MAINTAINER JumpServer Team <ibuler@qq.com>
|
||||||
|
|
||||||
@@ -24,9 +24,11 @@ ARG DEPENDENCIES=" \
|
|||||||
libjpeg-dev \
|
libjpeg-dev \
|
||||||
libldap2-dev \
|
libldap2-dev \
|
||||||
libsasl2-dev \
|
libsasl2-dev \
|
||||||
|
libssl-dev \
|
||||||
libxml2-dev \
|
libxml2-dev \
|
||||||
libxmlsec1-dev \
|
libxmlsec1-dev \
|
||||||
libxmlsec1-openssl \
|
libxmlsec1-openssl \
|
||||||
|
freerdp2-dev \
|
||||||
libaio-dev"
|
libaio-dev"
|
||||||
|
|
||||||
ARG TOOLS=" \
|
ARG TOOLS=" \
|
||||||
@@ -65,27 +67,36 @@ RUN --mount=type=cache,target=/var/cache/apt,sharing=locked,id=core \
|
|||||||
|
|
||||||
ARG DOWNLOAD_URL=https://download.jumpserver.org
|
ARG DOWNLOAD_URL=https://download.jumpserver.org
|
||||||
|
|
||||||
RUN mkdir -p /opt/oracle/ \
|
RUN set -ex \
|
||||||
&& cd /opt/oracle/ \
|
&& \
|
||||||
&& wget ${DOWNLOAD_URL}/public/instantclient-basiclite-linux.${TARGETARCH}-19.10.0.0.0.zip \
|
if [ "${TARGETARCH}" == "amd64" ] || [ "${TARGETARCH}" == "arm64" ]; then \
|
||||||
&& unzip instantclient-basiclite-linux.${TARGETARCH}-19.10.0.0.0.zip \
|
mkdir -p /opt/oracle; \
|
||||||
&& sh -c "echo /opt/oracle/instantclient_19_10 > /etc/ld.so.conf.d/oracle-instantclient.conf" \
|
cd /opt/oracle; \
|
||||||
&& ldconfig \
|
wget ${DOWNLOAD_URL}/public/instantclient-basiclite-linux.${TARGETARCH}-19.10.0.0.0.zip; \
|
||||||
&& rm -f instantclient-basiclite-linux.${TARGETARCH}-19.10.0.0.0.zip
|
unzip instantclient-basiclite-linux.${TARGETARCH}-19.10.0.0.0.zip; \
|
||||||
|
echo "/opt/oracle/instantclient_19_10" > /etc/ld.so.conf.d/oracle-instantclient.conf; \
|
||||||
|
ldconfig; \
|
||||||
|
rm -f instantclient-basiclite-linux.${TARGETARCH}-19.10.0.0.0.zip; \
|
||||||
|
fi
|
||||||
|
|
||||||
WORKDIR /tmp/build
|
WORKDIR /tmp/build
|
||||||
COPY ./requirements ./requirements
|
COPY ./requirements ./requirements
|
||||||
|
|
||||||
ARG PIP_MIRROR=https://pypi.douban.com/simple
|
ARG PIP_MIRROR=https://pypi.douban.com/simple
|
||||||
ENV PIP_MIRROR=$PIP_MIRROR
|
|
||||||
ARG PIP_JMS_MIRROR=https://pypi.douban.com/simple
|
ARG PIP_JMS_MIRROR=https://pypi.douban.com/simple
|
||||||
ENV PIP_JMS_MIRROR=$PIP_JMS_MIRROR
|
|
||||||
|
|
||||||
RUN --mount=type=cache,target=/root/.cache/pip \
|
RUN --mount=type=cache,target=/root/.cache/pip \
|
||||||
set -ex \
|
set -ex \
|
||||||
&& pip config set global.index-url ${PIP_MIRROR} \
|
&& pip config set global.index-url ${PIP_MIRROR} \
|
||||||
&& pip install --upgrade pip \
|
&& pip install --upgrade pip \
|
||||||
&& pip install --upgrade setuptools wheel \
|
&& pip install --upgrade setuptools wheel \
|
||||||
|
&& \
|
||||||
|
if [ "${TARGETARCH}" == "loong64" ]; then \
|
||||||
|
pip install https://download.jumpserver.org/pypi/simple/cryptography/cryptography-38.0.4-cp39-cp39-linux_loongarch64.whl; \
|
||||||
|
pip install https://download.jumpserver.org/pypi/simple/greenlet/greenlet-1.1.2-cp39-cp39-linux_loongarch64.whl; \
|
||||||
|
pip install https://download.jumpserver.org/pypi/simple/PyNaCl/PyNaCl-1.5.0-cp39-cp39-linux_loongarch64.whl; \
|
||||||
|
pip install https://download.jumpserver.org/pypi/simple/grpcio/grpcio-1.54.2-cp39-cp39-linux_loongarch64.whl; \
|
||||||
|
fi \
|
||||||
&& pip install $(grep -E 'jms|jumpserver' requirements/requirements.txt) -i ${PIP_JMS_MIRROR} \
|
&& pip install $(grep -E 'jms|jumpserver' requirements/requirements.txt) -i ${PIP_JMS_MIRROR} \
|
||||||
&& pip install -r requirements/requirements.txt
|
&& pip install -r requirements/requirements.txt
|
||||||
|
|
||||||
|
|||||||
@@ -1,96 +0,0 @@
|
|||||||
FROM python:3.9-slim as stage-build
|
|
||||||
ARG TARGETARCH
|
|
||||||
|
|
||||||
ARG VERSION
|
|
||||||
ENV VERSION=$VERSION
|
|
||||||
|
|
||||||
WORKDIR /opt/jumpserver
|
|
||||||
ADD . .
|
|
||||||
RUN cd utils && bash -ixeu build.sh
|
|
||||||
|
|
||||||
FROM python:3.9-slim
|
|
||||||
ARG TARGETARCH
|
|
||||||
MAINTAINER JumpServer Team <ibuler@qq.com>
|
|
||||||
|
|
||||||
ARG BUILD_DEPENDENCIES=" \
|
|
||||||
g++ \
|
|
||||||
make \
|
|
||||||
pkg-config"
|
|
||||||
|
|
||||||
ARG DEPENDENCIES=" \
|
|
||||||
freetds-dev \
|
|
||||||
libpq-dev \
|
|
||||||
libffi-dev \
|
|
||||||
libjpeg-dev \
|
|
||||||
libldap2-dev \
|
|
||||||
libsasl2-dev \
|
|
||||||
libssl-dev \
|
|
||||||
libxml2-dev \
|
|
||||||
libxmlsec1-dev \
|
|
||||||
libxmlsec1-openssl \
|
|
||||||
libaio-dev"
|
|
||||||
|
|
||||||
ARG TOOLS=" \
|
|
||||||
ca-certificates \
|
|
||||||
curl \
|
|
||||||
default-libmysqlclient-dev \
|
|
||||||
default-mysql-client \
|
|
||||||
locales \
|
|
||||||
openssh-client \
|
|
||||||
procps \
|
|
||||||
sshpass \
|
|
||||||
telnet \
|
|
||||||
unzip \
|
|
||||||
vim \
|
|
||||||
git \
|
|
||||||
wget"
|
|
||||||
|
|
||||||
RUN --mount=type=cache,target=/var/cache/apt,sharing=locked,id=core \
|
|
||||||
set -ex \
|
|
||||||
&& ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime \
|
|
||||||
&& apt-get update \
|
|
||||||
&& apt-get -y install --no-install-recommends ${BUILD_DEPENDENCIES} \
|
|
||||||
&& apt-get -y install --no-install-recommends ${DEPENDENCIES} \
|
|
||||||
&& apt-get -y install --no-install-recommends ${TOOLS} \
|
|
||||||
&& mkdir -p /root/.ssh/ \
|
|
||||||
&& echo "Host *\n\tStrictHostKeyChecking no\n\tUserKnownHostsFile /dev/null\n\tCiphers +aes128-cbc\n\tKexAlgorithms +diffie-hellman-group1-sha1\n\tHostKeyAlgorithms +ssh-rsa" > /root/.ssh/config \
|
|
||||||
&& echo "set mouse-=a" > ~/.vimrc \
|
|
||||||
&& echo "no" | dpkg-reconfigure dash \
|
|
||||||
&& echo "zh_CN.UTF-8" | dpkg-reconfigure locales \
|
|
||||||
&& sed -i "s@# export @export @g" ~/.bashrc \
|
|
||||||
&& sed -i "s@# alias @alias @g" ~/.bashrc \
|
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
|
||||||
|
|
||||||
WORKDIR /tmp/build
|
|
||||||
COPY ./requirements ./requirements
|
|
||||||
|
|
||||||
ARG PIP_MIRROR=https://pypi.douban.com/simple
|
|
||||||
ENV PIP_MIRROR=$PIP_MIRROR
|
|
||||||
ARG PIP_JMS_MIRROR=https://pypi.douban.com/simple
|
|
||||||
ENV PIP_JMS_MIRROR=$PIP_JMS_MIRROR
|
|
||||||
|
|
||||||
RUN --mount=type=cache,target=/root/.cache/pip \
|
|
||||||
set -ex \
|
|
||||||
&& pip config set global.index-url ${PIP_MIRROR} \
|
|
||||||
&& pip install --upgrade pip \
|
|
||||||
&& pip install --upgrade setuptools wheel \
|
|
||||||
&& pip install https://download.jumpserver.org/pypi/simple/cryptography/cryptography-38.0.4-cp39-cp39-linux_loongarch64.whl \
|
|
||||||
&& pip install https://download.jumpserver.org/pypi/simple/greenlet/greenlet-1.1.2-cp39-cp39-linux_loongarch64.whl \
|
|
||||||
&& pip install https://download.jumpserver.org/pypi/simple/PyNaCl/PyNaCl-1.5.0-cp39-cp39-linux_loongarch64.whl \
|
|
||||||
&& pip install https://download.jumpserver.org/pypi/simple/grpcio/grpcio-1.54.2-cp39-cp39-linux_loongarch64.whl \
|
|
||||||
&& pip install $(grep -E 'jms|jumpserver' requirements/requirements.txt) -i ${PIP_JMS_MIRROR} \
|
|
||||||
&& pip install -r requirements/requirements.txt
|
|
||||||
|
|
||||||
COPY --from=stage-build /opt/jumpserver/release/jumpserver /opt/jumpserver
|
|
||||||
RUN echo > /opt/jumpserver/config.yml \
|
|
||||||
&& rm -rf /tmp/build
|
|
||||||
|
|
||||||
WORKDIR /opt/jumpserver
|
|
||||||
VOLUME /opt/jumpserver/data
|
|
||||||
VOLUME /opt/jumpserver/logs
|
|
||||||
|
|
||||||
ENV LANG=zh_CN.UTF-8
|
|
||||||
|
|
||||||
EXPOSE 8080
|
|
||||||
|
|
||||||
ENTRYPOINT ["./entrypoint.sh"]
|
|
||||||
48
README.md
48
README.md
@@ -17,18 +17,16 @@
|
|||||||
9 年时间,倾情投入,用心做好一款开源堡垒机。
|
9 年时间,倾情投入,用心做好一款开源堡垒机。
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
| :warning: 注意 :warning: |
|
JumpServer 是广受欢迎的开源堡垒机,是符合 4A 规范的专业运维安全审计系统。
|
||||||
|:-------------------------------------------------------------------------------------------------------------------------:|
|
|
||||||
| 3.0 架构上和 2.0 变化较大,建议全新安装一套环境来体验。如需升级,请务必升级前进行备份,并[查阅文档](https://kb.fit2cloud.com/?p=06638d69-f109-4333-b5bf-65b17b297ed9) |
|
|
||||||
|
|
||||||
--------------------------
|
JumpServer 堡垒机帮助企业以更安全的方式管控和登录各种类型的资产,包括:
|
||||||
|
|
||||||
JumpServer 是广受欢迎的开源堡垒机,是符合 4A 规范的专业运维安全审计系统。JumpServer 堡垒机帮助企业以更安全的方式管控和登录各种类型的资产,包括:
|
|
||||||
|
|
||||||
- **SSH**: Linux / Unix / 网络设备 等;
|
- **SSH**: Linux / Unix / 网络设备 等;
|
||||||
- **Windows**: Web 方式连接 / 原生 RDP 连接;
|
- **Windows**: Web 方式连接 / 原生 RDP 连接;
|
||||||
- **数据库**: MySQL / Oracle / SQLServer / PostgreSQL 等;
|
- **数据库**: MySQL / MariaDB / PostgreSQL / Oracle / SQLServer / ClickHouse 等;
|
||||||
- **Kubernetes**: 支持连接到 K8s 集群中的 Pods;
|
- **NoSQL**: Redis / MongoDB 等;
|
||||||
|
- **GPT**: ChatGPT 等;
|
||||||
|
- **云服务**: Kubernetes / VMware vSphere 等;
|
||||||
- **Web 站点**: 各类系统的 Web 管理后台;
|
- **Web 站点**: 各类系统的 Web 管理后台;
|
||||||
- **应用**: 通过 Remote App 连接各类应用。
|
- **应用**: 通过 Remote App 连接各类应用。
|
||||||
|
|
||||||
@@ -81,11 +79,7 @@ JumpServer 是广受欢迎的开源堡垒机,是符合 4A 规范的专业运
|
|||||||
|
|
||||||
如果您在使用过程中有任何疑问或对建议,欢迎提交 [GitHub Issue](https://github.com/jumpserver/jumpserver/issues/new/choose)。
|
如果您在使用过程中有任何疑问或对建议,欢迎提交 [GitHub Issue](https://github.com/jumpserver/jumpserver/issues/new/choose)。
|
||||||
|
|
||||||
您也可以到我们的 [社区论坛](https://bbs.fit2cloud.com/c/js/5) 及微信交流群当中进行交流沟通。
|
您也可以到我们的 [社区论坛](https://bbs.fit2cloud.com/c/js/5) 当中进行交流沟通。
|
||||||
|
|
||||||
**微信交流群**
|
|
||||||
|
|
||||||
<img src="https://download.jumpserver.org/images/wecom-group.jpeg" alt="微信群二维码" width="200"/>
|
|
||||||
|
|
||||||
### 参与贡献
|
### 参与贡献
|
||||||
|
|
||||||
@@ -95,15 +89,20 @@ JumpServer 是广受欢迎的开源堡垒机,是符合 4A 规范的专业运
|
|||||||
|
|
||||||
## 组件项目
|
## 组件项目
|
||||||
|
|
||||||
| 项目 | 状态 | 描述 |
|
| 项目 | 状态 | 描述 |
|
||||||
|--------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------|
|
|--------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------|
|
||||||
| [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 项目 |
|
| [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 项目 |
|
| [Luna](https://github.com/jumpserver/luna) | <a href="https://github.com/jumpserver/luna/releases"><img alt="Luna release" src="https://img.shields.io/github/release/jumpserver/luna.svg" /></a> | JumpServer Web Terminal 项目 |
|
||||||
| [KoKo](https://github.com/jumpserver/koko) | <a href="https://github.com/jumpserver/koko/releases"><img alt="Koko release" src="https://img.shields.io/github/release/jumpserver/koko.svg" /></a> | JumpServer 字符协议 Connector 项目,替代原来 Python 版本的 [Coco](https://github.com/jumpserver/coco) |
|
| [KoKo](https://github.com/jumpserver/koko) | <a href="https://github.com/jumpserver/koko/releases"><img alt="Koko release" src="https://img.shields.io/github/release/jumpserver/koko.svg" /></a> | JumpServer 字符协议 Connector 项目 |
|
||||||
| [Lion](https://github.com/jumpserver/lion-release) | <a href="https://github.com/jumpserver/lion-release/releases"><img alt="Lion release" src="https://img.shields.io/github/release/jumpserver/lion-release.svg" /></a> | JumpServer 图形协议 Connector 项目,依赖 [Apache Guacamole](https://guacamole.apache.org/) |
|
| [Lion](https://github.com/jumpserver/lion-release) | <a href="https://github.com/jumpserver/lion-release/releases"><img alt="Lion release" src="https://img.shields.io/github/release/jumpserver/lion-release.svg" /></a> | JumpServer 图形协议 Connector 项目,依赖 [Apache Guacamole](https://guacamole.apache.org/) |
|
||||||
| [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 项目 |
|
| [Razor](https://github.com/jumpserver/razor) | <img alt="Chen" src="https://img.shields.io/badge/release-私有发布-red" /> | JumpServer RDP 代理 Connector 项目 |
|
||||||
| [Clients](https://github.com/jumpserver/clients) | <a href="https://github.com/jumpserver/clients/releases"><img alt="Clients release" src="https://img.shields.io/github/release/jumpserver/clients.svg" /> | JumpServer 客户端 项目 |
|
| [Tinker](https://github.com/jumpserver/tinker) | <img alt="Tinker" src="https://img.shields.io/badge/release-私有发布-red" /> | JumpServer 远程应用 Connector 项目 |
|
||||||
| [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 安装包 项目 |
|
| [Magnus](https://github.com/jumpserver/magnus-release) | <a href="https://github.com/jumpserver/magnus-release/releases"><img alt="Magnus release" src="https://img.shields.io/github/release/jumpserver/magnus-release.svg" /> | JumpServer 数据库代理 Connector 项目 |
|
||||||
|
| [Chen](https://github.com/jumpserver/chen-release) | <a href="https://github.com/jumpserver/chen-release/releases"><img alt="Chen release" src="https://img.shields.io/github/release/jumpserver/chen-release.svg" /> | JumpServer Web DB 项目,替代原来的 OmniDB |
|
||||||
|
| [Kael](https://github.com/jumpserver/kael) | <a href="https://github.com/jumpserver/kael/releases"><img alt="Kael release" src="https://img.shields.io/github/release/jumpserver/kael.svg" /> | JumpServer 连接 GPT 资产的组件项目 |
|
||||||
|
| [Wisp](https://github.com/jumpserver/wisp) | <a href="https://github.com/jumpserver/wisp/releases"><img alt="Magnus release" src="https://img.shields.io/github/release/jumpserver/wisp.svg" /> | JumpServer 各系统终端组件和 Core Api 通信的组件项目 |
|
||||||
|
| [Clients](https://github.com/jumpserver/clients) | <a href="https://github.com/jumpserver/clients/releases"><img alt="Clients release" src="https://img.shields.io/github/release/jumpserver/clients.svg" /> | JumpServer 客户端 项目 |
|
||||||
|
| [Installer](https://github.com/jumpserver/installer) | <a href="https://github.com/jumpserver/installer/releases"><img alt="Installer release" src="https://img.shields.io/github/release/jumpserver/installer.svg" /> | JumpServer 安装包 项目 |
|
||||||
|
|
||||||
## 安全说明
|
## 安全说明
|
||||||
|
|
||||||
@@ -113,11 +112,6 @@ JumpServer是一款安全产品,请参考 [基本安全建议](https://docs.ju
|
|||||||
- 邮箱:support@fit2cloud.com
|
- 邮箱:support@fit2cloud.com
|
||||||
- 电话:400-052-0755
|
- 电话:400-052-0755
|
||||||
|
|
||||||
## 致谢开源
|
|
||||||
|
|
||||||
- [Apache Guacamole](https://guacamole.apache.org/): Web 页面连接 RDP、SSH、VNC 等协议资产,JumpServer Lion 组件使用到该项目;
|
|
||||||
- [OmniDB](https://omnidb.org/): Web 页面连接使用数据库,JumpServer Web 数据库组件使用到该项目。
|
|
||||||
|
|
||||||
## License & Copyright
|
## License & Copyright
|
||||||
|
|
||||||
Copyright (c) 2014-2023 飞致云 FIT2CLOUD, All rights reserved.
|
Copyright (c) 2014-2023 飞致云 FIT2CLOUD, All rights reserved.
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ __all__ = [
|
|||||||
|
|
||||||
class AccountViewSet(OrgBulkModelViewSet):
|
class AccountViewSet(OrgBulkModelViewSet):
|
||||||
model = Account
|
model = Account
|
||||||
search_fields = ('username', 'asset__address', 'name')
|
search_fields = ('username', 'name', 'asset__name', 'asset__address')
|
||||||
filterset_class = AccountFilterSet
|
filterset_class = AccountFilterSet
|
||||||
serializer_classes = {
|
serializer_classes = {
|
||||||
'default': serializers.AccountSerializer,
|
'default': serializers.AccountSerializer,
|
||||||
|
|||||||
@@ -49,8 +49,7 @@ class AccountTemplateViewSet(OrgBulkModelViewSet):
|
|||||||
@action(methods=['get'], detail=False, url_path='su-from-account-templates')
|
@action(methods=['get'], detail=False, url_path='su-from-account-templates')
|
||||||
def su_from_account_templates(self, request, *args, **kwargs):
|
def su_from_account_templates(self, request, *args, **kwargs):
|
||||||
pk = request.query_params.get('template_id')
|
pk = request.query_params.get('template_id')
|
||||||
template = AccountTemplate.objects.filter(pk=pk).first()
|
templates = AccountTemplate.get_su_from_account_templates(pk)
|
||||||
templates = AccountTemplate.get_su_from_account_templates(template)
|
|
||||||
templates = self.filter_queryset(templates)
|
templates = self.filter_queryset(templates)
|
||||||
serializer = self.get_serializer(templates, many=True)
|
serializer = self.get_serializer(templates, many=True)
|
||||||
return Response(data=serializer.data)
|
return Response(data=serializer.data)
|
||||||
|
|||||||
@@ -26,6 +26,7 @@
|
|||||||
password: "{{ account.secret }}"
|
password: "{{ account.secret }}"
|
||||||
commands: "{{ params.commands }}"
|
commands: "{{ params.commands }}"
|
||||||
first_conn_delay_time: "{{ first_conn_delay_time | default(0.5) }}"
|
first_conn_delay_time: "{{ first_conn_delay_time | default(0.5) }}"
|
||||||
|
ignore_errors: true
|
||||||
when: ping_info is succeeded
|
when: ping_info is succeeded
|
||||||
register: change_info
|
register: change_info
|
||||||
|
|
||||||
@@ -35,6 +36,3 @@
|
|||||||
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 }}"
|
||||||
when:
|
|
||||||
- ping_info is succeeded
|
|
||||||
- change_info is succeeded
|
|
||||||
|
|||||||
@@ -15,5 +15,6 @@ params:
|
|||||||
|
|
||||||
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
|
||||||
|
|||||||
@@ -38,8 +38,8 @@
|
|||||||
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 }}"
|
||||||
|
ignore_errors: true
|
||||||
when: db_info is succeeded
|
when: db_info is succeeded
|
||||||
register: change_info
|
|
||||||
|
|
||||||
- name: Verify password
|
- name: Verify password
|
||||||
mongodb_ping:
|
mongodb_ping:
|
||||||
@@ -53,6 +53,3 @@
|
|||||||
ssl_certfile: "{{ jms_asset.secret_info.client_key }}"
|
ssl_certfile: "{{ jms_asset.secret_info.client_key }}"
|
||||||
connection_options:
|
connection_options:
|
||||||
- tlsAllowInvalidHostnames: "{{ jms_asset.spec_info.allow_invalid_cert}}"
|
- tlsAllowInvalidHostnames: "{{ jms_asset.spec_info.allow_invalid_cert}}"
|
||||||
when:
|
|
||||||
- db_info is succeeded
|
|
||||||
- change_info is succeeded
|
|
||||||
|
|||||||
@@ -7,5 +7,6 @@ method: change_secret
|
|||||||
|
|
||||||
i18n:
|
i18n:
|
||||||
MongoDB account change secret:
|
MongoDB account change secret:
|
||||||
zh: MongoDB 账号改密
|
zh: 使用 Ansible 模块 mongodb 执行 MongoDB 账号改密
|
||||||
ja: MongoDB アカウントのパスワード変更
|
ja: Ansible mongodb モジュールを使用して MongoDB アカウントのパスワード変更
|
||||||
|
en: Using Ansible module mongodb to change MongoDB account secret
|
||||||
|
|||||||
@@ -28,8 +28,8 @@
|
|||||||
password: "{{ account.secret }}"
|
password: "{{ account.secret }}"
|
||||||
host: "%"
|
host: "%"
|
||||||
priv: "{{ account.username + '.*:USAGE' if db_name == '' else db_name + '.*:ALL' }}"
|
priv: "{{ account.username + '.*:USAGE' if db_name == '' else db_name + '.*:ALL' }}"
|
||||||
|
ignore_errors: true
|
||||||
when: db_info is succeeded
|
when: db_info is succeeded
|
||||||
register: change_info
|
|
||||||
|
|
||||||
- name: Verify password
|
- name: Verify password
|
||||||
community.mysql.mysql_info:
|
community.mysql.mysql_info:
|
||||||
@@ -38,6 +38,3 @@
|
|||||||
login_host: "{{ jms_asset.address }}"
|
login_host: "{{ jms_asset.address }}"
|
||||||
login_port: "{{ jms_asset.port }}"
|
login_port: "{{ jms_asset.port }}"
|
||||||
filter: version
|
filter: version
|
||||||
when:
|
|
||||||
- db_info is succeeded
|
|
||||||
- change_info is succeeded
|
|
||||||
@@ -8,5 +8,6 @@ method: change_secret
|
|||||||
|
|
||||||
i18n:
|
i18n:
|
||||||
MySQL account change secret:
|
MySQL account change secret:
|
||||||
zh: MySQL 账号改密
|
zh: 使用 Ansible 模块 mysql 执行 MySQL 账号改密
|
||||||
ja: MySQL アカウントのパスワード変更
|
ja: Ansible mysql モジュールを使用して MySQL アカウントのパスワード変更
|
||||||
|
en: Using Ansible module mysql to change MySQL account secret
|
||||||
|
|||||||
@@ -29,8 +29,8 @@
|
|||||||
mode: "{{ jms_account.mode }}"
|
mode: "{{ jms_account.mode }}"
|
||||||
name: "{{ account.username }}"
|
name: "{{ account.username }}"
|
||||||
password: "{{ account.secret }}"
|
password: "{{ account.secret }}"
|
||||||
|
ignore_errors: true
|
||||||
when: db_info is succeeded
|
when: db_info is succeeded
|
||||||
register: change_info
|
|
||||||
|
|
||||||
- name: Verify password
|
- name: Verify password
|
||||||
oracle_ping:
|
oracle_ping:
|
||||||
@@ -39,6 +39,3 @@
|
|||||||
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 }}"
|
||||||
when:
|
|
||||||
- db_info is succeeded
|
|
||||||
- change_info is succeeded
|
|
||||||
|
|||||||
@@ -29,8 +29,8 @@
|
|||||||
name: "{{ account.username }}"
|
name: "{{ account.username }}"
|
||||||
password: "{{ account.secret }}"
|
password: "{{ account.secret }}"
|
||||||
role_attr_flags: LOGIN
|
role_attr_flags: LOGIN
|
||||||
|
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,8 +39,3 @@
|
|||||||
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 }}"
|
||||||
when:
|
|
||||||
- result is succeeded
|
|
||||||
- change_info is succeeded
|
|
||||||
register: result
|
|
||||||
failed_when: not result.is_available
|
|
||||||
|
|||||||
@@ -41,8 +41,8 @@
|
|||||||
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 }}'; select @@version"
|
||||||
|
ignore_errors: true
|
||||||
when: user_exist.query_results[0] | length != 0
|
when: user_exist.query_results[0] | length != 0
|
||||||
register: change_info
|
|
||||||
|
|
||||||
- name: Add SQLServer user
|
- name: Add SQLServer user
|
||||||
community.general.mssql_script:
|
community.general.mssql_script:
|
||||||
@@ -52,8 +52,8 @@
|
|||||||
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 }}'; select @@version"
|
||||||
|
ignore_errors: true
|
||||||
when: user_exist.query_results[0] | length == 0
|
when: user_exist.query_results[0] | length == 0
|
||||||
register: change_info
|
|
||||||
|
|
||||||
- name: Verify password
|
- name: Verify password
|
||||||
community.general.mssql_script:
|
community.general.mssql_script:
|
||||||
@@ -64,6 +64,3 @@
|
|||||||
name: '{{ jms_asset.spec_info.db_name }}'
|
name: '{{ jms_asset.spec_info.db_name }}'
|
||||||
script: |
|
script: |
|
||||||
SELECT @@version
|
SELECT @@version
|
||||||
when:
|
|
||||||
- db_info is succeeded
|
|
||||||
- change_info is succeeded
|
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
name: "{{ account.username }}"
|
name: "{{ account.username }}"
|
||||||
password: "{{ account.secret | password_hash('des') }}"
|
password: "{{ account.secret | password_hash('des') }}"
|
||||||
update_password: always
|
update_password: always
|
||||||
|
ignore_errors: true
|
||||||
when: account.secret_type == "password"
|
when: account.secret_type == "password"
|
||||||
|
|
||||||
- name: create user If it already exists, no operation will be performed
|
- name: create user If it already exists, no operation will be performed
|
||||||
|
|||||||
@@ -4,8 +4,58 @@ category: host
|
|||||||
type:
|
type:
|
||||||
- AIX
|
- AIX
|
||||||
method: change_secret
|
method: change_secret
|
||||||
|
params:
|
||||||
|
- name: sudo
|
||||||
|
type: str
|
||||||
|
label: 'Sudo'
|
||||||
|
default: '/bin/whoami'
|
||||||
|
help_text: "{{ 'Params sudo help text' | trans }}"
|
||||||
|
|
||||||
|
- name: shell
|
||||||
|
type: str
|
||||||
|
label: 'Shell'
|
||||||
|
default: '/bin/bash'
|
||||||
|
|
||||||
|
- name: home
|
||||||
|
type: str
|
||||||
|
label: "{{ 'Params home label' | trans }}"
|
||||||
|
default: ''
|
||||||
|
help_text: "{{ 'Params home help text' | trans }}"
|
||||||
|
|
||||||
|
- name: groups
|
||||||
|
type: str
|
||||||
|
label: "{{ 'Params groups label' | trans }}"
|
||||||
|
default: ''
|
||||||
|
help_text: "{{ 'Params groups help text' | trans }}"
|
||||||
|
|
||||||
i18n:
|
i18n:
|
||||||
AIX account change secret:
|
AIX account change secret:
|
||||||
zh: AIX 账号改密
|
zh: '使用 Ansible 模块 user 执行账号改密 (DES)'
|
||||||
ja: AIX アカウントのパスワード変更
|
ja: 'Ansible user モジュールを使用してアカウントのパスワード変更 (DES)'
|
||||||
|
en: 'Using Ansible module user to change account secret (DES)'
|
||||||
|
|
||||||
|
Params sudo help text:
|
||||||
|
zh: '使用逗号分隔多个命令,如: /bin/whoami,/sbin/ifconfig'
|
||||||
|
ja: 'コンマで区切って複数のコマンドを入力してください。例: /bin/whoami,/sbin/ifconfig'
|
||||||
|
en: 'Use commas to separate multiple commands, such as: /bin/whoami,/sbin/ifconfig'
|
||||||
|
|
||||||
|
Params home help text:
|
||||||
|
zh: '默认家目录 /home/{账号用户名}'
|
||||||
|
ja: 'デフォルトのホームディレクトリ /home/{アカウントユーザ名}'
|
||||||
|
en: 'Default home directory /home/{account username}'
|
||||||
|
|
||||||
|
Params groups help text:
|
||||||
|
zh: '请输入用户组,多个用户组使用逗号分隔(需填写已存在的用户组)'
|
||||||
|
ja: 'グループを入力してください。複数のグループはコンマで区切ってください(既存のグループを入力してください)'
|
||||||
|
en: 'Please enter the group. Multiple groups are separated by commas (please enter the existing group)'
|
||||||
|
|
||||||
|
Params home label:
|
||||||
|
zh: '家目录'
|
||||||
|
ja: 'ホームディレクトリ'
|
||||||
|
en: 'Home'
|
||||||
|
|
||||||
|
Params groups label:
|
||||||
|
zh: '用户组'
|
||||||
|
ja: 'グループ'
|
||||||
|
en: 'Groups'
|
||||||
|
|
||||||
|
|||||||
@@ -4,11 +4,32 @@
|
|||||||
- name: Test privileged account
|
- name: Test privileged account
|
||||||
ansible.builtin.ping:
|
ansible.builtin.ping:
|
||||||
|
|
||||||
|
- name: Check user
|
||||||
|
ansible.builtin.user:
|
||||||
|
name: "{{ account.username }}"
|
||||||
|
shell: "{{ params.shell }}"
|
||||||
|
home: "{{ params.home | default('/home/' + account.username, true) }}"
|
||||||
|
groups: "{{ params.groups }}"
|
||||||
|
expires: -1
|
||||||
|
state: present
|
||||||
|
|
||||||
|
- name: "Add {{ account.username }} group"
|
||||||
|
ansible.builtin.group:
|
||||||
|
name: "{{ account.username }}"
|
||||||
|
state: present
|
||||||
|
|
||||||
|
- name: Add user groups
|
||||||
|
ansible.builtin.user:
|
||||||
|
name: "{{ account.username }}"
|
||||||
|
groups: "{{ params.groups }}"
|
||||||
|
when: params.groups
|
||||||
|
|
||||||
- name: Change password
|
- name: Change password
|
||||||
ansible.builtin.user:
|
ansible.builtin.user:
|
||||||
name: "{{ account.username }}"
|
name: "{{ account.username }}"
|
||||||
password: "{{ account.secret | password_hash('sha512') }}"
|
password: "{{ account.secret | password_hash('sha512') }}"
|
||||||
update_password: always
|
update_password: always
|
||||||
|
ignore_errors: true
|
||||||
when: account.secret_type == "password"
|
when: account.secret_type == "password"
|
||||||
|
|
||||||
- name: create user If it already exists, no operation will be performed
|
- name: create user If it already exists, no operation will be performed
|
||||||
@@ -32,6 +53,16 @@
|
|||||||
exclusive: "{{ ssh_params.exclusive }}"
|
exclusive: "{{ ssh_params.exclusive }}"
|
||||||
when: account.secret_type == "ssh_key"
|
when: account.secret_type == "ssh_key"
|
||||||
|
|
||||||
|
- name: Set sudo setting
|
||||||
|
ansible.builtin.lineinfile:
|
||||||
|
dest: /etc/sudoers
|
||||||
|
state: present
|
||||||
|
regexp: "^{{ account.username }} ALL="
|
||||||
|
line: "{{ account.username + ' ALL=(ALL) NOPASSWD: ' + params.sudo }}"
|
||||||
|
validate: visudo -cf %s
|
||||||
|
when:
|
||||||
|
- params.sudo
|
||||||
|
|
||||||
- name: Refresh connection
|
- name: Refresh connection
|
||||||
ansible.builtin.meta: reset_connection
|
ansible.builtin.meta: reset_connection
|
||||||
|
|
||||||
|
|||||||
@@ -5,8 +5,59 @@ type:
|
|||||||
- unix
|
- unix
|
||||||
- linux
|
- linux
|
||||||
method: change_secret
|
method: change_secret
|
||||||
|
params:
|
||||||
|
- name: sudo
|
||||||
|
type: str
|
||||||
|
label: 'Sudo'
|
||||||
|
default: '/bin/whoami'
|
||||||
|
help_text: "{{ 'Params sudo help text' | trans }}"
|
||||||
|
|
||||||
|
- name: shell
|
||||||
|
type: str
|
||||||
|
label: 'Shell'
|
||||||
|
default: '/bin/bash'
|
||||||
|
help_text: ''
|
||||||
|
|
||||||
|
- name: home
|
||||||
|
type: str
|
||||||
|
label: "{{ 'Params home label' | trans }}"
|
||||||
|
default: ''
|
||||||
|
help_text: "{{ 'Params home help text' | trans }}"
|
||||||
|
|
||||||
|
- name: groups
|
||||||
|
type: str
|
||||||
|
label: "{{ 'Params groups label' | trans }}"
|
||||||
|
default: ''
|
||||||
|
help_text: "{{ 'Params groups help text' | trans }}"
|
||||||
|
|
||||||
i18n:
|
i18n:
|
||||||
Posix account change secret:
|
Posix account change secret:
|
||||||
zh: Posix 账号改密
|
zh: '使用 Ansible 模块 user 执行账号改密 (SHA512)'
|
||||||
ja: Posix アカウントのパスワード変更
|
ja: 'Ansible user モジュールを使用して アカウントのパスワード変更 (SHA512)'
|
||||||
|
en: 'Using Ansible module user to change account secret (SHA512)'
|
||||||
|
|
||||||
|
Params sudo help text:
|
||||||
|
zh: '使用逗号分隔多个命令,如: /bin/whoami,/sbin/ifconfig'
|
||||||
|
ja: 'コンマで区切って複数のコマンドを入力してください。例: /bin/whoami,/sbin/ifconfig'
|
||||||
|
en: 'Use commas to separate multiple commands, such as: /bin/whoami,/sbin/ifconfig'
|
||||||
|
|
||||||
|
Params home help text:
|
||||||
|
zh: '默认家目录 /home/{账号用户名}'
|
||||||
|
ja: 'デフォルトのホームディレクトリ /home/{アカウントユーザ名}'
|
||||||
|
en: 'Default home directory /home/{account username}'
|
||||||
|
|
||||||
|
Params groups help text:
|
||||||
|
zh: '请输入用户组,多个用户组使用逗号分隔(需填写已存在的用户组)'
|
||||||
|
ja: 'グループを入力してください。複数のグループはコンマで区切ってください(既存のグループを入力してください)'
|
||||||
|
en: 'Please enter the group. Multiple groups are separated by commas (please enter the existing group)'
|
||||||
|
|
||||||
|
Params home label:
|
||||||
|
zh: '家目录'
|
||||||
|
ja: 'ホームディレクトリ'
|
||||||
|
en: 'Home'
|
||||||
|
|
||||||
|
Params groups label:
|
||||||
|
zh: '用户组'
|
||||||
|
ja: 'グループ'
|
||||||
|
en: 'Groups'
|
||||||
|
|
||||||
|
|||||||
@@ -8,19 +8,16 @@
|
|||||||
# debug:
|
# debug:
|
||||||
# msg: "Username: {{ account.username }}, Password: {{ account.secret }}"
|
# msg: "Username: {{ account.username }}, Password: {{ account.secret }}"
|
||||||
|
|
||||||
|
|
||||||
- name: Get groups of a Windows user
|
|
||||||
ansible.windows.win_user:
|
|
||||||
name: "{{ jms_account.username }}"
|
|
||||||
register: user_info
|
|
||||||
|
|
||||||
- name: Change password
|
- name: Change password
|
||||||
ansible.windows.win_user:
|
ansible.windows.win_user:
|
||||||
|
fullname: "{{ account.username}}"
|
||||||
name: "{{ account.username }}"
|
name: "{{ account.username }}"
|
||||||
password: "{{ account.secret }}"
|
password: "{{ account.secret }}"
|
||||||
groups: "{{ user_info.groups[0].name }}"
|
password_never_expires: yes
|
||||||
|
groups: "{{ params.groups }}"
|
||||||
groups_action: add
|
groups_action: add
|
||||||
update_password: always
|
update_password: always
|
||||||
|
ignore_errors: true
|
||||||
when: account.secret_type == "password"
|
when: account.secret_type == "password"
|
||||||
|
|
||||||
- name: Refresh connection
|
- name: Refresh connection
|
||||||
|
|||||||
@@ -5,8 +5,22 @@ method: change_secret
|
|||||||
category: host
|
category: host
|
||||||
type:
|
type:
|
||||||
- windows
|
- windows
|
||||||
|
params:
|
||||||
|
- name: groups
|
||||||
|
type: str
|
||||||
|
label: '用户组'
|
||||||
|
default: 'Users,Remote Desktop Users'
|
||||||
|
help_text: "{{ 'Params groups help text' | trans }}"
|
||||||
|
|
||||||
|
|
||||||
i18n:
|
i18n:
|
||||||
Windows account change secret:
|
Windows account change secret:
|
||||||
zh: Windows 账号改密
|
zh: '使用 Ansible 模块 win_user 执行 Windows 账号改密'
|
||||||
ja: Windows アカウントのパスワード変更
|
ja: 'Ansible win_user モジュールを使用して Windows アカウントのパスワード変更'
|
||||||
|
en: 'Using Ansible module win_user to change Windows account secret'
|
||||||
|
|
||||||
|
Params groups help text:
|
||||||
|
zh: '请输入用户组,多个用户组使用逗号分隔(需填写已存在的用户组)'
|
||||||
|
ja: 'グループを入力してください。複数のグループはコンマで区切ってください(既存のグループを入力してください)'
|
||||||
|
en: 'Please enter the group. Multiple groups are separated by commas (please enter the existing group)'
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import re
|
||||||
|
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
|
|
||||||
__all__ = ['GatherAccountsFilter']
|
__all__ = ['GatherAccountsFilter']
|
||||||
@@ -27,18 +29,25 @@ class GatherAccountsFilter:
|
|||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def posix_filter(info):
|
def posix_filter(info):
|
||||||
|
username_pattern = re.compile(r'^(\S+)')
|
||||||
|
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}')
|
||||||
result = {}
|
result = {}
|
||||||
for line in info:
|
for line in info:
|
||||||
data = line.split('@')
|
usernames = username_pattern.findall(line)
|
||||||
if len(data) == 1:
|
username = ''.join(usernames)
|
||||||
result[line] = {}
|
if username:
|
||||||
|
result[username] = {}
|
||||||
|
else:
|
||||||
continue
|
continue
|
||||||
|
ip_addrs = ip_pattern.findall(line)
|
||||||
if len(data) != 3:
|
ip_addr = ''.join(ip_addrs)
|
||||||
continue
|
if ip_addr:
|
||||||
username, address, dt = data
|
result[username].update({'address': ip_addr})
|
||||||
date = timezone.datetime.strptime(f'{dt} +0800', '%b %d %H:%M:%S %Y %z')
|
login_times = login_time_pattern.findall(line)
|
||||||
result[username] = {'address': address, 'date': date}
|
if login_times:
|
||||||
|
date = timezone.datetime.strptime(f'{login_times[0]} +0800', '%b %d %H:%M:%S %Y %z')
|
||||||
|
result[username].update({'date': date})
|
||||||
return result
|
return result
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
ansible.builtin.shell:
|
ansible.builtin.shell:
|
||||||
cmd: >
|
cmd: >
|
||||||
users=$(getent passwd | grep -v nologin | grep -v shutdown | awk -F":" '{ print $1 }');for i in $users;
|
users=$(getent passwd | grep -v nologin | grep -v shutdown | awk -F":" '{ print $1 }');for i in $users;
|
||||||
do k=$(last -w -F $i -1 | head -1 | grep -v ^$ | awk '{ print $1"@"$3"@"$5,$6,$7,$8 }')
|
do k=$(last -w -F $i -1 | head -1 | grep -v ^$ | awk '{ print $0 }')
|
||||||
if [ -n "$k" ]; then
|
if [ -n "$k" ]; then
|
||||||
echo $k
|
echo $k
|
||||||
else
|
else
|
||||||
|
|||||||
@@ -8,5 +8,6 @@ method: gather_accounts
|
|||||||
|
|
||||||
i18n:
|
i18n:
|
||||||
Posix account gather:
|
Posix account gather:
|
||||||
zh: Posix 账号收集
|
zh: 使用命令 getent passwd 收集 Posix 资产账号
|
||||||
ja: Posix アカウントの収集
|
ja: コマンド getent を使用してアセットアカウントを収集する
|
||||||
|
en: Using command getent to gather accounts
|
||||||
|
|||||||
@@ -8,5 +8,6 @@ type:
|
|||||||
|
|
||||||
i18n:
|
i18n:
|
||||||
Windows account gather:
|
Windows account gather:
|
||||||
zh: Windows 账号收集
|
zh: 使用命令 net user 收集 Windows 账号
|
||||||
ja: Windows アカウントの収集
|
ja: コマンド net user を使用して Windows アカウントを収集する
|
||||||
|
en: Using command net user to gather accounts
|
||||||
|
|||||||
@@ -38,8 +38,8 @@
|
|||||||
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 }}"
|
||||||
|
ignore_errors: true
|
||||||
when: db_info is succeeded
|
when: db_info is succeeded
|
||||||
register: change_info
|
|
||||||
|
|
||||||
- name: Verify password
|
- name: Verify password
|
||||||
mongodb_ping:
|
mongodb_ping:
|
||||||
@@ -53,6 +53,3 @@
|
|||||||
ssl_certfile: "{{ jms_asset.secret_info.client_key }}"
|
ssl_certfile: "{{ jms_asset.secret_info.client_key }}"
|
||||||
connection_options:
|
connection_options:
|
||||||
- tlsAllowInvalidHostnames: "{{ jms_asset.spec_info.allow_invalid_cert}}"
|
- tlsAllowInvalidHostnames: "{{ jms_asset.spec_info.allow_invalid_cert}}"
|
||||||
when:
|
|
||||||
- db_info is succeeded
|
|
||||||
- change_info is succeeded
|
|
||||||
|
|||||||
@@ -7,5 +7,6 @@ method: push_account
|
|||||||
|
|
||||||
i18n:
|
i18n:
|
||||||
MongoDB account push:
|
MongoDB account push:
|
||||||
zh: MongoDB 账号推送
|
zh: 使用 Ansible 模块 mongodb 执行 MongoDB 账号推送
|
||||||
ja: MongoDB アカウントのプッシュ
|
ja: Ansible mongodb モジュールを使用してアカウントをプッシュする
|
||||||
|
en: Using Ansible module mongodb to push account
|
||||||
|
|||||||
@@ -28,8 +28,8 @@
|
|||||||
password: "{{ account.secret }}"
|
password: "{{ account.secret }}"
|
||||||
host: "%"
|
host: "%"
|
||||||
priv: "{{ account.username + '.*:USAGE' if db_name == '' else db_name + '.*:ALL' }}"
|
priv: "{{ account.username + '.*:USAGE' if db_name == '' else db_name + '.*:ALL' }}"
|
||||||
|
ignore_errors: true
|
||||||
when: db_info is succeeded
|
when: db_info is succeeded
|
||||||
register: change_info
|
|
||||||
|
|
||||||
- name: Verify password
|
- name: Verify password
|
||||||
community.mysql.mysql_info:
|
community.mysql.mysql_info:
|
||||||
@@ -38,6 +38,3 @@
|
|||||||
login_host: "{{ jms_asset.address }}"
|
login_host: "{{ jms_asset.address }}"
|
||||||
login_port: "{{ jms_asset.port }}"
|
login_port: "{{ jms_asset.port }}"
|
||||||
filter: version
|
filter: version
|
||||||
when:
|
|
||||||
- db_info is succeeded
|
|
||||||
- change_info is succeeded
|
|
||||||
@@ -8,5 +8,6 @@ method: push_account
|
|||||||
|
|
||||||
i18n:
|
i18n:
|
||||||
MySQL account push:
|
MySQL account push:
|
||||||
zh: MySQL 账号推送
|
zh: 使用 Ansible 模块 mysql 执行 MySQL 账号推送
|
||||||
ja: MySQL アカウントのプッシュ
|
ja: Ansible mysql モジュールを使用してアカウントをプッシュする
|
||||||
|
en: Using Ansible module mysql to push account
|
||||||
|
|||||||
@@ -29,8 +29,8 @@
|
|||||||
mode: "{{ jms_account.mode }}"
|
mode: "{{ jms_account.mode }}"
|
||||||
name: "{{ account.username }}"
|
name: "{{ account.username }}"
|
||||||
password: "{{ account.secret }}"
|
password: "{{ account.secret }}"
|
||||||
|
ignore_errors: true
|
||||||
when: db_info is succeeded
|
when: db_info is succeeded
|
||||||
register: change_info
|
|
||||||
|
|
||||||
- name: Verify password
|
- name: Verify password
|
||||||
oracle_ping:
|
oracle_ping:
|
||||||
@@ -39,6 +39,3 @@
|
|||||||
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 }}"
|
||||||
when:
|
|
||||||
- db_info is succeeded
|
|
||||||
- change_info is succeeded
|
|
||||||
|
|||||||
@@ -7,5 +7,6 @@ method: push_account
|
|||||||
|
|
||||||
i18n:
|
i18n:
|
||||||
Oracle account push:
|
Oracle account push:
|
||||||
zh: Oracle 账号推送
|
zh: 使用 Python 模块 oracledb 执行 Oracle 账号推送
|
||||||
ja: Oracle アカウントのプッシュ
|
ja: Python oracledb モジュールを使用してアカウントをプッシュする
|
||||||
|
en: Using Python module oracledb to push account
|
||||||
|
|||||||
@@ -29,8 +29,8 @@
|
|||||||
name: "{{ account.username }}"
|
name: "{{ account.username }}"
|
||||||
password: "{{ account.secret }}"
|
password: "{{ account.secret }}"
|
||||||
role_attr_flags: LOGIN
|
role_attr_flags: LOGIN
|
||||||
|
ignore_errors: true
|
||||||
when: result is succeeded
|
when: result is succeeded
|
||||||
register: change_info
|
|
||||||
|
|
||||||
- name: Verify password
|
- name: Verify password
|
||||||
community.postgresql.postgresql_ping:
|
community.postgresql.postgresql_ping:
|
||||||
@@ -42,5 +42,3 @@
|
|||||||
when:
|
when:
|
||||||
- result is succeeded
|
- result is succeeded
|
||||||
- change_info is succeeded
|
- change_info is succeeded
|
||||||
register: result
|
|
||||||
failed_when: not result.is_available
|
|
||||||
|
|||||||
@@ -7,5 +7,6 @@ method: push_account
|
|||||||
|
|
||||||
i18n:
|
i18n:
|
||||||
PostgreSQL account push:
|
PostgreSQL account push:
|
||||||
zh: PostgreSQL 账号推送
|
zh: 使用 Ansible 模块 postgresql 执行 PostgreSQL 账号推送
|
||||||
ja: PostgreSQL アカウントのプッシュ
|
ja: Ansible postgresql モジュールを使用してアカウントをプッシュする
|
||||||
|
en: Using Ansible module postgresql to push account
|
||||||
|
|||||||
@@ -41,6 +41,7 @@
|
|||||||
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 }}'; select @@version"
|
||||||
|
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,6 +53,7 @@
|
|||||||
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 }}'; select @@version"
|
||||||
|
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
|
||||||
|
|
||||||
@@ -64,6 +66,3 @@
|
|||||||
name: '{{ jms_asset.spec_info.db_name }}'
|
name: '{{ jms_asset.spec_info.db_name }}'
|
||||||
script: |
|
script: |
|
||||||
SELECT @@version
|
SELECT @@version
|
||||||
when:
|
|
||||||
- db_info is succeeded
|
|
||||||
- change_info is succeeded
|
|
||||||
|
|||||||
@@ -7,5 +7,6 @@ method: push_account
|
|||||||
|
|
||||||
i18n:
|
i18n:
|
||||||
SQLServer account push:
|
SQLServer account push:
|
||||||
zh: SQLServer 账号推送
|
zh: 使用 Ansible 模块 mssql 执行 SQLServer 账号推送
|
||||||
ja: SQLServer アカウントのプッシュ
|
ja: Ansible mssql モジュールを使用してアカウントをプッシュする
|
||||||
|
en: Using Ansible module mssql to push account
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
ansible.builtin.user:
|
ansible.builtin.user:
|
||||||
name: "{{ account.username }}"
|
name: "{{ account.username }}"
|
||||||
shell: "{{ params.shell }}"
|
shell: "{{ params.shell }}"
|
||||||
home: "{{ '/home/' + account.username }}"
|
home: "{{ params.home | default('/home/' + account.username, true) }}"
|
||||||
groups: "{{ params.groups }}"
|
groups: "{{ params.groups }}"
|
||||||
expires: -1
|
expires: -1
|
||||||
state: present
|
state: present
|
||||||
@@ -18,20 +18,6 @@
|
|||||||
name: "{{ account.username }}"
|
name: "{{ account.username }}"
|
||||||
state: present
|
state: present
|
||||||
|
|
||||||
- name: Check home dir exists
|
|
||||||
ansible.builtin.stat:
|
|
||||||
path: "{{ '/home/' + account.username }}"
|
|
||||||
register: home_existed
|
|
||||||
|
|
||||||
- name: Set home dir permission
|
|
||||||
ansible.builtin.file:
|
|
||||||
path: "{{ '/home/' + account.username }}"
|
|
||||||
owner: "{{ account.username }}"
|
|
||||||
group: "{{ account.username }}"
|
|
||||||
mode: "0700"
|
|
||||||
when:
|
|
||||||
- home_existed.stat.exists == true
|
|
||||||
|
|
||||||
- name: Add user groups
|
- name: Add user groups
|
||||||
ansible.builtin.user:
|
ansible.builtin.user:
|
||||||
name: "{{ account.username }}"
|
name: "{{ account.username }}"
|
||||||
@@ -43,6 +29,7 @@
|
|||||||
name: "{{ account.username }}"
|
name: "{{ account.username }}"
|
||||||
password: "{{ account.secret | password_hash('sha512') }}"
|
password: "{{ account.secret | password_hash('sha512') }}"
|
||||||
update_password: always
|
update_password: always
|
||||||
|
ignore_errors: true
|
||||||
when: account.secret_type == "password"
|
when: account.secret_type == "password"
|
||||||
|
|
||||||
- name: remove jumpserver ssh key
|
- name: remove jumpserver ssh key
|
||||||
|
|||||||
@@ -16,6 +16,12 @@ params:
|
|||||||
label: 'Shell'
|
label: 'Shell'
|
||||||
default: '/bin/bash'
|
default: '/bin/bash'
|
||||||
|
|
||||||
|
- name: home
|
||||||
|
type: str
|
||||||
|
label: '家目录'
|
||||||
|
default: ''
|
||||||
|
help_text: '默认家目录 /home/系统用户名: /home/username'
|
||||||
|
|
||||||
- name: groups
|
- name: groups
|
||||||
type: str
|
type: str
|
||||||
label: '用户组'
|
label: '用户组'
|
||||||
@@ -24,6 +30,7 @@ params:
|
|||||||
|
|
||||||
i18n:
|
i18n:
|
||||||
Aix account push:
|
Aix account push:
|
||||||
zh: Aix 账号推送
|
zh: 使用 Ansible 模块 user 执行 Aix 账号推送 (DES)
|
||||||
ja: Aix アカウントのプッシュ
|
ja: Ansible user モジュールを使用して Aix アカウントをプッシュする (DES)
|
||||||
|
en: Using Ansible module user to push account (DES)
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
ansible.builtin.user:
|
ansible.builtin.user:
|
||||||
name: "{{ account.username }}"
|
name: "{{ account.username }}"
|
||||||
shell: "{{ params.shell }}"
|
shell: "{{ params.shell }}"
|
||||||
home: "{{ '/home/' + account.username }}"
|
home: "{{ params.home | default('/home/' + account.username, true) }}"
|
||||||
groups: "{{ params.groups }}"
|
groups: "{{ params.groups }}"
|
||||||
expires: -1
|
expires: -1
|
||||||
state: present
|
state: present
|
||||||
@@ -18,20 +18,6 @@
|
|||||||
name: "{{ account.username }}"
|
name: "{{ account.username }}"
|
||||||
state: present
|
state: present
|
||||||
|
|
||||||
- name: Check home dir exists
|
|
||||||
ansible.builtin.stat:
|
|
||||||
path: "{{ '/home/' + account.username }}"
|
|
||||||
register: home_existed
|
|
||||||
|
|
||||||
- name: Set home dir permission
|
|
||||||
ansible.builtin.file:
|
|
||||||
path: "{{ '/home/' + account.username }}"
|
|
||||||
owner: "{{ account.username }}"
|
|
||||||
group: "{{ account.username }}"
|
|
||||||
mode: "0700"
|
|
||||||
when:
|
|
||||||
- home_existed.stat.exists == true
|
|
||||||
|
|
||||||
- name: Add user groups
|
- name: Add user groups
|
||||||
ansible.builtin.user:
|
ansible.builtin.user:
|
||||||
name: "{{ account.username }}"
|
name: "{{ account.username }}"
|
||||||
@@ -43,6 +29,7 @@
|
|||||||
name: "{{ account.username }}"
|
name: "{{ account.username }}"
|
||||||
password: "{{ account.secret | password_hash('sha512') }}"
|
password: "{{ account.secret | password_hash('sha512') }}"
|
||||||
update_password: always
|
update_password: always
|
||||||
|
ignore_errors: true
|
||||||
when: account.secret_type == "password"
|
when: account.secret_type == "password"
|
||||||
|
|
||||||
- name: remove jumpserver ssh key
|
- name: remove jumpserver ssh key
|
||||||
|
|||||||
@@ -18,6 +18,12 @@ params:
|
|||||||
default: '/bin/bash'
|
default: '/bin/bash'
|
||||||
help_text: ''
|
help_text: ''
|
||||||
|
|
||||||
|
- name: home
|
||||||
|
type: str
|
||||||
|
label: '家目录'
|
||||||
|
default: ''
|
||||||
|
help_text: '默认家目录 /home/系统用户名: /home/username'
|
||||||
|
|
||||||
- name: groups
|
- name: groups
|
||||||
type: str
|
type: str
|
||||||
label: '用户组'
|
label: '用户组'
|
||||||
@@ -26,5 +32,6 @@ params:
|
|||||||
|
|
||||||
i18n:
|
i18n:
|
||||||
Posix account push:
|
Posix account push:
|
||||||
zh: Posix 账号推送
|
zh: 使用 Ansible 模块 user 执行账号推送 (sha512)
|
||||||
ja: Posix アカウントのプッシュ
|
ja: Ansible user モジュールを使用してアカウントをプッシュする (sha512)
|
||||||
|
en: Using Ansible module user to push account (sha512)
|
||||||
|
|||||||
@@ -17,6 +17,7 @@
|
|||||||
groups: "{{ params.groups }}"
|
groups: "{{ params.groups }}"
|
||||||
groups_action: add
|
groups_action: add
|
||||||
update_password: always
|
update_password: always
|
||||||
|
ignore_errors: true
|
||||||
when: account.secret_type == "password"
|
when: account.secret_type == "password"
|
||||||
|
|
||||||
- name: Refresh connection
|
- name: Refresh connection
|
||||||
|
|||||||
@@ -14,5 +14,6 @@ params:
|
|||||||
|
|
||||||
i18n:
|
i18n:
|
||||||
Windows account push:
|
Windows account push:
|
||||||
zh: Windows 账号推送
|
zh: 使用 Ansible 模块 win_user 执行 Windows 账号推送
|
||||||
ja: Windows アカウントのプッシュ
|
ja: Ansible win_user モジュールを使用して Windows アカウントをプッシュする
|
||||||
|
en: Using Ansible module win_user to push account
|
||||||
|
|||||||
@@ -1,13 +0,0 @@
|
|||||||
id: verify_account_by_ssh
|
|
||||||
name: "{{ 'SSH account verify' | trans }}"
|
|
||||||
category:
|
|
||||||
- device
|
|
||||||
- host
|
|
||||||
type:
|
|
||||||
- all
|
|
||||||
method: verify_account
|
|
||||||
|
|
||||||
i18n:
|
|
||||||
SSH account verify:
|
|
||||||
zh: SSH 账号验证
|
|
||||||
ja: SSH アカウントの検証
|
|
||||||
15
apps/accounts/automations/verify_account/custom/rdp/main.yml
Normal file
15
apps/accounts/automations/verify_account/custom/rdp/main.yml
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
- hosts: custom
|
||||||
|
gather_facts: no
|
||||||
|
vars:
|
||||||
|
ansible_shell_type: sh
|
||||||
|
ansible_connection: local
|
||||||
|
|
||||||
|
tasks:
|
||||||
|
- name: Verify account (pyfreerdp)
|
||||||
|
rdp_ping:
|
||||||
|
login_host: "{{ jms_asset.address }}"
|
||||||
|
login_port: "{{ jms_asset.port }}"
|
||||||
|
login_user: "{{ account.username }}"
|
||||||
|
login_password: "{{ account.secret }}"
|
||||||
|
login_secret_type: "{{ account.secret_type }}"
|
||||||
|
login_private_key_path: "{{ account.private_key_path }}"
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
id: verify_account_by_rdp
|
||||||
|
name: "{{ 'Windows rdp account verify' | trans }}"
|
||||||
|
category:
|
||||||
|
- host
|
||||||
|
type:
|
||||||
|
- windows
|
||||||
|
method: verify_account
|
||||||
|
|
||||||
|
i18n:
|
||||||
|
Windows rdp account verify:
|
||||||
|
zh: 使用 Python 模块 pyfreerdp 验证账号
|
||||||
|
ja: Python モジュール pyfreerdp を使用してアカウントを検証する
|
||||||
|
en: Using Python module pyfreerdp to verify account
|
||||||
@@ -4,11 +4,11 @@
|
|||||||
ansible_connection: local
|
ansible_connection: local
|
||||||
|
|
||||||
tasks:
|
tasks:
|
||||||
- name: Verify account
|
- name: Verify account (paramiko)
|
||||||
ssh_ping:
|
ssh_ping:
|
||||||
login_host: "{{ jms_asset.address }}"
|
login_host: "{{ jms_asset.address }}"
|
||||||
login_port: "{{ jms_asset.port }}"
|
login_port: "{{ jms_asset.port }}"
|
||||||
login_user: "{{ account.username }}"
|
login_user: "{{ account.username }}"
|
||||||
login_password: "{{ account.secret }}"
|
login_password: "{{ account.secret }}"
|
||||||
login_secret_type: "{{ jms_account.secret_type }}"
|
login_secret_type: "{{ account.secret_type }}"
|
||||||
login_private_key_path: "{{ jms_account.private_key_path }}"
|
login_private_key_path: "{{ account.private_key_path }}"
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
id: verify_account_by_ssh
|
||||||
|
name: "{{ 'SSH account verify' | trans }}"
|
||||||
|
category:
|
||||||
|
- device
|
||||||
|
- host
|
||||||
|
type:
|
||||||
|
- all
|
||||||
|
method: verify_account
|
||||||
|
|
||||||
|
i18n:
|
||||||
|
SSH account verify:
|
||||||
|
zh: 使用 Python 模块 paramiko 验证账号
|
||||||
|
ja: Python モジュール paramiko を使用してアカウントを検証する
|
||||||
|
en: Using Python module paramiko to verify account
|
||||||
@@ -7,5 +7,6 @@ method: verify_account
|
|||||||
|
|
||||||
i18n:
|
i18n:
|
||||||
MongoDB account verify:
|
MongoDB account verify:
|
||||||
zh: MongoDB 账号验证
|
zh: 使用 Ansible 模块 mongodb 验证账号
|
||||||
ja: MongoDB アカウントの検証
|
ja: Ansible mongodb モジュールを使用してアカウントを検証する
|
||||||
|
en: Using Ansible module mongodb to verify account
|
||||||
|
|||||||
@@ -8,5 +8,7 @@ method: verify_account
|
|||||||
|
|
||||||
i18n:
|
i18n:
|
||||||
MySQL account verify:
|
MySQL account verify:
|
||||||
zh: MySQL 账号验证
|
zh: 使用 Ansible 模块 mysql 验证账号
|
||||||
ja: MySQL アカウントの検証
|
ja: Ansible mysql モジュールを使用してアカウントを検証する
|
||||||
|
en: Using Ansible module mysql to verify account
|
||||||
|
|
||||||
|
|||||||
@@ -7,5 +7,6 @@ method: verify_account
|
|||||||
|
|
||||||
i18n:
|
i18n:
|
||||||
Oracle account verify:
|
Oracle account verify:
|
||||||
zh: Oracle 账号验证
|
zh: 使用 Python 模块 oracledb 验证账号
|
||||||
ja: Oracle アカウントの検証
|
ja: Python モジュール oracledb を使用してアカウントを検証する
|
||||||
|
en: Using Python module oracledb to verify account
|
||||||
|
|||||||
@@ -7,5 +7,6 @@ method: verify_account
|
|||||||
|
|
||||||
i18n:
|
i18n:
|
||||||
PostgreSQL account verify:
|
PostgreSQL account verify:
|
||||||
zh: PostgreSQL 账号验证
|
zh: 使用 Ansible 模块 postgresql 验证账号
|
||||||
ja: PostgreSQL アカウントの検証
|
ja: Ansible postgresql モジュールを使用してアカウントを検証する
|
||||||
|
en: Using Ansible module postgresql to verify account
|
||||||
|
|||||||
@@ -7,5 +7,6 @@ method: verify_account
|
|||||||
|
|
||||||
i18n:
|
i18n:
|
||||||
SQLServer account verify:
|
SQLServer account verify:
|
||||||
zh: SQLServer 账号验证
|
zh: 使用 Ansible 模块 mssql 验证账号
|
||||||
ja: SQLServer アカウントの検証
|
ja: Ansible mssql モジュールを使用してアカウントを検証する
|
||||||
|
en: Using Ansible module mssql to verify account
|
||||||
|
|||||||
@@ -8,5 +8,6 @@ method: verify_account
|
|||||||
|
|
||||||
i18n:
|
i18n:
|
||||||
Posix account verify:
|
Posix account verify:
|
||||||
zh: Posix 账号验证
|
zh: 使用 Ansible 模块 ping 验证账号
|
||||||
ja: Posix アカウントの検証
|
ja: Ansible ping モジュールを使用してアカウントを検証する
|
||||||
|
en: Using Ansible module ping to verify account
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ type:
|
|||||||
- windows
|
- windows
|
||||||
|
|
||||||
i18n:
|
i18n:
|
||||||
Windows account verify:
|
Windows account verify:
|
||||||
zh: Windows 账号验证
|
zh: 使用 Ansible 模块 win_ping 验证账号
|
||||||
ja: Windows アカウントの検証
|
ja: Ansible win_ping モジュールを使用してアカウントを検証する
|
||||||
|
en: Using Ansible module win_ping to verify account
|
||||||
|
|||||||
@@ -7,12 +7,14 @@ class SecretType(TextChoices):
|
|||||||
SSH_KEY = 'ssh_key', _('SSH key')
|
SSH_KEY = 'ssh_key', _('SSH key')
|
||||||
ACCESS_KEY = 'access_key', _('Access key')
|
ACCESS_KEY = 'access_key', _('Access key')
|
||||||
TOKEN = 'token', _('Token')
|
TOKEN = 'token', _('Token')
|
||||||
|
API_KEY = 'api_key', _("API key")
|
||||||
|
|
||||||
|
|
||||||
class AliasAccount(TextChoices):
|
class AliasAccount(TextChoices):
|
||||||
ALL = '@ALL', _('All')
|
ALL = '@ALL', _('All')
|
||||||
INPUT = '@INPUT', _('Manual input')
|
INPUT = '@INPUT', _('Manual input')
|
||||||
USER = '@USER', _('Dynamic user')
|
USER = '@USER', _('Dynamic user')
|
||||||
|
ANON = '@ANON', _('Anonymous account')
|
||||||
|
|
||||||
|
|
||||||
class Source(TextChoices):
|
class Source(TextChoices):
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ class AccountFilterSet(BaseFilterSet):
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Account
|
model = Account
|
||||||
fields = ['id', 'asset_id', 'source_id']
|
fields = ['id', 'asset_id', 'source_id', 'secret_type']
|
||||||
|
|
||||||
|
|
||||||
class GatheredAccountFilterSet(BaseFilterSet):
|
class GatheredAccountFilterSet(BaseFilterSet):
|
||||||
|
|||||||
@@ -1,12 +1,14 @@
|
|||||||
# Generated by Django 3.2.14 on 2022-12-28 07:29
|
# Generated by Django 3.2.14 on 2022-12-28 07:29
|
||||||
|
|
||||||
|
import uuid
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
import simple_history.models
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
import common.db.encoder
|
import common.db.encoder
|
||||||
import common.db.fields
|
import common.db.fields
|
||||||
from django.conf import settings
|
|
||||||
from django.db import migrations, models
|
|
||||||
import django.db.models.deletion
|
|
||||||
import simple_history.models
|
|
||||||
import uuid
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
@@ -29,13 +31,16 @@ class Migration(migrations.Migration):
|
|||||||
('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)),
|
('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)),
|
||||||
('org_id',
|
('org_id',
|
||||||
models.CharField(blank=True, db_index=True, default='', max_length=36, verbose_name='Organization')),
|
models.CharField(blank=True, db_index=True, default='', max_length=36, verbose_name='Organization')),
|
||||||
('connectivity', models.CharField(choices=[('-', 'Unknown'), ('ok', 'Ok'), ('err', 'Error')], default='-', max_length=16, verbose_name='Connectivity')),
|
('connectivity',
|
||||||
|
models.CharField(choices=[('-', 'Unknown'), ('ok', 'Ok'), ('err', 'Error')], default='-',
|
||||||
|
max_length=16, verbose_name='Connectivity')),
|
||||||
('date_verified', models.DateTimeField(null=True, verbose_name='Date verified')),
|
('date_verified', models.DateTimeField(null=True, verbose_name='Date verified')),
|
||||||
('name', models.CharField(max_length=128, verbose_name='Name')),
|
('name', models.CharField(max_length=128, verbose_name='Name')),
|
||||||
('username', models.CharField(blank=True, db_index=True, max_length=128, verbose_name='Username')),
|
('username', models.CharField(blank=True, db_index=True, max_length=128, verbose_name='Username')),
|
||||||
('secret_type', models.CharField(
|
('secret_type', models.CharField(
|
||||||
choices=[('password', 'Password'), ('ssh_key', 'SSH key'), ('access_key', 'Access key'),
|
choices=[('password', 'Password'), ('ssh_key', 'SSH key'), ('access_key', 'Access key'),
|
||||||
('token', 'Token')], default='password', max_length=16, verbose_name='Secret type')),
|
('token', 'Token'), ('api_key', 'API key')], default='password', max_length=16,
|
||||||
|
verbose_name='Secret type')),
|
||||||
('secret', common.db.fields.EncryptTextField(blank=True, null=True, verbose_name='Secret')),
|
('secret', common.db.fields.EncryptTextField(blank=True, null=True, verbose_name='Secret')),
|
||||||
('privileged', models.BooleanField(default=False, verbose_name='Privileged')),
|
('privileged', models.BooleanField(default=False, verbose_name='Privileged')),
|
||||||
('is_active', models.BooleanField(default=True, verbose_name='Is active')),
|
('is_active', models.BooleanField(default=True, verbose_name='Is active')),
|
||||||
@@ -61,7 +66,8 @@ class Migration(migrations.Migration):
|
|||||||
('id', models.UUIDField(db_index=True, default=uuid.uuid4)),
|
('id', models.UUIDField(db_index=True, default=uuid.uuid4)),
|
||||||
('secret_type', models.CharField(
|
('secret_type', models.CharField(
|
||||||
choices=[('password', 'Password'), ('ssh_key', 'SSH key'), ('access_key', 'Access key'),
|
choices=[('password', 'Password'), ('ssh_key', 'SSH key'), ('access_key', 'Access key'),
|
||||||
('token', 'Token')], default='password', max_length=16, verbose_name='Secret type')),
|
('token', 'Token'), ('api_key', 'API key')], default='password', max_length=16,
|
||||||
|
verbose_name='Secret type')),
|
||||||
('secret', common.db.fields.EncryptTextField(blank=True, null=True, verbose_name='Secret')),
|
('secret', common.db.fields.EncryptTextField(blank=True, null=True, verbose_name='Secret')),
|
||||||
('version', models.IntegerField(default=0, verbose_name='Version')),
|
('version', models.IntegerField(default=0, verbose_name='Version')),
|
||||||
('history_id', models.AutoField(primary_key=True, serialize=False)),
|
('history_id', models.AutoField(primary_key=True, serialize=False)),
|
||||||
@@ -96,7 +102,8 @@ class Migration(migrations.Migration):
|
|||||||
('username', models.CharField(blank=True, db_index=True, max_length=128, verbose_name='Username')),
|
('username', models.CharField(blank=True, db_index=True, max_length=128, verbose_name='Username')),
|
||||||
('secret_type', models.CharField(
|
('secret_type', models.CharField(
|
||||||
choices=[('password', 'Password'), ('ssh_key', 'SSH key'), ('access_key', 'Access key'),
|
choices=[('password', 'Password'), ('ssh_key', 'SSH key'), ('access_key', 'Access key'),
|
||||||
('token', 'Token')], default='password', max_length=16, verbose_name='Secret type')),
|
('token', 'Token'), ('api_key', 'API key')], default='password', max_length=16,
|
||||||
|
verbose_name='Secret type')),
|
||||||
('secret', common.db.fields.EncryptTextField(blank=True, null=True, verbose_name='Secret')),
|
('secret', common.db.fields.EncryptTextField(blank=True, null=True, verbose_name='Secret')),
|
||||||
('privileged', models.BooleanField(default=False, verbose_name='Privileged')),
|
('privileged', models.BooleanField(default=False, verbose_name='Privileged')),
|
||||||
('is_active', models.BooleanField(default=True, verbose_name='Is active')),
|
('is_active', models.BooleanField(default=True, verbose_name='Is active')),
|
||||||
|
|||||||
@@ -1,11 +1,13 @@
|
|||||||
# Generated by Django 3.2.16 on 2022-12-30 08:08
|
# Generated by Django 3.2.16 on 2022-12-30 08:08
|
||||||
|
|
||||||
|
import uuid
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
import common.db.encoder
|
import common.db.encoder
|
||||||
import common.db.fields
|
import common.db.fields
|
||||||
from django.conf import settings
|
|
||||||
from django.db import migrations, models
|
|
||||||
import django.db.models.deletion
|
|
||||||
import uuid
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
@@ -53,7 +55,8 @@ class Migration(migrations.Migration):
|
|||||||
primary_key=True, serialize=False, to='assets.baseautomation')),
|
primary_key=True, serialize=False, to='assets.baseautomation')),
|
||||||
('secret_type', models.CharField(
|
('secret_type', models.CharField(
|
||||||
choices=[('password', 'Password'), ('ssh_key', 'SSH key'), ('access_key', 'Access key'),
|
choices=[('password', 'Password'), ('ssh_key', 'SSH key'), ('access_key', 'Access key'),
|
||||||
('token', 'Token')], default='password', max_length=16, verbose_name='Secret type')),
|
('token', 'Token'), ('api_key', 'API key')], default='password', max_length=16,
|
||||||
|
verbose_name='Secret type')),
|
||||||
('secret_strategy', models.CharField(choices=[('specific', 'Specific password'),
|
('secret_strategy', models.CharField(choices=[('specific', 'Specific password'),
|
||||||
('random_one', 'All assets use the same random password'),
|
('random_one', 'All assets use the same random password'),
|
||||||
('random_all',
|
('random_all',
|
||||||
@@ -156,7 +159,8 @@ class Migration(migrations.Migration):
|
|||||||
primary_key=True, serialize=False, to='assets.baseautomation')),
|
primary_key=True, serialize=False, to='assets.baseautomation')),
|
||||||
('secret_type', models.CharField(
|
('secret_type', models.CharField(
|
||||||
choices=[('password', 'Password'), ('ssh_key', 'SSH key'), ('access_key', 'Access key'),
|
choices=[('password', 'Password'), ('ssh_key', 'SSH key'), ('access_key', 'Access key'),
|
||||||
('token', 'Token')], default='password', max_length=16, verbose_name='Secret type')),
|
('token', 'Token'), ('api_key', 'API key')], default='password', max_length=16,
|
||||||
|
verbose_name='Secret type')),
|
||||||
('secret_strategy', models.CharField(choices=[('specific', 'Specific password'),
|
('secret_strategy', models.CharField(choices=[('specific', 'Specific password'),
|
||||||
('random_one', 'All assets use the same random password'),
|
('random_one', 'All assets use the same random password'),
|
||||||
('random_all',
|
('random_all',
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
from .base import *
|
|
||||||
from .account import *
|
from .account import *
|
||||||
from .automations import *
|
from .automations import *
|
||||||
|
from .base import *
|
||||||
|
|||||||
@@ -88,20 +88,33 @@ class Account(AbsConnectivity, BaseAccount):
|
|||||||
def has_secret(self):
|
def has_secret(self):
|
||||||
return bool(self.secret)
|
return bool(self.secret)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_special_account(cls, name):
|
||||||
|
if name == AliasAccount.INPUT.value:
|
||||||
|
return cls.get_manual_account()
|
||||||
|
elif name == AliasAccount.ANON.value:
|
||||||
|
return cls.get_anonymous_account()
|
||||||
|
else:
|
||||||
|
return cls(name=name, username=name, secret=None)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_manual_account(cls):
|
def get_manual_account(cls):
|
||||||
""" @INPUT 手动登录的账号(any) """
|
""" @INPUT 手动登录的账号(any) """
|
||||||
return cls(name=AliasAccount.INPUT.label, username=AliasAccount.INPUT.value, secret=None)
|
return cls(name=AliasAccount.INPUT.label, username=AliasAccount.INPUT.value, secret=None)
|
||||||
|
|
||||||
@lazyproperty
|
@classmethod
|
||||||
def versions(self):
|
def get_anonymous_account(cls):
|
||||||
return self.history.count()
|
return cls(name=AliasAccount.ANON.label, username=AliasAccount.ANON.value, secret=None)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_user_account(cls):
|
def get_user_account(cls):
|
||||||
""" @USER 动态用户的账号(self) """
|
""" @USER 动态用户的账号(self) """
|
||||||
return cls(name=AliasAccount.USER.label, username=AliasAccount.USER.value, secret=None)
|
return cls(name=AliasAccount.USER.label, username=AliasAccount.USER.value, secret=None)
|
||||||
|
|
||||||
|
@lazyproperty
|
||||||
|
def versions(self):
|
||||||
|
return self.history.count()
|
||||||
|
|
||||||
def get_su_from_accounts(self):
|
def get_su_from_accounts(self):
|
||||||
""" 排除自己和以自己为 su-from 的账号 """
|
""" 排除自己和以自己为 su-from 的账号 """
|
||||||
return self.asset.accounts.exclude(id=self.id).exclude(su_from=self)
|
return self.asset.accounts.exclude(id=self.id).exclude(su_from=self)
|
||||||
@@ -124,10 +137,13 @@ class AccountTemplate(BaseAccount):
|
|||||||
]
|
]
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_su_from_account_templates(cls, instance=None):
|
def get_su_from_account_templates(cls, pk=None):
|
||||||
if not instance:
|
if pk is None:
|
||||||
return cls.objects.all()
|
return cls.objects.all()
|
||||||
return cls.objects.exclude(Q(id=instance.id) | Q(su_from=instance))
|
return cls.objects.exclude(Q(id=pk) | Q(su_from_id=pk))
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f'{self.name}({self.username})'
|
||||||
|
|
||||||
def get_su_from_account(self, asset):
|
def get_su_from_account(self, asset):
|
||||||
su_from = self.su_from
|
su_from = self.su_from
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ from django.db import IntegrityError
|
|||||||
from django.db.models import Q
|
from django.db.models import Q
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
|
from rest_framework.generics import get_object_or_404
|
||||||
from rest_framework.validators import UniqueTogetherValidator
|
from rest_framework.validators import UniqueTogetherValidator
|
||||||
|
|
||||||
from accounts.const import SecretType, Source, AccountInvalidPolicy
|
from accounts.const import SecretType, Source, AccountInvalidPolicy
|
||||||
@@ -22,8 +23,8 @@ logger = get_logger(__name__)
|
|||||||
|
|
||||||
class AccountCreateUpdateSerializerMixin(serializers.Serializer):
|
class AccountCreateUpdateSerializerMixin(serializers.Serializer):
|
||||||
template = serializers.PrimaryKeyRelatedField(
|
template = serializers.PrimaryKeyRelatedField(
|
||||||
queryset=AccountTemplate.objects,
|
queryset=AccountTemplate.objects, required=False,
|
||||||
required=False, label=_("Template"), write_only=True
|
label=_("Template"), write_only=True, allow_null=True
|
||||||
)
|
)
|
||||||
push_now = serializers.BooleanField(
|
push_now = serializers.BooleanField(
|
||||||
default=False, label=_("Push now"), write_only=True
|
default=False, label=_("Push now"), write_only=True
|
||||||
@@ -33,7 +34,7 @@ class AccountCreateUpdateSerializerMixin(serializers.Serializer):
|
|||||||
)
|
)
|
||||||
on_invalid = LabeledChoiceField(
|
on_invalid = LabeledChoiceField(
|
||||||
choices=AccountInvalidPolicy.choices, default=AccountInvalidPolicy.ERROR,
|
choices=AccountInvalidPolicy.choices, default=AccountInvalidPolicy.ERROR,
|
||||||
write_only=True, label=_('Exist policy')
|
write_only=True, allow_null=True, label=_('Exist policy'),
|
||||||
)
|
)
|
||||||
_template = None
|
_template = None
|
||||||
clean_auth_fields: callable
|
clean_auth_fields: callable
|
||||||
@@ -60,10 +61,6 @@ class AccountCreateUpdateSerializerMixin(serializers.Serializer):
|
|||||||
self.from_template_if_need(data)
|
self.from_template_if_need(data)
|
||||||
self.set_uniq_name_if_need(data, asset)
|
self.set_uniq_name_if_need(data, asset)
|
||||||
|
|
||||||
def to_internal_value(self, data):
|
|
||||||
self.from_template_if_need(data)
|
|
||||||
return super().to_internal_value(data)
|
|
||||||
|
|
||||||
def set_uniq_name_if_need(self, initial_data, asset):
|
def set_uniq_name_if_need(self, initial_data, asset):
|
||||||
name = initial_data.get('name')
|
name = initial_data.get('name')
|
||||||
if name is not None:
|
if name is not None:
|
||||||
@@ -105,6 +102,15 @@ class AccountCreateUpdateSerializerMixin(serializers.Serializer):
|
|||||||
continue
|
continue
|
||||||
attrs[name] = value
|
attrs[name] = value
|
||||||
initial_data.update(attrs)
|
initial_data.update(attrs)
|
||||||
|
initial_data.update({
|
||||||
|
'source': Source.TEMPLATE,
|
||||||
|
'source_id': str(template.id)
|
||||||
|
})
|
||||||
|
asset_id = initial_data.get('asset')
|
||||||
|
if isinstance(asset_id, list) or not asset_id:
|
||||||
|
return
|
||||||
|
asset = get_object_or_404(Asset, pk=asset_id)
|
||||||
|
initial_data['su_from'] = template.get_su_from_account(asset)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def push_account_if_need(instance, push_now, params, stat):
|
def push_account_if_need(instance, push_now, params, stat):
|
||||||
@@ -149,19 +155,10 @@ class AccountCreateUpdateSerializerMixin(serializers.Serializer):
|
|||||||
else:
|
else:
|
||||||
raise serializers.ValidationError('Account already exists')
|
raise serializers.ValidationError('Account already exists')
|
||||||
|
|
||||||
def generate_source_data(self, validated_data):
|
|
||||||
template = self._template
|
|
||||||
if template is None:
|
|
||||||
return
|
|
||||||
|
|
||||||
validated_data['source'] = Source.TEMPLATE
|
|
||||||
validated_data['source_id'] = str(template.id)
|
|
||||||
|
|
||||||
def create(self, validated_data):
|
def create(self, validated_data):
|
||||||
push_now = validated_data.pop('push_now', None)
|
push_now = validated_data.pop('push_now', None)
|
||||||
params = validated_data.pop('params', None)
|
params = validated_data.pop('params', None)
|
||||||
self.clean_auth_fields(validated_data)
|
self.clean_auth_fields(validated_data)
|
||||||
self.generate_source_data(validated_data)
|
|
||||||
instance, stat = self.do_create(validated_data)
|
instance, stat = self.do_create(validated_data)
|
||||||
self.push_account_if_need(instance, push_now, params, stat)
|
self.push_account_if_need(instance, push_now, params, stat)
|
||||||
return instance
|
return instance
|
||||||
@@ -201,8 +198,11 @@ class AccountAssetSerializer(serializers.ModelSerializer):
|
|||||||
|
|
||||||
class AccountSerializer(AccountCreateUpdateSerializerMixin, BaseAccountSerializer):
|
class AccountSerializer(AccountCreateUpdateSerializerMixin, BaseAccountSerializer):
|
||||||
asset = AccountAssetSerializer(label=_('Asset'))
|
asset = AccountAssetSerializer(label=_('Asset'))
|
||||||
source = LabeledChoiceField(choices=Source.choices, label=_("Source"), read_only=True)
|
|
||||||
has_secret = serializers.BooleanField(label=_("Has secret"), read_only=True)
|
has_secret = serializers.BooleanField(label=_("Has secret"), read_only=True)
|
||||||
|
source = LabeledChoiceField(
|
||||||
|
choices=Source.choices, label=_("Source"), required=False,
|
||||||
|
allow_null=True, default=Source.LOCAL
|
||||||
|
)
|
||||||
su_from = ObjectRelatedField(
|
su_from = ObjectRelatedField(
|
||||||
required=False, queryset=Account.objects, allow_null=True, allow_empty=True,
|
required=False, queryset=Account.objects, allow_null=True, allow_empty=True,
|
||||||
label=_('Su from'), attrs=('id', 'name', 'username')
|
label=_('Su from'), attrs=('id', 'name', 'username')
|
||||||
@@ -215,11 +215,12 @@ class AccountSerializer(AccountCreateUpdateSerializerMixin, BaseAccountSerialize
|
|||||||
'source', 'source_id', 'connectivity',
|
'source', 'source_id', 'connectivity',
|
||||||
] + AccountCreateUpdateSerializerMixin.Meta.fields
|
] + AccountCreateUpdateSerializerMixin.Meta.fields
|
||||||
read_only_fields = BaseAccountSerializer.Meta.read_only_fields + [
|
read_only_fields = BaseAccountSerializer.Meta.read_only_fields + [
|
||||||
'source', 'source_id', 'connectivity'
|
'connectivity'
|
||||||
]
|
]
|
||||||
extra_kwargs = {
|
extra_kwargs = {
|
||||||
**BaseAccountSerializer.Meta.extra_kwargs,
|
**BaseAccountSerializer.Meta.extra_kwargs,
|
||||||
'name': {'required': False},
|
'name': {'required': False},
|
||||||
|
'source_id': {'required': False, 'allow_null': True},
|
||||||
}
|
}
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@@ -253,11 +254,14 @@ class AssetAccountBulkSerializer(
|
|||||||
fields = [
|
fields = [
|
||||||
'name', 'username', 'secret', 'secret_type', 'passphrase',
|
'name', 'username', 'secret', 'secret_type', 'passphrase',
|
||||||
'privileged', 'is_active', 'comment', 'template',
|
'privileged', 'is_active', 'comment', 'template',
|
||||||
'on_invalid', 'push_now', 'assets', 'su_from_username'
|
'on_invalid', 'push_now', 'assets', 'su_from_username',
|
||||||
|
'source', 'source_id',
|
||||||
]
|
]
|
||||||
extra_kwargs = {
|
extra_kwargs = {
|
||||||
'name': {'required': False},
|
'name': {'required': False},
|
||||||
'secret_type': {'required': False},
|
'secret_type': {'required': False},
|
||||||
|
'source': {'required': False, 'allow_null': True},
|
||||||
|
'source_id': {'required': False, 'allow_null': True},
|
||||||
}
|
}
|
||||||
|
|
||||||
def set_initial_value(self):
|
def set_initial_value(self):
|
||||||
@@ -397,7 +401,6 @@ class AssetAccountBulkSerializer(
|
|||||||
|
|
||||||
def create(self, validated_data):
|
def create(self, validated_data):
|
||||||
push_now = validated_data.pop('push_now', False)
|
push_now = validated_data.pop('push_now', False)
|
||||||
self.generate_source_data(validated_data)
|
|
||||||
results = self.perform_bulk_create(validated_data)
|
results = self.perform_bulk_create(validated_data)
|
||||||
self.push_accounts_if_need(results, push_now)
|
self.push_accounts_if_need(results, push_now)
|
||||||
for res in results:
|
for res in results:
|
||||||
|
|||||||
@@ -78,5 +78,8 @@ class BaseAccountSerializer(AuthValidateMixin, BulkOrgResourceModelSerializer):
|
|||||||
]
|
]
|
||||||
extra_kwargs = {
|
extra_kwargs = {
|
||||||
'spec_info': {'label': _('Spec info')},
|
'spec_info': {'label': _('Spec info')},
|
||||||
'username': {'help_text': _("Tip: If no username is required for authentication, fill in `null`")}
|
'username': {'help_text': _(
|
||||||
|
"Tip: If no username is required for authentication, fill in `null`, "
|
||||||
|
"If AD account, like `username@domain`"
|
||||||
|
)},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,8 +23,12 @@ class AccountTemplateSerializer(BaseAccountSerializer):
|
|||||||
def sync_accounts_secret(self, instance, diff):
|
def sync_accounts_secret(self, instance, diff):
|
||||||
if not self._is_sync_account or 'secret' not in diff:
|
if not self._is_sync_account or 'secret' not in diff:
|
||||||
return
|
return
|
||||||
|
query_data = {
|
||||||
accounts = Account.objects.filter(source_id=instance.id)
|
'source_id': instance.id,
|
||||||
|
'username': instance.username,
|
||||||
|
'secret_type': instance.secret_type
|
||||||
|
}
|
||||||
|
accounts = Account.objects.filter(**query_data)
|
||||||
instance.bulk_sync_account_secret(accounts, self.context['request'].user.id)
|
instance.bulk_sync_account_secret(accounts, self.context['request'].user.id)
|
||||||
|
|
||||||
def validate(self, attrs):
|
def validate(self, attrs):
|
||||||
@@ -38,7 +42,10 @@ class AccountTemplateSerializer(BaseAccountSerializer):
|
|||||||
if getattr(instance, k, None) != v
|
if getattr(instance, k, None) != v
|
||||||
}
|
}
|
||||||
instance = super().update(instance, validated_data)
|
instance = super().update(instance, validated_data)
|
||||||
self.sync_accounts_secret(instance, diff)
|
if {'username', 'secret_type'} & set(diff.keys()):
|
||||||
|
Account.objects.filter(source_id=instance.id).update(source_id=None)
|
||||||
|
else:
|
||||||
|
self.sync_accounts_secret(instance, diff)
|
||||||
return instance
|
return instance
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -63,15 +63,17 @@ class AutomationExecutionSerializer(serializers.ModelSerializer):
|
|||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_snapshot(obj):
|
def get_snapshot(obj):
|
||||||
tp = obj.snapshot['type']
|
tp = obj.snapshot.get('type', '')
|
||||||
|
type_display = tp if not hasattr(AutomationTypes, tp) \
|
||||||
|
else getattr(AutomationTypes, tp).label
|
||||||
snapshot = {
|
snapshot = {
|
||||||
'type': tp,
|
'type': tp,
|
||||||
'name': obj.snapshot['name'],
|
'name': obj.snapshot.get('name'),
|
||||||
'comment': obj.snapshot['comment'],
|
'comment': obj.snapshot.get('comment'),
|
||||||
'accounts': obj.snapshot['accounts'],
|
'accounts': obj.snapshot.get('accounts'),
|
||||||
'node_amount': len(obj.snapshot['nodes']),
|
'node_amount': len(obj.snapshot.get('nodes', [])),
|
||||||
'asset_amount': len(obj.snapshot['assets']),
|
'asset_amount': len(obj.snapshot.get('assets', [])),
|
||||||
'type_display': getattr(AutomationTypes, tp).label,
|
'type_display': type_display,
|
||||||
}
|
}
|
||||||
return snapshot
|
return snapshot
|
||||||
|
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ class ChangeSecretAutomationSerializer(AuthValidateMixin, BaseAutomationSerializ
|
|||||||
read_only_fields = BaseAutomationSerializer.Meta.read_only_fields
|
read_only_fields = BaseAutomationSerializer.Meta.read_only_fields
|
||||||
fields = BaseAutomationSerializer.Meta.fields + read_only_fields + [
|
fields = BaseAutomationSerializer.Meta.fields + read_only_fields + [
|
||||||
'secret_type', 'secret_strategy', 'secret', 'password_rules',
|
'secret_type', 'secret_strategy', 'secret', 'password_rules',
|
||||||
'ssh_key_change_strategy', 'passphrase', 'recipients',
|
'ssh_key_change_strategy', 'passphrase', 'recipients', 'params'
|
||||||
]
|
]
|
||||||
extra_kwargs = {**BaseAutomationSerializer.Meta.extra_kwargs, **{
|
extra_kwargs = {**BaseAutomationSerializer.Meta.extra_kwargs, **{
|
||||||
'accounts': {'required': True},
|
'accounts': {'required': True},
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ class PushAccountAutomationSerializer(ChangeSecretAutomationSerializer):
|
|||||||
|
|
||||||
class Meta(ChangeSecretAutomationSerializer.Meta):
|
class Meta(ChangeSecretAutomationSerializer.Meta):
|
||||||
model = PushAccountAutomation
|
model = PushAccountAutomation
|
||||||
fields = ['params'] + [
|
fields = [
|
||||||
n for n in ChangeSecretAutomationSerializer.Meta.fields
|
n for n in ChangeSecretAutomationSerializer.Meta.fields
|
||||||
if n not in ['recipients']
|
if n not in ['recipients']
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ urlpatterns = [
|
|||||||
|
|
||||||
path('push-account/<uuid:pk>/asset/remove/', api.PushAccountRemoveAssetApi.as_view(),
|
path('push-account/<uuid:pk>/asset/remove/', api.PushAccountRemoveAssetApi.as_view(),
|
||||||
name='push-account-remove-asset'),
|
name='push-account-remove-asset'),
|
||||||
path('push-accountt/<uuid:pk>/asset/add/', api.PushAccountAddAssetApi.as_view(), name='push-account-add-asset'),
|
path('push-account/<uuid:pk>/asset/add/', api.PushAccountAddAssetApi.as_view(), name='push-account-add-asset'),
|
||||||
path('push-account/<uuid:pk>/nodes/', api.PushAccountNodeAddRemoveApi.as_view(),
|
path('push-account/<uuid:pk>/nodes/', api.PushAccountNodeAddRemoveApi.as_view(),
|
||||||
name='push-account-add-or-remove-node'),
|
name='push-account-add-or-remove-node'),
|
||||||
path('push-account/<uuid:pk>/assets/', api.PushAccountAssetsListApi.as_view(), name='push-account-assets'),
|
path('push-account/<uuid:pk>/assets/', api.PushAccountAssetsListApi.as_view(), name='push-account-assets'),
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ from rest_framework import serializers
|
|||||||
from accounts.const import (
|
from accounts.const import (
|
||||||
SecretType, DEFAULT_PASSWORD_RULES
|
SecretType, DEFAULT_PASSWORD_RULES
|
||||||
)
|
)
|
||||||
from common.utils import gen_key_pair, random_string
|
from common.utils import ssh_key_gen, random_string
|
||||||
from common.utils import validate_ssh_private_key, parse_ssh_private_key_str
|
from common.utils import validate_ssh_private_key, parse_ssh_private_key_str
|
||||||
|
|
||||||
|
|
||||||
@@ -16,7 +16,7 @@ class SecretGenerator:
|
|||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def generate_ssh_key():
|
def generate_ssh_key():
|
||||||
private_key, public_key = gen_key_pair()
|
private_key, public_key = ssh_key_gen()
|
||||||
return private_key
|
return private_key
|
||||||
|
|
||||||
def generate_password(self):
|
def generate_password(self):
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
from .command_acl import *
|
from .command_acl import *
|
||||||
|
from .connect_method import *
|
||||||
from .login_acl import *
|
from .login_acl import *
|
||||||
from .login_asset_acl import *
|
from .login_asset_acl import *
|
||||||
from .login_asset_check import *
|
from .login_asset_check import *
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
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 orgs.mixins.api import OrgBulkModelViewSet
|
from orgs.mixins.api import OrgBulkModelViewSet
|
||||||
|
from .common import ACLUserAssetFilterMixin
|
||||||
from .. import models, serializers
|
from .. import models, serializers
|
||||||
|
|
||||||
__all__ = ['CommandFilterACLViewSet', 'CommandGroupViewSet']
|
__all__ = ['CommandFilterACLViewSet', 'CommandGroupViewSet']
|
||||||
@@ -13,10 +15,16 @@ class CommandGroupViewSet(OrgBulkModelViewSet):
|
|||||||
serializer_class = serializers.CommandGroupSerializer
|
serializer_class = serializers.CommandGroupSerializer
|
||||||
|
|
||||||
|
|
||||||
|
class CommandACLFilter(ACLUserAssetFilterMixin):
|
||||||
|
class Meta:
|
||||||
|
model = models.CommandFilterACL
|
||||||
|
fields = ['name', ]
|
||||||
|
|
||||||
|
|
||||||
class CommandFilterACLViewSet(OrgBulkModelViewSet):
|
class CommandFilterACLViewSet(OrgBulkModelViewSet):
|
||||||
model = models.CommandFilterACL
|
model = models.CommandFilterACL
|
||||||
filterset_fields = ('name',)
|
filterset_class = CommandACLFilter
|
||||||
search_fields = filterset_fields
|
search_fields = ['name']
|
||||||
serializer_class = serializers.CommandFilterACLSerializer
|
serializer_class = serializers.CommandFilterACLSerializer
|
||||||
rbac_perms = {
|
rbac_perms = {
|
||||||
'command_review': 'tickets.add_superticket'
|
'command_review': 'tickets.add_superticket'
|
||||||
|
|||||||
45
apps/acls/api/common.py
Normal file
45
apps/acls/api/common.py
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
from django.db.models import Q
|
||||||
|
from django_filters import rest_framework as drf_filters
|
||||||
|
|
||||||
|
from common.drf.filters import BaseFilterSet
|
||||||
|
from common.utils import is_uuid
|
||||||
|
|
||||||
|
|
||||||
|
class ACLUserFilterMixin(BaseFilterSet):
|
||||||
|
users = drf_filters.CharFilter(method='filter_user')
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def filter_user(queryset, name, value):
|
||||||
|
from users.models import User
|
||||||
|
if not value:
|
||||||
|
return queryset
|
||||||
|
if is_uuid(value):
|
||||||
|
user = User.objects.filter(id=value).first()
|
||||||
|
else:
|
||||||
|
q = Q(name=value) | Q(username=value)
|
||||||
|
user = User.objects.filter(q).first()
|
||||||
|
if not user:
|
||||||
|
return queryset.none()
|
||||||
|
q = queryset.model.users.get_filter_q(user)
|
||||||
|
return queryset.filter(q).distinct()
|
||||||
|
|
||||||
|
|
||||||
|
class ACLUserAssetFilterMixin(ACLUserFilterMixin):
|
||||||
|
assets = drf_filters.CharFilter(method='filter_asset')
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def filter_asset(queryset, name, value):
|
||||||
|
from assets.models import Asset
|
||||||
|
if not value:
|
||||||
|
return queryset
|
||||||
|
|
||||||
|
if is_uuid(value):
|
||||||
|
asset = Asset.objects.filter(id=value).first()
|
||||||
|
else:
|
||||||
|
q = Q(name=value) | Q(address=value)
|
||||||
|
asset = Asset.objects.filter(q).first()
|
||||||
|
if not asset:
|
||||||
|
return queryset.none()
|
||||||
|
|
||||||
|
q = queryset.model.assets.get_filter_q(asset)
|
||||||
|
return queryset.filter(q).distinct()
|
||||||
29
apps/acls/api/connect_method.py
Normal file
29
apps/acls/api/connect_method.py
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
from django_filters import rest_framework as drf_filters
|
||||||
|
|
||||||
|
from common.api import JMSBulkModelViewSet
|
||||||
|
from orgs.utils import tmp_to_root_org
|
||||||
|
from .common import ACLUserFilterMixin
|
||||||
|
from .. import serializers
|
||||||
|
from ..models import ConnectMethodACL
|
||||||
|
|
||||||
|
__all__ = ['ConnectMethodACLViewSet']
|
||||||
|
|
||||||
|
|
||||||
|
class ConnectMethodFilter(ACLUserFilterMixin):
|
||||||
|
methods = drf_filters.CharFilter(field_name="methods__contains", lookup_expr='exact')
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = ConnectMethodACL
|
||||||
|
fields = ['name', ]
|
||||||
|
|
||||||
|
|
||||||
|
class ConnectMethodACLViewSet(JMSBulkModelViewSet):
|
||||||
|
queryset = ConnectMethodACL.objects.all()
|
||||||
|
filterset_class = ConnectMethodFilter
|
||||||
|
search_fields = ('name',)
|
||||||
|
serializer_class = serializers.ConnectMethodACLSerializer
|
||||||
|
|
||||||
|
def filter_queryset(self, queryset):
|
||||||
|
with tmp_to_root_org():
|
||||||
|
return super().filter_queryset(queryset)
|
||||||
|
|
||||||
@@ -1,14 +1,26 @@
|
|||||||
from common.api import JMSBulkModelViewSet
|
from common.api import JMSBulkModelViewSet
|
||||||
from ..models import LoginACL
|
|
||||||
|
from orgs.utils import tmp_to_root_org
|
||||||
|
from .common import ACLUserFilterMixin
|
||||||
from .. import serializers
|
from .. import serializers
|
||||||
from ..filters import LoginAclFilter
|
from ..models import LoginACL
|
||||||
|
|
||||||
__all__ = ['LoginACLViewSet']
|
__all__ = ['LoginACLViewSet']
|
||||||
|
|
||||||
|
|
||||||
|
class LoginACLFilter(ACLUserFilterMixin):
|
||||||
|
class Meta:
|
||||||
|
model = LoginACL
|
||||||
|
fields = ('name', 'action')
|
||||||
|
|
||||||
|
|
||||||
class LoginACLViewSet(JMSBulkModelViewSet):
|
class LoginACLViewSet(JMSBulkModelViewSet):
|
||||||
queryset = LoginACL.objects.all()
|
queryset = LoginACL.objects.all()
|
||||||
filterset_class = LoginAclFilter
|
filterset_class = LoginACLFilter
|
||||||
search_fields = ('name',)
|
search_fields = ('name',)
|
||||||
serializer_class = serializers.LoginACLSerializer
|
serializer_class = serializers.LoginACLSerializer
|
||||||
|
|
||||||
|
def filter_queryset(self, queryset):
|
||||||
|
with tmp_to_root_org():
|
||||||
|
return super().filter_queryset(queryset)
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +1,18 @@
|
|||||||
from orgs.mixins.api import OrgBulkModelViewSet
|
from orgs.mixins.api import OrgBulkModelViewSet
|
||||||
|
from .common import ACLUserAssetFilterMixin
|
||||||
from .. import models, serializers
|
from .. import models, serializers
|
||||||
|
|
||||||
|
|
||||||
__all__ = ['LoginAssetACLViewSet']
|
__all__ = ['LoginAssetACLViewSet']
|
||||||
|
|
||||||
|
|
||||||
|
class LoginAssetACLFilter(ACLUserAssetFilterMixin):
|
||||||
|
class Meta:
|
||||||
|
model = models.LoginAssetACL
|
||||||
|
fields = ['name', ]
|
||||||
|
|
||||||
|
|
||||||
class LoginAssetACLViewSet(OrgBulkModelViewSet):
|
class LoginAssetACLViewSet(OrgBulkModelViewSet):
|
||||||
model = models.LoginAssetACL
|
model = models.LoginAssetACL
|
||||||
filterset_fields = ('name', )
|
filterset_class = LoginAssetACLFilter
|
||||||
search_fields = filterset_fields
|
search_fields = ['name']
|
||||||
serializer_class = serializers.LoginAssetACLSerializer
|
serializer_class = serializers.LoginAssetACLSerializer
|
||||||
|
|||||||
@@ -30,14 +30,21 @@ class LoginAssetCheckAPI(CreateAPIView):
|
|||||||
return serializer
|
return serializer
|
||||||
|
|
||||||
def check_review(self):
|
def check_review(self):
|
||||||
|
user = self.serializer.user
|
||||||
|
asset = self.serializer.asset
|
||||||
|
|
||||||
|
# 用户满足的 acls
|
||||||
|
queryset = LoginAssetACL.objects.all()
|
||||||
|
q = LoginAssetACL.users.get_filter_q(LoginAssetACL, 'users', user)
|
||||||
|
queryset = queryset.filter(q)
|
||||||
|
q = LoginAssetACL.assets.get_filter_q(LoginAssetACL, 'assets', asset)
|
||||||
|
queryset = queryset.filter(q)
|
||||||
|
account_username = self.serializer.validated_data.get('account_username')
|
||||||
|
queryset = queryset.filter(accounts__contains=account_username)
|
||||||
|
|
||||||
with tmp_to_org(self.serializer.asset.org):
|
with tmp_to_org(self.serializer.asset.org):
|
||||||
kwargs = {
|
acl = queryset.valid().first()
|
||||||
'user': self.serializer.user,
|
|
||||||
'asset': self.serializer.asset,
|
|
||||||
'account_username': self.serializer.validated_data.get('account_username'),
|
|
||||||
'action': LoginAssetACL.ActionChoices.review
|
|
||||||
}
|
|
||||||
acl = LoginAssetACL.filter_queryset(**kwargs).valid().first()
|
|
||||||
if acl:
|
if acl:
|
||||||
need_review = True
|
need_review = True
|
||||||
response_data = self._get_response_data_of_need_review(acl)
|
response_data = self._get_response_data_of_need_review(acl)
|
||||||
|
|||||||
9
apps/acls/const.py
Normal file
9
apps/acls/const.py
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
from django.db import models
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
|
|
||||||
|
class ActionChoices(models.TextChoices):
|
||||||
|
reject = 'reject', _('Reject')
|
||||||
|
accept = 'accept', _('Accept')
|
||||||
|
review = 'review', _('Review')
|
||||||
|
warning = 'warning', _('Warning')
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
from django_filters import rest_framework as filters
|
|
||||||
from common.drf.filters import BaseFilterSet
|
|
||||||
|
|
||||||
from acls.models import LoginACL
|
|
||||||
|
|
||||||
|
|
||||||
class LoginAclFilter(BaseFilterSet):
|
|
||||||
user = filters.UUIDFilter(field_name='user_id')
|
|
||||||
user_display = filters.CharFilter(field_name='user__name')
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
model = LoginACL
|
|
||||||
fields = (
|
|
||||||
'name', 'user', 'user_display', 'action'
|
|
||||||
)
|
|
||||||
@@ -1,14 +1,14 @@
|
|||||||
# Generated by Django 3.1 on 2021-03-11 09:53
|
# Generated by Django 3.1 on 2021-03-11 09:53
|
||||||
|
|
||||||
from django.conf import settings
|
|
||||||
import django.core.validators
|
|
||||||
from django.db import migrations, models
|
|
||||||
import django.db.models.deletion
|
|
||||||
import uuid
|
import uuid
|
||||||
|
|
||||||
|
import django.core.validators
|
||||||
|
import django.db.models.deletion
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
initial = True
|
initial = True
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
@@ -24,37 +24,51 @@ class Migration(migrations.Migration):
|
|||||||
('date_created', models.DateTimeField(auto_now_add=True, null=True, verbose_name='Date created')),
|
('date_created', models.DateTimeField(auto_now_add=True, null=True, verbose_name='Date created')),
|
||||||
('date_updated', models.DateTimeField(auto_now=True, verbose_name='Date updated')),
|
('date_updated', models.DateTimeField(auto_now=True, verbose_name='Date updated')),
|
||||||
('name', models.CharField(max_length=128, verbose_name='Name')),
|
('name', models.CharField(max_length=128, verbose_name='Name')),
|
||||||
('priority', models.IntegerField(default=50, help_text='1-100, the lower the value will be match first', validators=[django.core.validators.MinValueValidator(1), django.core.validators.MaxValueValidator(100)], verbose_name='Priority')),
|
('priority', models.IntegerField(default=50, help_text='1-100, the lower the value will be match first',
|
||||||
|
validators=[django.core.validators.MinValueValidator(1),
|
||||||
|
django.core.validators.MaxValueValidator(100)],
|
||||||
|
verbose_name='Priority')),
|
||||||
('is_active', models.BooleanField(default=True, verbose_name='Active')),
|
('is_active', models.BooleanField(default=True, verbose_name='Active')),
|
||||||
('comment', models.TextField(blank=True, default='', verbose_name='Comment')),
|
('comment', models.TextField(blank=True, default='', verbose_name='Comment')),
|
||||||
('ip_group', models.JSONField(default=list, verbose_name='Login IP')),
|
('ip_group', models.JSONField(default=list, verbose_name='Login IP')),
|
||||||
('action', models.CharField(choices=[('reject', 'Reject'), ('allow', 'Allow')], default='reject', max_length=64, verbose_name='Action')),
|
('action',
|
||||||
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='login_acls', to=settings.AUTH_USER_MODEL, verbose_name='User')),
|
models.CharField(choices=[('reject', 'Reject'), ('allow', 'Allow')], default='reject', max_length=64,
|
||||||
|
verbose_name='Action')),
|
||||||
|
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='login_acls',
|
||||||
|
to=settings.AUTH_USER_MODEL, verbose_name='User')),
|
||||||
],
|
],
|
||||||
options={
|
options={
|
||||||
'ordering': ('priority', '-date_updated', 'name'),
|
'ordering': ('priority', '-is_active', 'name'),
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name='LoginAssetACL',
|
name='LoginAssetACL',
|
||||||
fields=[
|
fields=[
|
||||||
('org_id', models.CharField(blank=True, db_index=True, default='', max_length=36, verbose_name='Organization')),
|
('org_id',
|
||||||
|
models.CharField(blank=True, db_index=True, default='', max_length=36, verbose_name='Organization')),
|
||||||
('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)),
|
('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)),
|
||||||
('created_by', models.CharField(blank=True, max_length=32, null=True, verbose_name='Created by')),
|
('created_by', models.CharField(blank=True, max_length=32, null=True, verbose_name='Created by')),
|
||||||
('date_created', models.DateTimeField(auto_now_add=True, null=True, verbose_name='Date created')),
|
('date_created', models.DateTimeField(auto_now_add=True, null=True, verbose_name='Date created')),
|
||||||
('date_updated', models.DateTimeField(auto_now=True, verbose_name='Date updated')),
|
('date_updated', models.DateTimeField(auto_now=True, verbose_name='Date updated')),
|
||||||
('name', models.CharField(max_length=128, verbose_name='Name')),
|
('name', models.CharField(max_length=128, verbose_name='Name')),
|
||||||
('priority', models.IntegerField(default=50, help_text='1-100, the lower the value will be match first', validators=[django.core.validators.MinValueValidator(1), django.core.validators.MaxValueValidator(100)], verbose_name='Priority')),
|
('priority', models.IntegerField(default=50, help_text='1-100, the lower the value will be match first',
|
||||||
|
validators=[django.core.validators.MinValueValidator(1),
|
||||||
|
django.core.validators.MaxValueValidator(100)],
|
||||||
|
verbose_name='Priority')),
|
||||||
('is_active', models.BooleanField(default=True, verbose_name='Active')),
|
('is_active', models.BooleanField(default=True, verbose_name='Active')),
|
||||||
('comment', models.TextField(blank=True, default='', verbose_name='Comment')),
|
('comment', models.TextField(blank=True, default='', verbose_name='Comment')),
|
||||||
('users', models.JSONField(verbose_name='User')),
|
('users', models.JSONField(verbose_name='User')),
|
||||||
('system_users', models.JSONField(verbose_name='System User')),
|
('system_users', models.JSONField(verbose_name='System User')),
|
||||||
('assets', models.JSONField(verbose_name='Asset')),
|
('assets', models.JSONField(verbose_name='Asset')),
|
||||||
('action', models.CharField(choices=[('login_confirm', 'Login confirm')], default='login_confirm', max_length=64, verbose_name='Action')),
|
('action',
|
||||||
('reviewers', models.ManyToManyField(blank=True, related_name='review_login_asset_acls', to=settings.AUTH_USER_MODEL, verbose_name='Reviewers')),
|
models.CharField(choices=[('login_confirm', 'Login confirm')], default='login_confirm', max_length=64,
|
||||||
|
verbose_name='Action')),
|
||||||
|
('reviewers',
|
||||||
|
models.ManyToManyField(blank=True, related_name='review_login_asset_acls', to=settings.AUTH_USER_MODEL,
|
||||||
|
verbose_name='Reviewers')),
|
||||||
],
|
],
|
||||||
options={
|
options={
|
||||||
'ordering': ('priority', '-date_updated', 'name'),
|
'ordering': ('priority', '-is_active', 'name'),
|
||||||
'unique_together': {('name', 'org_id')},
|
'unique_together': {('name', 'org_id')},
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
import django
|
import django
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.db import migrations, models, transaction
|
from django.db import migrations, models, transaction
|
||||||
from acls.models import LoginACL
|
|
||||||
|
|
||||||
LOGIN_CONFIRM_ZH = '登录复核'
|
LOGIN_CONFIRM_ZH = '登录复核'
|
||||||
LOGIN_CONFIRM_EN = 'Login confirm'
|
LOGIN_CONFIRM_EN = 'Login confirm'
|
||||||
@@ -90,10 +89,10 @@ class Migration(migrations.Migration):
|
|||||||
),
|
),
|
||||||
migrations.AlterModelOptions(
|
migrations.AlterModelOptions(
|
||||||
name='loginacl',
|
name='loginacl',
|
||||||
options={'ordering': ('priority', '-date_updated', 'name'), 'verbose_name': 'Login acl'},
|
options={'ordering': ('priority', '-is_active', 'name'), 'verbose_name': 'Login acl'},
|
||||||
),
|
),
|
||||||
migrations.AlterModelOptions(
|
migrations.AlterModelOptions(
|
||||||
name='loginassetacl',
|
name='loginassetacl',
|
||||||
options={'ordering': ('priority', '-date_updated', 'name'), 'verbose_name': 'Login asset acl'},
|
options={'ordering': ('priority', '-is_active', 'name'), 'verbose_name': 'Login asset acl'},
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ from django.db import migrations
|
|||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
('acls', '0002_auto_20210926_1047'),
|
('acls', '0002_auto_20210926_1047'),
|
||||||
]
|
]
|
||||||
@@ -12,10 +11,10 @@ class Migration(migrations.Migration):
|
|||||||
operations = [
|
operations = [
|
||||||
migrations.AlterModelOptions(
|
migrations.AlterModelOptions(
|
||||||
name='loginacl',
|
name='loginacl',
|
||||||
options={'ordering': ('priority', '-date_updated', 'name'), 'verbose_name': 'Login acl'},
|
options={'ordering': ('priority', '-is_active', 'name'), 'verbose_name': 'Login acl'},
|
||||||
),
|
),
|
||||||
migrations.AlterModelOptions(
|
migrations.AlterModelOptions(
|
||||||
name='loginassetacl',
|
name='loginassetacl',
|
||||||
options={'ordering': ('priority', '-date_updated', 'name'), 'verbose_name': 'Login asset acl'},
|
options={'ordering': ('priority', '-is_active', 'name'), 'verbose_name': 'Login asset acl'},
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ class Migration(migrations.Migration):
|
|||||||
],
|
],
|
||||||
options={
|
options={
|
||||||
'verbose_name': 'Command acl',
|
'verbose_name': 'Command acl',
|
||||||
'ordering': ('priority', '-date_updated', 'name'),
|
'ordering': ('priority', '-is_active', 'name'),
|
||||||
'unique_together': {('name', 'org_id')},
|
'unique_together': {('name', 'org_id')},
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -20,14 +20,14 @@ class Migration(migrations.Migration):
|
|||||||
),
|
),
|
||||||
migrations.AlterModelOptions(
|
migrations.AlterModelOptions(
|
||||||
name='commandfilteracl',
|
name='commandfilteracl',
|
||||||
options={'ordering': ('priority', 'date_updated', 'name'), 'verbose_name': 'Command acl'},
|
options={'ordering': ('priority', '-is_active', 'name'), 'verbose_name': 'Command acl'},
|
||||||
),
|
),
|
||||||
migrations.AlterModelOptions(
|
migrations.AlterModelOptions(
|
||||||
name='loginacl',
|
name='loginacl',
|
||||||
options={'ordering': ('priority', 'date_updated', 'name'), 'verbose_name': 'Login acl'},
|
options={'ordering': ('priority', '-is_active', 'name'), 'verbose_name': 'Login acl'},
|
||||||
),
|
),
|
||||||
migrations.AlterModelOptions(
|
migrations.AlterModelOptions(
|
||||||
name='loginassetacl',
|
name='loginassetacl',
|
||||||
options={'ordering': ('priority', 'date_updated', 'name'), 'verbose_name': 'Login asset acl'},
|
options={'ordering': ('priority', '-is_active', 'name'), 'verbose_name': 'Login asset acl'},
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|||||||
44
apps/acls/migrations/0011_auto_20230425_1704.py
Normal file
44
apps/acls/migrations/0011_auto_20230425_1704.py
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
# Generated by Django 3.2.17 on 2023-04-25 09:04
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
import common.db.fields
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
dependencies = [
|
||||||
|
('acls', '0010_alter_commandfilteracl_command_groups'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='commandfilteracl',
|
||||||
|
name='new_accounts',
|
||||||
|
field=models.JSONField(default=list, verbose_name='Accounts'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='commandfilteracl',
|
||||||
|
name='new_assets',
|
||||||
|
field=common.db.fields.JSONManyToManyField(default=dict, to='assets.Asset', verbose_name='Assets'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='commandfilteracl',
|
||||||
|
name='new_users',
|
||||||
|
field=common.db.fields.JSONManyToManyField(default=dict, to='users.User', verbose_name='Users'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='loginassetacl',
|
||||||
|
name='new_accounts',
|
||||||
|
field=models.JSONField(default=list, verbose_name='Accounts')
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='loginassetacl',
|
||||||
|
name='new_assets',
|
||||||
|
field=common.db.fields.JSONManyToManyField(default=dict, to='assets.Asset', verbose_name='Assets'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='loginassetacl',
|
||||||
|
name='new_users',
|
||||||
|
field=common.db.fields.JSONManyToManyField(default=dict, to='users.User', verbose_name='Users'),
|
||||||
|
),
|
||||||
|
]
|
||||||
41
apps/acls/migrations/0012_auto_20230426_1111.py
Normal file
41
apps/acls/migrations/0012_auto_20230426_1111.py
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
# Generated by Django 3.2.17 on 2023-04-26 03:11
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
def migrate_base_acl_users_assets_accounts(apps, *args):
|
||||||
|
cmd_acl_model = apps.get_model('acls', 'CommandFilterACL')
|
||||||
|
login_asset_acl_model = apps.get_model('acls', 'LoginAssetACL')
|
||||||
|
|
||||||
|
for model in [cmd_acl_model, login_asset_acl_model]:
|
||||||
|
for obj in model.objects.all():
|
||||||
|
user_names = (obj.users or {}).get('username_group', [])
|
||||||
|
obj.new_users = {
|
||||||
|
"type": "attrs",
|
||||||
|
"attrs": [{"name": "username", "value": user_names, "match": "in"}]
|
||||||
|
}
|
||||||
|
|
||||||
|
asset_names = (obj.assets or {}).get('name_group', [])
|
||||||
|
asset_attrs = []
|
||||||
|
if asset_names:
|
||||||
|
asset_attrs.append({"name": "name", "value": asset_names, "match": "in"})
|
||||||
|
asset_address = (obj.assets or {}).get('address_group', [])
|
||||||
|
if asset_address:
|
||||||
|
asset_attrs.append({"name": "address", "value": asset_address, "match": "ip_in"})
|
||||||
|
obj.new_assets = {"type": "attrs", "attrs": asset_attrs}
|
||||||
|
|
||||||
|
account_usernames = (obj.accounts or {}).get('username_group', [])
|
||||||
|
if '*' in account_usernames:
|
||||||
|
account_usernames = ['@ALL']
|
||||||
|
obj.new_accounts = account_usernames
|
||||||
|
obj.save()
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
dependencies = [
|
||||||
|
('acls', '0011_auto_20230425_1704'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RunPython(migrate_base_acl_users_assets_accounts)
|
||||||
|
]
|
||||||
66
apps/acls/migrations/0013_auto_20230426_1759.py
Normal file
66
apps/acls/migrations/0013_auto_20230426_1759.py
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
# Generated by Django 3.2.17 on 2023-04-26 09:59
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
dependencies = [
|
||||||
|
('acls', '0012_auto_20230426_1111'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='commandfilteracl',
|
||||||
|
name='accounts',
|
||||||
|
),
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='commandfilteracl',
|
||||||
|
name='assets',
|
||||||
|
),
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='commandfilteracl',
|
||||||
|
name='users',
|
||||||
|
),
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='loginassetacl',
|
||||||
|
name='accounts',
|
||||||
|
),
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='loginassetacl',
|
||||||
|
name='assets',
|
||||||
|
),
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='loginassetacl',
|
||||||
|
name='users',
|
||||||
|
),
|
||||||
|
migrations.RenameField(
|
||||||
|
model_name='commandfilteracl',
|
||||||
|
old_name='new_accounts',
|
||||||
|
new_name='accounts',
|
||||||
|
),
|
||||||
|
migrations.RenameField(
|
||||||
|
model_name='commandfilteracl',
|
||||||
|
old_name='new_assets',
|
||||||
|
new_name='assets',
|
||||||
|
),
|
||||||
|
migrations.RenameField(
|
||||||
|
model_name='commandfilteracl',
|
||||||
|
old_name='new_users',
|
||||||
|
new_name='users',
|
||||||
|
),
|
||||||
|
migrations.RenameField(
|
||||||
|
model_name='loginassetacl',
|
||||||
|
old_name='new_accounts',
|
||||||
|
new_name='accounts',
|
||||||
|
),
|
||||||
|
migrations.RenameField(
|
||||||
|
model_name='loginassetacl',
|
||||||
|
old_name='new_assets',
|
||||||
|
new_name='assets',
|
||||||
|
),
|
||||||
|
migrations.RenameField(
|
||||||
|
model_name='loginassetacl',
|
||||||
|
old_name='new_users',
|
||||||
|
new_name='users',
|
||||||
|
),
|
||||||
|
]
|
||||||
18
apps/acls/migrations/0014_loginassetacl_rules.py
Normal file
18
apps/acls/migrations/0014_loginassetacl_rules.py
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
# Generated by Django 3.2.17 on 2023-05-26 09:00
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('acls', '0013_auto_20230426_1759'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='loginassetacl',
|
||||||
|
name='rules',
|
||||||
|
field=models.JSONField(default=dict, verbose_name='Rule'),
|
||||||
|
),
|
||||||
|
]
|
||||||
46
apps/acls/migrations/0015_connectmethodacl.py
Normal file
46
apps/acls/migrations/0015_connectmethodacl.py
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
# Generated by Django 3.2.17 on 2023-06-06 06:23
|
||||||
|
|
||||||
|
import uuid
|
||||||
|
|
||||||
|
import django.core.validators
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
import common.db.fields
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
dependencies = [
|
||||||
|
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||||
|
('acls', '0014_loginassetacl_rules'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='ConnectMethodACL',
|
||||||
|
fields=[
|
||||||
|
('created_by', models.CharField(blank=True, max_length=128, null=True, verbose_name='Created by')),
|
||||||
|
('updated_by', models.CharField(blank=True, max_length=128, null=True, verbose_name='Updated by')),
|
||||||
|
('date_created', models.DateTimeField(auto_now_add=True, null=True, verbose_name='Date created')),
|
||||||
|
('date_updated', models.DateTimeField(auto_now=True, verbose_name='Date updated')),
|
||||||
|
('comment', models.TextField(blank=True, default='', verbose_name='Comment')),
|
||||||
|
('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)),
|
||||||
|
('name', models.CharField(max_length=128, unique=True, verbose_name='Name')),
|
||||||
|
('priority', models.IntegerField(default=50, help_text='1-100, the lower the value will be match first',
|
||||||
|
validators=[django.core.validators.MinValueValidator(1),
|
||||||
|
django.core.validators.MaxValueValidator(100)],
|
||||||
|
verbose_name='Priority')),
|
||||||
|
('action', models.CharField(default='reject', max_length=64, verbose_name='Action')),
|
||||||
|
('is_active', models.BooleanField(default=True, verbose_name='Active')),
|
||||||
|
('users', common.db.fields.JSONManyToManyField(default=dict, to='users.User', verbose_name='Users')),
|
||||||
|
('connect_methods', models.JSONField(default=list, verbose_name='Connect methods')),
|
||||||
|
(
|
||||||
|
'reviewers',
|
||||||
|
models.ManyToManyField(blank=True, to=settings.AUTH_USER_MODEL, verbose_name='Reviewers')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'ordering': ('priority', '-is_active', 'name'),
|
||||||
|
'abstract': False,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
||||||
47
apps/acls/migrations/0016_auto_20230606_1857.py
Normal file
47
apps/acls/migrations/0016_auto_20230606_1857.py
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
# Generated by Django 3.2.17 on 2023-06-06 10:57
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
import common.db.fields
|
||||||
|
|
||||||
|
|
||||||
|
def migrate_users_login_acls(apps, schema_editor):
|
||||||
|
login_acl_model = apps.get_model('acls', 'LoginACL')
|
||||||
|
|
||||||
|
name_used = []
|
||||||
|
login_acls = []
|
||||||
|
for login_acl in login_acl_model.objects.all().select_related('user'):
|
||||||
|
name = '{}_{}'.format(login_acl.name, login_acl.user.username)
|
||||||
|
if name.lower() in name_used:
|
||||||
|
name += '_{}'.format(str(login_acl.user_id)[:4])
|
||||||
|
name_used.append(name.lower())
|
||||||
|
login_acl.name = name
|
||||||
|
login_acl.users = {
|
||||||
|
"type": "ids", "ids": [str(login_acl.user_id)]
|
||||||
|
}
|
||||||
|
login_acls.append(login_acl)
|
||||||
|
login_acl_model.objects.bulk_update(login_acls, ['name', 'users'])
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
dependencies = [
|
||||||
|
('acls', '0015_connectmethodacl'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='loginacl',
|
||||||
|
name='users',
|
||||||
|
field=common.db.fields.JSONManyToManyField(default=dict, to='users.User', verbose_name='Users'),
|
||||||
|
),
|
||||||
|
migrations.RunPython(migrate_users_login_acls),
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='loginacl',
|
||||||
|
name='user',
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='loginacl',
|
||||||
|
name='name',
|
||||||
|
field=models.CharField(max_length=128, unique=True, verbose_name='Name'),
|
||||||
|
),
|
||||||
|
]
|
||||||
16
apps/acls/migrations/0017_alter_connectmethodacl_options.py
Normal file
16
apps/acls/migrations/0017_alter_connectmethodacl_options.py
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
# Generated by Django 3.2.19 on 2023-06-13 07:49
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
dependencies = [
|
||||||
|
('acls', '0016_auto_20230606_1857'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterModelOptions(
|
||||||
|
name='connectmethodacl',
|
||||||
|
options={'ordering': ('priority', '-is_active', 'name'), 'verbose_name': 'Connect method acl'},
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
from .command_acl import *
|
||||||
|
from .connect_method import *
|
||||||
from .login_acl import *
|
from .login_acl import *
|
||||||
from .login_asset_acl import *
|
from .login_asset_acl import *
|
||||||
from .command_acl import *
|
|
||||||
|
|||||||
@@ -1,25 +1,20 @@
|
|||||||
from django.core.validators import MinValueValidator, MaxValueValidator
|
from django.core.validators import MinValueValidator, MaxValueValidator
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.db.models import Q
|
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
|
from common.db.fields import JSONManyToManyField
|
||||||
from common.db.models import JMSBaseModel
|
from common.db.models import JMSBaseModel
|
||||||
from common.utils import contains_ip
|
from common.utils import contains_ip
|
||||||
|
from common.utils.time_period import contains_time_period
|
||||||
from orgs.mixins.models import OrgModelMixin, OrgManager
|
from orgs.mixins.models import OrgModelMixin, OrgManager
|
||||||
|
from ..const import ActionChoices
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
'ACLManager',
|
'BaseACL', 'UserBaseACL', 'UserAssetAccountBaseACL',
|
||||||
'BaseACL',
|
|
||||||
'BaseACLQuerySet',
|
|
||||||
'UserAssetAccountBaseACL',
|
|
||||||
'UserAssetAccountACLQuerySet'
|
|
||||||
]
|
]
|
||||||
|
|
||||||
|
from orgs.utils import tmp_to_root_org
|
||||||
class ActionChoices(models.TextChoices):
|
from orgs.utils import tmp_to_org
|
||||||
reject = 'reject', _('Reject')
|
|
||||||
accept = 'accept', _('Accept')
|
|
||||||
review = 'review', _('Review')
|
|
||||||
|
|
||||||
|
|
||||||
class BaseACLQuerySet(models.QuerySet):
|
class BaseACLQuerySet(models.QuerySet):
|
||||||
@@ -36,43 +31,8 @@ class BaseACLQuerySet(models.QuerySet):
|
|||||||
return self.inactive()
|
return self.inactive()
|
||||||
|
|
||||||
|
|
||||||
class UserAssetAccountACLQuerySet(BaseACLQuerySet):
|
|
||||||
def filter_user(self, username):
|
|
||||||
q = Q(users__username_group__contains=username) | \
|
|
||||||
Q(users__username_group__contains='*')
|
|
||||||
return self.filter(q)
|
|
||||||
|
|
||||||
def filter_asset(self, name=None, address=None):
|
|
||||||
queryset = self.filter()
|
|
||||||
if name:
|
|
||||||
q = Q(assets__name_group__contains=name) | \
|
|
||||||
Q(assets__name_group__contains='*')
|
|
||||||
queryset = queryset.filter(q)
|
|
||||||
if address:
|
|
||||||
ids = [
|
|
||||||
q.id for q in queryset
|
|
||||||
if contains_ip(address, q.assets.get('address_group', []))
|
|
||||||
]
|
|
||||||
queryset = queryset.filter(id__in=ids)
|
|
||||||
return queryset
|
|
||||||
|
|
||||||
def filter_account(self, username):
|
|
||||||
q = Q(accounts__username_group__contains=username) | \
|
|
||||||
Q(accounts__username_group__contains='*')
|
|
||||||
return self.filter(q)
|
|
||||||
|
|
||||||
|
|
||||||
class ACLManager(models.Manager):
|
|
||||||
def valid(self):
|
|
||||||
return self.get_queryset().valid()
|
|
||||||
|
|
||||||
|
|
||||||
class OrgACLManager(OrgManager, ACLManager):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class BaseACL(JMSBaseModel):
|
class BaseACL(JMSBaseModel):
|
||||||
name = models.CharField(max_length=128, verbose_name=_('Name'))
|
name = models.CharField(max_length=128, verbose_name=_('Name'), unique=True)
|
||||||
priority = models.IntegerField(
|
priority = models.IntegerField(
|
||||||
default=50, verbose_name=_("Priority"),
|
default=50, verbose_name=_("Priority"),
|
||||||
help_text=_("1-100, the lower the value will be match first"),
|
help_text=_("1-100, the lower the value will be match first"),
|
||||||
@@ -83,46 +43,85 @@ class BaseACL(JMSBaseModel):
|
|||||||
is_active = models.BooleanField(default=True, verbose_name=_("Active"))
|
is_active = models.BooleanField(default=True, verbose_name=_("Active"))
|
||||||
|
|
||||||
ActionChoices = ActionChoices
|
ActionChoices = ActionChoices
|
||||||
objects = ACLManager.from_queryset(BaseACLQuerySet)()
|
objects = BaseACLQuerySet.as_manager()
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
ordering = ('priority', 'date_updated', 'name')
|
ordering = ('priority', '-is_active', 'name')
|
||||||
abstract = True
|
abstract = True
|
||||||
|
|
||||||
def is_action(self, action):
|
def is_action(self, action):
|
||||||
return self.action == action
|
return self.action == action
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_user_acls(cls, user):
|
||||||
|
return cls.objects.none()
|
||||||
|
|
||||||
class UserAssetAccountBaseACL(BaseACL, OrgModelMixin):
|
@classmethod
|
||||||
# username_group
|
def get_match_rule_acls(cls, user, ip, acl_qs=None):
|
||||||
users = models.JSONField(verbose_name=_('User'))
|
if acl_qs is None:
|
||||||
# name_group, address_group
|
acl_qs = cls.get_user_acls(user)
|
||||||
assets = models.JSONField(verbose_name=_('Asset'))
|
if not acl_qs:
|
||||||
# username_group
|
return
|
||||||
accounts = models.JSONField(verbose_name=_('Account'))
|
|
||||||
|
|
||||||
objects = OrgACLManager.from_queryset(UserAssetAccountACLQuerySet)()
|
for acl in acl_qs:
|
||||||
|
if acl.is_action(ActionChoices.review) and not acl.reviewers.exists():
|
||||||
|
continue
|
||||||
|
ip_group = acl.rules.get('ip_group')
|
||||||
|
time_periods = acl.rules.get('time_period')
|
||||||
|
is_contain_ip = contains_ip(ip, ip_group) if ip_group else True
|
||||||
|
is_contain_time_period = contains_time_period(time_periods) if time_periods else True
|
||||||
|
|
||||||
|
if is_contain_ip and is_contain_time_period:
|
||||||
|
# 满足条件,则返回
|
||||||
|
return acl
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
class UserBaseACL(BaseACL):
|
||||||
|
users = JSONManyToManyField('users.User', default=dict, verbose_name=_('Users'))
|
||||||
|
|
||||||
class Meta(BaseACL.Meta):
|
class Meta(BaseACL.Meta):
|
||||||
unique_together = ('name', 'org_id')
|
abstract = True
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_user_acls(cls, user):
|
||||||
|
queryset = cls.objects.all()
|
||||||
|
with tmp_to_root_org():
|
||||||
|
q = cls.users.get_filter_q(user)
|
||||||
|
queryset = queryset.filter(q)
|
||||||
|
return queryset.filter(is_active=True).distinct()
|
||||||
|
|
||||||
|
|
||||||
|
class UserAssetAccountBaseACL(OrgModelMixin, UserBaseACL):
|
||||||
|
name = models.CharField(max_length=128, verbose_name=_('Name'))
|
||||||
|
assets = JSONManyToManyField('assets.Asset', default=dict, verbose_name=_('Assets'))
|
||||||
|
accounts = models.JSONField(default=list, verbose_name=_("Accounts"))
|
||||||
|
objects = OrgManager.from_queryset(BaseACLQuerySet)()
|
||||||
|
|
||||||
|
class Meta(UserBaseACL.Meta):
|
||||||
|
unique_together = [('name', 'org_id')]
|
||||||
abstract = True
|
abstract = True
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def filter_queryset(cls, user=None, asset=None, account=None, account_username=None, **kwargs):
|
def filter_queryset(cls, user=None, asset=None, account=None, account_username=None, **kwargs):
|
||||||
queryset = cls.objects.all()
|
queryset = cls.objects.all()
|
||||||
org_id = None
|
|
||||||
if user:
|
if user:
|
||||||
queryset = queryset.filter_user(user.username)
|
q = cls.users.get_filter_q(user)
|
||||||
if account:
|
queryset = queryset.filter(q)
|
||||||
org_id = account.org_id
|
|
||||||
queryset = queryset.filter_account(account.username)
|
|
||||||
if account_username:
|
|
||||||
queryset = queryset.filter_account(username=account_username)
|
|
||||||
if asset:
|
if asset:
|
||||||
org_id = asset.org_id
|
org_id = asset.org_id
|
||||||
queryset = queryset.filter_asset(asset.name, asset.address)
|
with tmp_to_org(org_id):
|
||||||
if org_id:
|
q = cls.assets.get_filter_q(asset)
|
||||||
kwargs['org_id'] = org_id
|
queryset = queryset.filter(q)
|
||||||
|
if account and not account_username:
|
||||||
|
account_username = account.username
|
||||||
|
if account_username:
|
||||||
|
q = models.Q(accounts__contains=account_username) | \
|
||||||
|
models.Q(accounts__contains='*') | \
|
||||||
|
models.Q(accounts__contains='@ALL')
|
||||||
|
queryset = queryset.filter(q)
|
||||||
if kwargs:
|
if kwargs:
|
||||||
queryset = queryset.filter(**kwargs)
|
queryset = queryset.filter(**kwargs)
|
||||||
return queryset
|
return queryset.valid().distinct()
|
||||||
|
|||||||
14
apps/acls/models/connect_method.py
Normal file
14
apps/acls/models/connect_method.py
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
from django.db import models
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
|
from .base import UserBaseACL
|
||||||
|
|
||||||
|
__all__ = ['ConnectMethodACL']
|
||||||
|
|
||||||
|
|
||||||
|
class ConnectMethodACL(UserBaseACL):
|
||||||
|
connect_methods = models.JSONField(default=list, verbose_name=_('Connect methods'))
|
||||||
|
|
||||||
|
class Meta(UserBaseACL.Meta):
|
||||||
|
verbose_name = _('Connect method acl')
|
||||||
|
abstract = False
|
||||||
@@ -2,20 +2,15 @@ from django.db import models
|
|||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
from common.utils import get_request_ip, get_ip_city
|
from common.utils import get_request_ip, get_ip_city
|
||||||
from common.utils.ip import contains_ip
|
|
||||||
from common.utils.time_period import contains_time_period
|
|
||||||
from common.utils.timezone import local_now_display
|
from common.utils.timezone import local_now_display
|
||||||
from .base import BaseACL
|
from .base import UserBaseACL
|
||||||
|
|
||||||
|
|
||||||
class LoginACL(BaseACL):
|
class LoginACL(UserBaseACL):
|
||||||
user = models.ForeignKey(
|
|
||||||
'users.User', on_delete=models.CASCADE, related_name='login_acls', verbose_name=_('User')
|
|
||||||
)
|
|
||||||
# 规则, ip_group, time_period
|
# 规则, ip_group, time_period
|
||||||
rules = models.JSONField(default=dict, verbose_name=_('Rule'))
|
rules = models.JSONField(default=dict, verbose_name=_('Rule'))
|
||||||
|
|
||||||
class Meta(BaseACL.Meta):
|
class Meta(UserBaseACL.Meta):
|
||||||
verbose_name = _('Login acl')
|
verbose_name = _('Login acl')
|
||||||
abstract = False
|
abstract = False
|
||||||
|
|
||||||
@@ -25,40 +20,18 @@ class LoginACL(BaseACL):
|
|||||||
def is_action(self, action):
|
def is_action(self, action):
|
||||||
return self.action == action
|
return self.action == action
|
||||||
|
|
||||||
@classmethod
|
def create_confirm_ticket(self, request, user):
|
||||||
def filter_acl(cls, user):
|
|
||||||
return user.login_acls.all().valid().distinct()
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def match(user, ip):
|
|
||||||
acl_qs = LoginACL.filter_acl(user)
|
|
||||||
if not acl_qs:
|
|
||||||
return
|
|
||||||
|
|
||||||
for acl in acl_qs:
|
|
||||||
if acl.is_action(LoginACL.ActionChoices.review) and \
|
|
||||||
not acl.reviewers.exists():
|
|
||||||
continue
|
|
||||||
ip_group = acl.rules.get('ip_group')
|
|
||||||
time_periods = acl.rules.get('time_period')
|
|
||||||
is_contain_ip = contains_ip(ip, ip_group)
|
|
||||||
is_contain_time_period = contains_time_period(time_periods)
|
|
||||||
if is_contain_ip and is_contain_time_period:
|
|
||||||
# 满足条件,则返回
|
|
||||||
return acl
|
|
||||||
|
|
||||||
def create_confirm_ticket(self, request):
|
|
||||||
from tickets import const
|
from tickets import const
|
||||||
from tickets.models import ApplyLoginTicket
|
from tickets.models import ApplyLoginTicket
|
||||||
from orgs.models import Organization
|
from orgs.models import Organization
|
||||||
title = _('Login confirm') + ' {}'.format(self.user)
|
title = _('Login confirm') + ' {}'.format(user)
|
||||||
login_ip = get_request_ip(request) if request else ''
|
login_ip = get_request_ip(request) if request else ''
|
||||||
login_ip = login_ip or '0.0.0.0'
|
login_ip = login_ip or '0.0.0.0'
|
||||||
login_city = get_ip_city(login_ip)
|
login_city = get_ip_city(login_ip)
|
||||||
login_datetime = local_now_display()
|
login_datetime = local_now_display()
|
||||||
data = {
|
data = {
|
||||||
'title': title,
|
'title': title,
|
||||||
'applicant': self.user,
|
'applicant': user,
|
||||||
'apply_login_ip': login_ip,
|
'apply_login_ip': login_ip,
|
||||||
'org_id': Organization.ROOT_ID,
|
'org_id': Organization.ROOT_ID,
|
||||||
'apply_login_city': login_city,
|
'apply_login_city': login_city,
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
|
from django.db import models
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
|
|
||||||
from .base import UserAssetAccountBaseACL
|
from .base import UserAssetAccountBaseACL
|
||||||
|
|
||||||
|
|
||||||
class LoginAssetACL(UserAssetAccountBaseACL):
|
class LoginAssetACL(UserAssetAccountBaseACL):
|
||||||
|
# 规则, ip_group, time_period
|
||||||
|
rules = models.JSONField(default=dict, verbose_name=_('Rule'))
|
||||||
|
|
||||||
class Meta(UserAssetAccountBaseACL.Meta):
|
class Meta(UserAssetAccountBaseACL.Meta):
|
||||||
verbose_name = _('Login asset acl')
|
verbose_name = _('Login asset acl')
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
|
from .command_acl import *
|
||||||
|
from .connect_method import *
|
||||||
from .login_acl import *
|
from .login_acl import *
|
||||||
from .login_asset_acl import *
|
from .login_asset_acl import *
|
||||||
from .login_asset_check import *
|
from .login_asset_check import *
|
||||||
from .command_acl import *
|
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
|
|
||||||
from acls.models.base import ActionChoices
|
from acls.models.base import BaseACL
|
||||||
from common.serializers.fields import LabeledChoiceField, ObjectRelatedField
|
from common.serializers.fields import JSONManyToManyField, LabeledChoiceField
|
||||||
from jumpserver.utils import has_valid_xpack_license
|
from jumpserver.utils import has_valid_xpack_license
|
||||||
from orgs.models import Organization
|
from orgs.models import Organization
|
||||||
from users.models import User
|
from ..const import ActionChoices
|
||||||
|
|
||||||
common_help_text = _(
|
common_help_text = _(
|
||||||
"With * indicating a match all. "
|
"With * indicating a match all. "
|
||||||
@@ -21,7 +21,7 @@ class ACLUsersSerializer(serializers.Serializer):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class ACLAssestsSerializer(serializers.Serializer):
|
class ACLAssetsSerializer(serializers.Serializer):
|
||||||
address_group_help_text = _(
|
address_group_help_text = _(
|
||||||
"With * indicating a match all. "
|
"With * indicating a match all. "
|
||||||
"Such as: "
|
"Such as: "
|
||||||
@@ -61,57 +61,34 @@ class ActionAclSerializer(serializers.Serializer):
|
|||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
self.set_action_choices()
|
self.set_action_choices()
|
||||||
|
|
||||||
def set_action_choices(self):
|
|
||||||
action = self.fields.get("action")
|
|
||||||
if not action:
|
|
||||||
return
|
|
||||||
choices = action.choices
|
|
||||||
if not has_valid_xpack_license():
|
|
||||||
choices.pop(ActionChoices.review, None)
|
|
||||||
action._choices = choices
|
|
||||||
|
|
||||||
|
|
||||||
class BaseUserAssetAccountACLSerializerMixin(ActionAclSerializer, serializers.Serializer):
|
|
||||||
users = ACLUsersSerializer(label=_('User'))
|
|
||||||
assets = ACLAssestsSerializer(label=_('Asset'))
|
|
||||||
accounts = ACLAccountsSerializer(label=_('Account'))
|
|
||||||
users_username_group = serializers.ListField(
|
|
||||||
source='users.username_group', read_only=True, child=serializers.CharField(),
|
|
||||||
label=_('User (username)')
|
|
||||||
)
|
|
||||||
assets_name_group = serializers.ListField(
|
|
||||||
source='assets.name_group', read_only=True, child=serializers.CharField(),
|
|
||||||
label=_('Asset (name)')
|
|
||||||
)
|
|
||||||
assets_address_group = serializers.ListField(
|
|
||||||
source='assets.address_group', read_only=True, child=serializers.CharField(),
|
|
||||||
label=_('Asset (address)')
|
|
||||||
)
|
|
||||||
accounts_username_group = serializers.ListField(
|
|
||||||
source='accounts.username_group', read_only=True, child=serializers.CharField(),
|
|
||||||
label=_('Account (username)')
|
|
||||||
)
|
|
||||||
reviewers = ObjectRelatedField(
|
|
||||||
queryset=User.objects, many=True, required=False, label=_('Reviewers')
|
|
||||||
)
|
|
||||||
reviewers_amount = serializers.IntegerField(
|
|
||||||
read_only=True, source="reviewers.count", label=_('Reviewers amount')
|
|
||||||
)
|
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
|
action_choices_exclude = [ActionChoices.warning]
|
||||||
|
|
||||||
|
def set_action_choices(self):
|
||||||
|
field_action = self.fields.get("action")
|
||||||
|
if not field_action:
|
||||||
|
return
|
||||||
|
if not has_valid_xpack_license():
|
||||||
|
field_action._choices.pop(ActionChoices.review, None)
|
||||||
|
for choice in self.Meta.action_choices_exclude:
|
||||||
|
field_action._choices.pop(choice, None)
|
||||||
|
|
||||||
|
|
||||||
|
class BaseACLSerializer(ActionAclSerializer, serializers.Serializer):
|
||||||
|
class Meta(ActionAclSerializer.Meta):
|
||||||
|
model = BaseACL
|
||||||
fields_mini = ["id", "name"]
|
fields_mini = ["id", "name"]
|
||||||
fields_small = fields_mini + [
|
fields_small = fields_mini + [
|
||||||
'users_username_group', 'assets_address_group', 'assets_name_group',
|
"is_active", "priority", "action",
|
||||||
'accounts_username_group',
|
"date_created", "date_updated",
|
||||||
"users", "accounts", "assets", "is_active",
|
"comment", "created_by", "org_id",
|
||||||
"date_created", "date_updated", "priority",
|
|
||||||
"action", "comment", "created_by", "org_id",
|
|
||||||
]
|
]
|
||||||
fields_m2m = ["reviewers", "reviewers_amount"]
|
fields_m2m = ["reviewers", ]
|
||||||
fields = fields_small + fields_m2m
|
fields = fields_small + fields_m2m
|
||||||
extra_kwargs = {
|
extra_kwargs = {
|
||||||
"priority": {"default": 50},
|
"priority": {"default": 50},
|
||||||
"is_active": {"default": True},
|
"is_active": {"default": True},
|
||||||
|
'reviewers': {'label': _('Recipients')},
|
||||||
}
|
}
|
||||||
|
|
||||||
def validate_reviewers(self, reviewers):
|
def validate_reviewers(self, reviewers):
|
||||||
@@ -133,3 +110,18 @@ class BaseUserAssetAccountACLSerializerMixin(ActionAclSerializer, serializers.Se
|
|||||||
)
|
)
|
||||||
raise serializers.ValidationError(error)
|
raise serializers.ValidationError(error)
|
||||||
return valid_reviewers
|
return valid_reviewers
|
||||||
|
|
||||||
|
|
||||||
|
class BaseUserACLSerializer(BaseACLSerializer):
|
||||||
|
users = JSONManyToManyField(label=_('User'))
|
||||||
|
|
||||||
|
class Meta(BaseACLSerializer.Meta):
|
||||||
|
fields = BaseACLSerializer.Meta.fields + ['users']
|
||||||
|
|
||||||
|
|
||||||
|
class BaseUserAssetAccountACLSerializer(BaseUserACLSerializer):
|
||||||
|
assets = JSONManyToManyField(label=_('Asset'))
|
||||||
|
accounts = serializers.ListField(label=_('Account'))
|
||||||
|
|
||||||
|
class Meta(BaseUserACLSerializer.Meta):
|
||||||
|
fields = BaseUserACLSerializer.Meta.fields + ['assets', 'accounts']
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
|
|
||||||
from terminal.models import Session
|
|
||||||
from acls.models import CommandGroup, CommandFilterACL
|
from acls.models import CommandGroup, CommandFilterACL
|
||||||
from common.utils import lazyproperty, get_object_or_none
|
|
||||||
from common.serializers.fields import ObjectRelatedField, LabeledChoiceField
|
from common.serializers.fields import ObjectRelatedField, LabeledChoiceField
|
||||||
from orgs.utils import tmp_to_root_org
|
from common.utils import lazyproperty, get_object_or_none
|
||||||
from orgs.mixins.serializers import BulkOrgResourceModelSerializer
|
from orgs.mixins.serializers import BulkOrgResourceModelSerializer
|
||||||
from .base import BaseUserAssetAccountACLSerializerMixin as BaseSerializer
|
from orgs.utils import tmp_to_root_org
|
||||||
|
from terminal.models import Session
|
||||||
|
from .base import BaseUserAssetAccountACLSerializer as BaseSerializer
|
||||||
|
|
||||||
__all__ = ["CommandFilterACLSerializer", "CommandGroupSerializer", "CommandReviewSerializer"]
|
__all__ = ["CommandFilterACLSerializer", "CommandGroupSerializer", "CommandReviewSerializer"]
|
||||||
|
|
||||||
@@ -27,13 +27,12 @@ class CommandFilterACLSerializer(BaseSerializer, BulkOrgResourceModelSerializer)
|
|||||||
command_groups = ObjectRelatedField(
|
command_groups = ObjectRelatedField(
|
||||||
queryset=CommandGroup.objects, many=True, required=False, label=_('Command group')
|
queryset=CommandGroup.objects, many=True, required=False, label=_('Command group')
|
||||||
)
|
)
|
||||||
command_groups_amount = serializers.IntegerField(
|
|
||||||
source='command_groups.count', read_only=True, label=_('Command group amount')
|
|
||||||
)
|
|
||||||
|
|
||||||
class Meta(BaseSerializer.Meta):
|
class Meta(BaseSerializer.Meta):
|
||||||
model = CommandFilterACL
|
model = CommandFilterACL
|
||||||
fields = BaseSerializer.Meta.fields + ['command_groups', 'command_groups_amount']
|
fields = BaseSerializer.Meta.fields + ['command_groups']
|
||||||
|
# 默认都支持所有的 actions
|
||||||
|
action_choices_exclude = []
|
||||||
|
|
||||||
|
|
||||||
class CommandReviewSerializer(serializers.Serializer):
|
class CommandReviewSerializer(serializers.Serializer):
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user