mirror of
https://github.com/jumpserver/jumpserver.git
synced 2025-12-15 08:32:48 +00:00
Compare commits
322 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
214c0c892a | ||
|
|
0e0c860d59 | ||
|
|
e0da9ee30c | ||
|
|
499e008c6d | ||
|
|
896cb061ab | ||
|
|
11464151e2 | ||
|
|
4311e52681 | ||
|
|
a7c06f62f6 | ||
|
|
33db6de372 | ||
|
|
0bea545b6f | ||
|
|
424066b38f | ||
|
|
13d895b22e | ||
|
|
d5c51a4f0e | ||
|
|
7c965706d4 | ||
|
|
f59499f77e | ||
|
|
98536cbc24 | ||
|
|
7c29e60a82 | ||
|
|
9e68d7d1a0 | ||
|
|
77b7134404 | ||
|
|
fb7a839c16 | ||
|
|
e80855068e | ||
|
|
5832246e5f | ||
|
|
dbef45e23b | ||
|
|
dedc42d775 | ||
|
|
f64073f48f | ||
|
|
70754ad748 | ||
|
|
3158980057 | ||
|
|
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 | ||
|
|
4a56875bda | ||
|
|
48fca8f0f3 | ||
|
|
2f5d094abb | ||
|
|
31600ba66c | ||
|
|
a17fa5a518 | ||
|
|
59d964d57a | ||
|
|
2981bfffb1 | ||
|
|
0596b74fa1 | ||
|
|
ebaa8d2637 | ||
|
|
b368b6aef4 | ||
|
|
44967b1af1 | ||
|
|
6c19fd4192 | ||
|
|
bb27be0924 | ||
|
|
4e5ab5a605 | ||
|
|
b0b14fe2e1 | ||
|
|
36aa0d301b | ||
|
|
3fa80351e0 | ||
|
|
1fef273669 | ||
|
|
04e95d378c | ||
|
|
9058a79c5c | ||
|
|
a7fed21819 | ||
|
|
cfc91047fd | ||
|
|
4ce2d991dd | ||
|
|
449e7ce454 | ||
|
|
9cc9600a4c | ||
|
|
f7e0f533e0 | ||
|
|
c7c3f711bf | ||
|
|
ec10ee3298 | ||
|
|
155c241ef7 | ||
|
|
341dd6adfb | ||
|
|
89b75835a6 | ||
|
|
ee2172ca82 | ||
|
|
98802e21a0 | ||
|
|
7c850a8a1e | ||
|
|
5b4979bdb1 | ||
|
|
6afcf7bf42 | ||
|
|
afb49f4040 | ||
|
|
4e20cf6036 | ||
|
|
9ecde3024a | ||
|
|
daf6dbaf73 | ||
|
|
7edb024abe | ||
|
|
1c7634b394 | ||
|
|
ff4f01fb56 | ||
|
|
fd5f57d9b7 | ||
|
|
f06059837d | ||
|
|
b98aa377b6 | ||
|
|
42abad75d9 | ||
|
|
ebb0e796ce | ||
|
|
24fd87f7bc | ||
|
|
90cc2a2519 | ||
|
|
9802aec881 | ||
|
|
737032418a | ||
|
|
2aa03d5b79 | ||
|
|
926550bf26 | ||
|
|
240f700b92 | ||
|
|
4000986d1d | ||
|
|
0e98990e17 | ||
|
|
8309f00e5e | ||
|
|
ad96fd2a96 | ||
|
|
e6bbaac7de | ||
|
|
f0cc64c74e | ||
|
|
65ca953f5b | ||
|
|
873c019b58 | ||
|
|
b5599fd3a6 | ||
|
|
1933e82587 | ||
|
|
6b6900cfd4 | ||
|
|
185f33c3e0 | ||
|
|
3f1858a105 | ||
|
|
1fef9a2cf0 | ||
|
|
38a9b90a8b | ||
|
|
b376491020 | ||
|
|
3367f65b02 | ||
|
|
7a97496f70 | ||
|
|
bda748d547 | ||
|
|
7ff22cbc34 | ||
|
|
1ec4cbdf38 | ||
|
|
ccd6b8c48a | ||
|
|
a112d3c99d | ||
|
|
ee7f1f8f5e | ||
|
|
127f6730f6 | ||
|
|
22b56d73b6 | ||
|
|
9934456af4 | ||
|
|
3585ca2d49 | ||
|
|
f842546042 | ||
|
|
5a6e13721d | ||
|
|
a0151b8d44 | ||
|
|
62e5389f80 | ||
|
|
a1d24f030e | ||
|
|
78ddb75b7a | ||
|
|
90090a7fc7 | ||
|
|
ea1c94c6db | ||
|
|
338ab5c634 | ||
|
|
58d055f114 | ||
|
|
9eec2909ed | ||
|
|
632627db11 | ||
|
|
a19586f8b8 | ||
|
|
8fe5ab42e8 | ||
|
|
f51af9736b | ||
|
|
20b7b794d8 | ||
|
|
2a196743f5 | ||
|
|
917620736b | ||
|
|
19d29d6637 | ||
|
|
c824ae4478 | ||
|
|
3cdb81cf4a | ||
|
|
378eee0402 | ||
|
|
9d2ae7d1ed | ||
|
|
c991a73632 | ||
|
|
149ca1afce | ||
|
|
a1f65bccc5 | ||
|
|
a105748a55 | ||
|
|
f1ee454254 | ||
|
|
a6ab886968 | ||
|
|
f85daa088f | ||
|
|
ede53d3b6b | ||
|
|
eb9ac213d5 | ||
|
|
06052b85a2 | ||
|
|
01827c7b3a | ||
|
|
14e572813f |
@@ -1,4 +1,4 @@
|
||||
FROM python:3.9-slim as stage-build
|
||||
FROM python:3.9-slim-bullseye as stage-build
|
||||
ARG TARGETARCH
|
||||
|
||||
ARG VERSION
|
||||
@@ -8,7 +8,7 @@ WORKDIR /opt/jumpserver
|
||||
ADD . .
|
||||
RUN cd utils && bash -ixeu build.sh
|
||||
|
||||
FROM python:3.9-slim
|
||||
FROM python:3.9-slim-bullseye
|
||||
ARG TARGETARCH
|
||||
MAINTAINER JumpServer Team <ibuler@qq.com>
|
||||
|
||||
@@ -27,6 +27,7 @@ ARG DEPENDENCIES=" \
|
||||
libxml2-dev \
|
||||
libxmlsec1-dev \
|
||||
libxmlsec1-openssl \
|
||||
freerdp2-dev \
|
||||
libaio-dev"
|
||||
|
||||
ARG TOOLS=" \
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM python:3.9-slim as stage-build
|
||||
FROM python:3.9-slim-buster as stage-build
|
||||
ARG TARGETARCH
|
||||
|
||||
ARG VERSION
|
||||
@@ -8,7 +8,7 @@ WORKDIR /opt/jumpserver
|
||||
ADD . .
|
||||
RUN cd utils && bash -ixeu build.sh
|
||||
|
||||
FROM python:3.9-slim
|
||||
FROM python:3.9-slim-buster
|
||||
ARG TARGETARCH
|
||||
MAINTAINER JumpServer Team <ibuler@qq.com>
|
||||
|
||||
@@ -28,6 +28,7 @@ ARG DEPENDENCIES=" \
|
||||
libxml2-dev \
|
||||
libxmlsec1-dev \
|
||||
libxmlsec1-openssl \
|
||||
freerdp2-dev \
|
||||
libaio-dev"
|
||||
|
||||
ARG TOOLS=" \
|
||||
@@ -77,7 +78,7 @@ RUN --mount=type=cache,target=/root/.cache/pip \
|
||||
&& 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.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
|
||||
|
||||
|
||||
22
README.md
22
README.md
@@ -23,7 +23,14 @@
|
||||
|
||||
--------------------------
|
||||
|
||||
JumpServer 是广受欢迎的开源堡垒机,是符合 4A 规范的专业运维安全审计系统。
|
||||
JumpServer 是广受欢迎的开源堡垒机,是符合 4A 规范的专业运维安全审计系统。JumpServer 堡垒机帮助企业以更安全的方式管控和登录各种类型的资产,包括:
|
||||
|
||||
- **SSH**: Linux / Unix / 网络设备 等;
|
||||
- **Windows**: Web 方式连接 / 原生 RDP 连接;
|
||||
- **数据库**: MySQL / Oracle / SQLServer / PostgreSQL 等;
|
||||
- **Kubernetes**: 支持连接到 K8s 集群中的 Pods;
|
||||
- **Web 站点**: 各类系统的 Web 管理后台;
|
||||
- **应用**: 通过 Remote App 连接各类应用。
|
||||
|
||||
## 产品特色
|
||||
|
||||
@@ -33,8 +40,6 @@ JumpServer 是广受欢迎的开源堡垒机,是符合 4A 规范的专业运
|
||||
- **多云支持**: 一套系统,同时管理不同云上面的资产;
|
||||
- **多租户**: 一套系统,多个子公司或部门同时使用;
|
||||
- **云端存储**: 审计录像云端存储,永不丢失;
|
||||
- **多应用支持**: 全面支持各类资产,包括服务器、数据库、Windows RemoteApp、Kubernetes 等;
|
||||
- **安全可靠**: 被广泛使用、验证和信赖,连续 9 年的持续研发投入和产品更新升级。
|
||||
|
||||
## UI 展示
|
||||
|
||||
@@ -72,12 +77,13 @@ JumpServer 是广受欢迎的开源堡垒机,是符合 4A 规范的专业运
|
||||
- [东方明珠:JumpServer高效管控异构化、分布式云端资产](https://blog.fit2cloud.com/?p=687)
|
||||
- [江苏农信:JumpServer堡垒机助力行业云安全运维](https://blog.fit2cloud.com/?p=666)
|
||||
|
||||
## 社区
|
||||
## 社区交流
|
||||
|
||||
如果您在使用过程中有任何疑问或对建议,欢迎提交 [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) 及微信交流群当中进行交流沟通。
|
||||
|
||||
**微信交流群**
|
||||
|
||||
<img src="https://download.jumpserver.org/images/wecom-group.jpeg" alt="微信群二维码" width="200"/>
|
||||
|
||||
@@ -107,7 +113,7 @@ JumpServer是一款安全产品,请参考 [基本安全建议](https://docs.ju
|
||||
- 邮箱:support@fit2cloud.com
|
||||
- 电话:400-052-0755
|
||||
|
||||
## 致谢
|
||||
## 致谢开源
|
||||
|
||||
- [Apache Guacamole](https://guacamole.apache.org/): Web 页面连接 RDP、SSH、VNC 等协议资产,JumpServer Lion 组件使用到该项目;
|
||||
- [OmniDB](https://omnidb.org/): Web 页面连接使用数据库,JumpServer Web 数据库组件使用到该项目。
|
||||
|
||||
@@ -22,7 +22,7 @@ __all__ = [
|
||||
|
||||
class AccountViewSet(OrgBulkModelViewSet):
|
||||
model = Account
|
||||
search_fields = ('username', 'asset__address', 'name')
|
||||
search_fields = ('username', 'name', 'asset__name', 'asset__address')
|
||||
filterset_class = AccountFilterSet
|
||||
serializer_classes = {
|
||||
'default': serializers.AccountSerializer,
|
||||
@@ -32,6 +32,7 @@ class AccountViewSet(OrgBulkModelViewSet):
|
||||
'su_from_accounts': 'accounts.view_account',
|
||||
'clear_secret': 'accounts.change_account',
|
||||
}
|
||||
export_as_zip = True
|
||||
|
||||
@action(methods=['get'], detail=False, url_path='su-from-accounts')
|
||||
def su_from_accounts(self, request, *args, **kwargs):
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
from django_filters import rest_framework as drf_filters
|
||||
from rest_framework.decorators import action
|
||||
from rest_framework.response import Response
|
||||
|
||||
from assets.const import Protocol
|
||||
from accounts import serializers
|
||||
from accounts.models import AccountTemplate
|
||||
from orgs.mixins.api import OrgBulkModelViewSet
|
||||
from rbac.permissions import RBACPermission
|
||||
from assets.const import Protocol
|
||||
from common.drf.filters import BaseFilterSet
|
||||
from common.permissions import UserConfirmation, ConfirmType
|
||||
from common.views.mixins import RecordViewLogMixin
|
||||
from common.drf.filters import BaseFilterSet
|
||||
from orgs.mixins.api import OrgBulkModelViewSet
|
||||
from rbac.permissions import RBACPermission
|
||||
|
||||
|
||||
class AccountTemplateFilterSet(BaseFilterSet):
|
||||
@@ -27,6 +29,8 @@ class AccountTemplateFilterSet(BaseFilterSet):
|
||||
continue
|
||||
_st = protocol_secret_type_map[p].get('secret_types', [])
|
||||
secret_types.update(_st)
|
||||
if not secret_types:
|
||||
secret_types = ['password']
|
||||
queryset = queryset.filter(secret_type__in=secret_types)
|
||||
return queryset
|
||||
|
||||
@@ -36,8 +40,20 @@ class AccountTemplateViewSet(OrgBulkModelViewSet):
|
||||
filterset_class = AccountTemplateFilterSet
|
||||
search_fields = ('username', 'name')
|
||||
serializer_classes = {
|
||||
'default': serializers.AccountTemplateSerializer
|
||||
'default': serializers.AccountTemplateSerializer,
|
||||
}
|
||||
rbac_perms = {
|
||||
'su_from_account_templates': 'accounts.view_accounttemplate',
|
||||
}
|
||||
|
||||
@action(methods=['get'], detail=False, url_path='su-from-account-templates')
|
||||
def su_from_account_templates(self, request, *args, **kwargs):
|
||||
pk = request.query_params.get('template_id')
|
||||
template = AccountTemplate.objects.filter(pk=pk).first()
|
||||
templates = AccountTemplate.get_su_from_account_templates(template)
|
||||
templates = self.filter_queryset(templates)
|
||||
serializer = self.get_serializer(templates, many=True)
|
||||
return Response(data=serializer.data)
|
||||
|
||||
|
||||
class AccountTemplateSecretsViewSet(RecordViewLogMixin, AccountTemplateViewSet):
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
password: "{{ account.secret }}"
|
||||
commands: "{{ params.commands }}"
|
||||
first_conn_delay_time: "{{ first_conn_delay_time | default(0.5) }}"
|
||||
ignore_errors: true
|
||||
when: ping_info is succeeded
|
||||
register: change_info
|
||||
|
||||
@@ -35,6 +36,3 @@
|
||||
login_password: "{{ account.secret }}"
|
||||
login_host: "{{ jms_asset.address }}"
|
||||
login_port: "{{ jms_asset.port }}"
|
||||
when:
|
||||
- ping_info is succeeded
|
||||
- change_info is succeeded
|
||||
|
||||
@@ -15,5 +15,6 @@ params:
|
||||
|
||||
i18n:
|
||||
SSH account change secret:
|
||||
zh: SSH 账号改密
|
||||
ja: SSH アカウントのパスワード変更
|
||||
zh: 使用 SSH 命令行自定义改密
|
||||
ja: SSH コマンドライン方式でカスタムパスワード変更
|
||||
en: Custom password change by SSH command line
|
||||
|
||||
@@ -38,8 +38,8 @@
|
||||
db: "{{ jms_asset.spec_info.db_name }}"
|
||||
name: "{{ account.username }}"
|
||||
password: "{{ account.secret }}"
|
||||
ignore_errors: true
|
||||
when: db_info is succeeded
|
||||
register: change_info
|
||||
|
||||
- name: Verify password
|
||||
mongodb_ping:
|
||||
@@ -53,6 +53,3 @@
|
||||
ssl_certfile: "{{ jms_asset.secret_info.client_key }}"
|
||||
connection_options:
|
||||
- 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:
|
||||
MongoDB account change secret:
|
||||
zh: MongoDB 账号改密
|
||||
ja: MongoDB アカウントのパスワード変更
|
||||
zh: 使用 Ansible 模块 mongodb 执行 MongoDB 账号改密
|
||||
ja: Ansible mongodb モジュールを使用して MongoDB アカウントのパスワード変更
|
||||
en: Using Ansible module mongodb to change MongoDB account secret
|
||||
|
||||
@@ -28,8 +28,8 @@
|
||||
password: "{{ account.secret }}"
|
||||
host: "%"
|
||||
priv: "{{ account.username + '.*:USAGE' if db_name == '' else db_name + '.*:ALL' }}"
|
||||
ignore_errors: true
|
||||
when: db_info is succeeded
|
||||
register: change_info
|
||||
|
||||
- name: Verify password
|
||||
community.mysql.mysql_info:
|
||||
@@ -38,6 +38,3 @@
|
||||
login_host: "{{ jms_asset.address }}"
|
||||
login_port: "{{ jms_asset.port }}"
|
||||
filter: version
|
||||
when:
|
||||
- db_info is succeeded
|
||||
- change_info is succeeded
|
||||
@@ -8,5 +8,6 @@ method: change_secret
|
||||
|
||||
i18n:
|
||||
MySQL account change secret:
|
||||
zh: MySQL 账号改密
|
||||
ja: MySQL アカウントのパスワード変更
|
||||
zh: 使用 Ansible 模块 mysql 执行 MySQL 账号改密
|
||||
ja: Ansible mysql モジュールを使用して MySQL アカウントのパスワード変更
|
||||
en: Using Ansible module mysql to change MySQL account secret
|
||||
|
||||
@@ -29,8 +29,8 @@
|
||||
mode: "{{ jms_account.mode }}"
|
||||
name: "{{ account.username }}"
|
||||
password: "{{ account.secret }}"
|
||||
ignore_errors: true
|
||||
when: db_info is succeeded
|
||||
register: change_info
|
||||
|
||||
- name: Verify password
|
||||
oracle_ping:
|
||||
@@ -39,6 +39,3 @@
|
||||
login_host: "{{ jms_asset.address }}"
|
||||
login_port: "{{ jms_asset.port }}"
|
||||
login_database: "{{ jms_asset.spec_info.db_name }}"
|
||||
when:
|
||||
- db_info is succeeded
|
||||
- change_info is succeeded
|
||||
|
||||
@@ -29,8 +29,8 @@
|
||||
name: "{{ account.username }}"
|
||||
password: "{{ account.secret }}"
|
||||
role_attr_flags: LOGIN
|
||||
ignore_errors: true
|
||||
when: result is succeeded
|
||||
register: change_info
|
||||
|
||||
- name: Verify password
|
||||
community.postgresql.postgresql_ping:
|
||||
@@ -39,8 +39,3 @@
|
||||
login_host: "{{ jms_asset.address }}"
|
||||
login_port: "{{ jms_asset.port }}"
|
||||
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 }}"
|
||||
name: '{{ jms_asset.spec_info.db_name }}'
|
||||
script: "ALTER LOGIN {{ account.username }} WITH PASSWORD = '{{ account.secret }}'; select @@version"
|
||||
ignore_errors: true
|
||||
when: user_exist.query_results[0] | length != 0
|
||||
register: change_info
|
||||
|
||||
- name: Add SQLServer user
|
||||
community.general.mssql_script:
|
||||
@@ -52,8 +52,8 @@
|
||||
login_port: "{{ jms_asset.port }}"
|
||||
name: '{{ jms_asset.spec_info.db_name }}'
|
||||
script: "CREATE LOGIN {{ account.username }} WITH PASSWORD = '{{ account.secret }}'; select @@version"
|
||||
ignore_errors: true
|
||||
when: user_exist.query_results[0] | length == 0
|
||||
register: change_info
|
||||
|
||||
- name: Verify password
|
||||
community.general.mssql_script:
|
||||
@@ -64,6 +64,3 @@
|
||||
name: '{{ jms_asset.spec_info.db_name }}'
|
||||
script: |
|
||||
SELECT @@version
|
||||
when:
|
||||
- db_info is succeeded
|
||||
- change_info is succeeded
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
name: "{{ account.username }}"
|
||||
password: "{{ account.secret | password_hash('des') }}"
|
||||
update_password: always
|
||||
ignore_errors: true
|
||||
when: account.secret_type == "password"
|
||||
|
||||
- name: create user If it already exists, no operation will be performed
|
||||
|
||||
@@ -7,5 +7,6 @@ method: change_secret
|
||||
|
||||
i18n:
|
||||
AIX account change secret:
|
||||
zh: AIX 账号改密
|
||||
ja: AIX アカウントのパスワード変更
|
||||
zh: 使用 Ansible 模块 user 执行账号改密 (DES)
|
||||
ja: Ansible user モジュールを使用してアカウントのパスワード変更 (DES)
|
||||
en: Using Ansible module user to change account secret (DES)
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
name: "{{ account.username }}"
|
||||
password: "{{ account.secret | password_hash('sha512') }}"
|
||||
update_password: always
|
||||
ignore_errors: true
|
||||
when: account.secret_type == "password"
|
||||
|
||||
- name: create user If it already exists, no operation will be performed
|
||||
|
||||
@@ -8,5 +8,6 @@ method: change_secret
|
||||
|
||||
i18n:
|
||||
Posix account change secret:
|
||||
zh: Posix 账号改密
|
||||
ja: Posix アカウントのパスワード変更
|
||||
zh: 使用 Ansible 模块 user 执行账号改密 (SHA512)
|
||||
ja: Ansible user モジュールを使用して アカウントのパスワード変更 (SHA512)
|
||||
en: Using Ansible module user to change account secret (SHA512)
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
groups: "{{ user_info.groups[0].name }}"
|
||||
groups_action: add
|
||||
update_password: always
|
||||
ignore_errors: true
|
||||
when: account.secret_type == "password"
|
||||
|
||||
- name: Refresh connection
|
||||
|
||||
@@ -8,5 +8,6 @@ type:
|
||||
|
||||
i18n:
|
||||
Windows account change secret:
|
||||
zh: Windows 账号改密
|
||||
ja: Windows アカウントのパスワード変更
|
||||
zh: 使用 Ansible 模块 win_user 执行 Windows 账号改密
|
||||
ja: Ansible win_user モジュールを使用して Windows アカウントのパスワード変更
|
||||
en: Using Ansible module win_user to change Windows account secret
|
||||
|
||||
@@ -72,14 +72,14 @@ class ChangeSecretManager(AccountBasePlaybookManager):
|
||||
return []
|
||||
|
||||
asset = privilege_account.asset
|
||||
accounts = asset.accounts.exclude(username=privilege_account.username)
|
||||
accounts = asset.accounts.all()
|
||||
accounts = accounts.filter(id__in=self.account_ids)
|
||||
if self.secret_type:
|
||||
accounts = accounts.filter(secret_type=self.secret_type)
|
||||
|
||||
if settings.CHANGE_AUTH_PLAN_SECURE_MODE_ENABLED:
|
||||
accounts = accounts.filter(privileged=False).exclude(
|
||||
username__in=['root', 'administrator']
|
||||
username__in=['root', 'administrator', privilege_account.username]
|
||||
)
|
||||
return accounts
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import re
|
||||
|
||||
from django.utils import timezone
|
||||
|
||||
__all__ = ['GatherAccountsFilter']
|
||||
@@ -13,8 +15,8 @@ class GatherAccountsFilter:
|
||||
def mysql_filter(info):
|
||||
result = {}
|
||||
for _, user_dict in info.items():
|
||||
for username, data in user_dict.items():
|
||||
if data.get('account_locked') == 'N':
|
||||
for username, _ in user_dict.items():
|
||||
if len(username.split('.')) == 1:
|
||||
result[username] = {}
|
||||
return result
|
||||
|
||||
@@ -27,18 +29,25 @@ class GatherAccountsFilter:
|
||||
|
||||
@staticmethod
|
||||
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 = {}
|
||||
for line in info:
|
||||
data = line.split('@')
|
||||
if len(data) == 1:
|
||||
result[line] = {}
|
||||
usernames = username_pattern.findall(line)
|
||||
username = ''.join(usernames)
|
||||
if username:
|
||||
result[username] = {}
|
||||
else:
|
||||
continue
|
||||
|
||||
if len(data) != 3:
|
||||
continue
|
||||
username, address, dt = data
|
||||
date = timezone.datetime.strptime(f'{dt} +0800', '%b %d %H:%M:%S %Y %z')
|
||||
result[username] = {'address': address, 'date': date}
|
||||
ip_addrs = ip_pattern.findall(line)
|
||||
ip_addr = ''.join(ip_addrs)
|
||||
if ip_addr:
|
||||
result[username].update({'address': ip_addr})
|
||||
login_times = login_time_pattern.findall(line)
|
||||
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
|
||||
|
||||
@staticmethod
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
ansible.builtin.shell:
|
||||
cmd: >
|
||||
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
|
||||
echo $k
|
||||
else
|
||||
|
||||
@@ -8,5 +8,6 @@ method: gather_accounts
|
||||
|
||||
i18n:
|
||||
Posix account gather:
|
||||
zh: Posix 账号收集
|
||||
ja: Posix アカウントの収集
|
||||
zh: 使用命令 getent passwd 收集 Posix 资产账号
|
||||
ja: コマンド getent を使用してアセットアカウントを収集する
|
||||
en: Using command getent to gather accounts
|
||||
|
||||
@@ -8,5 +8,6 @@ type:
|
||||
|
||||
i18n:
|
||||
Windows account gather:
|
||||
zh: Windows 账号收集
|
||||
ja: Windows アカウントの収集
|
||||
zh: 使用命令 net user 收集 Windows 账号
|
||||
ja: コマンド net user を使用して Windows アカウントを収集する
|
||||
en: Using command net user to gather accounts
|
||||
|
||||
@@ -38,8 +38,8 @@
|
||||
db: "{{ jms_asset.spec_info.db_name }}"
|
||||
name: "{{ account.username }}"
|
||||
password: "{{ account.secret }}"
|
||||
ignore_errors: true
|
||||
when: db_info is succeeded
|
||||
register: change_info
|
||||
|
||||
- name: Verify password
|
||||
mongodb_ping:
|
||||
@@ -53,6 +53,3 @@
|
||||
ssl_certfile: "{{ jms_asset.secret_info.client_key }}"
|
||||
connection_options:
|
||||
- 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:
|
||||
MongoDB account push:
|
||||
zh: MongoDB 账号推送
|
||||
ja: MongoDB アカウントのプッシュ
|
||||
zh: 使用 Ansible 模块 mongodb 执行 MongoDB 账号推送
|
||||
ja: Ansible mongodb モジュールを使用してアカウントをプッシュする
|
||||
en: Using Ansible module mongodb to push account
|
||||
|
||||
@@ -28,8 +28,8 @@
|
||||
password: "{{ account.secret }}"
|
||||
host: "%"
|
||||
priv: "{{ account.username + '.*:USAGE' if db_name == '' else db_name + '.*:ALL' }}"
|
||||
ignore_errors: true
|
||||
when: db_info is succeeded
|
||||
register: change_info
|
||||
|
||||
- name: Verify password
|
||||
community.mysql.mysql_info:
|
||||
@@ -38,6 +38,3 @@
|
||||
login_host: "{{ jms_asset.address }}"
|
||||
login_port: "{{ jms_asset.port }}"
|
||||
filter: version
|
||||
when:
|
||||
- db_info is succeeded
|
||||
- change_info is succeeded
|
||||
@@ -8,5 +8,6 @@ method: push_account
|
||||
|
||||
i18n:
|
||||
MySQL account push:
|
||||
zh: MySQL 账号推送
|
||||
ja: MySQL アカウントのプッシュ
|
||||
zh: 使用 Ansible 模块 mysql 执行 MySQL 账号推送
|
||||
ja: Ansible mysql モジュールを使用してアカウントをプッシュする
|
||||
en: Using Ansible module mysql to push account
|
||||
|
||||
@@ -29,8 +29,8 @@
|
||||
mode: "{{ jms_account.mode }}"
|
||||
name: "{{ account.username }}"
|
||||
password: "{{ account.secret }}"
|
||||
ignore_errors: true
|
||||
when: db_info is succeeded
|
||||
register: change_info
|
||||
|
||||
- name: Verify password
|
||||
oracle_ping:
|
||||
@@ -39,6 +39,3 @@
|
||||
login_host: "{{ jms_asset.address }}"
|
||||
login_port: "{{ jms_asset.port }}"
|
||||
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:
|
||||
Oracle account push:
|
||||
zh: Oracle 账号推送
|
||||
ja: Oracle アカウントのプッシュ
|
||||
zh: 使用 Python 模块 oracledb 执行 Oracle 账号推送
|
||||
ja: Python oracledb モジュールを使用してアカウントをプッシュする
|
||||
en: Using Python module oracledb to push account
|
||||
|
||||
@@ -29,8 +29,8 @@
|
||||
name: "{{ account.username }}"
|
||||
password: "{{ account.secret }}"
|
||||
role_attr_flags: LOGIN
|
||||
ignore_errors: true
|
||||
when: result is succeeded
|
||||
register: change_info
|
||||
|
||||
- name: Verify password
|
||||
community.postgresql.postgresql_ping:
|
||||
@@ -42,5 +42,3 @@
|
||||
when:
|
||||
- result is succeeded
|
||||
- change_info is succeeded
|
||||
register: result
|
||||
failed_when: not result.is_available
|
||||
|
||||
@@ -7,5 +7,6 @@ method: push_account
|
||||
|
||||
i18n:
|
||||
PostgreSQL account push:
|
||||
zh: PostgreSQL 账号推送
|
||||
ja: PostgreSQL アカウントのプッシュ
|
||||
zh: 使用 Ansible 模块 postgresql 执行 PostgreSQL 账号推送
|
||||
ja: Ansible postgresql モジュールを使用してアカウントをプッシュする
|
||||
en: Using Ansible module postgresql to push account
|
||||
|
||||
@@ -41,6 +41,7 @@
|
||||
login_port: "{{ jms_asset.port }}"
|
||||
name: '{{ jms_asset.spec_info.db_name }}'
|
||||
script: "ALTER LOGIN {{ account.username }} WITH PASSWORD = '{{ account.secret }}'; select @@version"
|
||||
ignore_errors: true
|
||||
when: user_exist.query_results[0] | length != 0
|
||||
register: change_info
|
||||
|
||||
@@ -52,6 +53,7 @@
|
||||
login_port: "{{ jms_asset.port }}"
|
||||
name: '{{ jms_asset.spec_info.db_name }}'
|
||||
script: "CREATE LOGIN {{ account.username }} WITH PASSWORD = '{{ account.secret }}'; select @@version"
|
||||
ignore_errors: true
|
||||
when: user_exist.query_results[0] | length == 0
|
||||
register: change_info
|
||||
|
||||
@@ -64,6 +66,3 @@
|
||||
name: '{{ jms_asset.spec_info.db_name }}'
|
||||
script: |
|
||||
SELECT @@version
|
||||
when:
|
||||
- db_info is succeeded
|
||||
- change_info is succeeded
|
||||
|
||||
@@ -7,5 +7,6 @@ method: push_account
|
||||
|
||||
i18n:
|
||||
SQLServer account push:
|
||||
zh: SQLServer 账号推送
|
||||
ja: SQLServer アカウントのプッシュ
|
||||
zh: 使用 Ansible 模块 mssql 执行 SQLServer 账号推送
|
||||
ja: Ansible mssql モジュールを使用してアカウントをプッシュする
|
||||
en: Using Ansible module mssql to push account
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
ansible.builtin.user:
|
||||
name: "{{ account.username }}"
|
||||
shell: "{{ params.shell }}"
|
||||
home: "{{ '/home/' + account.username }}"
|
||||
home: "{{ params.home | default('/home/' + account.username, true) }}"
|
||||
groups: "{{ params.groups }}"
|
||||
expires: -1
|
||||
state: present
|
||||
@@ -18,20 +18,6 @@
|
||||
name: "{{ account.username }}"
|
||||
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
|
||||
ansible.builtin.user:
|
||||
name: "{{ account.username }}"
|
||||
@@ -43,6 +29,7 @@
|
||||
name: "{{ account.username }}"
|
||||
password: "{{ account.secret | password_hash('sha512') }}"
|
||||
update_password: always
|
||||
ignore_errors: true
|
||||
when: account.secret_type == "password"
|
||||
|
||||
- name: remove jumpserver ssh key
|
||||
|
||||
@@ -16,6 +16,12 @@ params:
|
||||
label: 'Shell'
|
||||
default: '/bin/bash'
|
||||
|
||||
- name: home
|
||||
type: str
|
||||
label: '家目录'
|
||||
default: ''
|
||||
help_text: '默认家目录 /home/系统用户名: /home/username'
|
||||
|
||||
- name: groups
|
||||
type: str
|
||||
label: '用户组'
|
||||
@@ -24,6 +30,7 @@ params:
|
||||
|
||||
i18n:
|
||||
Aix account push:
|
||||
zh: Aix 账号推送
|
||||
ja: Aix アカウントのプッシュ
|
||||
zh: 使用 Ansible 模块 user 执行 Aix 账号推送 (DES)
|
||||
ja: Ansible user モジュールを使用して Aix アカウントをプッシュする (DES)
|
||||
en: Using Ansible module user to push account (DES)
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
ansible.builtin.user:
|
||||
name: "{{ account.username }}"
|
||||
shell: "{{ params.shell }}"
|
||||
home: "{{ '/home/' + account.username }}"
|
||||
home: "{{ params.home | default('/home/' + account.username, true) }}"
|
||||
groups: "{{ params.groups }}"
|
||||
expires: -1
|
||||
state: present
|
||||
@@ -18,20 +18,6 @@
|
||||
name: "{{ account.username }}"
|
||||
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
|
||||
ansible.builtin.user:
|
||||
name: "{{ account.username }}"
|
||||
@@ -43,6 +29,7 @@
|
||||
name: "{{ account.username }}"
|
||||
password: "{{ account.secret | password_hash('sha512') }}"
|
||||
update_password: always
|
||||
ignore_errors: true
|
||||
when: account.secret_type == "password"
|
||||
|
||||
- name: remove jumpserver ssh key
|
||||
|
||||
@@ -18,6 +18,12 @@ params:
|
||||
default: '/bin/bash'
|
||||
help_text: ''
|
||||
|
||||
- name: home
|
||||
type: str
|
||||
label: '家目录'
|
||||
default: ''
|
||||
help_text: '默认家目录 /home/系统用户名: /home/username'
|
||||
|
||||
- name: groups
|
||||
type: str
|
||||
label: '用户组'
|
||||
@@ -26,5 +32,6 @@ params:
|
||||
|
||||
i18n:
|
||||
Posix account push:
|
||||
zh: Posix 账号推送
|
||||
ja: Posix アカウントのプッシュ
|
||||
zh: 使用 Ansible 模块 user 执行账号推送 (sha512)
|
||||
ja: Ansible user モジュールを使用してアカウントをプッシュする (sha512)
|
||||
en: Using Ansible module user to push account (sha512)
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
groups: "{{ params.groups }}"
|
||||
groups_action: add
|
||||
update_password: always
|
||||
ignore_errors: true
|
||||
when: account.secret_type == "password"
|
||||
|
||||
- name: Refresh connection
|
||||
|
||||
@@ -14,5 +14,6 @@ params:
|
||||
|
||||
i18n:
|
||||
Windows account push:
|
||||
zh: Windows 账号推送
|
||||
ja: Windows アカウントのプッシュ
|
||||
zh: 使用 Ansible 模块 win_user 执行 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
|
||||
|
||||
tasks:
|
||||
- name: Verify account
|
||||
- name: Verify account (paramiko)
|
||||
ssh_ping:
|
||||
login_host: "{{ jms_asset.address }}"
|
||||
login_port: "{{ jms_asset.port }}"
|
||||
login_user: "{{ account.username }}"
|
||||
login_password: "{{ account.secret }}"
|
||||
login_secret_type: "{{ jms_account.secret_type }}"
|
||||
login_private_key_path: "{{ jms_account.private_key_path }}"
|
||||
login_secret_type: "{{ account.secret_type }}"
|
||||
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:
|
||||
MongoDB account verify:
|
||||
zh: MongoDB 账号验证
|
||||
ja: MongoDB アカウントの検証
|
||||
zh: 使用 Ansible 模块 mongodb 验证账号
|
||||
ja: Ansible mongodb モジュールを使用してアカウントを検証する
|
||||
en: Using Ansible module mongodb to verify account
|
||||
|
||||
@@ -8,5 +8,7 @@ method: verify_account
|
||||
|
||||
i18n:
|
||||
MySQL account verify:
|
||||
zh: MySQL 账号验证
|
||||
ja: MySQL アカウントの検証
|
||||
zh: 使用 Ansible 模块 mysql 验证账号
|
||||
ja: Ansible mysql モジュールを使用してアカウントを検証する
|
||||
en: Using Ansible module mysql to verify account
|
||||
|
||||
|
||||
@@ -7,5 +7,6 @@ method: verify_account
|
||||
|
||||
i18n:
|
||||
Oracle account verify:
|
||||
zh: Oracle 账号验证
|
||||
ja: Oracle アカウントの検証
|
||||
zh: 使用 Python 模块 oracledb 验证账号
|
||||
ja: Python モジュール oracledb を使用してアカウントを検証する
|
||||
en: Using Python module oracledb to verify account
|
||||
|
||||
@@ -7,5 +7,6 @@ method: verify_account
|
||||
|
||||
i18n:
|
||||
PostgreSQL account verify:
|
||||
zh: PostgreSQL 账号验证
|
||||
ja: PostgreSQL アカウントの検証
|
||||
zh: 使用 Ansible 模块 postgresql 验证账号
|
||||
ja: Ansible postgresql モジュールを使用してアカウントを検証する
|
||||
en: Using Ansible module postgresql to verify account
|
||||
|
||||
@@ -7,5 +7,6 @@ method: verify_account
|
||||
|
||||
i18n:
|
||||
SQLServer account verify:
|
||||
zh: SQLServer 账号验证
|
||||
ja: SQLServer アカウントの検証
|
||||
zh: 使用 Ansible 模块 mssql 验证账号
|
||||
ja: Ansible mssql モジュールを使用してアカウントを検証する
|
||||
en: Using Ansible module mssql to verify account
|
||||
|
||||
@@ -8,5 +8,6 @@ method: verify_account
|
||||
|
||||
i18n:
|
||||
Posix account verify:
|
||||
zh: Posix 账号验证
|
||||
ja: Posix アカウントの検証
|
||||
zh: 使用 Ansible 模块 ping 验证账号
|
||||
ja: Ansible ping モジュールを使用してアカウントを検証する
|
||||
en: Using Ansible module ping to verify account
|
||||
|
||||
@@ -7,6 +7,7 @@ type:
|
||||
- windows
|
||||
|
||||
i18n:
|
||||
Windows account verify:
|
||||
zh: Windows 账号验证
|
||||
ja: Windows アカウントの検証
|
||||
Windows account verify:
|
||||
zh: 使用 Ansible 模块 win_ping 验证账号
|
||||
ja: Ansible win_ping モジュールを使用してアカウントを検証する
|
||||
en: Using Ansible module win_ping to verify account
|
||||
|
||||
@@ -48,7 +48,7 @@ class SecretStrategy(models.TextChoices):
|
||||
class SSHKeyStrategy(models.TextChoices):
|
||||
add = 'add', _('Append SSH KEY')
|
||||
set = 'set', _('Empty and append SSH KEY')
|
||||
set_jms = 'set_jms', _('Replace (The key generated by JumpServer) ')
|
||||
set_jms = 'set_jms', _('Replace (Replace only keys pushed by JumpServer) ')
|
||||
|
||||
|
||||
class TriggerChoice(models.TextChoices, TreeChoices):
|
||||
|
||||
@@ -5,7 +5,6 @@ from django_filters import rest_framework as drf_filters
|
||||
|
||||
from assets.models import Node
|
||||
from common.drf.filters import BaseFilterSet
|
||||
|
||||
from .models import Account, GatheredAccount
|
||||
|
||||
|
||||
@@ -46,7 +45,7 @@ class AccountFilterSet(BaseFilterSet):
|
||||
|
||||
class Meta:
|
||||
model = Account
|
||||
fields = ['id', 'asset_id']
|
||||
fields = ['id', 'asset_id', 'source_id']
|
||||
|
||||
|
||||
class GatheredAccountFilterSet(BaseFilterSet):
|
||||
|
||||
29
apps/accounts/migrations/0011_auto_20230506_1443.py
Normal file
29
apps/accounts/migrations/0011_auto_20230506_1443.py
Normal file
@@ -0,0 +1,29 @@
|
||||
# Generated by Django 3.2.17 on 2023-05-06 06:43
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('accounts', '0010_gatheraccountsautomation_is_sync_account'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='accounttemplate',
|
||||
name='su_from',
|
||||
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='su_to', to='accounts.accounttemplate', verbose_name='Su from'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='changesecretautomation',
|
||||
name='ssh_key_change_strategy',
|
||||
field=models.CharField(choices=[('add', 'Append SSH KEY'), ('set', 'Empty and append SSH KEY'), ('set_jms', 'Replace (Replace only keys pushed by JumpServer) ')], default='add', max_length=16, verbose_name='SSH key change strategy'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='pushaccountautomation',
|
||||
name='ssh_key_change_strategy',
|
||||
field=models.CharField(choices=[('add', 'Append SSH KEY'), ('set', 'Empty and append SSH KEY'), ('set_jms', 'Replace (Replace only keys pushed by JumpServer) ')], default='add', max_length=16, verbose_name='SSH key change strategy'),
|
||||
),
|
||||
]
|
||||
@@ -1,4 +1,6 @@
|
||||
from django.db import models
|
||||
from django.db.models import Count, Q
|
||||
from django.utils import timezone
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from simple_history.models import HistoricalRecords
|
||||
|
||||
@@ -106,6 +108,11 @@ class Account(AbsConnectivity, BaseAccount):
|
||||
|
||||
|
||||
class AccountTemplate(BaseAccount):
|
||||
su_from = models.ForeignKey(
|
||||
'self', related_name='su_to', null=True,
|
||||
on_delete=models.SET_NULL, verbose_name=_("Su from")
|
||||
)
|
||||
|
||||
class Meta:
|
||||
verbose_name = _('Account template')
|
||||
unique_together = (
|
||||
@@ -116,5 +123,62 @@ class AccountTemplate(BaseAccount):
|
||||
('change_accounttemplatesecret', _('Can change asset account template secret')),
|
||||
]
|
||||
|
||||
@classmethod
|
||||
def get_su_from_account_templates(cls, instance=None):
|
||||
if not instance:
|
||||
return cls.objects.all()
|
||||
return cls.objects.exclude(Q(id=instance.id) | Q(su_from=instance))
|
||||
|
||||
def get_su_from_account(self, asset):
|
||||
su_from = self.su_from
|
||||
if su_from and asset.platform.su_enabled:
|
||||
account = asset.accounts.filter(
|
||||
username=su_from.username,
|
||||
secret_type=su_from.secret_type
|
||||
).first()
|
||||
return account
|
||||
|
||||
def __str__(self):
|
||||
return self.username
|
||||
|
||||
@staticmethod
|
||||
def bulk_update_accounts(accounts, data):
|
||||
history_model = Account.history.model
|
||||
account_ids = accounts.values_list('id', flat=True)
|
||||
history_accounts = history_model.objects.filter(id__in=account_ids)
|
||||
account_id_count_map = {
|
||||
str(i['id']): i['count']
|
||||
for i in history_accounts.values('id').order_by('id')
|
||||
.annotate(count=Count(1)).values('id', 'count')
|
||||
}
|
||||
|
||||
for account in accounts:
|
||||
account_id = str(account.id)
|
||||
account.version = account_id_count_map.get(account_id) + 1
|
||||
for k, v in data.items():
|
||||
setattr(account, k, v)
|
||||
Account.objects.bulk_update(accounts, ['version', 'secret'])
|
||||
|
||||
@staticmethod
|
||||
def bulk_create_history_accounts(accounts, user_id):
|
||||
history_model = Account.history.model
|
||||
history_account_objs = []
|
||||
for account in accounts:
|
||||
history_account_objs.append(
|
||||
history_model(
|
||||
id=account.id,
|
||||
version=account.version,
|
||||
secret=account.secret,
|
||||
secret_type=account.secret_type,
|
||||
history_user_id=user_id,
|
||||
history_date=timezone.now()
|
||||
)
|
||||
)
|
||||
history_model.objects.bulk_create(history_account_objs)
|
||||
|
||||
def bulk_sync_account_secret(self, accounts, user_id):
|
||||
""" 批量同步账号密码 """
|
||||
if not accounts:
|
||||
return
|
||||
self.bulk_update_accounts(accounts, {'secret': self.secret})
|
||||
self.bulk_create_history_accounts(accounts, user_id)
|
||||
|
||||
@@ -28,7 +28,6 @@ class ChangeSecretMixin(models.Model):
|
||||
default=SSHKeyStrategy.add, verbose_name=_('SSH key change strategy')
|
||||
)
|
||||
|
||||
accounts: list[str] # account usernames
|
||||
get_all_assets: callable # get all assets
|
||||
|
||||
class Meta:
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import uuid
|
||||
from copy import deepcopy
|
||||
|
||||
from django.db import IntegrityError
|
||||
from django.db.models import Q
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from rest_framework import serializers
|
||||
from rest_framework.generics import get_object_or_404
|
||||
from rest_framework.validators import UniqueTogetherValidator
|
||||
|
||||
from accounts.const import SecretType, Source, AccountInvalidPolicy
|
||||
@@ -21,8 +23,8 @@ logger = get_logger(__name__)
|
||||
|
||||
class AccountCreateUpdateSerializerMixin(serializers.Serializer):
|
||||
template = serializers.PrimaryKeyRelatedField(
|
||||
queryset=AccountTemplate.objects,
|
||||
required=False, label=_("Template"), write_only=True
|
||||
queryset=AccountTemplate.objects, required=False,
|
||||
label=_("Template"), write_only=True, allow_null=True
|
||||
)
|
||||
push_now = serializers.BooleanField(
|
||||
default=False, label=_("Push now"), write_only=True
|
||||
@@ -32,9 +34,10 @@ class AccountCreateUpdateSerializerMixin(serializers.Serializer):
|
||||
)
|
||||
on_invalid = LabeledChoiceField(
|
||||
choices=AccountInvalidPolicy.choices, default=AccountInvalidPolicy.ERROR,
|
||||
write_only=True, label=_('Exist policy')
|
||||
write_only=True, allow_null=True, label=_('Exist policy'),
|
||||
)
|
||||
_template = None
|
||||
clean_auth_fields: callable
|
||||
|
||||
class Meta:
|
||||
fields = ['template', 'push_now', 'params', 'on_invalid']
|
||||
@@ -58,10 +61,6 @@ class AccountCreateUpdateSerializerMixin(serializers.Serializer):
|
||||
self.from_template_if_need(data)
|
||||
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):
|
||||
name = initial_data.get('name')
|
||||
if name is not None:
|
||||
@@ -91,7 +90,7 @@ class AccountCreateUpdateSerializerMixin(serializers.Serializer):
|
||||
|
||||
self._template = template
|
||||
# Set initial data from template
|
||||
ignore_fields = ['id', 'date_created', 'date_updated', 'org_id']
|
||||
ignore_fields = ['id', 'date_created', 'date_updated', 'su_from', 'org_id']
|
||||
field_names = [
|
||||
field.name for field in template._meta.fields
|
||||
if field.name not in ignore_fields
|
||||
@@ -103,6 +102,15 @@ class AccountCreateUpdateSerializerMixin(serializers.Serializer):
|
||||
continue
|
||||
attrs[name] = value
|
||||
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
|
||||
def push_account_if_need(instance, push_now, params, stat):
|
||||
@@ -147,17 +155,10 @@ class AccountCreateUpdateSerializerMixin(serializers.Serializer):
|
||||
else:
|
||||
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):
|
||||
push_now = validated_data.pop('push_now', None)
|
||||
params = validated_data.pop('params', None)
|
||||
self.generate_source_data(validated_data)
|
||||
self.clean_auth_fields(validated_data)
|
||||
instance, stat = self.do_create(validated_data)
|
||||
self.push_account_if_need(instance, push_now, params, stat)
|
||||
return instance
|
||||
@@ -197,8 +198,11 @@ class AccountAssetSerializer(serializers.ModelSerializer):
|
||||
|
||||
class AccountSerializer(AccountCreateUpdateSerializerMixin, BaseAccountSerializer):
|
||||
asset = AccountAssetSerializer(label=_('Asset'))
|
||||
source = LabeledChoiceField(choices=Source.choices, label=_("Source"), 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(
|
||||
required=False, queryset=Account.objects, allow_null=True, allow_empty=True,
|
||||
label=_('Su from'), attrs=('id', 'name', 'username')
|
||||
@@ -211,11 +215,12 @@ class AccountSerializer(AccountCreateUpdateSerializerMixin, BaseAccountSerialize
|
||||
'source', 'source_id', 'connectivity',
|
||||
] + AccountCreateUpdateSerializerMixin.Meta.fields
|
||||
read_only_fields = BaseAccountSerializer.Meta.read_only_fields + [
|
||||
'source', 'source_id', 'connectivity'
|
||||
'connectivity'
|
||||
]
|
||||
extra_kwargs = {
|
||||
**BaseAccountSerializer.Meta.extra_kwargs,
|
||||
'name': {'required': False},
|
||||
'source_id': {'required': False, 'allow_null': True},
|
||||
}
|
||||
|
||||
@classmethod
|
||||
@@ -238,18 +243,25 @@ class AssetAccountBulkSerializerResultSerializer(serializers.Serializer):
|
||||
class AssetAccountBulkSerializer(
|
||||
AccountCreateUpdateSerializerMixin, AuthValidateMixin, serializers.ModelSerializer
|
||||
):
|
||||
su_from_username = serializers.CharField(
|
||||
max_length=128, required=False, write_only=True, allow_null=True, label=_("Su from"),
|
||||
allow_blank=True,
|
||||
)
|
||||
assets = serializers.PrimaryKeyRelatedField(queryset=Asset.objects, many=True, label=_('Assets'))
|
||||
|
||||
class Meta:
|
||||
model = Account
|
||||
fields = [
|
||||
'name', 'username', 'secret', 'secret_type',
|
||||
'name', 'username', 'secret', 'secret_type', 'passphrase',
|
||||
'privileged', 'is_active', 'comment', 'template',
|
||||
'on_invalid', 'push_now', 'assets',
|
||||
'on_invalid', 'push_now', 'assets', 'su_from_username',
|
||||
'source', 'source_id',
|
||||
]
|
||||
extra_kwargs = {
|
||||
'name': {'required': False},
|
||||
'secret_type': {'required': False},
|
||||
'source': {'required': False, 'allow_null': True},
|
||||
'source_id': {'required': False, 'allow_null': True},
|
||||
}
|
||||
|
||||
def set_initial_value(self):
|
||||
@@ -293,8 +305,21 @@ class AssetAccountBulkSerializer(
|
||||
raise serializers.ValidationError(_('Account already exists'))
|
||||
return instance, True, 'created'
|
||||
|
||||
def generate_su_from_data(self, validated_data):
|
||||
template = self._template
|
||||
asset = validated_data['asset']
|
||||
su_from = validated_data.get('su_from')
|
||||
su_from_username = validated_data.pop('su_from_username', None)
|
||||
if template:
|
||||
su_from = template.get_su_from_account(asset)
|
||||
elif su_from_username:
|
||||
su_from = asset.accounts.filter(username=su_from_username).first()
|
||||
validated_data['su_from'] = su_from
|
||||
|
||||
def perform_create(self, vd, handler):
|
||||
lookup = self.get_filter_lookup(vd)
|
||||
vd = deepcopy(vd)
|
||||
self.generate_su_from_data(vd)
|
||||
try:
|
||||
instance, changed, state = handler(vd, lookup)
|
||||
except IntegrityError:
|
||||
@@ -335,6 +360,7 @@ class AssetAccountBulkSerializer(
|
||||
vd = vd.copy()
|
||||
vd['asset'] = asset
|
||||
try:
|
||||
self.clean_auth_fields(vd)
|
||||
instance, changed, state = self.perform_create(vd, create_handler)
|
||||
_results[asset] = {
|
||||
'changed': changed, 'instance': instance.id, 'state': state
|
||||
@@ -375,7 +401,6 @@ class AssetAccountBulkSerializer(
|
||||
|
||||
def create(self, validated_data):
|
||||
push_now = validated_data.pop('push_now', False)
|
||||
self.generate_source_data(validated_data)
|
||||
results = self.perform_bulk_create(validated_data)
|
||||
self.push_accounts_if_need(results, push_now)
|
||||
for res in results:
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
from django.utils.translation import ugettext as _
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from rest_framework import serializers
|
||||
|
||||
from accounts.models import AccountBackupAutomation, AccountBackupExecution
|
||||
|
||||
@@ -78,4 +78,5 @@ class BaseAccountSerializer(AuthValidateMixin, BulkOrgResourceModelSerializer):
|
||||
]
|
||||
extra_kwargs = {
|
||||
'spec_info': {'label': _('Spec info')},
|
||||
'username': {'help_text': _("Tip: If no username is required for authentication, fill in `null`")}
|
||||
}
|
||||
|
||||
@@ -1,86 +1,51 @@
|
||||
from django.db.transaction import atomic
|
||||
from django.db.utils import IntegrityError
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from rest_framework import serializers
|
||||
|
||||
from accounts.models import AccountTemplate, Account
|
||||
from assets.models import Asset
|
||||
from common.serializers import SecretReadableMixin
|
||||
from common.serializers.fields import ObjectRelatedField
|
||||
from .base import BaseAccountSerializer
|
||||
|
||||
|
||||
class AccountTemplateSerializer(BaseAccountSerializer):
|
||||
is_sync_account = serializers.BooleanField(default=False, write_only=True)
|
||||
_is_sync_account = False
|
||||
|
||||
su_from = ObjectRelatedField(
|
||||
required=False, queryset=AccountTemplate.objects, allow_null=True,
|
||||
allow_empty=True, label=_('Su from'), attrs=('id', 'name', 'username')
|
||||
)
|
||||
|
||||
class Meta(BaseAccountSerializer.Meta):
|
||||
model = AccountTemplate
|
||||
fields = BaseAccountSerializer.Meta.fields + ['is_sync_account', 'su_from']
|
||||
|
||||
@staticmethod
|
||||
def account_save(data, account):
|
||||
for field, value in data.items():
|
||||
setattr(account, field, value)
|
||||
try:
|
||||
account.save(update_fields=list(data.keys()))
|
||||
except IntegrityError:
|
||||
pass
|
||||
|
||||
# TODO 数据库访问的太多了 后期优化
|
||||
@atomic()
|
||||
def bulk_update_accounts(self, instance, diff):
|
||||
accounts = Account.objects.filter(source_id=instance.id)
|
||||
if not accounts:
|
||||
def sync_accounts_secret(self, instance, diff):
|
||||
if not self._is_sync_account or 'secret' not in diff:
|
||||
return
|
||||
query_data = {
|
||||
'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)
|
||||
|
||||
diff.pop('secret', None)
|
||||
name = diff.pop('name', None)
|
||||
username = diff.pop('username', None)
|
||||
secret_type = diff.pop('secret_type', None)
|
||||
update_accounts = []
|
||||
for account in accounts:
|
||||
for field, value in diff.items():
|
||||
setattr(account, field, value)
|
||||
update_accounts.append(account)
|
||||
|
||||
if update_accounts:
|
||||
Account.objects.bulk_update(update_accounts, diff.keys())
|
||||
|
||||
if name:
|
||||
for account in accounts:
|
||||
data = {'name': name}
|
||||
self.account_save(data, account)
|
||||
|
||||
if secret_type and username:
|
||||
asset_ids_supports = self.get_asset_ids_supports(accounts, secret_type)
|
||||
for account in accounts:
|
||||
asset_id = account.asset_id
|
||||
if asset_id not in asset_ids_supports:
|
||||
data = {'username': username}
|
||||
self.account_save(data, account)
|
||||
continue
|
||||
data = {'username': username, 'secret_type': secret_type, 'secret': instance.secret}
|
||||
self.account_save(data, account)
|
||||
elif secret_type:
|
||||
asset_ids_supports = self.get_asset_ids_supports(accounts, secret_type)
|
||||
for account in accounts:
|
||||
asset_id = account.asset_id
|
||||
if asset_id not in asset_ids_supports:
|
||||
continue
|
||||
data = {'secret_type': secret_type, 'secret': instance.secret}
|
||||
self.account_save(data, account)
|
||||
elif username:
|
||||
for account in accounts:
|
||||
data = {'username': username}
|
||||
self.account_save(data, account)
|
||||
|
||||
@staticmethod
|
||||
def get_asset_ids_supports(accounts, secret_type):
|
||||
asset_ids = accounts.values_list('asset_id', flat=True)
|
||||
secret_type_supports = Asset.get_secret_type_assets(asset_ids, secret_type)
|
||||
return [asset.id for asset in secret_type_supports]
|
||||
def validate(self, attrs):
|
||||
self._is_sync_account = attrs.pop('is_sync_account', None)
|
||||
attrs = super().validate(attrs)
|
||||
return attrs
|
||||
|
||||
def update(self, instance, validated_data):
|
||||
# diff = {
|
||||
# k: v for k, v in validated_data.items()
|
||||
# if getattr(instance, k) != v
|
||||
# }
|
||||
diff = {
|
||||
k: v for k, v in validated_data.items()
|
||||
if getattr(instance, k, None) != v
|
||||
}
|
||||
instance = super().update(instance, validated_data)
|
||||
# self.bulk_update_accounts(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
|
||||
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
from django.utils.translation import ugettext as _
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from rest_framework import serializers
|
||||
|
||||
from accounts.models import AutomationExecution
|
||||
@@ -63,15 +63,17 @@ class AutomationExecutionSerializer(serializers.ModelSerializer):
|
||||
|
||||
@staticmethod
|
||||
def get_snapshot(obj):
|
||||
tp = obj.snapshot['type']
|
||||
tp = obj.snapshot.get('type', '')
|
||||
type_display = tp if not hasattr(AutomationTypes, tp) \
|
||||
else getattr(AutomationTypes, tp).label
|
||||
snapshot = {
|
||||
'type': tp,
|
||||
'name': obj.snapshot['name'],
|
||||
'comment': obj.snapshot['comment'],
|
||||
'accounts': obj.snapshot['accounts'],
|
||||
'node_amount': len(obj.snapshot['nodes']),
|
||||
'asset_amount': len(obj.snapshot['assets']),
|
||||
'type_display': getattr(AutomationTypes, tp).label,
|
||||
'name': obj.snapshot.get('name'),
|
||||
'comment': obj.snapshot.get('comment'),
|
||||
'accounts': obj.snapshot.get('accounts'),
|
||||
'node_amount': len(obj.snapshot.get('nodes', [])),
|
||||
'asset_amount': len(obj.snapshot.get('assets', [])),
|
||||
'type_display': type_display,
|
||||
}
|
||||
return snapshot
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
from .command_acl import *
|
||||
from .connect_method import *
|
||||
from .login_acl import *
|
||||
from .login_asset_acl import *
|
||||
from .login_asset_check import *
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
from rest_framework.decorators import action
|
||||
from rest_framework.response import Response
|
||||
|
||||
from orgs.mixins.api import OrgBulkModelViewSet
|
||||
from .common import ACLUserAssetFilterMixin
|
||||
from .. import models, serializers
|
||||
|
||||
__all__ = ['CommandFilterACLViewSet', 'CommandGroupViewSet']
|
||||
@@ -13,10 +15,16 @@ class CommandGroupViewSet(OrgBulkModelViewSet):
|
||||
serializer_class = serializers.CommandGroupSerializer
|
||||
|
||||
|
||||
class CommandACLFilter(ACLUserAssetFilterMixin):
|
||||
class Meta:
|
||||
model = models.CommandFilterACL
|
||||
fields = ['name', ]
|
||||
|
||||
|
||||
class CommandFilterACLViewSet(OrgBulkModelViewSet):
|
||||
model = models.CommandFilterACL
|
||||
filterset_fields = ('name',)
|
||||
search_fields = filterset_fields
|
||||
filterset_class = CommandACLFilter
|
||||
search_fields = ['name']
|
||||
serializer_class = serializers.CommandFilterACLSerializer
|
||||
rbac_perms = {
|
||||
'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 ..models import LoginACL
|
||||
|
||||
from orgs.utils import tmp_to_root_org
|
||||
from .common import ACLUserFilterMixin
|
||||
from .. import serializers
|
||||
from ..filters import LoginAclFilter
|
||||
from ..models import LoginACL
|
||||
|
||||
__all__ = ['LoginACLViewSet']
|
||||
|
||||
|
||||
class LoginACLFilter(ACLUserFilterMixin):
|
||||
class Meta:
|
||||
model = LoginACL
|
||||
fields = ('name', 'action')
|
||||
|
||||
|
||||
class LoginACLViewSet(JMSBulkModelViewSet):
|
||||
queryset = LoginACL.objects.all()
|
||||
filterset_class = LoginAclFilter
|
||||
filterset_class = LoginACLFilter
|
||||
search_fields = ('name',)
|
||||
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 .common import ACLUserAssetFilterMixin
|
||||
from .. import models, serializers
|
||||
|
||||
|
||||
__all__ = ['LoginAssetACLViewSet']
|
||||
|
||||
|
||||
class LoginAssetACLFilter(ACLUserAssetFilterMixin):
|
||||
class Meta:
|
||||
model = models.LoginAssetACL
|
||||
fields = ['name', ]
|
||||
|
||||
|
||||
class LoginAssetACLViewSet(OrgBulkModelViewSet):
|
||||
model = models.LoginAssetACL
|
||||
filterset_fields = ('name', )
|
||||
search_fields = filterset_fields
|
||||
filterset_class = LoginAssetACLFilter
|
||||
search_fields = ['name']
|
||||
serializer_class = serializers.LoginAssetACLSerializer
|
||||
|
||||
@@ -30,14 +30,21 @@ class LoginAssetCheckAPI(CreateAPIView):
|
||||
return serializer
|
||||
|
||||
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):
|
||||
kwargs = {
|
||||
'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()
|
||||
acl = queryset.valid().first()
|
||||
|
||||
if acl:
|
||||
need_review = True
|
||||
response_data = self._get_response_data_of_need_review(acl)
|
||||
|
||||
@@ -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
|
||||
|
||||
from django.conf import settings
|
||||
import django.core.validators
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
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):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
@@ -24,37 +24,51 @@ class Migration(migrations.Migration):
|
||||
('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')),
|
||||
('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')),
|
||||
('comment', models.TextField(blank=True, default='', verbose_name='Comment')),
|
||||
('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')),
|
||||
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='login_acls', to=settings.AUTH_USER_MODEL, verbose_name='User')),
|
||||
('action',
|
||||
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={
|
||||
'ordering': ('priority', '-date_updated', 'name'),
|
||||
'ordering': ('priority', '-is_active', 'name'),
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='LoginAssetACL',
|
||||
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)),
|
||||
('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_updated', models.DateTimeField(auto_now=True, verbose_name='Date updated')),
|
||||
('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')),
|
||||
('comment', models.TextField(blank=True, default='', verbose_name='Comment')),
|
||||
('users', models.JSONField(verbose_name='User')),
|
||||
('system_users', models.JSONField(verbose_name='System User')),
|
||||
('assets', models.JSONField(verbose_name='Asset')),
|
||||
('action', 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')),
|
||||
('action',
|
||||
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={
|
||||
'ordering': ('priority', '-date_updated', 'name'),
|
||||
'ordering': ('priority', '-is_active', 'name'),
|
||||
'unique_together': {('name', 'org_id')},
|
||||
},
|
||||
),
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
import django
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models, transaction
|
||||
from acls.models import LoginACL
|
||||
|
||||
LOGIN_CONFIRM_ZH = '登录复核'
|
||||
LOGIN_CONFIRM_EN = 'Login confirm'
|
||||
@@ -90,10 +89,10 @@ class Migration(migrations.Migration):
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='loginacl',
|
||||
options={'ordering': ('priority', '-date_updated', 'name'), 'verbose_name': 'Login acl'},
|
||||
options={'ordering': ('priority', '-is_active', 'name'), 'verbose_name': 'Login acl'},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
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):
|
||||
|
||||
dependencies = [
|
||||
('acls', '0002_auto_20210926_1047'),
|
||||
]
|
||||
@@ -12,10 +11,10 @@ class Migration(migrations.Migration):
|
||||
operations = [
|
||||
migrations.AlterModelOptions(
|
||||
name='loginacl',
|
||||
options={'ordering': ('priority', '-date_updated', 'name'), 'verbose_name': 'Login acl'},
|
||||
options={'ordering': ('priority', '-is_active', 'name'), 'verbose_name': 'Login acl'},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
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={
|
||||
'verbose_name': 'Command acl',
|
||||
'ordering': ('priority', '-date_updated', 'name'),
|
||||
'ordering': ('priority', '-is_active', 'name'),
|
||||
'unique_together': {('name', 'org_id')},
|
||||
},
|
||||
),
|
||||
|
||||
@@ -20,14 +20,14 @@ class Migration(migrations.Migration):
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='commandfilteracl',
|
||||
options={'ordering': ('priority', 'date_updated', 'name'), 'verbose_name': 'Command acl'},
|
||||
options={'ordering': ('priority', '-is_active', 'name'), 'verbose_name': 'Command acl'},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='loginacl',
|
||||
options={'ordering': ('priority', 'date_updated', 'name'), 'verbose_name': 'Login acl'},
|
||||
options={'ordering': ('priority', '-is_active', 'name'), 'verbose_name': 'Login acl'},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
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_asset_acl import *
|
||||
from .command_acl import *
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
from django.core.validators import MinValueValidator, MaxValueValidator
|
||||
from django.db import models
|
||||
from django.db.models import Q
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from common.db.fields import JSONManyToManyField
|
||||
from common.db.models import JMSBaseModel
|
||||
from common.utils import contains_ip
|
||||
from common.utils.time_period import contains_time_period
|
||||
from orgs.mixins.models import OrgModelMixin, OrgManager
|
||||
|
||||
__all__ = [
|
||||
'ACLManager',
|
||||
'BaseACL',
|
||||
'BaseACLQuerySet',
|
||||
'UserAssetAccountBaseACL',
|
||||
'UserAssetAccountACLQuerySet'
|
||||
'BaseACL', 'UserBaseACL', 'UserAssetAccountBaseACL',
|
||||
]
|
||||
|
||||
from orgs.utils import tmp_to_root_org
|
||||
from orgs.utils import tmp_to_org
|
||||
|
||||
|
||||
class ActionChoices(models.TextChoices):
|
||||
reject = 'reject', _('Reject')
|
||||
@@ -36,43 +36,8 @@ class BaseACLQuerySet(models.QuerySet):
|
||||
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):
|
||||
name = models.CharField(max_length=128, verbose_name=_('Name'))
|
||||
name = models.CharField(max_length=128, verbose_name=_('Name'), unique=True)
|
||||
priority = models.IntegerField(
|
||||
default=50, verbose_name=_("Priority"),
|
||||
help_text=_("1-100, the lower the value will be match first"),
|
||||
@@ -83,46 +48,85 @@ class BaseACL(JMSBaseModel):
|
||||
is_active = models.BooleanField(default=True, verbose_name=_("Active"))
|
||||
|
||||
ActionChoices = ActionChoices
|
||||
objects = ACLManager.from_queryset(BaseACLQuerySet)()
|
||||
objects = BaseACLQuerySet.as_manager()
|
||||
|
||||
class Meta:
|
||||
ordering = ('priority', 'date_updated', 'name')
|
||||
ordering = ('priority', '-is_active', 'name')
|
||||
abstract = True
|
||||
|
||||
def is_action(self, action):
|
||||
return self.action == action
|
||||
|
||||
@classmethod
|
||||
def get_user_acls(cls, user):
|
||||
return cls.objects.none()
|
||||
|
||||
class UserAssetAccountBaseACL(BaseACL, OrgModelMixin):
|
||||
# username_group
|
||||
users = models.JSONField(verbose_name=_('User'))
|
||||
# name_group, address_group
|
||||
assets = models.JSONField(verbose_name=_('Asset'))
|
||||
# username_group
|
||||
accounts = models.JSONField(verbose_name=_('Account'))
|
||||
@classmethod
|
||||
def get_match_rule_acls(cls, user, ip, acl_qs=None):
|
||||
if acl_qs is None:
|
||||
acl_qs = cls.get_user_acls(user)
|
||||
if not acl_qs:
|
||||
return
|
||||
|
||||
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):
|
||||
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
|
||||
|
||||
@classmethod
|
||||
def filter_queryset(cls, user=None, asset=None, account=None, account_username=None, **kwargs):
|
||||
queryset = cls.objects.all()
|
||||
org_id = None
|
||||
|
||||
if user:
|
||||
queryset = queryset.filter_user(user.username)
|
||||
if account:
|
||||
org_id = account.org_id
|
||||
queryset = queryset.filter_account(account.username)
|
||||
if account_username:
|
||||
queryset = queryset.filter_account(username=account_username)
|
||||
q = cls.users.get_filter_q(user)
|
||||
queryset = queryset.filter(q)
|
||||
|
||||
if asset:
|
||||
org_id = asset.org_id
|
||||
queryset = queryset.filter_asset(asset.name, asset.address)
|
||||
if org_id:
|
||||
kwargs['org_id'] = org_id
|
||||
with tmp_to_org(org_id):
|
||||
q = cls.assets.get_filter_q(asset)
|
||||
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:
|
||||
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 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 .base import BaseACL
|
||||
from .base import UserBaseACL
|
||||
|
||||
|
||||
class LoginACL(BaseACL):
|
||||
user = models.ForeignKey(
|
||||
'users.User', on_delete=models.CASCADE, related_name='login_acls', verbose_name=_('User')
|
||||
)
|
||||
class LoginACL(UserBaseACL):
|
||||
# 规则, ip_group, time_period
|
||||
rules = models.JSONField(default=dict, verbose_name=_('Rule'))
|
||||
|
||||
class Meta(BaseACL.Meta):
|
||||
class Meta(UserBaseACL.Meta):
|
||||
verbose_name = _('Login acl')
|
||||
abstract = False
|
||||
|
||||
@@ -25,40 +20,18 @@ class LoginACL(BaseACL):
|
||||
def is_action(self, action):
|
||||
return self.action == action
|
||||
|
||||
@classmethod
|
||||
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):
|
||||
def create_confirm_ticket(self, request, user):
|
||||
from tickets import const
|
||||
from tickets.models import ApplyLoginTicket
|
||||
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 = login_ip or '0.0.0.0'
|
||||
login_city = get_ip_city(login_ip)
|
||||
login_datetime = local_now_display()
|
||||
data = {
|
||||
'title': title,
|
||||
'applicant': self.user,
|
||||
'applicant': user,
|
||||
'apply_login_ip': login_ip,
|
||||
'org_id': Organization.ROOT_ID,
|
||||
'apply_login_city': login_city,
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
from django.db import models
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
|
||||
from .base import UserAssetAccountBaseACL
|
||||
|
||||
|
||||
class LoginAssetACL(UserAssetAccountBaseACL):
|
||||
# 规则, ip_group, time_period
|
||||
rules = models.JSONField(default=dict, verbose_name=_('Rule'))
|
||||
|
||||
class Meta(UserAssetAccountBaseACL.Meta):
|
||||
verbose_name = _('Login asset acl')
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
from .command_acl import *
|
||||
from .connect_method import *
|
||||
from .login_acl import *
|
||||
from .login_asset_acl import *
|
||||
from .login_asset_check import *
|
||||
from .command_acl import *
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from rest_framework import serializers
|
||||
|
||||
from acls.models.base import ActionChoices
|
||||
from common.serializers.fields import LabeledChoiceField, ObjectRelatedField
|
||||
from acls.models.base import ActionChoices, BaseACL
|
||||
from common.serializers.fields import JSONManyToManyField, LabeledChoiceField
|
||||
from jumpserver.utils import has_valid_xpack_license
|
||||
from orgs.models import Organization
|
||||
from users.models import User
|
||||
|
||||
common_help_text = _(
|
||||
"With * indicating a match all. "
|
||||
@@ -20,7 +20,7 @@ class ACLUsersSerializer(serializers.Serializer):
|
||||
)
|
||||
|
||||
|
||||
class ACLAssestsSerializer(serializers.Serializer):
|
||||
class ACLAssetsSerializer(serializers.Serializer):
|
||||
address_group_help_text = _(
|
||||
"With * indicating a match all. "
|
||||
"Such as: "
|
||||
@@ -51,46 +51,35 @@ class ACLAccountsSerializer(serializers.Serializer):
|
||||
)
|
||||
|
||||
|
||||
class BaseUserAssetAccountACLSerializerMixin(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 ActionAclSerializer(serializers.Serializer):
|
||||
action = LabeledChoiceField(
|
||||
choices=ActionChoices.choices, default=ActionChoices.reject, label=_("Action")
|
||||
)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.set_action_choices()
|
||||
|
||||
def set_action_choices(self):
|
||||
action = self.fields.get("action")
|
||||
if not action:
|
||||
return
|
||||
choices = action.choices
|
||||
if not has_valid_xpack_license():
|
||||
choices.pop(ActionChoices.review, None)
|
||||
action._choices = choices
|
||||
|
||||
|
||||
class BaserACLSerializer(ActionAclSerializer, serializers.Serializer):
|
||||
class Meta:
|
||||
model = BaseACL
|
||||
fields_mini = ["id", "name"]
|
||||
fields_small = fields_mini + [
|
||||
'users_username_group', 'assets_address_group', 'assets_name_group',
|
||||
'accounts_username_group',
|
||||
"users", "accounts", "assets", "is_active",
|
||||
"date_created", "date_updated", "priority",
|
||||
"action", "comment", "created_by", "org_id",
|
||||
"is_active", "priority", "action",
|
||||
"date_created", "date_updated",
|
||||
"comment", "created_by", "org_id",
|
||||
]
|
||||
fields_m2m = ["reviewers", "reviewers_amount"]
|
||||
fields_m2m = ["reviewers", ]
|
||||
fields = fields_small + fields_m2m
|
||||
extra_kwargs = {
|
||||
"priority": {"default": 50},
|
||||
@@ -116,3 +105,18 @@ class BaseUserAssetAccountACLSerializerMixin(serializers.Serializer):
|
||||
)
|
||||
raise serializers.ValidationError(error)
|
||||
return valid_reviewers
|
||||
|
||||
|
||||
class BaserUserACLSerializer(BaserACLSerializer):
|
||||
users = JSONManyToManyField(label=_('User'))
|
||||
|
||||
class Meta(BaserACLSerializer.Meta):
|
||||
fields = BaserACLSerializer.Meta.fields + ['users']
|
||||
|
||||
|
||||
class BaseUserAssetAccountACLSerializer(BaserUserACLSerializer):
|
||||
assets = JSONManyToManyField(label=_('Asset'))
|
||||
accounts = serializers.ListField(label=_('Account'))
|
||||
|
||||
class Meta(BaserUserACLSerializer.Meta):
|
||||
fields = BaserUserACLSerializer.Meta.fields + ['assets', 'accounts']
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from rest_framework import serializers
|
||||
|
||||
from terminal.models import Session
|
||||
from acls.models import CommandGroup, CommandFilterACL
|
||||
from common.utils import lazyproperty, get_object_or_none
|
||||
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 .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"]
|
||||
|
||||
@@ -27,13 +27,10 @@ class CommandFilterACLSerializer(BaseSerializer, BulkOrgResourceModelSerializer)
|
||||
command_groups = ObjectRelatedField(
|
||||
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):
|
||||
model = CommandFilterACL
|
||||
fields = BaseSerializer.Meta.fields + ['command_groups', 'command_groups_amount']
|
||||
fields = BaseSerializer.Meta.fields + ['command_groups']
|
||||
|
||||
|
||||
class CommandReviewSerializer(serializers.Serializer):
|
||||
|
||||
23
apps/acls/serializers/connect_method.py
Normal file
23
apps/acls/serializers/connect_method.py
Normal file
@@ -0,0 +1,23 @@
|
||||
from orgs.mixins.serializers import BulkOrgResourceModelSerializer
|
||||
from .base import BaseUserAssetAccountACLSerializer as BaseSerializer
|
||||
from ..models import ConnectMethodACL
|
||||
|
||||
__all__ = ["ConnectMethodACLSerializer"]
|
||||
|
||||
|
||||
class ConnectMethodACLSerializer(BaseSerializer, BulkOrgResourceModelSerializer):
|
||||
class Meta(BaseSerializer.Meta):
|
||||
model = ConnectMethodACL
|
||||
fields = [
|
||||
i for i in BaseSerializer.Meta.fields + ['connect_methods']
|
||||
if i not in ['assets', 'accounts']
|
||||
]
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
field_action = self.fields.get('action')
|
||||
if not field_action:
|
||||
return
|
||||
# 仅支持拒绝
|
||||
for k in ['review', 'accept']:
|
||||
field_action._choices.pop(k, None)
|
||||
@@ -1,62 +1,22 @@
|
||||
from django.utils.translation import ugettext as _
|
||||
from rest_framework import serializers
|
||||
|
||||
from common.serializers import BulkModelSerializer, MethodSerializer
|
||||
from common.serializers.fields import ObjectRelatedField, LabeledChoiceField
|
||||
from jumpserver.utils import has_valid_xpack_license
|
||||
from users.models import User
|
||||
from common.serializers import MethodSerializer
|
||||
from orgs.mixins.serializers import BulkOrgResourceModelSerializer
|
||||
from .base import BaserUserACLSerializer
|
||||
from .rules import RuleSerializer
|
||||
from ..models import LoginACL
|
||||
from ..models.base import ActionChoices
|
||||
|
||||
__all__ = [
|
||||
"LoginACLSerializer",
|
||||
]
|
||||
__all__ = ["LoginACLSerializer"]
|
||||
|
||||
common_help_text = _(
|
||||
"With * indicating a match all. "
|
||||
)
|
||||
common_help_text = _("With * indicating a match all. ")
|
||||
|
||||
|
||||
class LoginACLSerializer(BulkModelSerializer):
|
||||
user = ObjectRelatedField(queryset=User.objects, label=_("User"))
|
||||
reviewers = ObjectRelatedField(
|
||||
queryset=User.objects, label=_("Reviewers"), many=True, required=False
|
||||
)
|
||||
action = LabeledChoiceField(choices=ActionChoices.choices, label=_('Action'))
|
||||
reviewers_amount = serializers.IntegerField(
|
||||
read_only=True, source="reviewers.count", label=_("Reviewers amount")
|
||||
)
|
||||
class LoginACLSerializer(BaserUserACLSerializer, BulkOrgResourceModelSerializer):
|
||||
rules = MethodSerializer(label=_('Rule'))
|
||||
|
||||
class Meta:
|
||||
class Meta(BaserUserACLSerializer.Meta):
|
||||
model = LoginACL
|
||||
fields_mini = ["id", "name"]
|
||||
fields_small = fields_mini + [
|
||||
"priority", "user", "rules", "action",
|
||||
"is_active", "date_created", "date_updated",
|
||||
"comment", "created_by",
|
||||
]
|
||||
fields_fk = ["user"]
|
||||
fields_m2m = ["reviewers", "reviewers_amount"]
|
||||
fields = fields_small + fields_fk + fields_m2m
|
||||
extra_kwargs = {
|
||||
"priority": {"default": 50},
|
||||
"is_active": {"default": True},
|
||||
}
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.set_action_choices()
|
||||
|
||||
def set_action_choices(self):
|
||||
action = self.fields.get("action")
|
||||
if not action:
|
||||
return
|
||||
choices = action.choices
|
||||
if not has_valid_xpack_license():
|
||||
choices.pop(LoginACL.ActionChoices.review, None)
|
||||
action._choices = choices
|
||||
fields = BaserUserACLSerializer.Meta.fields + ['rules', ]
|
||||
|
||||
def get_rules_serializer(self):
|
||||
return RuleSerializer()
|
||||
|
||||
@@ -1,11 +1,20 @@
|
||||
from orgs.mixins.serializers import BulkOrgResourceModelSerializer
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from .base import BaseUserAssetAccountACLSerializerMixin as BaseSerializer
|
||||
from common.serializers import MethodSerializer
|
||||
from orgs.mixins.serializers import BulkOrgResourceModelSerializer
|
||||
from .base import BaseUserAssetAccountACLSerializer as BaseSerializer
|
||||
from .rules import RuleSerializer
|
||||
from ..models import LoginAssetACL
|
||||
|
||||
__all__ = ["LoginAssetACLSerializer"]
|
||||
|
||||
|
||||
class LoginAssetACLSerializer(BaseSerializer, BulkOrgResourceModelSerializer):
|
||||
rules = MethodSerializer(label=_('Rule'))
|
||||
|
||||
class Meta(BaseSerializer.Meta):
|
||||
model = LoginAssetACL
|
||||
fields = BaseSerializer.Meta.fields + ['rules']
|
||||
|
||||
def get_rules_serializer(self):
|
||||
return RuleSerializer()
|
||||
|
||||
@@ -10,6 +10,7 @@ router.register(r'login-acls', api.LoginACLViewSet, 'login-acl')
|
||||
router.register(r'login-asset-acls', api.LoginAssetACLViewSet, 'login-asset-acl')
|
||||
router.register(r'command-filter-acls', api.CommandFilterACLViewSet, 'command-filter-acl')
|
||||
router.register(r'command-groups', api.CommandGroupViewSet, 'command-group')
|
||||
router.register(r'connect-method-acls', api.ConnectMethodACLViewSet, 'connect-method-acl')
|
||||
|
||||
urlpatterns = [
|
||||
path('login-asset/check/', api.LoginAssetCheckAPI.as_view(), name='login-asset-check'),
|
||||
|
||||
@@ -15,7 +15,7 @@ from assets.filters import IpInFilterBackend, LabelFilterBackend, NodeFilterBack
|
||||
from assets.models import Asset, Gateway, Platform
|
||||
from assets.tasks import test_assets_connectivity_manual, update_assets_hardware_info_manual
|
||||
from common.api import SuggestionMixin
|
||||
from common.drf.filters import BaseFilterSet
|
||||
from common.drf.filters import BaseFilterSet, AttrRulesFilterBackend
|
||||
from common.utils import get_logger, is_uuid
|
||||
from orgs.mixins import generics
|
||||
from orgs.mixins.api import OrgBulkModelViewSet
|
||||
@@ -25,7 +25,7 @@ from ...notifications import BulkUpdatePlatformSkipAssetUserMsg
|
||||
logger = get_logger(__file__)
|
||||
__all__ = [
|
||||
"AssetViewSet", "AssetTaskCreateApi",
|
||||
"AssetsTaskCreateApi", 'AssetFilterSet'
|
||||
"AssetsTaskCreateApi", 'AssetFilterSet',
|
||||
]
|
||||
|
||||
|
||||
@@ -35,6 +35,7 @@ class AssetFilterSet(BaseFilterSet):
|
||||
domain = django_filters.CharFilter(method='filter_domain')
|
||||
type = django_filters.CharFilter(field_name="platform__type", lookup_expr="exact")
|
||||
category = django_filters.CharFilter(field_name="platform__category", lookup_expr="exact")
|
||||
protocols = django_filters.CharFilter(method='filter_protocols')
|
||||
domain_enabled = django_filters.BooleanFilter(
|
||||
field_name="platform__domain_enabled", lookup_expr="exact"
|
||||
)
|
||||
@@ -78,6 +79,11 @@ class AssetFilterSet(BaseFilterSet):
|
||||
else:
|
||||
return queryset.filter(domain__name__contains=value)
|
||||
|
||||
@staticmethod
|
||||
def filter_protocols(queryset, name, value):
|
||||
value = value.split(',')
|
||||
return queryset.filter(protocols__name__in=value).distinct()
|
||||
|
||||
@staticmethod
|
||||
def filter_labels(queryset, name, value):
|
||||
if ':' in value:
|
||||
@@ -85,7 +91,7 @@ class AssetFilterSet(BaseFilterSet):
|
||||
queryset = queryset.filter(labels__name=n, labels__value=v)
|
||||
else:
|
||||
q = Q(labels__name__contains=value) | Q(labels__value__contains=value)
|
||||
queryset = queryset.filter(q)
|
||||
queryset = queryset.filter(q).distinct()
|
||||
return queryset
|
||||
|
||||
|
||||
@@ -95,7 +101,7 @@ class AssetViewSet(SuggestionMixin, NodeFilterMixin, OrgBulkModelViewSet):
|
||||
"""
|
||||
model = Asset
|
||||
filterset_class = AssetFilterSet
|
||||
search_fields = ("name", "address")
|
||||
search_fields = ("name", "address", "comment")
|
||||
ordering_fields = ('name', 'connectivity', 'platform', 'date_updated')
|
||||
serializer_classes = (
|
||||
("default", serializers.AssetSerializer),
|
||||
@@ -110,7 +116,10 @@ class AssetViewSet(SuggestionMixin, NodeFilterMixin, OrgBulkModelViewSet):
|
||||
("spec_info", "assets.view_asset"),
|
||||
("gathered_info", "assets.view_asset"),
|
||||
)
|
||||
extra_filter_backends = [LabelFilterBackend, IpInFilterBackend, NodeFilterBackend]
|
||||
extra_filter_backends = [
|
||||
LabelFilterBackend, IpInFilterBackend,
|
||||
NodeFilterBackend, AttrRulesFilterBackend
|
||||
]
|
||||
|
||||
def get_serializer_class(self):
|
||||
cls = super().get_serializer_class()
|
||||
|
||||
@@ -3,6 +3,7 @@ from rest_framework.decorators import action
|
||||
from rest_framework.response import Response
|
||||
|
||||
from common.api import JMSGenericViewSet
|
||||
from common.permissions import IsValidUser
|
||||
from assets.serializers import CategorySerializer, TypeSerializer
|
||||
from assets.const import AllTypes
|
||||
|
||||
@@ -14,7 +15,7 @@ class CategoryViewSet(ListModelMixin, JMSGenericViewSet):
|
||||
'default': CategorySerializer,
|
||||
'types': TypeSerializer
|
||||
}
|
||||
permission_classes = ()
|
||||
permission_classes = (IsValidUser,)
|
||||
|
||||
def get_queryset(self):
|
||||
return AllTypes.categories()
|
||||
|
||||
@@ -2,7 +2,7 @@ from typing import List
|
||||
|
||||
from rest_framework.request import Request
|
||||
|
||||
from assets.models import Node, PlatformProtocol
|
||||
from assets.models import Node, PlatformProtocol, Protocol
|
||||
from assets.utils import get_node_from_request, is_query_node_all_assets
|
||||
from common.utils import lazyproperty, timeit
|
||||
|
||||
@@ -57,32 +57,50 @@ class SerializeToTreeNodeMixin:
|
||||
]
|
||||
return data
|
||||
|
||||
@lazyproperty
|
||||
def support_types(self):
|
||||
from assets.const import AllTypes
|
||||
return AllTypes.get_types_values(exclude_custom=True)
|
||||
|
||||
def get_icon(self, asset):
|
||||
if asset.type in self.support_types:
|
||||
return asset.type
|
||||
else:
|
||||
return 'file'
|
||||
|
||||
@timeit
|
||||
def serialize_assets(self, assets, node_key=None):
|
||||
def serialize_assets(self, assets, node_key=None, pid=None):
|
||||
sftp_enabled_platform = PlatformProtocol.objects \
|
||||
.filter(name='ssh', setting__sftp_enabled=True) \
|
||||
.values_list('platform', flat=True).distinct()
|
||||
.values_list('platform', flat=True) \
|
||||
.distinct()
|
||||
if node_key is None:
|
||||
get_pid = lambda asset: getattr(asset, 'parent_key', '')
|
||||
else:
|
||||
get_pid = lambda asset: node_key
|
||||
|
||||
ssh_asset_ids = [
|
||||
str(i) for i in
|
||||
Protocol.objects.filter(name='ssh').values_list('asset_id', flat=True)
|
||||
]
|
||||
data = [
|
||||
{
|
||||
'id': str(asset.id),
|
||||
'name': asset.name,
|
||||
'title': f'{asset.address}\n{asset.comment}',
|
||||
'pId': get_pid(asset),
|
||||
'title':
|
||||
f'{asset.address}\n{asset.comment}'
|
||||
if asset.comment else asset.address,
|
||||
'pId': pid or get_pid(asset),
|
||||
'isParent': False,
|
||||
'open': False,
|
||||
'iconSkin': asset.type,
|
||||
'iconSkin': self.get_icon(asset),
|
||||
'chkDisabled': not asset.is_active,
|
||||
'meta': {
|
||||
'type': 'asset',
|
||||
'data': {
|
||||
'platform_type': asset.platform.type,
|
||||
'org_name': asset.org_name,
|
||||
'sftp': asset.platform_id in sftp_enabled_platform,
|
||||
'sftp': (asset.platform_id in sftp_enabled_platform) \
|
||||
and (str(asset.id) in ssh_asset_ids),
|
||||
'name': asset.name,
|
||||
'address': asset.address
|
||||
},
|
||||
|
||||
@@ -3,10 +3,12 @@
|
||||
from django.db.models import Q
|
||||
from rest_framework.generics import get_object_or_404
|
||||
from rest_framework.response import Response
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from assets.locks import NodeAddChildrenLock
|
||||
from common.tree import TreeNodeSerializer
|
||||
from common.utils import get_logger
|
||||
from common.exceptions import JMSException
|
||||
from orgs.mixins import generics
|
||||
from orgs.utils import current_org
|
||||
from .mixin import SerializeToTreeNodeMixin
|
||||
@@ -41,7 +43,11 @@ class NodeChildrenApi(generics.ListCreateAPIView):
|
||||
data = serializer.validated_data
|
||||
_id = data.get("id")
|
||||
value = data.get("value")
|
||||
if not value:
|
||||
if value:
|
||||
children = self.instance.get_children()
|
||||
if children.filter(value=value).exists():
|
||||
raise JMSException(_('The same level node name cannot be the same'))
|
||||
else:
|
||||
value = self.instance.get_next_child_preset_name()
|
||||
node = self.instance.create_child(value=value, _id=_id)
|
||||
# 避免查询 full value
|
||||
@@ -163,8 +169,10 @@ class CategoryTreeApi(SerializeToTreeNodeMixin, generics.ListAPIView):
|
||||
# 资源数量统计可选项 (asset, account)
|
||||
count_resource = self.request.query_params.get('count_resource', 'asset')
|
||||
|
||||
if include_asset and self.request.query_params.get('key'):
|
||||
if not self.request.query_params.get('key'):
|
||||
nodes = AllTypes.to_tree_nodes(include_asset, count_resource=count_resource)
|
||||
elif include_asset:
|
||||
nodes = self.get_assets()
|
||||
else:
|
||||
nodes = AllTypes.to_tree_nodes(include_asset, count_resource=count_resource)
|
||||
nodes = []
|
||||
return Response(data=nodes)
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user