mirror of
https://github.com/jumpserver/jumpserver.git
synced 2025-12-19 10:32:49 +00:00
Compare commits
22 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e4cf81d96c | ||
|
|
a098bc6c06 | ||
|
|
86870ad985 | ||
|
|
c602bf7224 | ||
|
|
86dab4fc6e | ||
|
|
a85a80a945 | ||
|
|
349edc10aa | ||
|
|
44918e3cb5 | ||
|
|
9a2f6c0d70 | ||
|
|
934969a8f1 | ||
|
|
57162c1628 | ||
|
|
32fb36867f | ||
|
|
158b589028 | ||
|
|
d64277353c | ||
|
|
bff6f397ce | ||
|
|
0ad461a804 | ||
|
|
a1dcef0ba0 | ||
|
|
dbb1ee3a75 | ||
|
|
d6bd207a17 | ||
|
|
e69ba27ff4 | ||
|
|
adbe7c07c6 | ||
|
|
d1eacf53d4 |
@@ -8,4 +8,4 @@ celerybeat.pid
|
|||||||
### Vagrant ###
|
### Vagrant ###
|
||||||
.vagrant/
|
.vagrant/
|
||||||
apps/xpack/.git
|
apps/xpack/.git
|
||||||
.history/
|
|
||||||
|
|||||||
5
.github/ISSUE_TEMPLATE/----.md
vendored
5
.github/ISSUE_TEMPLATE/----.md
vendored
@@ -3,10 +3,7 @@ name: 需求建议
|
|||||||
about: 提出针对本项目的想法和建议
|
about: 提出针对本项目的想法和建议
|
||||||
title: "[Feature] "
|
title: "[Feature] "
|
||||||
labels: 类型:需求
|
labels: 类型:需求
|
||||||
assignees:
|
assignees: ibuler
|
||||||
- ibuler
|
|
||||||
- baijiangjie
|
|
||||||
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
6
.github/ISSUE_TEMPLATE/bug---.md
vendored
6
.github/ISSUE_TEMPLATE/bug---.md
vendored
@@ -3,13 +3,11 @@ name: Bug 提交
|
|||||||
about: 提交产品缺陷帮助我们更好的改进
|
about: 提交产品缺陷帮助我们更好的改进
|
||||||
title: "[Bug] "
|
title: "[Bug] "
|
||||||
labels: 类型:bug
|
labels: 类型:bug
|
||||||
assignees:
|
assignees: wojiushixiaobai
|
||||||
- wojiushixiaobai
|
|
||||||
- baijiangjie
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
**JumpServer 版本( v2.28 之前的版本不再支持 )**
|
**JumpServer 版本(v1.5.9以下不再支持)**
|
||||||
|
|
||||||
|
|
||||||
**浏览器版本**
|
**浏览器版本**
|
||||||
|
|||||||
4
.github/ISSUE_TEMPLATE/question.md
vendored
4
.github/ISSUE_TEMPLATE/question.md
vendored
@@ -3,9 +3,7 @@ name: 问题咨询
|
|||||||
about: 提出针对本项目安装部署、使用及其他方面的相关问题
|
about: 提出针对本项目安装部署、使用及其他方面的相关问题
|
||||||
title: "[Question] "
|
title: "[Question] "
|
||||||
labels: 类型:提问
|
labels: 类型:提问
|
||||||
assignees:
|
assignees: wojiushixiaobai
|
||||||
- wojiushixiaobai
|
|
||||||
- baijiangjie
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
1
.github/workflows/jms-build-test.yml
vendored
1
.github/workflows/jms-build-test.yml
vendored
@@ -24,7 +24,6 @@ jobs:
|
|||||||
build-args: |
|
build-args: |
|
||||||
APT_MIRROR=http://deb.debian.org
|
APT_MIRROR=http://deb.debian.org
|
||||||
PIP_MIRROR=https://pypi.org/simple
|
PIP_MIRROR=https://pypi.org/simple
|
||||||
PIP_JMS_MIRROR=https://pypi.org/simple
|
|
||||||
cache-from: type=gha
|
cache-from: type=gha
|
||||||
cache-to: type=gha,mode=max
|
cache-to: type=gha,mode=max
|
||||||
|
|
||||||
|
|||||||
2
.github/workflows/sync-gitee.yml
vendored
2
.github/workflows/sync-gitee.yml
vendored
@@ -20,4 +20,4 @@ jobs:
|
|||||||
SSH_PRIVATE_KEY: ${{ secrets.GITEE_SSH_PRIVATE_KEY }}
|
SSH_PRIVATE_KEY: ${{ secrets.GITEE_SSH_PRIVATE_KEY }}
|
||||||
with:
|
with:
|
||||||
source-repo: 'git@github.com:jumpserver/jumpserver.git'
|
source-repo: 'git@github.com:jumpserver/jumpserver.git'
|
||||||
destination-repo: 'git@gitee.com:fit2cloud-feizhiyun/JumpServer.git'
|
destination-repo: 'git@gitee.com:jumpserver/jumpserver.git'
|
||||||
|
|||||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -43,4 +43,3 @@ releashe
|
|||||||
/apps/script.py
|
/apps/script.py
|
||||||
data/*
|
data/*
|
||||||
test.py
|
test.py
|
||||||
.history/
|
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ RUN --mount=type=cache,target=/var/cache/apt,sharing=locked,id=core \
|
|||||||
&& apt-get -y install --no-install-recommends ${DEPENDENCIES} \
|
&& apt-get -y install --no-install-recommends ${DEPENDENCIES} \
|
||||||
&& apt-get -y install --no-install-recommends ${TOOLS} \
|
&& apt-get -y install --no-install-recommends ${TOOLS} \
|
||||||
&& mkdir -p /root/.ssh/ \
|
&& mkdir -p /root/.ssh/ \
|
||||||
&& echo "Host *\n\tStrictHostKeyChecking no\n\tUserKnownHostsFile /dev/null\n\tCiphers +aes128-cbc\n\tKexAlgorithms +diffie-hellman-group1-sha1\n\tHostKeyAlgorithms +ssh-rsa" > /root/.ssh/config \
|
&& echo "Host *\n\tStrictHostKeyChecking no\n\tUserKnownHostsFile /dev/null" > /root/.ssh/config \
|
||||||
&& echo "set mouse-=a" > ~/.vimrc \
|
&& echo "set mouse-=a" > ~/.vimrc \
|
||||||
&& echo "no" | dpkg-reconfigure dash \
|
&& echo "no" | dpkg-reconfigure dash \
|
||||||
&& echo "zh_CN.UTF-8" | dpkg-reconfigure locales \
|
&& echo "zh_CN.UTF-8" | dpkg-reconfigure locales \
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ RUN --mount=type=cache,target=/var/cache/apt,sharing=locked,id=core \
|
|||||||
&& apt-get -y install --no-install-recommends ${DEPENDENCIES} \
|
&& apt-get -y install --no-install-recommends ${DEPENDENCIES} \
|
||||||
&& apt-get -y install --no-install-recommends ${TOOLS} \
|
&& apt-get -y install --no-install-recommends ${TOOLS} \
|
||||||
&& mkdir -p /root/.ssh/ \
|
&& mkdir -p /root/.ssh/ \
|
||||||
&& echo "Host *\n\tStrictHostKeyChecking no\n\tUserKnownHostsFile /dev/null\n\tCiphers +aes128-cbc\n\tKexAlgorithms +diffie-hellman-group1-sha1\n\tHostKeyAlgorithms +ssh-rsa" > /root/.ssh/config \
|
&& echo "Host *\n\tStrictHostKeyChecking no\n\tUserKnownHostsFile /dev/null" > /root/.ssh/config \
|
||||||
&& echo "set mouse-=a" > ~/.vimrc \
|
&& echo "set mouse-=a" > ~/.vimrc \
|
||||||
&& echo "no" | dpkg-reconfigure dash \
|
&& echo "no" | dpkg-reconfigure dash \
|
||||||
&& echo "zh_CN.UTF-8" | dpkg-reconfigure locales \
|
&& echo "zh_CN.UTF-8" | dpkg-reconfigure locales \
|
||||||
@@ -76,8 +76,8 @@ RUN --mount=type=cache,target=/root/.cache/pip \
|
|||||||
&& pip install --upgrade setuptools wheel \
|
&& pip install --upgrade setuptools wheel \
|
||||||
&& pip install https://download.jumpserver.org/pypi/simple/cryptography/cryptography-38.0.4-cp39-cp39-linux_loongarch64.whl \
|
&& pip install https://download.jumpserver.org/pypi/simple/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/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 $(grep 'PyNaCl' requirements/requirements.txt) \
|
||||||
&& pip install https://download.jumpserver.org/pypi/simple/grpcio/grpcio-1.54.0-cp39-cp39-linux_loongarch64.whl \
|
&& GRPC_PYTHON_BUILD_SYSTEM_OPENSSL=true pip install grpcio \
|
||||||
&& pip install $(grep -E 'jms|jumpserver' requirements/requirements.txt) -i ${PIP_JMS_MIRROR} \
|
&& pip install $(grep -E 'jms|jumpserver' requirements/requirements.txt) -i ${PIP_JMS_MIRROR} \
|
||||||
&& pip install -r requirements/requirements.txt
|
&& pip install -r requirements/requirements.txt
|
||||||
|
|
||||||
|
|||||||
17
README.md
17
README.md
@@ -10,17 +10,6 @@
|
|||||||
<a href="https://github.com/jumpserver/jumpserver"><img src="https://img.shields.io/github/stars/jumpserver/jumpserver?color=%231890FF&style=flat-square" alt="Stars"></a>
|
<a href="https://github.com/jumpserver/jumpserver"><img src="https://img.shields.io/github/stars/jumpserver/jumpserver?color=%231890FF&style=flat-square" alt="Stars"></a>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
|
||||||
<p align="center">
|
|
||||||
JumpServer <a href="https://github.com/jumpserver/jumpserver/releases/tag/v3.0.0">v3.0</a> 正式发布。
|
|
||||||
<br>
|
|
||||||
9 年时间,倾情投入,用心做好一款开源堡垒机。
|
|
||||||
</p>
|
|
||||||
|
|
||||||
| :warning: 注意 :warning: |
|
|
||||||
|:-------------------------------------------------------------------------------------------------------------------------:|
|
|
||||||
| 3.0 架构上和 2.0 变化较大,建议全新安装一套环境来体验。如需升级,请务必升级前进行备份,并[查阅文档](https://kb.fit2cloud.com/?p=06638d69-f109-4333-b5bf-65b17b297ed9) |
|
|
||||||
|
|
||||||
--------------------------
|
--------------------------
|
||||||
|
|
||||||
JumpServer 是广受欢迎的开源堡垒机,是符合 4A 规范的专业运维安全审计系统。
|
JumpServer 是广受欢迎的开源堡垒机,是符合 4A 规范的专业运维安全审计系统。
|
||||||
@@ -38,7 +27,7 @@ JumpServer 是广受欢迎的开源堡垒机,是符合 4A 规范的专业运
|
|||||||
|
|
||||||
## UI 展示
|
## UI 展示
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
## 在线体验
|
## 在线体验
|
||||||
|
|
||||||
@@ -52,9 +41,9 @@ JumpServer 是广受欢迎的开源堡垒机,是符合 4A 规范的专业运
|
|||||||
|
|
||||||
## 快速开始
|
## 快速开始
|
||||||
|
|
||||||
- [快速入门](https://docs.jumpserver.org/zh/v3/quick_start/)
|
- [极速安装](https://docs.jumpserver.org/zh/master/install/setup_by_fast/)
|
||||||
|
- [手动安装](https://github.com/jumpserver/installer)
|
||||||
- [产品文档](https://docs.jumpserver.org)
|
- [产品文档](https://docs.jumpserver.org)
|
||||||
- [在线学习](https://edu.fit2cloud.com/page/2635362)
|
|
||||||
- [知识库](https://kb.fit2cloud.com/categories/jumpserver)
|
- [知识库](https://kb.fit2cloud.com/categories/jumpserver)
|
||||||
|
|
||||||
## 案例研究
|
## 案例研究
|
||||||
|
|||||||
@@ -1,22 +1,20 @@
|
|||||||
from django.shortcuts import get_object_or_404
|
from django.shortcuts import get_object_or_404
|
||||||
from rest_framework.decorators import action
|
from rest_framework.decorators import action
|
||||||
from rest_framework.generics import ListAPIView, CreateAPIView
|
from rest_framework.generics import ListAPIView
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
from rest_framework.status import HTTP_200_OK
|
|
||||||
|
|
||||||
from accounts import serializers
|
from accounts import serializers
|
||||||
from accounts.filters import AccountFilterSet
|
from accounts.filters import AccountFilterSet
|
||||||
from accounts.models import Account
|
from accounts.models import Account
|
||||||
from assets.models import Asset, Node
|
from assets.models import Asset, Node
|
||||||
from common.api import ExtraFilterFieldsMixin
|
from common.permissions import UserConfirmation, ConfirmType
|
||||||
from common.permissions import UserConfirmation, ConfirmType, IsValidUser
|
|
||||||
from common.views.mixins import RecordViewLogMixin
|
from common.views.mixins import RecordViewLogMixin
|
||||||
from orgs.mixins.api import OrgBulkModelViewSet
|
from orgs.mixins.api import OrgBulkModelViewSet
|
||||||
from rbac.permissions import RBACPermission
|
from rbac.permissions import RBACPermission
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
'AccountViewSet', 'AccountSecretsViewSet',
|
'AccountViewSet', 'AccountSecretsViewSet',
|
||||||
'AccountHistoriesSecretAPI', 'AssetAccountBulkCreateApi',
|
'AccountHistoriesSecretAPI'
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
@@ -30,7 +28,7 @@ class AccountViewSet(OrgBulkModelViewSet):
|
|||||||
rbac_perms = {
|
rbac_perms = {
|
||||||
'partial_update': ['accounts.change_account'],
|
'partial_update': ['accounts.change_account'],
|
||||||
'su_from_accounts': 'accounts.view_account',
|
'su_from_accounts': 'accounts.view_account',
|
||||||
'clear_secret': 'accounts.change_account',
|
'username_suggestions': 'accounts.view_account',
|
||||||
}
|
}
|
||||||
|
|
||||||
@action(methods=['get'], detail=False, url_path='su-from-accounts')
|
@action(methods=['get'], detail=False, url_path='su-from-accounts')
|
||||||
@@ -45,15 +43,12 @@ class AccountViewSet(OrgBulkModelViewSet):
|
|||||||
asset = get_object_or_404(Asset, pk=asset_id)
|
asset = get_object_or_404(Asset, pk=asset_id)
|
||||||
accounts = asset.accounts.all()
|
accounts = asset.accounts.all()
|
||||||
else:
|
else:
|
||||||
accounts = Account.objects.none()
|
accounts = []
|
||||||
accounts = self.filter_queryset(accounts)
|
accounts = self.filter_queryset(accounts)
|
||||||
serializer = serializers.AccountSerializer(accounts, many=True)
|
serializer = serializers.AccountSerializer(accounts, many=True)
|
||||||
return Response(data=serializer.data)
|
return Response(data=serializer.data)
|
||||||
|
|
||||||
@action(
|
@action(methods=['get'], detail=False, url_path='username-suggestions')
|
||||||
methods=['get'], detail=False, url_path='username-suggestions',
|
|
||||||
permission_classes=[IsValidUser]
|
|
||||||
)
|
|
||||||
def username_suggestions(self, request, *args, **kwargs):
|
def username_suggestions(self, request, *args, **kwargs):
|
||||||
asset_ids = request.query_params.get('assets')
|
asset_ids = request.query_params.get('assets')
|
||||||
node_keys = request.query_params.get('keys')
|
node_keys = request.query_params.get('keys')
|
||||||
@@ -76,12 +71,6 @@ class AccountViewSet(OrgBulkModelViewSet):
|
|||||||
usernames = common + others
|
usernames = common + others
|
||||||
return Response(data=usernames)
|
return Response(data=usernames)
|
||||||
|
|
||||||
@action(methods=['patch'], detail=False, url_path='clear-secret')
|
|
||||||
def clear_secret(self, request, *args, **kwargs):
|
|
||||||
account_ids = request.data.get('account_ids', [])
|
|
||||||
self.model.objects.filter(id__in=account_ids).update(secret=None)
|
|
||||||
return Response(status=HTTP_200_OK)
|
|
||||||
|
|
||||||
|
|
||||||
class AccountSecretsViewSet(RecordViewLogMixin, AccountViewSet):
|
class AccountSecretsViewSet(RecordViewLogMixin, AccountViewSet):
|
||||||
"""
|
"""
|
||||||
@@ -98,21 +87,7 @@ class AccountSecretsViewSet(RecordViewLogMixin, AccountViewSet):
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class AssetAccountBulkCreateApi(CreateAPIView):
|
class AccountHistoriesSecretAPI(RecordViewLogMixin, ListAPIView):
|
||||||
serializer_class = serializers.AssetAccountBulkSerializer
|
|
||||||
rbac_perms = {
|
|
||||||
'POST': 'accounts.add_account',
|
|
||||||
}
|
|
||||||
|
|
||||||
def create(self, request, *args, **kwargs):
|
|
||||||
serializer = self.get_serializer(data=request.data)
|
|
||||||
serializer.is_valid(raise_exception=True)
|
|
||||||
data = serializer.create(serializer.validated_data)
|
|
||||||
serializer = serializers.AssetAccountBulkSerializerResultSerializer(data, many=True)
|
|
||||||
return Response(data=serializer.data, status=HTTP_200_OK)
|
|
||||||
|
|
||||||
|
|
||||||
class AccountHistoriesSecretAPI(ExtraFilterFieldsMixin, RecordViewLogMixin, ListAPIView):
|
|
||||||
model = Account.history.model
|
model = Account.history.model
|
||||||
serializer_class = serializers.AccountHistorySerializer
|
serializer_class = serializers.AccountHistorySerializer
|
||||||
http_method_names = ['get', 'options']
|
http_method_names = ['get', 'options']
|
||||||
@@ -124,10 +99,6 @@ class AccountHistoriesSecretAPI(ExtraFilterFieldsMixin, RecordViewLogMixin, List
|
|||||||
def get_object(self):
|
def get_object(self):
|
||||||
return get_object_or_404(Account, pk=self.kwargs.get('pk'))
|
return get_object_or_404(Account, pk=self.kwargs.get('pk'))
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def filter_spm_queryset(resource_ids, queryset):
|
|
||||||
return queryset.filter(history_id__in=resource_ids)
|
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
account = self.get_object()
|
account = self.get_object()
|
||||||
histories = account.history.all()
|
histories = account.history.all()
|
||||||
|
|||||||
@@ -24,16 +24,15 @@ class AccountsTaskCreateAPI(CreateAPIView):
|
|||||||
def perform_create(self, serializer):
|
def perform_create(self, serializer):
|
||||||
data = serializer.validated_data
|
data = serializer.validated_data
|
||||||
accounts = data.get('accounts', [])
|
accounts = data.get('accounts', [])
|
||||||
params = data.get('params')
|
|
||||||
account_ids = [str(a.id) for a in accounts]
|
account_ids = [str(a.id) for a in accounts]
|
||||||
|
|
||||||
if data['action'] == 'push':
|
if data['action'] == 'push':
|
||||||
task = push_accounts_to_assets_task.delay(account_ids, params)
|
task = push_accounts_to_assets_task.delay(account_ids)
|
||||||
else:
|
else:
|
||||||
account = accounts[0]
|
account = accounts[0]
|
||||||
asset = account.asset
|
asset = account.asset
|
||||||
if not asset.auto_config['ansible_enabled'] or \
|
if not asset.auto_info['ansible_enabled'] or \
|
||||||
not asset.auto_config['ping_enabled']:
|
not asset.auto_info['ping_enabled']:
|
||||||
raise NotSupportedTemporarilyError()
|
raise NotSupportedTemporarilyError()
|
||||||
task = verify_accounts_connectivity_task.delay(account_ids)
|
task = verify_accounts_connectivity_task.delay(account_ids)
|
||||||
|
|
||||||
|
|||||||
@@ -1,41 +1,15 @@
|
|||||||
from django_filters import rest_framework as drf_filters
|
from rbac.permissions import RBACPermission
|
||||||
|
|
||||||
from accounts import serializers
|
|
||||||
from accounts.models import AccountTemplate
|
|
||||||
from assets.const import Protocol
|
|
||||||
from common.drf.filters import BaseFilterSet
|
|
||||||
from common.permissions import UserConfirmation, ConfirmType
|
from common.permissions import UserConfirmation, ConfirmType
|
||||||
|
|
||||||
from common.views.mixins import RecordViewLogMixin
|
from common.views.mixins import RecordViewLogMixin
|
||||||
from orgs.mixins.api import OrgBulkModelViewSet
|
from orgs.mixins.api import OrgBulkModelViewSet
|
||||||
from rbac.permissions import RBACPermission
|
from accounts import serializers
|
||||||
|
from accounts.models import AccountTemplate
|
||||||
|
|
||||||
class AccountTemplateFilterSet(BaseFilterSet):
|
|
||||||
protocols = drf_filters.CharFilter(method='filter_protocols')
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
model = AccountTemplate
|
|
||||||
fields = ('username', 'name')
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def filter_protocols(queryset, name, value):
|
|
||||||
secret_types = set()
|
|
||||||
protocols = value.split(',')
|
|
||||||
protocol_secret_type_map = Protocol.settings()
|
|
||||||
for p in protocols:
|
|
||||||
if p not in protocol_secret_type_map:
|
|
||||||
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
|
|
||||||
|
|
||||||
|
|
||||||
class AccountTemplateViewSet(OrgBulkModelViewSet):
|
class AccountTemplateViewSet(OrgBulkModelViewSet):
|
||||||
model = AccountTemplate
|
model = AccountTemplate
|
||||||
filterset_class = AccountTemplateFilterSet
|
filterset_fields = ("username", 'name')
|
||||||
search_fields = ('username', 'name')
|
search_fields = ('username', 'name')
|
||||||
serializer_classes = {
|
serializer_classes = {
|
||||||
'default': serializers.AccountTemplateSerializer
|
'default': serializers.AccountTemplateSerializer
|
||||||
|
|||||||
@@ -1,11 +1,13 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
#
|
#
|
||||||
|
from django.utils.translation import ugettext_lazy as _
|
||||||
from rest_framework import status
|
from rest_framework import status
|
||||||
from rest_framework.decorators import action
|
from rest_framework.decorators import action
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
|
|
||||||
from accounts import serializers
|
from accounts import serializers
|
||||||
from accounts.const import AutomationTypes
|
from accounts.const import AutomationTypes
|
||||||
|
from accounts.const import Source
|
||||||
from accounts.filters import GatheredAccountFilterSet
|
from accounts.filters import GatheredAccountFilterSet
|
||||||
from accounts.models import GatherAccountsAutomation
|
from accounts.models import GatherAccountsAutomation
|
||||||
from accounts.models import GatheredAccount
|
from accounts.models import GatheredAccount
|
||||||
@@ -48,12 +50,22 @@ class GatheredAccountViewSet(OrgBulkModelViewSet):
|
|||||||
'default': serializers.GatheredAccountSerializer,
|
'default': serializers.GatheredAccountSerializer,
|
||||||
}
|
}
|
||||||
rbac_perms = {
|
rbac_perms = {
|
||||||
'sync_accounts': 'assets.add_gatheredaccount',
|
'sync_account': 'assets.add_gatheredaccount',
|
||||||
}
|
}
|
||||||
|
|
||||||
@action(methods=['post'], detail=False, url_path='sync-accounts')
|
@action(methods=['post'], detail=True, url_path='sync')
|
||||||
def sync_accounts(self, request, *args, **kwargs):
|
def sync_account(self, request, *args, **kwargs):
|
||||||
gathered_account_ids = request.data.get('gathered_account_ids')
|
gathered_account = super().get_object()
|
||||||
gathered_accounts = self.model.objects.filter(id__in=gathered_account_ids)
|
asset = gathered_account.asset
|
||||||
self.model.sync_accounts(gathered_accounts)
|
username = gathered_account.username
|
||||||
|
accounts = asset.accounts.filter(username=username)
|
||||||
|
|
||||||
|
if accounts.exists():
|
||||||
|
accounts.update(source=Source.COLLECTED)
|
||||||
|
else:
|
||||||
|
asset.accounts.model.objects.create(
|
||||||
|
asset=asset, username=username,
|
||||||
|
name=f'{username}-{_("Collected")}',
|
||||||
|
source=Source.COLLECTED
|
||||||
|
)
|
||||||
return Response(status=status.HTTP_201_CREATED)
|
return Response(status=status.HTTP_201_CREATED)
|
||||||
|
|||||||
@@ -1,40 +0,0 @@
|
|||||||
- hosts: custom
|
|
||||||
gather_facts: no
|
|
||||||
vars:
|
|
||||||
ansible_connection: local
|
|
||||||
|
|
||||||
tasks:
|
|
||||||
- name: Test privileged account
|
|
||||||
ssh_ping:
|
|
||||||
login_host: "{{ jms_asset.address }}"
|
|
||||||
login_port: "{{ jms_asset.port }}"
|
|
||||||
login_user: "{{ jms_account.username }}"
|
|
||||||
login_password: "{{ jms_account.secret }}"
|
|
||||||
login_secret_type: "{{ jms_account.secret_type }}"
|
|
||||||
login_private_key_path: "{{ jms_account.private_key_path }}"
|
|
||||||
register: ping_info
|
|
||||||
|
|
||||||
- name: Change asset password
|
|
||||||
custom_command:
|
|
||||||
login_user: "{{ jms_account.username }}"
|
|
||||||
login_password: "{{ jms_account.secret }}"
|
|
||||||
login_host: "{{ jms_asset.address }}"
|
|
||||||
login_port: "{{ jms_asset.port }}"
|
|
||||||
login_secret_type: "{{ jms_account.secret_type }}"
|
|
||||||
login_private_key_path: "{{ jms_account.private_key_path }}"
|
|
||||||
name: "{{ account.username }}"
|
|
||||||
password: "{{ account.secret }}"
|
|
||||||
commands: "{{ params.commands }}"
|
|
||||||
first_conn_delay_time: "{{ first_conn_delay_time | default(0.5) }}"
|
|
||||||
when: ping_info is succeeded
|
|
||||||
register: change_info
|
|
||||||
|
|
||||||
- name: Verify password
|
|
||||||
ssh_ping:
|
|
||||||
login_user: "{{ account.username }}"
|
|
||||||
login_password: "{{ account.secret }}"
|
|
||||||
login_host: "{{ jms_asset.address }}"
|
|
||||||
login_port: "{{ jms_asset.port }}"
|
|
||||||
when:
|
|
||||||
- ping_info is succeeded
|
|
||||||
- change_info is succeeded
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
id: change_secret_by_ssh
|
|
||||||
name: "{{ 'SSH account change secret' | trans }}"
|
|
||||||
category:
|
|
||||||
- device
|
|
||||||
- host
|
|
||||||
type:
|
|
||||||
- all
|
|
||||||
method: change_secret
|
|
||||||
params:
|
|
||||||
- name: commands
|
|
||||||
type: list
|
|
||||||
label: '自定义命令'
|
|
||||||
default: [ '' ]
|
|
||||||
help_text: '自定义命令中如需包含账号的 账号、密码、SSH 连接的用户密码 字段,<br />请使用 {username}、{password}、{login_password}格式,执行任务时会进行替换 。<br />比如针对 Cisco 主机进行改密,一般需要配置五条命令:<br />1. enable<br />2. {login_password}<br />3. configure terminal<br />4. username {username} privilege 0 password {password} <br />5. end'
|
|
||||||
|
|
||||||
i18n:
|
|
||||||
SSH account change secret:
|
|
||||||
zh: SSH 账号改密
|
|
||||||
ja: SSH アカウントのパスワード変更
|
|
||||||
@@ -1,11 +1,6 @@
|
|||||||
id: change_secret_mongodb
|
id: change_secret_mongodb
|
||||||
name: "{{ 'MongoDB account change secret' | trans }}"
|
name: Change secret for MongoDB
|
||||||
category: database
|
category: database
|
||||||
type:
|
type:
|
||||||
- mongodb
|
- mongodb
|
||||||
method: change_secret
|
method: change_secret
|
||||||
|
|
||||||
i18n:
|
|
||||||
MongoDB account change secret:
|
|
||||||
zh: MongoDB 账号改密
|
|
||||||
ja: MongoDB アカウントのパスワード変更
|
|
||||||
|
|||||||
@@ -1,12 +1,7 @@
|
|||||||
id: change_secret_mysql
|
id: change_secret_mysql
|
||||||
name: "{{ 'MySQL account change secret' | trans }}"
|
name: Change secret for MySQL
|
||||||
category: database
|
category: database
|
||||||
type:
|
type:
|
||||||
- mysql
|
- mysql
|
||||||
- mariadb
|
- mariadb
|
||||||
method: change_secret
|
method: change_secret
|
||||||
|
|
||||||
i18n:
|
|
||||||
MySQL account change secret:
|
|
||||||
zh: MySQL 账号改密
|
|
||||||
ja: MySQL アカウントのパスワード変更
|
|
||||||
|
|||||||
@@ -1,11 +1,6 @@
|
|||||||
id: change_secret_oracle
|
id: change_secret_oracle
|
||||||
name: "{{ 'Oracle account change secret' | trans }}"
|
name: Change secret for Oracle
|
||||||
category: database
|
category: database
|
||||||
type:
|
type:
|
||||||
- oracle
|
- oracle
|
||||||
method: change_secret
|
method: change_secret
|
||||||
|
|
||||||
i18n:
|
|
||||||
Oracle account change secret:
|
|
||||||
zh: Oracle 账号改密
|
|
||||||
ja: Oracle アカウントのパスワード変更
|
|
||||||
|
|||||||
@@ -1,11 +1,6 @@
|
|||||||
id: change_secret_postgresql
|
id: change_secret_postgresql
|
||||||
name: "{{ 'PostgreSQL account change secret' | trans }}"
|
name: Change secret for PostgreSQL
|
||||||
category: database
|
category: database
|
||||||
type:
|
type:
|
||||||
- postgresql
|
- postgresql
|
||||||
method: change_secret
|
method: change_secret
|
||||||
|
|
||||||
i18n:
|
|
||||||
PostgreSQL account change secret:
|
|
||||||
zh: PostgreSQL 账号改密
|
|
||||||
ja: PostgreSQL アカウントのパスワード変更
|
|
||||||
|
|||||||
@@ -1,11 +1,6 @@
|
|||||||
id: change_secret_sqlserver
|
id: change_secret_sqlserver
|
||||||
name: "{{ 'SQLServer account change secret' | trans }}"
|
name: Change secret for SQLServer
|
||||||
category: database
|
category: database
|
||||||
type:
|
type:
|
||||||
- sqlserver
|
- sqlserver
|
||||||
method: change_secret
|
method: change_secret
|
||||||
|
|
||||||
i18n:
|
|
||||||
SQLServer account change secret:
|
|
||||||
zh: SQLServer 账号改密
|
|
||||||
ja: SQLServer アカウントのパスワード変更
|
|
||||||
|
|||||||
@@ -9,28 +9,28 @@
|
|||||||
name: "{{ account.username }}"
|
name: "{{ account.username }}"
|
||||||
password: "{{ account.secret | password_hash('des') }}"
|
password: "{{ account.secret | password_hash('des') }}"
|
||||||
update_password: always
|
update_password: always
|
||||||
when: account.secret_type == "password"
|
when: secret_type == "password"
|
||||||
|
|
||||||
- name: create user If it already exists, no operation will be performed
|
- name: create user If it already exists, no operation will be performed
|
||||||
ansible.builtin.user:
|
ansible.builtin.user:
|
||||||
name: "{{ account.username }}"
|
name: "{{ account.username }}"
|
||||||
when: account.secret_type == "ssh_key"
|
when: secret_type == "ssh_key"
|
||||||
|
|
||||||
- name: remove jumpserver ssh key
|
- name: remove jumpserver ssh key
|
||||||
ansible.builtin.lineinfile:
|
ansible.builtin.lineinfile:
|
||||||
dest: "{{ ssh_params.dest }}"
|
dest: "{{ kwargs.dest }}"
|
||||||
regexp: "{{ ssh_params.regexp }}"
|
regexp: "{{ kwargs.regexp }}"
|
||||||
state: absent
|
state: absent
|
||||||
when:
|
when:
|
||||||
- account.secret_type == "ssh_key"
|
- secret_type == "ssh_key"
|
||||||
- ssh_params.strategy == "set_jms"
|
- kwargs.strategy == "set_jms"
|
||||||
|
|
||||||
- name: Change SSH key
|
- name: Change SSH key
|
||||||
ansible.builtin.authorized_key:
|
ansible.builtin.authorized_key:
|
||||||
user: "{{ account.username }}"
|
user: "{{ account.username }}"
|
||||||
key: "{{ account.secret }}"
|
key: "{{ account.secret }}"
|
||||||
exclusive: "{{ ssh_params.exclusive }}"
|
exclusive: "{{ kwargs.exclusive }}"
|
||||||
when: account.secret_type == "ssh_key"
|
when: secret_type == "ssh_key"
|
||||||
|
|
||||||
- name: Refresh connection
|
- name: Refresh connection
|
||||||
ansible.builtin.meta: reset_connection
|
ansible.builtin.meta: reset_connection
|
||||||
@@ -42,7 +42,7 @@
|
|||||||
ansible_user: "{{ account.username }}"
|
ansible_user: "{{ account.username }}"
|
||||||
ansible_password: "{{ account.secret }}"
|
ansible_password: "{{ account.secret }}"
|
||||||
ansible_become: no
|
ansible_become: no
|
||||||
when: account.secret_type == "password"
|
when: secret_type == "password"
|
||||||
|
|
||||||
- name: Verify SSH key
|
- name: Verify SSH key
|
||||||
ansible.builtin.ping:
|
ansible.builtin.ping:
|
||||||
@@ -51,4 +51,4 @@
|
|||||||
ansible_user: "{{ account.username }}"
|
ansible_user: "{{ account.username }}"
|
||||||
ansible_ssh_private_key_file: "{{ account.private_key_path }}"
|
ansible_ssh_private_key_file: "{{ account.private_key_path }}"
|
||||||
ansible_become: no
|
ansible_become: no
|
||||||
when: account.secret_type == "ssh_key"
|
when: secret_type == "ssh_key"
|
||||||
|
|||||||
@@ -1,11 +1,6 @@
|
|||||||
id: change_secret_aix
|
id: change_secret_aix
|
||||||
name: "{{ 'AIX account change secret' | trans }}"
|
name: Change secret for aix
|
||||||
category: host
|
category: host
|
||||||
type:
|
type:
|
||||||
- AIX
|
- AIX
|
||||||
method: change_secret
|
method: change_secret
|
||||||
|
|
||||||
i18n:
|
|
||||||
AIX account change secret:
|
|
||||||
zh: AIX 账号改密
|
|
||||||
ja: AIX アカウントのパスワード変更
|
|
||||||
|
|||||||
@@ -9,28 +9,28 @@
|
|||||||
name: "{{ account.username }}"
|
name: "{{ account.username }}"
|
||||||
password: "{{ account.secret | password_hash('sha512') }}"
|
password: "{{ account.secret | password_hash('sha512') }}"
|
||||||
update_password: always
|
update_password: always
|
||||||
when: account.secret_type == "password"
|
when: secret_type == "password"
|
||||||
|
|
||||||
- name: create user If it already exists, no operation will be performed
|
- name: create user If it already exists, no operation will be performed
|
||||||
ansible.builtin.user:
|
ansible.builtin.user:
|
||||||
name: "{{ account.username }}"
|
name: "{{ account.username }}"
|
||||||
when: account.secret_type == "ssh_key"
|
when: secret_type == "ssh_key"
|
||||||
|
|
||||||
- name: remove jumpserver ssh key
|
- name: remove jumpserver ssh key
|
||||||
ansible.builtin.lineinfile:
|
ansible.builtin.lineinfile:
|
||||||
dest: "{{ ssh_params.dest }}"
|
dest: "{{ kwargs.dest }}"
|
||||||
regexp: "{{ ssh_params.regexp }}"
|
regexp: "{{ kwargs.regexp }}"
|
||||||
state: absent
|
state: absent
|
||||||
when:
|
when:
|
||||||
- account.secret_type == "ssh_key"
|
- secret_type == "ssh_key"
|
||||||
- ssh_params.strategy == "set_jms"
|
- kwargs.strategy == "set_jms"
|
||||||
|
|
||||||
- name: Change SSH key
|
- name: Change SSH key
|
||||||
ansible.builtin.authorized_key:
|
ansible.builtin.authorized_key:
|
||||||
user: "{{ account.username }}"
|
user: "{{ account.username }}"
|
||||||
key: "{{ account.secret }}"
|
key: "{{ account.secret }}"
|
||||||
exclusive: "{{ ssh_params.exclusive }}"
|
exclusive: "{{ kwargs.exclusive }}"
|
||||||
when: account.secret_type == "ssh_key"
|
when: secret_type == "ssh_key"
|
||||||
|
|
||||||
- name: Refresh connection
|
- name: Refresh connection
|
||||||
ansible.builtin.meta: reset_connection
|
ansible.builtin.meta: reset_connection
|
||||||
@@ -42,7 +42,7 @@
|
|||||||
ansible_user: "{{ account.username }}"
|
ansible_user: "{{ account.username }}"
|
||||||
ansible_password: "{{ account.secret }}"
|
ansible_password: "{{ account.secret }}"
|
||||||
ansible_become: no
|
ansible_become: no
|
||||||
when: account.secret_type == "password"
|
when: secret_type == "password"
|
||||||
|
|
||||||
- name: Verify SSH key
|
- name: Verify SSH key
|
||||||
ansible.builtin.ping:
|
ansible.builtin.ping:
|
||||||
@@ -51,4 +51,4 @@
|
|||||||
ansible_user: "{{ account.username }}"
|
ansible_user: "{{ account.username }}"
|
||||||
ansible_ssh_private_key_file: "{{ account.private_key_path }}"
|
ansible_ssh_private_key_file: "{{ account.private_key_path }}"
|
||||||
ansible_become: no
|
ansible_become: no
|
||||||
when: account.secret_type == "ssh_key"
|
when: secret_type == "ssh_key"
|
||||||
|
|||||||
@@ -1,12 +1,7 @@
|
|||||||
id: change_secret_posix
|
id: change_secret_posix
|
||||||
name: "{{ 'Posix account change secret' | trans }}"
|
name: Change secret for posix
|
||||||
category: host
|
category: host
|
||||||
type:
|
type:
|
||||||
- unix
|
- unix
|
||||||
- linux
|
- linux
|
||||||
method: change_secret
|
method: change_secret
|
||||||
|
|
||||||
i18n:
|
|
||||||
Posix account change secret:
|
|
||||||
zh: Posix 账号改密
|
|
||||||
ja: Posix アカウントのパスワード変更
|
|
||||||
|
|||||||
@@ -1,12 +1,7 @@
|
|||||||
id: change_secret_local_windows
|
id: change_secret_local_windows
|
||||||
name: "{{ 'Windows account change secret' | trans }}"
|
name: Change secret local account for Windows
|
||||||
version: 1
|
version: 1
|
||||||
method: change_secret
|
method: change_secret
|
||||||
category: host
|
category: host
|
||||||
type:
|
type:
|
||||||
- windows
|
- windows
|
||||||
|
|
||||||
i18n:
|
|
||||||
Windows account change secret:
|
|
||||||
zh: Windows 账号改密
|
|
||||||
ja: Windows アカウントのパスワード変更
|
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ from accounts.models import ChangeSecretRecord
|
|||||||
from accounts.notifications import ChangeSecretExecutionTaskMsg
|
from accounts.notifications import ChangeSecretExecutionTaskMsg
|
||||||
from accounts.serializers import ChangeSecretRecordBackUpSerializer
|
from accounts.serializers import ChangeSecretRecordBackUpSerializer
|
||||||
from assets.const import HostTypes
|
from assets.const import HostTypes
|
||||||
from common.utils import get_logger
|
from common.utils import get_logger, lazyproperty
|
||||||
from common.utils.file import encrypt_and_compress_zip_file
|
from common.utils.file import encrypt_and_compress_zip_file
|
||||||
from common.utils.timezone import local_now_display
|
from common.utils.timezone import local_now_display
|
||||||
from users.models import User
|
from users.models import User
|
||||||
@@ -28,23 +28,23 @@ class ChangeSecretManager(AccountBasePlaybookManager):
|
|||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
self.method_hosts_mapper = defaultdict(list)
|
self.method_hosts_mapper = defaultdict(list)
|
||||||
self.secret_type = self.execution.snapshot.get('secret_type')
|
self.secret_type = self.execution.snapshot['secret_type']
|
||||||
self.secret_strategy = self.execution.snapshot.get(
|
self.secret_strategy = self.execution.snapshot.get(
|
||||||
'secret_strategy', SecretStrategy.custom
|
'secret_strategy', SecretStrategy.custom
|
||||||
)
|
)
|
||||||
self.ssh_key_change_strategy = self.execution.snapshot.get(
|
self.ssh_key_change_strategy = self.execution.snapshot.get(
|
||||||
'ssh_key_change_strategy', SSHKeyStrategy.add
|
'ssh_key_change_strategy', SSHKeyStrategy.add
|
||||||
)
|
)
|
||||||
self.account_ids = self.execution.snapshot['accounts']
|
self.snapshot_account_usernames = self.execution.snapshot['accounts']
|
||||||
self.name_recorder_mapper = {} # 做个映射,方便后面处理
|
self.name_recorder_mapper = {} # 做个映射,方便后面处理
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def method_type(cls):
|
def method_type(cls):
|
||||||
return AutomationTypes.change_secret
|
return AutomationTypes.change_secret
|
||||||
|
|
||||||
def get_ssh_params(self, account, secret, secret_type):
|
def get_kwargs(self, account, secret):
|
||||||
kwargs = {}
|
kwargs = {}
|
||||||
if secret_type != SecretType.SSH_KEY:
|
if self.secret_type != SecretType.SSH_KEY:
|
||||||
return kwargs
|
return kwargs
|
||||||
kwargs['strategy'] = self.ssh_key_change_strategy
|
kwargs['strategy'] = self.ssh_key_change_strategy
|
||||||
kwargs['exclusive'] = 'yes' if kwargs['strategy'] == SSHKeyStrategy.set else 'no'
|
kwargs['exclusive'] = 'yes' if kwargs['strategy'] == SSHKeyStrategy.set else 'no'
|
||||||
@@ -54,34 +54,18 @@ class ChangeSecretManager(AccountBasePlaybookManager):
|
|||||||
kwargs['regexp'] = '.*{}$'.format(secret.split()[2].strip())
|
kwargs['regexp'] = '.*{}$'.format(secret.split()[2].strip())
|
||||||
return kwargs
|
return kwargs
|
||||||
|
|
||||||
def secret_generator(self, secret_type):
|
@lazyproperty
|
||||||
|
def secret_generator(self):
|
||||||
return SecretGenerator(
|
return SecretGenerator(
|
||||||
self.secret_strategy, secret_type,
|
self.secret_strategy, self.secret_type,
|
||||||
self.execution.snapshot.get('password_rules')
|
self.execution.snapshot.get('password_rules')
|
||||||
)
|
)
|
||||||
|
|
||||||
def get_secret(self, secret_type):
|
def get_secret(self):
|
||||||
if self.secret_strategy == SecretStrategy.custom:
|
if self.secret_strategy == SecretStrategy.custom:
|
||||||
return self.execution.snapshot['secret']
|
return self.execution.snapshot['secret']
|
||||||
else:
|
else:
|
||||||
return self.secret_generator(secret_type).get_secret()
|
return self.secret_generator.get_secret()
|
||||||
|
|
||||||
def get_accounts(self, privilege_account):
|
|
||||||
if not privilege_account:
|
|
||||||
print(f'not privilege account')
|
|
||||||
return []
|
|
||||||
|
|
||||||
asset = privilege_account.asset
|
|
||||||
accounts = asset.accounts.exclude(username=privilege_account.username)
|
|
||||||
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']
|
|
||||||
)
|
|
||||||
return accounts
|
|
||||||
|
|
||||||
def host_callback(
|
def host_callback(
|
||||||
self, host, asset=None, account=None,
|
self, host, asset=None, account=None,
|
||||||
@@ -94,10 +78,17 @@ class ChangeSecretManager(AccountBasePlaybookManager):
|
|||||||
if host.get('error'):
|
if host.get('error'):
|
||||||
return host
|
return host
|
||||||
|
|
||||||
accounts = self.get_accounts(account)
|
accounts = asset.accounts.all()
|
||||||
|
if account:
|
||||||
|
accounts = accounts.exclude(username=account.username)
|
||||||
|
|
||||||
|
if '*' not in self.snapshot_account_usernames:
|
||||||
|
accounts = accounts.filter(username__in=self.snapshot_account_usernames)
|
||||||
|
|
||||||
|
accounts = accounts.filter(secret_type=self.secret_type)
|
||||||
if not accounts:
|
if not accounts:
|
||||||
print('没有发现待改密账号: %s 用户ID: %s 类型: %s' % (
|
print('没有发现待改密账号: %s 用户名: %s 类型: %s' % (
|
||||||
asset.name, self.account_ids, self.secret_type
|
asset.name, self.snapshot_account_usernames, self.secret_type
|
||||||
))
|
))
|
||||||
return []
|
return []
|
||||||
|
|
||||||
@@ -106,17 +97,16 @@ class ChangeSecretManager(AccountBasePlaybookManager):
|
|||||||
method_hosts = [h for h in method_hosts if h != host['name']]
|
method_hosts = [h for h in method_hosts if h != host['name']]
|
||||||
inventory_hosts = []
|
inventory_hosts = []
|
||||||
records = []
|
records = []
|
||||||
|
host['secret_type'] = self.secret_type
|
||||||
|
|
||||||
if asset.type == HostTypes.WINDOWS and self.secret_type == SecretType.SSH_KEY:
|
if asset.type == HostTypes.WINDOWS and self.secret_type == SecretType.SSH_KEY:
|
||||||
print(f'Windows {asset} does not support ssh key push')
|
print(f'Windows {asset} does not support ssh key push \n')
|
||||||
return inventory_hosts
|
return inventory_hosts
|
||||||
|
|
||||||
host['ssh_params'] = {}
|
|
||||||
for account in accounts:
|
for account in accounts:
|
||||||
h = deepcopy(host)
|
h = deepcopy(host)
|
||||||
secret_type = account.secret_type
|
|
||||||
h['name'] += '(' + account.username + ')'
|
h['name'] += '(' + account.username + ')'
|
||||||
new_secret = self.get_secret(secret_type)
|
new_secret = self.get_secret()
|
||||||
|
|
||||||
recorder = ChangeSecretRecord(
|
recorder = ChangeSecretRecord(
|
||||||
asset=asset, account=account, execution=self.execution,
|
asset=asset, account=account, execution=self.execution,
|
||||||
@@ -126,15 +116,15 @@ class ChangeSecretManager(AccountBasePlaybookManager):
|
|||||||
self.name_recorder_mapper[h['name']] = recorder
|
self.name_recorder_mapper[h['name']] = recorder
|
||||||
|
|
||||||
private_key_path = None
|
private_key_path = None
|
||||||
if secret_type == SecretType.SSH_KEY:
|
if self.secret_type == SecretType.SSH_KEY:
|
||||||
private_key_path = self.generate_private_key_path(new_secret, path_dir)
|
private_key_path = self.generate_private_key_path(new_secret, path_dir)
|
||||||
new_secret = self.generate_public_key(new_secret)
|
new_secret = self.generate_public_key(new_secret)
|
||||||
|
|
||||||
h['ssh_params'].update(self.get_ssh_params(account, new_secret, secret_type))
|
h['kwargs'] = self.get_kwargs(account, new_secret)
|
||||||
h['account'] = {
|
h['account'] = {
|
||||||
'name': account.name,
|
'name': account.name,
|
||||||
'username': account.username,
|
'username': account.username,
|
||||||
'secret_type': secret_type,
|
'secret_type': account.secret_type,
|
||||||
'secret': new_secret,
|
'secret': new_secret,
|
||||||
'private_key_path': private_key_path
|
'private_key_path': private_key_path
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,6 @@
|
|||||||
id: gather_accounts_mongodb
|
id: gather_accounts_mongodb
|
||||||
name: "{{ 'MongoDB account gather' | trans }}"
|
name: Gather account from MongoDB
|
||||||
category: database
|
category: database
|
||||||
type:
|
type:
|
||||||
- mongodb
|
- mongodb
|
||||||
method: gather_accounts
|
method: gather_accounts
|
||||||
|
|
||||||
i18n:
|
|
||||||
MongoDB account gather:
|
|
||||||
zh: MongoDB 账号收集
|
|
||||||
ja: MongoDB アカウントの収集
|
|
||||||
|
|||||||
@@ -1,12 +1,7 @@
|
|||||||
id: gather_accounts_mysql
|
id: gather_accounts_mysql
|
||||||
name: "{{ 'MySQL account gather' | trans }}"
|
name: Gather account from MySQL
|
||||||
category: database
|
category: database
|
||||||
type:
|
type:
|
||||||
- mysql
|
- mysql
|
||||||
- mariadb
|
- mariadb
|
||||||
method: gather_accounts
|
method: gather_accounts
|
||||||
|
|
||||||
i18n:
|
|
||||||
MySQL account gather:
|
|
||||||
zh: MySQL 账号收集
|
|
||||||
ja: MySQL アカウントの収集
|
|
||||||
|
|||||||
@@ -1,11 +1,6 @@
|
|||||||
id: gather_accounts_oracle
|
id: gather_accounts_oracle
|
||||||
name: "{{ 'Oracle account gather' | trans }}"
|
name: Gather account from Oracle
|
||||||
category: database
|
category: database
|
||||||
type:
|
type:
|
||||||
- oracle
|
- oracle
|
||||||
method: gather_accounts
|
method: gather_accounts
|
||||||
|
|
||||||
i18n:
|
|
||||||
Oracle account gather:
|
|
||||||
zh: Oracle 账号收集
|
|
||||||
ja: Oracle アカウントの収集
|
|
||||||
|
|||||||
@@ -1,11 +1,6 @@
|
|||||||
id: gather_accounts_postgresql
|
id: gather_accounts_postgresql
|
||||||
name: "{{ 'PostgreSQL account gather' | trans }}"
|
name: Gather account for PostgreSQL
|
||||||
category: database
|
category: database
|
||||||
type:
|
type:
|
||||||
- postgresql
|
- postgresql
|
||||||
method: gather_accounts
|
method: gather_accounts
|
||||||
|
|
||||||
i18n:
|
|
||||||
PostgreSQL account gather:
|
|
||||||
zh: PostgreSQL 账号收集
|
|
||||||
ja: PostgreSQL アカウントの収集
|
|
||||||
|
|||||||
@@ -60,6 +60,4 @@ class GatherAccountsFilter:
|
|||||||
if not run_method_name:
|
if not run_method_name:
|
||||||
return info
|
return info
|
||||||
|
|
||||||
if hasattr(self, f'{run_method_name}_filter'):
|
|
||||||
return getattr(self, f'{run_method_name}_filter')(info)
|
return getattr(self, f'{run_method_name}_filter')(info)
|
||||||
return info
|
|
||||||
|
|||||||
@@ -1,12 +1,7 @@
|
|||||||
id: gather_accounts_posix
|
id: gather_accounts_posix
|
||||||
name: "{{ 'Posix account gather' | trans }}"
|
name: Gather posix account
|
||||||
category: host
|
category: host
|
||||||
type:
|
type:
|
||||||
- linux
|
- linux
|
||||||
- unix
|
- unix
|
||||||
method: gather_accounts
|
method: gather_accounts
|
||||||
|
|
||||||
i18n:
|
|
||||||
Posix account gather:
|
|
||||||
zh: Posix 账号收集
|
|
||||||
ja: Posix アカウントの収集
|
|
||||||
|
|||||||
@@ -1,12 +1,7 @@
|
|||||||
id: gather_accounts_windows
|
id: gather_accounts_windows
|
||||||
name: "{{ 'Windows account gather' | trans }}"
|
name: Gather account windows
|
||||||
version: 1
|
version: 1
|
||||||
method: gather_accounts
|
method: gather_accounts
|
||||||
category: host
|
category: host
|
||||||
type:
|
type:
|
||||||
- windows
|
- windows
|
||||||
|
|
||||||
i18n:
|
|
||||||
Windows account gather:
|
|
||||||
zh: Windows 账号收集
|
|
||||||
ja: Windows アカウントの収集
|
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ class GatherAccountsManager(AccountBasePlaybookManager):
|
|||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
self.host_asset_mapper = {}
|
self.host_asset_mapper = {}
|
||||||
self.is_sync_account = self.execution.snapshot.get('is_sync_account')
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def method_type(cls):
|
def method_type(cls):
|
||||||
@@ -23,41 +22,29 @@ class GatherAccountsManager(AccountBasePlaybookManager):
|
|||||||
self.host_asset_mapper[host['name']] = asset
|
self.host_asset_mapper[host['name']] = asset
|
||||||
return host
|
return host
|
||||||
|
|
||||||
def filter_success_result(self, tp, result):
|
def filter_success_result(self, host, result):
|
||||||
result = GatherAccountsFilter(tp).run(self.method_id_meta_mapper, result)
|
result = GatherAccountsFilter(host).run(self.method_id_meta_mapper, result)
|
||||||
return result
|
return result
|
||||||
@staticmethod
|
|
||||||
def generate_data(asset, result):
|
|
||||||
data = []
|
|
||||||
for username, info in result.items():
|
|
||||||
d = {'asset': asset, 'username': username, 'present': True}
|
|
||||||
if info.get('date'):
|
|
||||||
d['date_last_login'] = info['date']
|
|
||||||
if info.get('address'):
|
|
||||||
d['address_last_login'] = info['address'][:32]
|
|
||||||
data.append(d)
|
|
||||||
return data
|
|
||||||
|
|
||||||
def update_or_create_accounts(self, asset, result):
|
@staticmethod
|
||||||
data = self.generate_data(asset, result)
|
def update_or_create_gathered_accounts(asset, result):
|
||||||
with tmp_to_org(asset.org_id):
|
with tmp_to_org(asset.org_id):
|
||||||
gathered_accounts = []
|
|
||||||
GatheredAccount.objects.filter(asset=asset, present=True).update(present=False)
|
GatheredAccount.objects.filter(asset=asset, present=True).update(present=False)
|
||||||
for d in data:
|
for username, data in result.items():
|
||||||
username = d['username']
|
d = {'asset': asset, 'username': username, 'present': True}
|
||||||
gathered_account, __ = GatheredAccount.objects.update_or_create(
|
if data.get('date'):
|
||||||
|
d['date_last_login'] = data['date']
|
||||||
|
if data.get('address'):
|
||||||
|
d['address_last_login'] = data['address'][:32]
|
||||||
|
GatheredAccount.objects.update_or_create(
|
||||||
defaults=d, asset=asset, username=username,
|
defaults=d, asset=asset, username=username,
|
||||||
)
|
)
|
||||||
gathered_accounts.append(gathered_account)
|
|
||||||
if not self.is_sync_account:
|
|
||||||
return
|
|
||||||
GatheredAccount.sync_accounts(gathered_accounts)
|
|
||||||
|
|
||||||
def on_host_success(self, host, result):
|
def on_host_success(self, host, result):
|
||||||
info = result.get('debug', {}).get('res', {}).get('info', {})
|
info = result.get('debug', {}).get('res', {}).get('info', {})
|
||||||
asset = self.host_asset_mapper.get(host)
|
asset = self.host_asset_mapper.get(host)
|
||||||
if asset and info:
|
if asset and info:
|
||||||
result = self.filter_success_result(asset.type, info)
|
result = self.filter_success_result(asset.type, info)
|
||||||
self.update_or_create_accounts(asset, result)
|
self.update_or_create_gathered_accounts(asset, result)
|
||||||
else:
|
else:
|
||||||
logger.error("Not found info".format(host))
|
logger.error("Not found info".format(host))
|
||||||
|
|||||||
@@ -1,6 +1,30 @@
|
|||||||
import os
|
import os
|
||||||
|
import copy
|
||||||
|
|
||||||
|
from accounts.const import AutomationTypes
|
||||||
from assets.automations.methods import get_platform_automation_methods
|
from assets.automations.methods import get_platform_automation_methods
|
||||||
|
|
||||||
|
|
||||||
|
def copy_change_secret_to_push_account(methods):
|
||||||
|
push_account = AutomationTypes.push_account
|
||||||
|
change_secret = AutomationTypes.change_secret
|
||||||
|
copy_methods = copy.deepcopy(methods)
|
||||||
|
for method in copy_methods:
|
||||||
|
if not method['id'].startswith(change_secret):
|
||||||
|
continue
|
||||||
|
copy_method = copy.deepcopy(method)
|
||||||
|
copy_method['method'] = push_account.value
|
||||||
|
copy_method['id'] = copy_method['id'].replace(
|
||||||
|
change_secret, push_account
|
||||||
|
)
|
||||||
|
copy_method['name'] = copy_method['name'].replace(
|
||||||
|
'Change secret', 'Push account'
|
||||||
|
)
|
||||||
|
methods.append(copy_method)
|
||||||
|
return methods
|
||||||
|
|
||||||
|
|
||||||
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
|
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
|
||||||
platform_automation_methods = get_platform_automation_methods(BASE_DIR)
|
automation_methods = get_platform_automation_methods(BASE_DIR)
|
||||||
|
|
||||||
|
platform_automation_methods = copy_change_secret_to_push_account(automation_methods)
|
||||||
|
|||||||
@@ -1,58 +0,0 @@
|
|||||||
- hosts: mongodb
|
|
||||||
gather_facts: no
|
|
||||||
vars:
|
|
||||||
ansible_python_interpreter: /usr/local/bin/python
|
|
||||||
|
|
||||||
tasks:
|
|
||||||
- name: Test MongoDB connection
|
|
||||||
mongodb_ping:
|
|
||||||
login_user: "{{ jms_account.username }}"
|
|
||||||
login_password: "{{ jms_account.secret }}"
|
|
||||||
login_host: "{{ jms_asset.address }}"
|
|
||||||
login_port: "{{ jms_asset.port }}"
|
|
||||||
login_database: "{{ jms_asset.spec_info.db_name }}"
|
|
||||||
ssl: "{{ jms_asset.spec_info.use_ssl }}"
|
|
||||||
ssl_ca_certs: "{{ jms_asset.secret_info.ca_cert }}"
|
|
||||||
ssl_certfile: "{{ jms_asset.secret_info.client_key }}"
|
|
||||||
connection_options:
|
|
||||||
- tlsAllowInvalidHostnames: "{{ jms_asset.spec_info.allow_invalid_cert}}"
|
|
||||||
register: db_info
|
|
||||||
|
|
||||||
- name: Display MongoDB version
|
|
||||||
debug:
|
|
||||||
var: db_info.server_version
|
|
||||||
when: db_info is succeeded
|
|
||||||
|
|
||||||
- name: Change MongoDB password
|
|
||||||
mongodb_user:
|
|
||||||
login_user: "{{ jms_account.username }}"
|
|
||||||
login_password: "{{ jms_account.secret }}"
|
|
||||||
login_host: "{{ jms_asset.address }}"
|
|
||||||
login_port: "{{ jms_asset.port }}"
|
|
||||||
login_database: "{{ jms_asset.spec_info.db_name }}"
|
|
||||||
ssl: "{{ jms_asset.spec_info.use_ssl }}"
|
|
||||||
ssl_ca_certs: "{{ jms_asset.secret_info.ca_cert }}"
|
|
||||||
ssl_certfile: "{{ jms_asset.secret_info.client_key }}"
|
|
||||||
connection_options:
|
|
||||||
- tlsAllowInvalidHostnames: "{{ jms_asset.spec_info.allow_invalid_cert}}"
|
|
||||||
db: "{{ jms_asset.spec_info.db_name }}"
|
|
||||||
name: "{{ account.username }}"
|
|
||||||
password: "{{ account.secret }}"
|
|
||||||
when: db_info is succeeded
|
|
||||||
register: change_info
|
|
||||||
|
|
||||||
- name: Verify password
|
|
||||||
mongodb_ping:
|
|
||||||
login_user: "{{ account.username }}"
|
|
||||||
login_password: "{{ account.secret }}"
|
|
||||||
login_host: "{{ jms_asset.address }}"
|
|
||||||
login_port: "{{ jms_asset.port }}"
|
|
||||||
login_database: "{{ jms_asset.spec_info.db_name }}"
|
|
||||||
ssl: "{{ jms_asset.spec_info.use_ssl }}"
|
|
||||||
ssl_ca_certs: "{{ jms_asset.secret_info.ca_cert }}"
|
|
||||||
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
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
id: push_account_mongodb
|
|
||||||
name: "{{ 'MongoDB account push' | trans }}"
|
|
||||||
category: database
|
|
||||||
type:
|
|
||||||
- mongodb
|
|
||||||
method: push_account
|
|
||||||
|
|
||||||
i18n:
|
|
||||||
MongoDB account push:
|
|
||||||
zh: MongoDB 账号推送
|
|
||||||
ja: MongoDB アカウントのプッシュ
|
|
||||||
@@ -1,43 +0,0 @@
|
|||||||
- hosts: mysql
|
|
||||||
gather_facts: no
|
|
||||||
vars:
|
|
||||||
ansible_python_interpreter: /usr/local/bin/python
|
|
||||||
db_name: "{{ jms_asset.spec_info.db_name }}"
|
|
||||||
|
|
||||||
tasks:
|
|
||||||
- name: Test MySQL connection
|
|
||||||
community.mysql.mysql_info:
|
|
||||||
login_user: "{{ jms_account.username }}"
|
|
||||||
login_password: "{{ jms_account.secret }}"
|
|
||||||
login_host: "{{ jms_asset.address }}"
|
|
||||||
login_port: "{{ jms_asset.port }}"
|
|
||||||
filter: version
|
|
||||||
register: db_info
|
|
||||||
|
|
||||||
- name: MySQL version
|
|
||||||
debug:
|
|
||||||
var: db_info.version.full
|
|
||||||
|
|
||||||
- name: Change MySQL password
|
|
||||||
community.mysql.mysql_user:
|
|
||||||
login_user: "{{ jms_account.username }}"
|
|
||||||
login_password: "{{ jms_account.secret }}"
|
|
||||||
login_host: "{{ jms_asset.address }}"
|
|
||||||
login_port: "{{ jms_asset.port }}"
|
|
||||||
name: "{{ account.username }}"
|
|
||||||
password: "{{ account.secret }}"
|
|
||||||
host: "%"
|
|
||||||
priv: "{{ account.username + '.*:USAGE' if db_name == '' else db_name + '.*:ALL' }}"
|
|
||||||
when: db_info is succeeded
|
|
||||||
register: change_info
|
|
||||||
|
|
||||||
- name: Verify password
|
|
||||||
community.mysql.mysql_info:
|
|
||||||
login_user: "{{ account.username }}"
|
|
||||||
login_password: "{{ account.secret }}"
|
|
||||||
login_host: "{{ jms_asset.address }}"
|
|
||||||
login_port: "{{ jms_asset.port }}"
|
|
||||||
filter: version
|
|
||||||
when:
|
|
||||||
- db_info is succeeded
|
|
||||||
- change_info is succeeded
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
id: push_account_mysql
|
|
||||||
name: "{{ 'MySQL account push' | trans }}"
|
|
||||||
category: database
|
|
||||||
type:
|
|
||||||
- mysql
|
|
||||||
- mariadb
|
|
||||||
method: push_account
|
|
||||||
|
|
||||||
i18n:
|
|
||||||
MySQL account push:
|
|
||||||
zh: MySQL 账号推送
|
|
||||||
ja: MySQL アカウントのプッシュ
|
|
||||||
@@ -1,44 +0,0 @@
|
|||||||
- hosts: oracle
|
|
||||||
gather_facts: no
|
|
||||||
vars:
|
|
||||||
ansible_python_interpreter: /usr/local/bin/python
|
|
||||||
|
|
||||||
tasks:
|
|
||||||
- name: Test Oracle connection
|
|
||||||
oracle_ping:
|
|
||||||
login_user: "{{ jms_account.username }}"
|
|
||||||
login_password: "{{ jms_account.secret }}"
|
|
||||||
login_host: "{{ jms_asset.address }}"
|
|
||||||
login_port: "{{ jms_asset.port }}"
|
|
||||||
login_database: "{{ jms_asset.spec_info.db_name }}"
|
|
||||||
mode: "{{ jms_account.mode }}"
|
|
||||||
register: db_info
|
|
||||||
|
|
||||||
- name: Display Oracle version
|
|
||||||
debug:
|
|
||||||
var: db_info.server_version
|
|
||||||
when: db_info is succeeded
|
|
||||||
|
|
||||||
- name: Change Oracle password
|
|
||||||
oracle_user:
|
|
||||||
login_user: "{{ jms_account.username }}"
|
|
||||||
login_password: "{{ jms_account.secret }}"
|
|
||||||
login_host: "{{ jms_asset.address }}"
|
|
||||||
login_port: "{{ jms_asset.port }}"
|
|
||||||
login_database: "{{ jms_asset.spec_info.db_name }}"
|
|
||||||
mode: "{{ jms_account.mode }}"
|
|
||||||
name: "{{ account.username }}"
|
|
||||||
password: "{{ account.secret }}"
|
|
||||||
when: db_info is succeeded
|
|
||||||
register: change_info
|
|
||||||
|
|
||||||
- name: Verify password
|
|
||||||
oracle_ping:
|
|
||||||
login_user: "{{ account.username }}"
|
|
||||||
login_password: "{{ account.secret }}"
|
|
||||||
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
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
id: push_account_oracle
|
|
||||||
name: "{{ 'Oracle account push' | trans }}"
|
|
||||||
category: database
|
|
||||||
type:
|
|
||||||
- oracle
|
|
||||||
method: push_account
|
|
||||||
|
|
||||||
i18n:
|
|
||||||
Oracle account push:
|
|
||||||
zh: Oracle 账号推送
|
|
||||||
ja: Oracle アカウントのプッシュ
|
|
||||||
@@ -1,46 +0,0 @@
|
|||||||
- hosts: postgre
|
|
||||||
gather_facts: no
|
|
||||||
vars:
|
|
||||||
ansible_python_interpreter: /usr/local/bin/python
|
|
||||||
|
|
||||||
tasks:
|
|
||||||
- name: Test PostgreSQL connection
|
|
||||||
community.postgresql.postgresql_ping:
|
|
||||||
login_user: "{{ jms_account.username }}"
|
|
||||||
login_password: "{{ jms_account.secret }}"
|
|
||||||
login_host: "{{ jms_asset.address }}"
|
|
||||||
login_port: "{{ jms_asset.port }}"
|
|
||||||
login_db: "{{ jms_asset.spec_info.db_name }}"
|
|
||||||
register: result
|
|
||||||
failed_when: not result.is_available
|
|
||||||
|
|
||||||
- name: Display PostgreSQL version
|
|
||||||
debug:
|
|
||||||
var: result.server_version.full
|
|
||||||
when: result is succeeded
|
|
||||||
|
|
||||||
- name: Change PostgreSQL password
|
|
||||||
community.postgresql.postgresql_user:
|
|
||||||
login_user: "{{ jms_account.username }}"
|
|
||||||
login_password: "{{ jms_account.secret }}"
|
|
||||||
login_host: "{{ jms_asset.address }}"
|
|
||||||
login_port: "{{ jms_asset.port }}"
|
|
||||||
db: "{{ jms_asset.spec_info.db_name }}"
|
|
||||||
name: "{{ account.username }}"
|
|
||||||
password: "{{ account.secret }}"
|
|
||||||
role_attr_flags: LOGIN
|
|
||||||
when: result is succeeded
|
|
||||||
register: change_info
|
|
||||||
|
|
||||||
- name: Verify password
|
|
||||||
community.postgresql.postgresql_ping:
|
|
||||||
login_user: "{{ account.username }}"
|
|
||||||
login_password: "{{ account.secret }}"
|
|
||||||
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
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
id: push_account_postgresql
|
|
||||||
name: "{{ 'PostgreSQL account push' | trans }}"
|
|
||||||
category: database
|
|
||||||
type:
|
|
||||||
- postgresql
|
|
||||||
method: push_account
|
|
||||||
|
|
||||||
i18n:
|
|
||||||
PostgreSQL account push:
|
|
||||||
zh: PostgreSQL 账号推送
|
|
||||||
ja: PostgreSQL アカウントのプッシュ
|
|
||||||
@@ -1,69 +0,0 @@
|
|||||||
- hosts: sqlserver
|
|
||||||
gather_facts: no
|
|
||||||
vars:
|
|
||||||
ansible_python_interpreter: /usr/local/bin/python
|
|
||||||
|
|
||||||
tasks:
|
|
||||||
- name: Test SQLServer connection
|
|
||||||
community.general.mssql_script:
|
|
||||||
login_user: "{{ jms_account.username }}"
|
|
||||||
login_password: "{{ jms_account.secret }}"
|
|
||||||
login_host: "{{ jms_asset.address }}"
|
|
||||||
login_port: "{{ jms_asset.port }}"
|
|
||||||
name: '{{ jms_asset.spec_info.db_name }}'
|
|
||||||
script: |
|
|
||||||
SELECT @@version
|
|
||||||
register: db_info
|
|
||||||
|
|
||||||
- name: SQLServer version
|
|
||||||
set_fact:
|
|
||||||
info:
|
|
||||||
version: "{{ db_info.query_results[0][0][0][0].splitlines()[0] }}"
|
|
||||||
- debug:
|
|
||||||
var: info
|
|
||||||
|
|
||||||
- name: Check whether SQLServer User exist
|
|
||||||
community.general.mssql_script:
|
|
||||||
login_user: "{{ jms_account.username }}"
|
|
||||||
login_password: "{{ jms_account.secret }}"
|
|
||||||
login_host: "{{ jms_asset.address }}"
|
|
||||||
login_port: "{{ jms_asset.port }}"
|
|
||||||
name: '{{ jms_asset.spec_info.db_name }}'
|
|
||||||
script: "SELECT 1 from sys.sql_logins WHERE name='{{ account.username }}';"
|
|
||||||
when: db_info is succeeded
|
|
||||||
register: user_exist
|
|
||||||
|
|
||||||
- name: Change SQLServer password
|
|
||||||
community.general.mssql_script:
|
|
||||||
login_user: "{{ jms_account.username }}"
|
|
||||||
login_password: "{{ jms_account.secret }}"
|
|
||||||
login_host: "{{ jms_asset.address }}"
|
|
||||||
login_port: "{{ jms_asset.port }}"
|
|
||||||
name: '{{ jms_asset.spec_info.db_name }}'
|
|
||||||
script: "ALTER LOGIN {{ account.username }} WITH PASSWORD = '{{ account.secret }}'; select @@version"
|
|
||||||
when: user_exist.query_results[0] | length != 0
|
|
||||||
register: change_info
|
|
||||||
|
|
||||||
- name: Add SQLServer user
|
|
||||||
community.general.mssql_script:
|
|
||||||
login_user: "{{ jms_account.username }}"
|
|
||||||
login_password: "{{ jms_account.secret }}"
|
|
||||||
login_host: "{{ jms_asset.address }}"
|
|
||||||
login_port: "{{ jms_asset.port }}"
|
|
||||||
name: '{{ jms_asset.spec_info.db_name }}'
|
|
||||||
script: "CREATE LOGIN {{ account.username }} WITH PASSWORD = '{{ account.secret }}'; select @@version"
|
|
||||||
when: user_exist.query_results[0] | length == 0
|
|
||||||
register: change_info
|
|
||||||
|
|
||||||
- name: Verify password
|
|
||||||
community.general.mssql_script:
|
|
||||||
login_user: "{{ account.username }}"
|
|
||||||
login_password: "{{ account.secret }}"
|
|
||||||
login_host: "{{ jms_asset.address }}"
|
|
||||||
login_port: "{{ jms_asset.port }}"
|
|
||||||
name: '{{ jms_asset.spec_info.db_name }}'
|
|
||||||
script: |
|
|
||||||
SELECT @@version
|
|
||||||
when:
|
|
||||||
- db_info is succeeded
|
|
||||||
- change_info is succeeded
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
id: push_account_sqlserver
|
|
||||||
name: "{{ 'SQLServer account push' | trans }}"
|
|
||||||
category: database
|
|
||||||
type:
|
|
||||||
- sqlserver
|
|
||||||
method: push_account
|
|
||||||
|
|
||||||
i18n:
|
|
||||||
SQLServer account push:
|
|
||||||
zh: SQLServer 账号推送
|
|
||||||
ja: SQLServer アカウントのプッシュ
|
|
||||||
@@ -1,93 +0,0 @@
|
|||||||
- hosts: demo
|
|
||||||
gather_facts: no
|
|
||||||
tasks:
|
|
||||||
- name: Test privileged account
|
|
||||||
ansible.builtin.ping:
|
|
||||||
|
|
||||||
- name: Push user
|
|
||||||
ansible.builtin.user:
|
|
||||||
name: "{{ account.username }}"
|
|
||||||
shell: "{{ params.shell }}"
|
|
||||||
home: "{{ '/home/' + account.username }}"
|
|
||||||
groups: "{{ params.groups }}"
|
|
||||||
expires: -1
|
|
||||||
state: present
|
|
||||||
|
|
||||||
- name: "Add {{ account.username }} group"
|
|
||||||
ansible.builtin.group:
|
|
||||||
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 }}"
|
|
||||||
groups: "{{ params.groups }}"
|
|
||||||
when: params.groups
|
|
||||||
|
|
||||||
- name: Push user password
|
|
||||||
ansible.builtin.user:
|
|
||||||
name: "{{ account.username }}"
|
|
||||||
password: "{{ account.secret | password_hash('sha512') }}"
|
|
||||||
update_password: always
|
|
||||||
when: account.secret_type == "password"
|
|
||||||
|
|
||||||
- name: remove jumpserver ssh key
|
|
||||||
ansible.builtin.lineinfile:
|
|
||||||
dest: "{{ ssh_params.dest }}"
|
|
||||||
regexp: "{{ ssh_params.regexp }}"
|
|
||||||
state: absent
|
|
||||||
when:
|
|
||||||
- account.secret_type == "ssh_key"
|
|
||||||
- ssh_params.strategy == "set_jms"
|
|
||||||
|
|
||||||
- name: Push SSH key
|
|
||||||
ansible.builtin.authorized_key:
|
|
||||||
user: "{{ account.username }}"
|
|
||||||
key: "{{ account.secret }}"
|
|
||||||
exclusive: "{{ ssh_params.exclusive }}"
|
|
||||||
when: account.secret_type == "ssh_key"
|
|
||||||
|
|
||||||
- name: Set sudo setting
|
|
||||||
ansible.builtin.lineinfile:
|
|
||||||
dest: /etc/sudoers
|
|
||||||
state: present
|
|
||||||
regexp: "^{{ account.username }} ALL="
|
|
||||||
line: "{{ account.username + ' ALL=(ALL) NOPASSWD: ' + params.sudo }}"
|
|
||||||
validate: visudo -cf %s
|
|
||||||
when:
|
|
||||||
- params.sudo
|
|
||||||
|
|
||||||
- name: Refresh connection
|
|
||||||
ansible.builtin.meta: reset_connection
|
|
||||||
|
|
||||||
- name: Verify password
|
|
||||||
ansible.builtin.ping:
|
|
||||||
become: no
|
|
||||||
vars:
|
|
||||||
ansible_user: "{{ account.username }}"
|
|
||||||
ansible_password: "{{ account.secret }}"
|
|
||||||
ansible_become: no
|
|
||||||
when: account.secret_type == "password"
|
|
||||||
|
|
||||||
- name: Verify SSH key
|
|
||||||
ansible.builtin.ping:
|
|
||||||
become: no
|
|
||||||
vars:
|
|
||||||
ansible_user: "{{ account.username }}"
|
|
||||||
ansible_ssh_private_key_file: "{{ account.private_key_path }}"
|
|
||||||
ansible_become: no
|
|
||||||
when: account.secret_type == "ssh_key"
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
id: push_account_aix
|
|
||||||
name: "{{ 'Aix account push' | trans }}"
|
|
||||||
category: host
|
|
||||||
type:
|
|
||||||
- AIX
|
|
||||||
method: push_account
|
|
||||||
params:
|
|
||||||
- name: sudo
|
|
||||||
type: str
|
|
||||||
label: 'Sudo'
|
|
||||||
default: '/bin/whoami'
|
|
||||||
help_text: '使用逗号分隔多个命令,如: /bin/whoami,/sbin/ifconfig'
|
|
||||||
|
|
||||||
- name: shell
|
|
||||||
type: str
|
|
||||||
label: 'Shell'
|
|
||||||
default: '/bin/bash'
|
|
||||||
|
|
||||||
- name: groups
|
|
||||||
type: str
|
|
||||||
label: '用户组'
|
|
||||||
default: ''
|
|
||||||
help_text: '请输入用户组,多个用户组使用逗号分隔(需填写已存在的用户组)'
|
|
||||||
|
|
||||||
i18n:
|
|
||||||
Aix account push:
|
|
||||||
zh: Aix 账号推送
|
|
||||||
ja: Aix アカウントのプッシュ
|
|
||||||
|
|
||||||
@@ -1,93 +0,0 @@
|
|||||||
- hosts: demo
|
|
||||||
gather_facts: no
|
|
||||||
tasks:
|
|
||||||
- name: Test privileged account
|
|
||||||
ansible.builtin.ping:
|
|
||||||
|
|
||||||
- name: Push user
|
|
||||||
ansible.builtin.user:
|
|
||||||
name: "{{ account.username }}"
|
|
||||||
shell: "{{ params.shell }}"
|
|
||||||
home: "{{ '/home/' + account.username }}"
|
|
||||||
groups: "{{ params.groups }}"
|
|
||||||
expires: -1
|
|
||||||
state: present
|
|
||||||
|
|
||||||
- name: "Add {{ account.username }} group"
|
|
||||||
ansible.builtin.group:
|
|
||||||
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 }}"
|
|
||||||
groups: "{{ params.groups }}"
|
|
||||||
when: params.groups
|
|
||||||
|
|
||||||
- name: Push user password
|
|
||||||
ansible.builtin.user:
|
|
||||||
name: "{{ account.username }}"
|
|
||||||
password: "{{ account.secret | password_hash('sha512') }}"
|
|
||||||
update_password: always
|
|
||||||
when: account.secret_type == "password"
|
|
||||||
|
|
||||||
- name: remove jumpserver ssh key
|
|
||||||
ansible.builtin.lineinfile:
|
|
||||||
dest: "{{ ssh_params.dest }}"
|
|
||||||
regexp: "{{ ssh_params.regexp }}"
|
|
||||||
state: absent
|
|
||||||
when:
|
|
||||||
- account.secret_type == "ssh_key"
|
|
||||||
- ssh_params.strategy == "set_jms"
|
|
||||||
|
|
||||||
- name: Push SSH key
|
|
||||||
ansible.builtin.authorized_key:
|
|
||||||
user: "{{ account.username }}"
|
|
||||||
key: "{{ account.secret }}"
|
|
||||||
exclusive: "{{ ssh_params.exclusive }}"
|
|
||||||
when: account.secret_type == "ssh_key"
|
|
||||||
|
|
||||||
- name: Set sudo setting
|
|
||||||
ansible.builtin.lineinfile:
|
|
||||||
dest: /etc/sudoers
|
|
||||||
state: present
|
|
||||||
regexp: "^{{ account.username }} ALL="
|
|
||||||
line: "{{ account.username + ' ALL=(ALL) NOPASSWD: ' + params.sudo }}"
|
|
||||||
validate: visudo -cf %s
|
|
||||||
when:
|
|
||||||
- params.sudo
|
|
||||||
|
|
||||||
- name: Refresh connection
|
|
||||||
ansible.builtin.meta: reset_connection
|
|
||||||
|
|
||||||
- name: Verify password
|
|
||||||
ansible.builtin.ping:
|
|
||||||
become: no
|
|
||||||
vars:
|
|
||||||
ansible_user: "{{ account.username }}"
|
|
||||||
ansible_password: "{{ account.secret }}"
|
|
||||||
ansible_become: no
|
|
||||||
when: account.secret_type == "password"
|
|
||||||
|
|
||||||
- name: Verify SSH key
|
|
||||||
ansible.builtin.ping:
|
|
||||||
become: no
|
|
||||||
vars:
|
|
||||||
ansible_user: "{{ account.username }}"
|
|
||||||
ansible_ssh_private_key_file: "{{ account.private_key_path }}"
|
|
||||||
ansible_become: no
|
|
||||||
when: account.secret_type == "ssh_key"
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
id: push_account_posix
|
|
||||||
name: "{{ 'Posix account push' | trans }}"
|
|
||||||
category: host
|
|
||||||
type:
|
|
||||||
- unix
|
|
||||||
- linux
|
|
||||||
method: push_account
|
|
||||||
params:
|
|
||||||
- name: sudo
|
|
||||||
type: str
|
|
||||||
label: 'Sudo'
|
|
||||||
default: '/bin/whoami'
|
|
||||||
help_text: '使用逗号分隔多个命令,如: /bin/whoami,/sbin/ifconfig'
|
|
||||||
|
|
||||||
- name: shell
|
|
||||||
type: str
|
|
||||||
label: 'Shell'
|
|
||||||
default: '/bin/bash'
|
|
||||||
help_text: ''
|
|
||||||
|
|
||||||
- name: groups
|
|
||||||
type: str
|
|
||||||
label: '用户组'
|
|
||||||
default: ''
|
|
||||||
help_text: '请输入用户组,多个用户组使用逗号分隔(需填写已存在的用户组)'
|
|
||||||
|
|
||||||
i18n:
|
|
||||||
Posix account push:
|
|
||||||
zh: Posix 账号推送
|
|
||||||
ja: Posix アカウントのプッシュ
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
- hosts: demo
|
|
||||||
gather_facts: no
|
|
||||||
tasks:
|
|
||||||
- name: Test privileged account
|
|
||||||
ansible.windows.win_ping:
|
|
||||||
|
|
||||||
# - name: Print variables
|
|
||||||
# debug:
|
|
||||||
# msg: "Username: {{ account.username }}, Password: {{ account.secret }}"
|
|
||||||
|
|
||||||
- name: Push user password
|
|
||||||
ansible.windows.win_user:
|
|
||||||
fullname: "{{ account.username}}"
|
|
||||||
name: "{{ account.username }}"
|
|
||||||
password: "{{ account.secret }}"
|
|
||||||
password_never_expires: yes
|
|
||||||
groups: "{{ params.groups }}"
|
|
||||||
groups_action: add
|
|
||||||
update_password: always
|
|
||||||
when: account.secret_type == "password"
|
|
||||||
|
|
||||||
- name: Refresh connection
|
|
||||||
ansible.builtin.meta: reset_connection
|
|
||||||
|
|
||||||
- name: Verify password
|
|
||||||
ansible.windows.win_ping:
|
|
||||||
vars:
|
|
||||||
ansible_user: "{{ account.username }}"
|
|
||||||
ansible_password: "{{ account.secret }}"
|
|
||||||
when: account.secret_type == "password"
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
id: push_account_local_windows
|
|
||||||
name: "{{ 'Windows account push' | trans }}"
|
|
||||||
version: 1
|
|
||||||
method: push_account
|
|
||||||
category: host
|
|
||||||
type:
|
|
||||||
- windows
|
|
||||||
params:
|
|
||||||
- name: groups
|
|
||||||
type: str
|
|
||||||
label: '用户组'
|
|
||||||
default: 'Users,Remote Desktop Users'
|
|
||||||
help_text: '请输入用户组,多个用户组使用逗号分隔(需填写已存在的用户组)'
|
|
||||||
|
|
||||||
i18n:
|
|
||||||
Windows account push:
|
|
||||||
zh: Windows 账号推送
|
|
||||||
ja: Windows アカウントのプッシュ
|
|
||||||
@@ -1,6 +1,9 @@
|
|||||||
from copy import deepcopy
|
from copy import deepcopy
|
||||||
|
|
||||||
from accounts.const import AutomationTypes, SecretType, Connectivity
|
from django.db.models import QuerySet
|
||||||
|
|
||||||
|
from accounts.const import AutomationTypes, SecretType
|
||||||
|
from accounts.models import Account
|
||||||
from assets.const import HostTypes
|
from assets.const import HostTypes
|
||||||
from common.utils import get_logger
|
from common.utils import get_logger
|
||||||
from ..base.manager import AccountBasePlaybookManager
|
from ..base.manager import AccountBasePlaybookManager
|
||||||
@@ -16,6 +19,36 @@ class PushAccountManager(ChangeSecretManager, AccountBasePlaybookManager):
|
|||||||
def method_type(cls):
|
def method_type(cls):
|
||||||
return AutomationTypes.push_account
|
return AutomationTypes.push_account
|
||||||
|
|
||||||
|
def create_nonlocal_accounts(self, accounts, snapshot_account_usernames, asset):
|
||||||
|
secret_type = self.secret_type
|
||||||
|
usernames = accounts.filter(secret_type=secret_type).values_list(
|
||||||
|
'username', flat=True
|
||||||
|
)
|
||||||
|
create_usernames = set(snapshot_account_usernames) - set(usernames)
|
||||||
|
create_account_objs = [
|
||||||
|
Account(
|
||||||
|
name=f'{username}-{secret_type}', username=username,
|
||||||
|
secret_type=secret_type, asset=asset,
|
||||||
|
)
|
||||||
|
for username in create_usernames
|
||||||
|
]
|
||||||
|
Account.objects.bulk_create(create_account_objs)
|
||||||
|
|
||||||
|
def get_accounts(self, privilege_account, accounts: QuerySet):
|
||||||
|
if not privilege_account:
|
||||||
|
print(f'not privilege account')
|
||||||
|
return []
|
||||||
|
snapshot_account_usernames = self.execution.snapshot['accounts']
|
||||||
|
if '*' in snapshot_account_usernames:
|
||||||
|
return accounts.exclude(username=privilege_account.username)
|
||||||
|
|
||||||
|
asset = privilege_account.asset
|
||||||
|
self.create_nonlocal_accounts(accounts, snapshot_account_usernames, asset)
|
||||||
|
accounts = asset.accounts.exclude(username=privilege_account.username).filter(
|
||||||
|
username__in=snapshot_account_usernames, secret_type=self.secret_type
|
||||||
|
)
|
||||||
|
return accounts
|
||||||
|
|
||||||
def host_callback(self, host, asset=None, account=None, automation=None, path_dir=None, **kwargs):
|
def host_callback(self, host, asset=None, account=None, automation=None, path_dir=None, **kwargs):
|
||||||
host = super(ChangeSecretManager, self).host_callback(
|
host = super(ChangeSecretManager, self).host_callback(
|
||||||
host, asset=asset, account=account, automation=automation,
|
host, asset=asset, account=account, automation=automation,
|
||||||
@@ -24,37 +57,34 @@ class PushAccountManager(ChangeSecretManager, AccountBasePlaybookManager):
|
|||||||
if host.get('error'):
|
if host.get('error'):
|
||||||
return host
|
return host
|
||||||
|
|
||||||
accounts = self.get_accounts(account)
|
accounts = asset.accounts.all()
|
||||||
|
accounts = self.get_accounts(account, accounts)
|
||||||
inventory_hosts = []
|
inventory_hosts = []
|
||||||
|
host['secret_type'] = self.secret_type
|
||||||
if asset.type == HostTypes.WINDOWS and self.secret_type == SecretType.SSH_KEY:
|
if asset.type == HostTypes.WINDOWS and self.secret_type == SecretType.SSH_KEY:
|
||||||
msg = f'Windows {asset} does not support ssh key push'
|
msg = f'Windows {asset} does not support ssh key push \n'
|
||||||
print(msg)
|
print(msg)
|
||||||
return inventory_hosts
|
return inventory_hosts
|
||||||
|
|
||||||
host['ssh_params'] = {}
|
|
||||||
for account in accounts:
|
for account in accounts:
|
||||||
h = deepcopy(host)
|
h = deepcopy(host)
|
||||||
secret_type = account.secret_type
|
|
||||||
h['name'] += '(' + account.username + ')'
|
h['name'] += '(' + account.username + ')'
|
||||||
if self.secret_type is None:
|
new_secret = self.get_secret()
|
||||||
new_secret = account.secret
|
|
||||||
else:
|
|
||||||
new_secret = self.get_secret(secret_type)
|
|
||||||
|
|
||||||
self.name_recorder_mapper[h['name']] = {
|
self.name_recorder_mapper[h['name']] = {
|
||||||
'account': account, 'new_secret': new_secret,
|
'account': account, 'new_secret': new_secret,
|
||||||
}
|
}
|
||||||
|
|
||||||
private_key_path = None
|
private_key_path = None
|
||||||
if secret_type == SecretType.SSH_KEY:
|
if self.secret_type == SecretType.SSH_KEY:
|
||||||
private_key_path = self.generate_private_key_path(new_secret, path_dir)
|
private_key_path = self.generate_private_key_path(new_secret, path_dir)
|
||||||
new_secret = self.generate_public_key(new_secret)
|
new_secret = self.generate_public_key(new_secret)
|
||||||
|
|
||||||
h['ssh_params'].update(self.get_ssh_params(account, new_secret, secret_type))
|
h['kwargs'] = self.get_kwargs(account, new_secret)
|
||||||
h['account'] = {
|
h['account'] = {
|
||||||
'name': account.name,
|
'name': account.name,
|
||||||
'username': account.username,
|
'username': account.username,
|
||||||
'secret_type': secret_type,
|
'secret_type': account.secret_type,
|
||||||
'secret': new_secret,
|
'secret': new_secret,
|
||||||
'private_key_path': private_key_path
|
'private_key_path': private_key_path
|
||||||
}
|
}
|
||||||
@@ -74,7 +104,6 @@ class PushAccountManager(ChangeSecretManager, AccountBasePlaybookManager):
|
|||||||
return
|
return
|
||||||
account.secret = new_secret
|
account.secret = new_secret
|
||||||
account.save(update_fields=['secret'])
|
account.save(update_fields=['secret'])
|
||||||
account.set_connectivity(Connectivity.OK)
|
|
||||||
|
|
||||||
def on_host_error(self, host, error, result):
|
def on_host_error(self, host, error, result):
|
||||||
pass
|
pass
|
||||||
@@ -83,9 +112,9 @@ class PushAccountManager(ChangeSecretManager, AccountBasePlaybookManager):
|
|||||||
logger.error("Pust account error: ", e)
|
logger.error("Pust account error: ", e)
|
||||||
|
|
||||||
def run(self, *args, **kwargs):
|
def run(self, *args, **kwargs):
|
||||||
if self.secret_type and not self.check_secret():
|
if not self.check_secret():
|
||||||
return
|
return
|
||||||
super(ChangeSecretManager, self).run(*args, **kwargs)
|
super().run(*args, **kwargs)
|
||||||
|
|
||||||
# @classmethod
|
# @classmethod
|
||||||
# def trigger_by_asset_create(cls, asset):
|
# def trigger_by_asset_create(cls, asset):
|
||||||
|
|||||||
@@ -1,14 +0,0 @@
|
|||||||
- hosts: custom
|
|
||||||
gather_facts: no
|
|
||||||
vars:
|
|
||||||
ansible_connection: local
|
|
||||||
|
|
||||||
tasks:
|
|
||||||
- name: Verify account
|
|
||||||
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 }}"
|
|
||||||
@@ -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 アカウントの検証
|
|
||||||
@@ -1,11 +1,6 @@
|
|||||||
id: verify_account_mongodb
|
id: verify_account_mongodb
|
||||||
name: "{{ 'MongoDB account verify' | trans }}"
|
name: Verify account from MongoDB
|
||||||
category: database
|
category: database
|
||||||
type:
|
type:
|
||||||
- mongodb
|
- mongodb
|
||||||
method: verify_account
|
method: verify_account
|
||||||
|
|
||||||
i18n:
|
|
||||||
MongoDB account verify:
|
|
||||||
zh: MongoDB 账号验证
|
|
||||||
ja: MongoDB アカウントの検証
|
|
||||||
|
|||||||
@@ -1,12 +1,7 @@
|
|||||||
id: verify_account_mysql
|
id: verify_account_mysql
|
||||||
name: "{{ 'MySQL account verify' | trans }}"
|
name: Verify account from MySQL
|
||||||
category: database
|
category: database
|
||||||
type:
|
type:
|
||||||
- mysql
|
- mysql
|
||||||
- mariadb
|
- mariadb
|
||||||
method: verify_account
|
method: verify_account
|
||||||
|
|
||||||
i18n:
|
|
||||||
MySQL account verify:
|
|
||||||
zh: MySQL 账号验证
|
|
||||||
ja: MySQL アカウントの検証
|
|
||||||
|
|||||||
@@ -1,11 +1,6 @@
|
|||||||
id: verify_account_oracle
|
id: verify_account_oracle
|
||||||
name: "{{ 'Oracle account verify' | trans }}"
|
name: Verify account from Oracle
|
||||||
category: database
|
category: database
|
||||||
type:
|
type:
|
||||||
- oracle
|
- oracle
|
||||||
method: verify_account
|
method: verify_account
|
||||||
|
|
||||||
i18n:
|
|
||||||
Oracle account verify:
|
|
||||||
zh: Oracle 账号验证
|
|
||||||
ja: Oracle アカウントの検証
|
|
||||||
|
|||||||
@@ -1,11 +1,6 @@
|
|||||||
id: verify_account_postgresql
|
id: verify_account_postgresql
|
||||||
name: "{{ 'PostgreSQL account verify' | trans }}"
|
name: Verify account for PostgreSQL
|
||||||
category: database
|
category: database
|
||||||
type:
|
type:
|
||||||
- postgresql
|
- postgresql
|
||||||
method: verify_account
|
method: verify_account
|
||||||
|
|
||||||
i18n:
|
|
||||||
PostgreSQL account verify:
|
|
||||||
zh: PostgreSQL 账号验证
|
|
||||||
ja: PostgreSQL アカウントの検証
|
|
||||||
|
|||||||
@@ -1,11 +1,6 @@
|
|||||||
id: verify_account_sqlserver
|
id: verify_account_sqlserver
|
||||||
name: "{{ 'SQLServer account verify' | trans }}"
|
name: Verify account from SQLServer
|
||||||
category: database
|
category: database
|
||||||
type:
|
type:
|
||||||
- sqlserver
|
- sqlserver
|
||||||
method: verify_account
|
method: verify_account
|
||||||
|
|
||||||
i18n:
|
|
||||||
SQLServer account verify:
|
|
||||||
zh: SQLServer 账号验证
|
|
||||||
ja: SQLServer アカウントの検証
|
|
||||||
@@ -1,12 +1,7 @@
|
|||||||
id: verify_account_posix
|
id: verify_account_posix
|
||||||
name: "{{ 'Posix account verify' | trans }}"
|
name: Verify posix account
|
||||||
category: host
|
category: host
|
||||||
type:
|
type:
|
||||||
- linux
|
- linux
|
||||||
- unix
|
- unix
|
||||||
method: verify_account
|
method: verify_account
|
||||||
|
|
||||||
i18n:
|
|
||||||
Posix account verify:
|
|
||||||
zh: Posix 账号验证
|
|
||||||
ja: Posix アカウントの検証
|
|
||||||
@@ -1,9 +1,6 @@
|
|||||||
- hosts: windows
|
- hosts: windows
|
||||||
gather_facts: no
|
gather_facts: no
|
||||||
tasks:
|
tasks:
|
||||||
- name: Refresh connection
|
|
||||||
ansible.builtin.meta: reset_connection
|
|
||||||
|
|
||||||
- name: Verify account
|
- name: Verify account
|
||||||
ansible.windows.win_ping:
|
ansible.windows.win_ping:
|
||||||
vars:
|
vars:
|
||||||
|
|||||||
@@ -1,12 +1,7 @@
|
|||||||
id: verify_account_windows
|
id: verify_account_windows
|
||||||
name: "{{ 'Windows account verify' | trans }}"
|
name: Verify account windows
|
||||||
version: 1
|
version: 1
|
||||||
method: verify_account
|
method: verify_account
|
||||||
category: host
|
category: host
|
||||||
type:
|
type:
|
||||||
- windows
|
- windows
|
||||||
|
|
||||||
i18n:
|
|
||||||
Windows account verify:
|
|
||||||
zh: Windows 账号验证
|
|
||||||
ja: Windows アカウントの検証
|
|
||||||
|
|||||||
@@ -25,15 +25,6 @@ class VerifyAccountManager(AccountBasePlaybookManager):
|
|||||||
f.write('ssh_args = -o ControlMaster=no -o ControlPersist=no\n')
|
f.write('ssh_args = -o ControlMaster=no -o ControlPersist=no\n')
|
||||||
return path
|
return path
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def method_type(cls):
|
|
||||||
return AutomationTypes.verify_account
|
|
||||||
|
|
||||||
def get_accounts(self, privilege_account, accounts: QuerySet):
|
|
||||||
account_ids = self.execution.snapshot['accounts']
|
|
||||||
accounts = accounts.filter(id__in=account_ids)
|
|
||||||
return accounts
|
|
||||||
|
|
||||||
def host_callback(self, host, asset=None, account=None, automation=None, path_dir=None, **kwargs):
|
def host_callback(self, host, asset=None, account=None, automation=None, path_dir=None, **kwargs):
|
||||||
host = super().host_callback(
|
host = super().host_callback(
|
||||||
host, asset=asset, account=account,
|
host, asset=asset, account=account,
|
||||||
@@ -71,6 +62,16 @@ class VerifyAccountManager(AccountBasePlaybookManager):
|
|||||||
inventory_hosts.append(h)
|
inventory_hosts.append(h)
|
||||||
return inventory_hosts
|
return inventory_hosts
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def method_type(cls):
|
||||||
|
return AutomationTypes.verify_account
|
||||||
|
|
||||||
|
def get_accounts(self, privilege_account, accounts: QuerySet):
|
||||||
|
snapshot_account_usernames = self.execution.snapshot['accounts']
|
||||||
|
if '*' not in snapshot_account_usernames:
|
||||||
|
accounts = accounts.filter(username__in=snapshot_account_usernames)
|
||||||
|
return accounts
|
||||||
|
|
||||||
def on_host_success(self, host, result):
|
def on_host_success(self, host, result):
|
||||||
account = self.host_account_mapper.get(host)
|
account = self.host_account_mapper.get(host)
|
||||||
account.set_connectivity(Connectivity.OK)
|
account.set_connectivity(Connectivity.OK)
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
|
from common.utils import get_logger
|
||||||
from accounts.const import AutomationTypes
|
from accounts.const import AutomationTypes
|
||||||
from assets.automations.ping_gateway.manager import PingGatewayManager
|
from assets.automations.ping_gateway.manager import PingGatewayManager
|
||||||
from common.utils import get_logger
|
|
||||||
|
|
||||||
logger = get_logger(__name__)
|
logger = get_logger(__name__)
|
||||||
|
|
||||||
@@ -16,6 +16,6 @@ class VerifyGatewayAccountManager(PingGatewayManager):
|
|||||||
logger.info(">>> 开始执行测试网关账号可连接性任务")
|
logger.info(">>> 开始执行测试网关账号可连接性任务")
|
||||||
|
|
||||||
def get_accounts(self, gateway):
|
def get_accounts(self, gateway):
|
||||||
account_ids = self.execution.snapshot['accounts']
|
usernames = self.execution.snapshot['accounts']
|
||||||
accounts = gateway.accounts.filter(id__in=account_ids)
|
accounts = gateway.accounts.filter(username__in=usernames)
|
||||||
return accounts
|
return accounts
|
||||||
|
|||||||
@@ -18,10 +18,3 @@ class AliasAccount(TextChoices):
|
|||||||
class Source(TextChoices):
|
class Source(TextChoices):
|
||||||
LOCAL = 'local', _('Local')
|
LOCAL = 'local', _('Local')
|
||||||
COLLECTED = 'collected', _('Collected')
|
COLLECTED = 'collected', _('Collected')
|
||||||
TEMPLATE = 'template', _('Template')
|
|
||||||
|
|
||||||
|
|
||||||
class AccountInvalidPolicy(TextChoices):
|
|
||||||
SKIP = 'skip', _('Skip')
|
|
||||||
UPDATE = 'update', _('Update')
|
|
||||||
ERROR = 'error', _('Failed')
|
|
||||||
|
|||||||
@@ -1,69 +0,0 @@
|
|||||||
# Generated by Django 3.2.16 on 2023-03-07 07:36
|
|
||||||
|
|
||||||
from django.db import migrations
|
|
||||||
from django.db.models import Q
|
|
||||||
|
|
||||||
|
|
||||||
def get_nodes_all_assets(apps, *nodes):
|
|
||||||
node_model = apps.get_model('assets', 'Node')
|
|
||||||
asset_model = apps.get_model('assets', 'Asset')
|
|
||||||
node_ids = set()
|
|
||||||
descendant_node_query = Q()
|
|
||||||
for n in nodes:
|
|
||||||
node_ids.add(n.id)
|
|
||||||
descendant_node_query |= Q(key__istartswith=f'{n.key}:')
|
|
||||||
if descendant_node_query:
|
|
||||||
_ids = node_model.objects.order_by().filter(descendant_node_query).values_list('id', flat=True)
|
|
||||||
node_ids.update(_ids)
|
|
||||||
return asset_model.objects.order_by().filter(nodes__id__in=node_ids).distinct()
|
|
||||||
|
|
||||||
|
|
||||||
def get_all_assets(apps, snapshot):
|
|
||||||
node_model = apps.get_model('assets', 'Node')
|
|
||||||
asset_model = apps.get_model('assets', 'Asset')
|
|
||||||
asset_ids = snapshot.get('assets', [])
|
|
||||||
node_ids = snapshot.get('nodes', [])
|
|
||||||
|
|
||||||
nodes = node_model.objects.filter(id__in=node_ids)
|
|
||||||
node_asset_ids = get_nodes_all_assets(apps, *nodes).values_list('id', flat=True)
|
|
||||||
asset_ids = set(list(asset_ids) + list(node_asset_ids))
|
|
||||||
return asset_model.objects.filter(id__in=asset_ids)
|
|
||||||
|
|
||||||
|
|
||||||
def migrate_account_usernames_to_ids(apps, schema_editor):
|
|
||||||
db_alias = schema_editor.connection.alias
|
|
||||||
execution_model = apps.get_model('accounts', 'AutomationExecution')
|
|
||||||
account_model = apps.get_model('accounts', 'Account')
|
|
||||||
executions = execution_model.objects.using(db_alias).all()
|
|
||||||
executions_update = []
|
|
||||||
for execution in executions:
|
|
||||||
snapshot = execution.snapshot
|
|
||||||
accounts = account_model.objects.none()
|
|
||||||
account_usernames = snapshot.get('accounts', [])
|
|
||||||
for asset in get_all_assets(apps, snapshot):
|
|
||||||
accounts = accounts | asset.accounts.all()
|
|
||||||
secret_type = snapshot.get('secret_type')
|
|
||||||
if secret_type:
|
|
||||||
ids = accounts.filter(
|
|
||||||
username__in=account_usernames,
|
|
||||||
secret_type=secret_type
|
|
||||||
).values_list('id', flat=True)
|
|
||||||
else:
|
|
||||||
ids = accounts.filter(
|
|
||||||
username__in=account_usernames
|
|
||||||
).values_list('id', flat=True)
|
|
||||||
snapshot['accounts'] = [str(_id) for _id in ids]
|
|
||||||
execution.snapshot = snapshot
|
|
||||||
executions_update.append(execution)
|
|
||||||
|
|
||||||
execution_model.objects.bulk_update(executions_update, ['snapshot'])
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
dependencies = [
|
|
||||||
('accounts', '0008_alter_gatheredaccount_options'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.RunPython(migrate_account_usernames_to_ids),
|
|
||||||
]
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
# Generated by Django 3.2.16 on 2023-03-23 08:39
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
dependencies = [
|
|
||||||
('accounts', '0009_account_usernames_to_ids'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='gatheraccountsautomation',
|
|
||||||
name='is_sync_account',
|
|
||||||
field=models.BooleanField(blank=True, default=False, verbose_name='Is sync account'),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='account',
|
|
||||||
name='source_id',
|
|
||||||
field=models.CharField(max_length=128, null=True, blank=True, verbose_name='Source ID'),
|
|
||||||
),
|
|
||||||
]
|
|
||||||
@@ -53,7 +53,6 @@ class Account(AbsConnectivity, BaseAccount):
|
|||||||
version = models.IntegerField(default=0, verbose_name=_('Version'))
|
version = models.IntegerField(default=0, verbose_name=_('Version'))
|
||||||
history = AccountHistoricalRecords(included_fields=['id', 'secret', 'secret_type', 'version'])
|
history = AccountHistoricalRecords(included_fields=['id', 'secret', 'secret_type', 'version'])
|
||||||
source = models.CharField(max_length=30, default=Source.LOCAL, verbose_name=_('Source'))
|
source = models.CharField(max_length=30, default=Source.LOCAL, verbose_name=_('Source'))
|
||||||
source_id = models.CharField(max_length=128, null=True, blank=True, verbose_name=_('Source ID'))
|
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = _('Account')
|
verbose_name = _('Account')
|
||||||
|
|||||||
@@ -1,12 +1,11 @@
|
|||||||
from django.db import models
|
from django.db import models
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
|
from common.db import fields
|
||||||
|
from common.db.models import JMSBaseModel
|
||||||
from accounts.const import (
|
from accounts.const import (
|
||||||
AutomationTypes, SecretType, SecretStrategy, SSHKeyStrategy
|
AutomationTypes, SecretType, SecretStrategy, SSHKeyStrategy
|
||||||
)
|
)
|
||||||
from accounts.models import Account
|
|
||||||
from common.db import fields
|
|
||||||
from common.db.models import JMSBaseModel
|
|
||||||
from .base import AccountBaseAutomation
|
from .base import AccountBaseAutomation
|
||||||
|
|
||||||
__all__ = ['ChangeSecretAutomation', 'ChangeSecretRecord', 'ChangeSecretMixin']
|
__all__ = ['ChangeSecretAutomation', 'ChangeSecretRecord', 'ChangeSecretMixin']
|
||||||
@@ -28,34 +27,18 @@ class ChangeSecretMixin(models.Model):
|
|||||||
default=SSHKeyStrategy.add, verbose_name=_('SSH key change strategy')
|
default=SSHKeyStrategy.add, verbose_name=_('SSH key change strategy')
|
||||||
)
|
)
|
||||||
|
|
||||||
get_all_assets: callable # get all assets
|
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
abstract = True
|
abstract = True
|
||||||
|
|
||||||
def create_nonlocal_accounts(self, usernames, asset):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def get_account_ids(self):
|
|
||||||
usernames = self.accounts
|
|
||||||
accounts = Account.objects.none()
|
|
||||||
for asset in self.get_all_assets():
|
|
||||||
self.create_nonlocal_accounts(usernames, asset)
|
|
||||||
accounts = accounts | asset.accounts.all()
|
|
||||||
account_ids = accounts.filter(
|
|
||||||
username__in=usernames, secret_type=self.secret_type
|
|
||||||
).values_list('id', flat=True)
|
|
||||||
return [str(_id) for _id in account_ids]
|
|
||||||
|
|
||||||
def to_attr_json(self):
|
def to_attr_json(self):
|
||||||
attr_json = super().to_attr_json()
|
attr_json = super().to_attr_json()
|
||||||
attr_json.update({
|
attr_json.update({
|
||||||
'secret': self.secret,
|
'secret': self.secret,
|
||||||
'secret_type': self.secret_type,
|
'secret_type': self.secret_type,
|
||||||
'accounts': self.get_account_ids(),
|
|
||||||
'password_rules': self.password_rules,
|
|
||||||
'secret_strategy': self.secret_strategy,
|
'secret_strategy': self.secret_strategy,
|
||||||
|
'password_rules': self.password_rules,
|
||||||
'ssh_key_change_strategy': self.ssh_key_change_strategy,
|
'ssh_key_change_strategy': self.ssh_key_change_strategy,
|
||||||
|
|
||||||
})
|
})
|
||||||
return attr_json
|
return attr_json
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,7 @@
|
|||||||
from django.db import models
|
from django.db import models
|
||||||
from django.db.models import Q
|
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
from accounts.const import AutomationTypes, Source
|
from accounts.const import AutomationTypes
|
||||||
from accounts.models import Account
|
|
||||||
from orgs.mixins.models import JMSOrgBaseModel
|
from orgs.mixins.models import JMSOrgBaseModel
|
||||||
from .base import AccountBaseAutomation
|
from .base import AccountBaseAutomation
|
||||||
|
|
||||||
@@ -21,25 +19,6 @@ class GatheredAccount(JMSOrgBaseModel):
|
|||||||
def address(self):
|
def address(self):
|
||||||
return self.asset.address
|
return self.asset.address
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def sync_accounts(gathered_accounts):
|
|
||||||
account_objs = []
|
|
||||||
for gathered_account in gathered_accounts:
|
|
||||||
asset_id = gathered_account.asset_id
|
|
||||||
username = gathered_account.username
|
|
||||||
accounts = Account.objects.filter(
|
|
||||||
Q(asset_id=asset_id, username=username) |
|
|
||||||
Q(asset_id=asset_id, name=username)
|
|
||||||
)
|
|
||||||
if accounts.exists():
|
|
||||||
continue
|
|
||||||
account = Account(
|
|
||||||
asset_id=asset_id, username=username,
|
|
||||||
name=username, source=Source.COLLECTED
|
|
||||||
)
|
|
||||||
account_objs.append(account)
|
|
||||||
Account.objects.bulk_create(account_objs)
|
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = _('Gather account automation')
|
verbose_name = _('Gather account automation')
|
||||||
unique_together = [
|
unique_together = [
|
||||||
@@ -52,17 +31,6 @@ class GatheredAccount(JMSOrgBaseModel):
|
|||||||
|
|
||||||
|
|
||||||
class GatherAccountsAutomation(AccountBaseAutomation):
|
class GatherAccountsAutomation(AccountBaseAutomation):
|
||||||
is_sync_account = models.BooleanField(
|
|
||||||
default=False, blank=True, verbose_name=_("Is sync account")
|
|
||||||
)
|
|
||||||
|
|
||||||
def to_attr_json(self):
|
|
||||||
attr_json = super().to_attr_json()
|
|
||||||
attr_json.update({
|
|
||||||
'is_sync_account': self.is_sync_account,
|
|
||||||
})
|
|
||||||
return attr_json
|
|
||||||
|
|
||||||
def save(self, *args, **kwargs):
|
def save(self, *args, **kwargs):
|
||||||
self.type = AutomationTypes.gather_accounts
|
self.type = AutomationTypes.gather_accounts
|
||||||
super().save(*args, **kwargs)
|
super().save(*args, **kwargs)
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ from django.db import models
|
|||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
from accounts.const import AutomationTypes
|
from accounts.const import AutomationTypes
|
||||||
from accounts.models import Account
|
|
||||||
from jumpserver.utils import has_valid_xpack_license
|
from jumpserver.utils import has_valid_xpack_license
|
||||||
from .base import AccountBaseAutomation
|
from .base import AccountBaseAutomation
|
||||||
from .change_secret import ChangeSecretMixin
|
from .change_secret import ChangeSecretMixin
|
||||||
@@ -15,21 +14,6 @@ class PushAccountAutomation(ChangeSecretMixin, AccountBaseAutomation):
|
|||||||
username = models.CharField(max_length=128, verbose_name=_('Username'))
|
username = models.CharField(max_length=128, verbose_name=_('Username'))
|
||||||
action = models.CharField(max_length=16, verbose_name=_('Action'))
|
action = models.CharField(max_length=16, verbose_name=_('Action'))
|
||||||
|
|
||||||
def create_nonlocal_accounts(self, usernames, asset):
|
|
||||||
secret_type = self.secret_type
|
|
||||||
account_usernames = asset.accounts.filter(secret_type=self.secret_type).values_list(
|
|
||||||
'username', flat=True
|
|
||||||
)
|
|
||||||
create_usernames = set(usernames) - set(account_usernames)
|
|
||||||
create_account_objs = [
|
|
||||||
Account(
|
|
||||||
name=f'{username}-{secret_type}', username=username,
|
|
||||||
secret_type=secret_type, asset=asset,
|
|
||||||
)
|
|
||||||
for username in create_usernames
|
|
||||||
]
|
|
||||||
Account.objects.bulk_create(create_account_objs)
|
|
||||||
|
|
||||||
def set_period_schedule(self):
|
def set_period_schedule(self):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@@ -51,8 +35,7 @@ class PushAccountAutomation(ChangeSecretMixin, AccountBaseAutomation):
|
|||||||
def to_attr_json(self):
|
def to_attr_json(self):
|
||||||
attr_json = super().to_attr_json()
|
attr_json = super().to_attr_json()
|
||||||
attr_json.update({
|
attr_json.update({
|
||||||
'username': self.username,
|
'username': self.username
|
||||||
'params': self.params,
|
|
||||||
})
|
})
|
||||||
return attr_json
|
return attr_json
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ from accounts.const import SecretType
|
|||||||
from common.db import fields
|
from common.db import fields
|
||||||
from common.utils import (
|
from common.utils import (
|
||||||
ssh_key_string_to_obj, ssh_key_gen, get_logger,
|
ssh_key_string_to_obj, ssh_key_gen, get_logger,
|
||||||
random_string, lazyproperty, parse_ssh_public_key_str, is_openssh_format_key
|
random_string, lazyproperty, parse_ssh_public_key_str
|
||||||
)
|
)
|
||||||
from orgs.mixins.models import JMSOrgBaseModel, OrgManager
|
from orgs.mixins.models import JMSOrgBaseModel, OrgManager
|
||||||
|
|
||||||
@@ -118,13 +118,7 @@ class BaseAccount(JMSOrgBaseModel):
|
|||||||
key_name = '.' + md5(self.private_key.encode('utf-8')).hexdigest()
|
key_name = '.' + md5(self.private_key.encode('utf-8')).hexdigest()
|
||||||
key_path = os.path.join(tmp_dir, key_name)
|
key_path = os.path.join(tmp_dir, key_name)
|
||||||
if not os.path.exists(key_path):
|
if not os.path.exists(key_path):
|
||||||
# https://github.com/ansible/ansible-runner/issues/544
|
self.private_key_obj.write_private_key_file(key_path)
|
||||||
# ssh requires OpenSSH format keys to have a full ending newline.
|
|
||||||
# It does not require this for old-style PEM keys.
|
|
||||||
with open(key_path, 'w') as f:
|
|
||||||
f.write(self.secret)
|
|
||||||
if is_openssh_format_key(self.secret.encode('utf-8')):
|
|
||||||
f.write("\n")
|
|
||||||
os.chmod(key_path, 0o400)
|
os.chmod(key_path, 0o400)
|
||||||
return key_path
|
return key_path
|
||||||
|
|
||||||
|
|||||||
@@ -1,179 +1,75 @@
|
|||||||
import uuid
|
|
||||||
|
|
||||||
from django.db import IntegrityError
|
|
||||||
from django.db.models import Q
|
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
from rest_framework.validators import UniqueTogetherValidator
|
|
||||||
|
|
||||||
from accounts.const import SecretType, Source, AccountInvalidPolicy
|
from accounts.const import SecretType, Source
|
||||||
from accounts.models import Account, AccountTemplate
|
from accounts.models import Account, AccountTemplate
|
||||||
from accounts.tasks import push_accounts_to_assets_task
|
from accounts.tasks import push_accounts_to_assets_task
|
||||||
from assets.const import Category, AllTypes
|
from assets.const import Category, AllTypes
|
||||||
from assets.models import Asset
|
from assets.models import Asset
|
||||||
from common.serializers import SecretReadableMixin
|
from common.serializers import SecretReadableMixin, BulkModelSerializer
|
||||||
from common.serializers.fields import ObjectRelatedField, LabeledChoiceField
|
from common.serializers.fields import ObjectRelatedField, LabeledChoiceField
|
||||||
from common.utils import get_logger
|
from .base import BaseAccountSerializer
|
||||||
from .base import BaseAccountSerializer, AuthValidateMixin
|
|
||||||
|
|
||||||
logger = get_logger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
class AccountCreateUpdateSerializerMixin(serializers.Serializer):
|
class AccountSerializerCreateValidateMixin:
|
||||||
template = serializers.PrimaryKeyRelatedField(
|
from_id: str
|
||||||
queryset=AccountTemplate.objects,
|
template: bool
|
||||||
required=False, label=_("Template"), write_only=True
|
push_now: bool
|
||||||
)
|
replace_attrs: callable
|
||||||
push_now = serializers.BooleanField(
|
|
||||||
default=False, label=_("Push now"), write_only=True
|
|
||||||
)
|
|
||||||
params = serializers.JSONField(
|
|
||||||
decoder=None, encoder=None, required=False, style={'base_template': 'textarea.html'}
|
|
||||||
)
|
|
||||||
on_invalid = LabeledChoiceField(
|
|
||||||
choices=AccountInvalidPolicy.choices, default=AccountInvalidPolicy.ERROR,
|
|
||||||
write_only=True, label=_('Exist policy')
|
|
||||||
)
|
|
||||||
_template = None
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
fields = ['template', 'push_now', 'params', 'on_invalid']
|
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
|
||||||
super().__init__(*args, **kwargs)
|
|
||||||
self.set_initial_value()
|
|
||||||
|
|
||||||
def set_initial_value(self):
|
|
||||||
if not getattr(self, 'initial_data', None):
|
|
||||||
return
|
|
||||||
if isinstance(self.initial_data, dict):
|
|
||||||
initial_data = [self.initial_data]
|
|
||||||
else:
|
|
||||||
initial_data = self.initial_data
|
|
||||||
|
|
||||||
for data in initial_data:
|
|
||||||
if not data.get('asset') and not self.instance:
|
|
||||||
raise serializers.ValidationError({'asset': UniqueTogetherValidator.missing_message})
|
|
||||||
asset = data.get('asset') or self.instance.asset
|
|
||||||
self.from_template_if_need(data)
|
|
||||||
self.set_uniq_name_if_need(data, asset)
|
|
||||||
|
|
||||||
def to_internal_value(self, data):
|
def to_internal_value(self, data):
|
||||||
self.from_template_if_need(data)
|
from_id = data.pop('id', None)
|
||||||
return super().to_internal_value(data)
|
ret = super().to_internal_value(data)
|
||||||
|
self.from_id = from_id
|
||||||
|
return ret
|
||||||
|
|
||||||
def set_uniq_name_if_need(self, initial_data, asset):
|
def set_secret(self, attrs):
|
||||||
name = initial_data.get('name')
|
_id = self.from_id
|
||||||
if name is not None:
|
template = attrs.pop('template', None)
|
||||||
return
|
|
||||||
if not name:
|
|
||||||
name = initial_data.get('username')
|
|
||||||
if self.instance and self.instance.name == name:
|
|
||||||
return
|
|
||||||
if Account.objects.filter(name=name, asset=asset).exists():
|
|
||||||
name = name + '_' + uuid.uuid4().hex[:4]
|
|
||||||
initial_data['name'] = name
|
|
||||||
|
|
||||||
def from_template_if_need(self, initial_data):
|
if _id and template:
|
||||||
if isinstance(initial_data, str):
|
account_template = AccountTemplate.objects.get(id=_id)
|
||||||
return
|
attrs['secret'] = account_template.secret
|
||||||
|
elif _id and not template:
|
||||||
|
account = Account.objects.get(id=_id)
|
||||||
|
attrs['secret'] = account.secret
|
||||||
|
return attrs
|
||||||
|
|
||||||
template_id = initial_data.pop('template', None)
|
def validate(self, attrs):
|
||||||
if not template_id:
|
attrs = super().validate(attrs)
|
||||||
return
|
return self.set_secret(attrs)
|
||||||
|
|
||||||
if isinstance(template_id, (str, uuid.UUID)):
|
|
||||||
template = AccountTemplate.objects.filter(id=template_id).first()
|
|
||||||
else:
|
|
||||||
template = template_id
|
|
||||||
if not template:
|
|
||||||
raise serializers.ValidationError({'template': 'Template not found'})
|
|
||||||
|
|
||||||
self._template = template
|
|
||||||
# Set initial data from template
|
|
||||||
ignore_fields = ['id', 'date_created', 'date_updated', 'org_id']
|
|
||||||
field_names = [
|
|
||||||
field.name for field in template._meta.fields
|
|
||||||
if field.name not in ignore_fields
|
|
||||||
]
|
|
||||||
attrs = {}
|
|
||||||
for name in field_names:
|
|
||||||
value = getattr(template, name, None)
|
|
||||||
if value is None:
|
|
||||||
continue
|
|
||||||
attrs[name] = value
|
|
||||||
initial_data.update(attrs)
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def push_account_if_need(instance, push_now, params, stat):
|
def push_account(instance, push_now):
|
||||||
if not push_now or stat not in ['created', 'updated']:
|
if not push_now:
|
||||||
return
|
return
|
||||||
push_accounts_to_assets_task.delay([str(instance.id)], params)
|
push_accounts_to_assets_task.delay([str(instance.id)])
|
||||||
|
|
||||||
def get_validators(self):
|
|
||||||
_validators = super().get_validators()
|
|
||||||
if getattr(self, 'initial_data', None) is None:
|
|
||||||
return _validators
|
|
||||||
|
|
||||||
on_invalid = self.initial_data.get('on_invalid')
|
|
||||||
if on_invalid == AccountInvalidPolicy.ERROR and not self.parent:
|
|
||||||
return _validators
|
|
||||||
_validators = [v for v in _validators if not isinstance(v, UniqueTogetherValidator)]
|
|
||||||
return _validators
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def do_create(vd):
|
|
||||||
on_invalid = vd.pop('on_invalid', None)
|
|
||||||
|
|
||||||
q = Q()
|
|
||||||
if vd.get('name'):
|
|
||||||
q |= Q(name=vd['name'])
|
|
||||||
if vd.get('username'):
|
|
||||||
q |= Q(username=vd['username'], secret_type=vd.get('secret_type'))
|
|
||||||
|
|
||||||
instance = Account.objects.filter(asset=vd['asset']).filter(q).first()
|
|
||||||
# 不存在这个资产,不用关系策略
|
|
||||||
if not instance:
|
|
||||||
instance = Account.objects.create(**vd)
|
|
||||||
return instance, 'created'
|
|
||||||
|
|
||||||
if on_invalid == AccountInvalidPolicy.SKIP:
|
|
||||||
return instance, 'skipped'
|
|
||||||
elif on_invalid == AccountInvalidPolicy.UPDATE:
|
|
||||||
for k, v in vd.items():
|
|
||||||
setattr(instance, k, v)
|
|
||||||
instance.save()
|
|
||||||
return instance, 'updated'
|
|
||||||
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):
|
def create(self, validated_data):
|
||||||
push_now = validated_data.pop('push_now', None)
|
push_now = validated_data.pop('push_now', None)
|
||||||
params = validated_data.pop('params', None)
|
instance = super().create(validated_data)
|
||||||
self.generate_source_data(validated_data)
|
self.push_account(instance, push_now)
|
||||||
instance, stat = self.do_create(validated_data)
|
|
||||||
self.push_account_if_need(instance, push_now, params, stat)
|
|
||||||
return instance
|
return instance
|
||||||
|
|
||||||
def update(self, instance, validated_data):
|
def update(self, instance, validated_data):
|
||||||
# account cannot be modified
|
# account cannot be modified
|
||||||
validated_data.pop('username', None)
|
validated_data.pop('username', None)
|
||||||
validated_data.pop('on_invalid', None)
|
|
||||||
push_now = validated_data.pop('push_now', None)
|
push_now = validated_data.pop('push_now', None)
|
||||||
params = validated_data.pop('params', None)
|
|
||||||
validated_data['source_id'] = None
|
|
||||||
instance = super().update(instance, validated_data)
|
instance = super().update(instance, validated_data)
|
||||||
self.push_account_if_need(instance, push_now, params, 'updated')
|
self.push_account(instance, push_now)
|
||||||
return instance
|
return instance
|
||||||
|
|
||||||
|
|
||||||
|
class AccountSerializerCreateMixin(AccountSerializerCreateValidateMixin, BulkModelSerializer):
|
||||||
|
template = serializers.BooleanField(
|
||||||
|
default=False, label=_("Template"), write_only=True
|
||||||
|
)
|
||||||
|
push_now = serializers.BooleanField(
|
||||||
|
default=False, label=_("Push now"), write_only=True
|
||||||
|
)
|
||||||
|
has_secret = serializers.BooleanField(label=_("Has secret"), read_only=True)
|
||||||
|
|
||||||
|
|
||||||
class AccountAssetSerializer(serializers.ModelSerializer):
|
class AccountAssetSerializer(serializers.ModelSerializer):
|
||||||
platform = ObjectRelatedField(read_only=True)
|
platform = ObjectRelatedField(read_only=True)
|
||||||
category = LabeledChoiceField(choices=Category.choices, read_only=True, label=_('Category'))
|
category = LabeledChoiceField(choices=Category.choices, read_only=True, label=_('Category'))
|
||||||
@@ -181,11 +77,11 @@ class AccountAssetSerializer(serializers.ModelSerializer):
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Asset
|
model = Asset
|
||||||
fields = ['id', 'name', 'address', 'type', 'category', 'platform', 'auto_config']
|
fields = ['id', 'name', 'address', 'type', 'category', 'platform', 'auto_info']
|
||||||
|
|
||||||
def to_internal_value(self, data):
|
def to_internal_value(self, data):
|
||||||
if isinstance(data, dict):
|
if isinstance(data, dict):
|
||||||
i = data.get('id') or data.get('pk')
|
i = data.get('id')
|
||||||
else:
|
else:
|
||||||
i = data
|
i = data
|
||||||
|
|
||||||
@@ -195,10 +91,9 @@ class AccountAssetSerializer(serializers.ModelSerializer):
|
|||||||
raise serializers.ValidationError(_('Asset not found'))
|
raise serializers.ValidationError(_('Asset not found'))
|
||||||
|
|
||||||
|
|
||||||
class AccountSerializer(AccountCreateUpdateSerializerMixin, BaseAccountSerializer):
|
class AccountSerializer(AccountSerializerCreateMixin, BaseAccountSerializer):
|
||||||
asset = AccountAssetSerializer(label=_('Asset'))
|
asset = AccountAssetSerializer(label=_('Asset'))
|
||||||
source = LabeledChoiceField(choices=Source.choices, label=_("Source"), read_only=True)
|
source = LabeledChoiceField(choices=Source.choices, label=_("Source"), read_only=True)
|
||||||
has_secret = serializers.BooleanField(label=_("Has secret"), read_only=True)
|
|
||||||
su_from = ObjectRelatedField(
|
su_from = ObjectRelatedField(
|
||||||
required=False, queryset=Account.objects, allow_null=True, allow_empty=True,
|
required=False, queryset=Account.objects, allow_null=True, allow_empty=True,
|
||||||
label=_('Su from'), attrs=('id', 'name', 'username')
|
label=_('Su from'), attrs=('id', 'name', 'username')
|
||||||
@@ -207,182 +102,27 @@ class AccountSerializer(AccountCreateUpdateSerializerMixin, BaseAccountSerialize
|
|||||||
class Meta(BaseAccountSerializer.Meta):
|
class Meta(BaseAccountSerializer.Meta):
|
||||||
model = Account
|
model = Account
|
||||||
fields = BaseAccountSerializer.Meta.fields + [
|
fields = BaseAccountSerializer.Meta.fields + [
|
||||||
'su_from', 'asset', 'version',
|
'su_from', 'asset', 'template', 'version',
|
||||||
'source', 'source_id', 'connectivity',
|
'push_now', 'source', 'connectivity',
|
||||||
] + AccountCreateUpdateSerializerMixin.Meta.fields
|
|
||||||
read_only_fields = BaseAccountSerializer.Meta.read_only_fields + [
|
|
||||||
'source', 'source_id', 'connectivity'
|
|
||||||
]
|
]
|
||||||
extra_kwargs = {
|
extra_kwargs = {
|
||||||
**BaseAccountSerializer.Meta.extra_kwargs,
|
**BaseAccountSerializer.Meta.extra_kwargs,
|
||||||
'name': {'required': False},
|
'name': {'required': False, 'allow_null': True},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def validate_name(self, value):
|
||||||
|
if not value:
|
||||||
|
value = self.initial_data.get('username')
|
||||||
|
return value
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def setup_eager_loading(cls, queryset):
|
def setup_eager_loading(cls, queryset):
|
||||||
""" Perform necessary eager loading of data. """
|
""" Perform necessary eager loading of data. """
|
||||||
queryset = queryset.prefetch_related(
|
queryset = queryset \
|
||||||
'asset', 'asset__platform',
|
.prefetch_related('asset', 'asset__platform', 'asset__platform__automation')
|
||||||
'asset__platform__automation'
|
|
||||||
)
|
|
||||||
return queryset
|
return queryset
|
||||||
|
|
||||||
|
|
||||||
class AssetAccountBulkSerializerResultSerializer(serializers.Serializer):
|
|
||||||
asset = serializers.CharField(read_only=True, label=_('Asset'))
|
|
||||||
state = serializers.CharField(read_only=True, label=_('State'))
|
|
||||||
error = serializers.CharField(read_only=True, label=_('Error'))
|
|
||||||
changed = serializers.BooleanField(read_only=True, label=_('Changed'))
|
|
||||||
|
|
||||||
|
|
||||||
class AssetAccountBulkSerializer(
|
|
||||||
AccountCreateUpdateSerializerMixin, AuthValidateMixin, serializers.ModelSerializer
|
|
||||||
):
|
|
||||||
assets = serializers.PrimaryKeyRelatedField(queryset=Asset.objects, many=True, label=_('Assets'))
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
model = Account
|
|
||||||
fields = [
|
|
||||||
'name', 'username', 'secret', 'secret_type',
|
|
||||||
'privileged', 'is_active', 'comment', 'template',
|
|
||||||
'on_invalid', 'push_now', 'assets',
|
|
||||||
]
|
|
||||||
extra_kwargs = {
|
|
||||||
'name': {'required': False},
|
|
||||||
'secret_type': {'required': False},
|
|
||||||
}
|
|
||||||
|
|
||||||
def set_initial_value(self):
|
|
||||||
if not getattr(self, 'initial_data', None):
|
|
||||||
return
|
|
||||||
initial_data = self.initial_data
|
|
||||||
self.from_template_if_need(initial_data)
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def get_filter_lookup(vd):
|
|
||||||
return {
|
|
||||||
'username': vd['username'],
|
|
||||||
'secret_type': vd['secret_type'],
|
|
||||||
'asset': vd['asset'],
|
|
||||||
}
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def get_uniq_name(vd):
|
|
||||||
return vd['name'] + '-' + uuid.uuid4().hex[:4]
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def _handle_update_create(vd, lookup):
|
|
||||||
ori = Account.objects.filter(**lookup).first()
|
|
||||||
if ori and ori.secret == vd.get('secret'):
|
|
||||||
return ori, False, 'skipped'
|
|
||||||
|
|
||||||
instance, value = Account.objects.update_or_create(defaults=vd, **lookup)
|
|
||||||
state = 'created' if value else 'updated'
|
|
||||||
return instance, True, state
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def _handle_skip_create(vd, lookup):
|
|
||||||
instance, value = Account.objects.get_or_create(defaults=vd, **lookup)
|
|
||||||
state = 'created' if value else 'skipped'
|
|
||||||
return instance, value, state
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def _handle_err_create(vd, lookup):
|
|
||||||
instance, value = Account.objects.get_or_create(defaults=vd, **lookup)
|
|
||||||
if not value:
|
|
||||||
raise serializers.ValidationError(_('Account already exists'))
|
|
||||||
return instance, True, 'created'
|
|
||||||
|
|
||||||
def perform_create(self, vd, handler):
|
|
||||||
lookup = self.get_filter_lookup(vd)
|
|
||||||
try:
|
|
||||||
instance, changed, state = handler(vd, lookup)
|
|
||||||
except IntegrityError:
|
|
||||||
vd['name'] = self.get_uniq_name(vd)
|
|
||||||
instance, changed, state = handler(vd, lookup)
|
|
||||||
return instance, changed, state
|
|
||||||
|
|
||||||
def get_create_handler(self, on_invalid):
|
|
||||||
if on_invalid == 'update':
|
|
||||||
handler = self._handle_update_create
|
|
||||||
elif on_invalid == 'skip':
|
|
||||||
handler = self._handle_skip_create
|
|
||||||
else:
|
|
||||||
handler = self._handle_err_create
|
|
||||||
return handler
|
|
||||||
|
|
||||||
def perform_bulk_create(self, vd):
|
|
||||||
assets = vd.pop('assets')
|
|
||||||
on_invalid = vd.pop('on_invalid', 'skip')
|
|
||||||
secret_type = vd.get('secret_type', 'password')
|
|
||||||
|
|
||||||
if not vd.get('name'):
|
|
||||||
vd['name'] = vd.get('username')
|
|
||||||
|
|
||||||
create_handler = self.get_create_handler(on_invalid)
|
|
||||||
asset_ids = [asset.id for asset in assets]
|
|
||||||
secret_type_supports = Asset.get_secret_type_assets(asset_ids, secret_type)
|
|
||||||
|
|
||||||
_results = {}
|
|
||||||
for asset in assets:
|
|
||||||
if asset not in secret_type_supports:
|
|
||||||
_results[asset] = {
|
|
||||||
'error': _('Asset does not support this secret type: %s') % secret_type,
|
|
||||||
'state': 'error',
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
|
|
||||||
vd = vd.copy()
|
|
||||||
vd['asset'] = asset
|
|
||||||
try:
|
|
||||||
instance, changed, state = self.perform_create(vd, create_handler)
|
|
||||||
_results[asset] = {
|
|
||||||
'changed': changed, 'instance': instance.id, 'state': state
|
|
||||||
}
|
|
||||||
except serializers.ValidationError as e:
|
|
||||||
_results[asset] = {'error': e.detail[0], 'state': 'error'}
|
|
||||||
except Exception as e:
|
|
||||||
logger.exception(e)
|
|
||||||
_results[asset] = {'error': str(e), 'state': 'error'}
|
|
||||||
|
|
||||||
results = [{'asset': asset, **result} for asset, result in _results.items()]
|
|
||||||
state_score = {'created': 3, 'updated': 2, 'skipped': 1, 'error': 0}
|
|
||||||
results = sorted(results, key=lambda x: state_score.get(x['state'], 4))
|
|
||||||
|
|
||||||
if on_invalid != 'error':
|
|
||||||
return results
|
|
||||||
|
|
||||||
errors = []
|
|
||||||
errors.extend([result for result in results if result['state'] == 'error'])
|
|
||||||
for result in results:
|
|
||||||
if result['state'] != 'skipped':
|
|
||||||
continue
|
|
||||||
errors.append({
|
|
||||||
'error': _('Account has exist'),
|
|
||||||
'state': 'error',
|
|
||||||
'asset': str(result['asset'])
|
|
||||||
})
|
|
||||||
if errors:
|
|
||||||
raise serializers.ValidationError(errors)
|
|
||||||
return results
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def push_accounts_if_need(results, push_now):
|
|
||||||
if not push_now:
|
|
||||||
return
|
|
||||||
accounts = [str(v['instance']) for v in results if v.get('instance')]
|
|
||||||
push_accounts_to_assets_task.delay(accounts)
|
|
||||||
|
|
||||||
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:
|
|
||||||
res['asset'] = str(res['asset'])
|
|
||||||
return results
|
|
||||||
|
|
||||||
|
|
||||||
class AccountSecretSerializer(SecretReadableMixin, AccountSerializer):
|
class AccountSecretSerializer(SecretReadableMixin, AccountSerializer):
|
||||||
class Meta(AccountSerializer.Meta):
|
class Meta(AccountSerializer.Meta):
|
||||||
extra_kwargs = {
|
extra_kwargs = {
|
||||||
@@ -392,19 +132,11 @@ class AccountSecretSerializer(SecretReadableMixin, AccountSerializer):
|
|||||||
|
|
||||||
class AccountHistorySerializer(serializers.ModelSerializer):
|
class AccountHistorySerializer(serializers.ModelSerializer):
|
||||||
secret_type = LabeledChoiceField(choices=SecretType.choices, label=_('Secret type'))
|
secret_type = LabeledChoiceField(choices=SecretType.choices, label=_('Secret type'))
|
||||||
id = serializers.IntegerField(label=_('ID'), source='history_id', read_only=True)
|
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Account.history.model
|
model = Account.history.model
|
||||||
fields = [
|
fields = ['id', 'secret', 'secret_type', 'version', 'history_date', 'history_user']
|
||||||
'id', 'secret', 'secret_type', 'version', 'history_date',
|
|
||||||
'history_user'
|
|
||||||
]
|
|
||||||
read_only_fields = fields
|
read_only_fields = fields
|
||||||
extra_kwargs = {
|
|
||||||
'history_user': {'label': _('User')},
|
|
||||||
'history_date': {'label': _('Date')},
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class AccountTaskSerializer(serializers.Serializer):
|
class AccountTaskSerializer(serializers.Serializer):
|
||||||
@@ -418,7 +150,3 @@ class AccountTaskSerializer(serializers.Serializer):
|
|||||||
queryset=Account.objects, required=False, allow_empty=True, many=True
|
queryset=Account.objects, required=False, allow_empty=True, many=True
|
||||||
)
|
)
|
||||||
task = serializers.CharField(read_only=True)
|
task = serializers.CharField(read_only=True)
|
||||||
params = serializers.JSONField(
|
|
||||||
decoder=None, encoder=None, required=False,
|
|
||||||
style={'base_template': 'textarea.html'}
|
|
||||||
)
|
|
||||||
|
|||||||
@@ -13,10 +13,10 @@ __all__ = ['AuthValidateMixin', 'BaseAccountSerializer']
|
|||||||
|
|
||||||
class AuthValidateMixin(serializers.Serializer):
|
class AuthValidateMixin(serializers.Serializer):
|
||||||
secret_type = LabeledChoiceField(
|
secret_type = LabeledChoiceField(
|
||||||
choices=SecretType.choices, label=_('Secret type'), default='password'
|
choices=SecretType.choices, required=True, label=_('Secret type')
|
||||||
)
|
)
|
||||||
secret = EncryptedField(
|
secret = EncryptedField(
|
||||||
label=_('Secret'), required=False, max_length=40960, allow_blank=True,
|
label=_('Secret/Password'), required=False, max_length=40960, allow_blank=True,
|
||||||
allow_null=True, write_only=True,
|
allow_null=True, write_only=True,
|
||||||
)
|
)
|
||||||
passphrase = serializers.CharField(
|
passphrase = serializers.CharField(
|
||||||
@@ -33,8 +33,7 @@ class AuthValidateMixin(serializers.Serializer):
|
|||||||
return secret
|
return secret
|
||||||
elif secret_type == SecretType.SSH_KEY:
|
elif secret_type == SecretType.SSH_KEY:
|
||||||
passphrase = passphrase if passphrase else None
|
passphrase = passphrase if passphrase else None
|
||||||
secret = validate_ssh_key(secret, passphrase)
|
return validate_ssh_key(secret, passphrase)
|
||||||
return secret
|
|
||||||
else:
|
else:
|
||||||
return secret
|
return secret
|
||||||
|
|
||||||
@@ -42,9 +41,8 @@ class AuthValidateMixin(serializers.Serializer):
|
|||||||
secret_type = validated_data.get('secret_type')
|
secret_type = validated_data.get('secret_type')
|
||||||
passphrase = validated_data.get('passphrase')
|
passphrase = validated_data.get('passphrase')
|
||||||
secret = validated_data.pop('secret', None)
|
secret = validated_data.pop('secret', None)
|
||||||
validated_data['secret'] = self.handle_secret(
|
self.handle_secret(secret, secret_type, passphrase)
|
||||||
secret, secret_type, passphrase
|
validated_data['secret'] = secret
|
||||||
)
|
|
||||||
for field in ('secret',):
|
for field in ('secret',):
|
||||||
value = validated_data.get(field)
|
value = validated_data.get(field)
|
||||||
if not value:
|
if not value:
|
||||||
@@ -77,5 +75,6 @@ class BaseAccountSerializer(AuthValidateMixin, BulkOrgResourceModelSerializer):
|
|||||||
'date_verified', 'created_by', 'date_created',
|
'date_verified', 'created_by', 'date_created',
|
||||||
]
|
]
|
||||||
extra_kwargs = {
|
extra_kwargs = {
|
||||||
|
'name': {'required': True},
|
||||||
'spec_info': {'label': _('Spec info')},
|
'spec_info': {'label': _('Spec info')},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,4 @@
|
|||||||
from django.db.transaction import atomic
|
from accounts.models import AccountTemplate
|
||||||
from django.db.utils import IntegrityError
|
|
||||||
|
|
||||||
from accounts.models import AccountTemplate, Account
|
|
||||||
from assets.models import Asset
|
|
||||||
from common.serializers import SecretReadableMixin
|
from common.serializers import SecretReadableMixin
|
||||||
from .base import BaseAccountSerializer
|
from .base import BaseAccountSerializer
|
||||||
|
|
||||||
@@ -11,77 +7,17 @@ class AccountTemplateSerializer(BaseAccountSerializer):
|
|||||||
class Meta(BaseAccountSerializer.Meta):
|
class Meta(BaseAccountSerializer.Meta):
|
||||||
model = AccountTemplate
|
model = AccountTemplate
|
||||||
|
|
||||||
@staticmethod
|
# @classmethod
|
||||||
def account_save(data, account):
|
# def validate_required(cls, attrs):
|
||||||
for field, value in data.items():
|
# # TODO 选择模版后检查一些必填项
|
||||||
setattr(account, field, value)
|
# required_field_dict = {}
|
||||||
try:
|
# error = _('This field is required.')
|
||||||
account.save(update_fields=list(data.keys()))
|
# for k, v in cls().fields.items():
|
||||||
except IntegrityError:
|
# if v.required and k not in attrs:
|
||||||
pass
|
# required_field_dict[k] = error
|
||||||
|
# if not required_field_dict:
|
||||||
# TODO 数据库访问的太多了 后期优化
|
# return
|
||||||
@atomic()
|
# raise serializers.ValidationError(required_field_dict)
|
||||||
def bulk_update_accounts(self, instance, diff):
|
|
||||||
accounts = Account.objects.filter(source_id=instance.id)
|
|
||||||
if not accounts:
|
|
||||||
return
|
|
||||||
|
|
||||||
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 update(self, instance, validated_data):
|
|
||||||
# diff = {
|
|
||||||
# k: v for k, v in validated_data.items()
|
|
||||||
# if getattr(instance, k) != v
|
|
||||||
# }
|
|
||||||
instance = super().update(instance, validated_data)
|
|
||||||
# self.bulk_update_accounts(instance, diff)
|
|
||||||
return instance
|
|
||||||
|
|
||||||
|
|
||||||
class AccountTemplateSecretSerializer(SecretReadableMixin, AccountTemplateSerializer):
|
class AccountTemplateSecretSerializer(SecretReadableMixin, AccountTemplateSerializer):
|
||||||
|
|||||||
@@ -58,7 +58,6 @@ class ChangeSecretAutomationSerializer(AuthValidateMixin, BaseAutomationSerializ
|
|||||||
"Currently only mail sending is supported"
|
"Currently only mail sending is supported"
|
||||||
)},
|
)},
|
||||||
}}
|
}}
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def model_type(self):
|
def model_type(self):
|
||||||
return AutomationTypes.change_secret
|
return AutomationTypes.change_secret
|
||||||
|
|||||||
@@ -17,8 +17,7 @@ class GatherAccountAutomationSerializer(BaseAutomationSerializer):
|
|||||||
class Meta:
|
class Meta:
|
||||||
model = GatherAccountsAutomation
|
model = GatherAccountsAutomation
|
||||||
read_only_fields = BaseAutomationSerializer.Meta.read_only_fields
|
read_only_fields = BaseAutomationSerializer.Meta.read_only_fields
|
||||||
fields = BaseAutomationSerializer.Meta.fields \
|
fields = BaseAutomationSerializer.Meta.fields + read_only_fields
|
||||||
+ ['is_sync_account'] + read_only_fields
|
|
||||||
|
|
||||||
extra_kwargs = BaseAutomationSerializer.Meta.extra_kwargs
|
extra_kwargs = BaseAutomationSerializer.Meta.extra_kwargs
|
||||||
|
|
||||||
|
|||||||
@@ -7,10 +7,9 @@ from .change_secret import (
|
|||||||
|
|
||||||
|
|
||||||
class PushAccountAutomationSerializer(ChangeSecretAutomationSerializer):
|
class PushAccountAutomationSerializer(ChangeSecretAutomationSerializer):
|
||||||
|
|
||||||
class Meta(ChangeSecretAutomationSerializer.Meta):
|
class Meta(ChangeSecretAutomationSerializer.Meta):
|
||||||
model = PushAccountAutomation
|
model = PushAccountAutomation
|
||||||
fields = ['params'] + [
|
fields = [
|
||||||
n for n in ChangeSecretAutomationSerializer.Meta.fields
|
n for n in ChangeSecretAutomationSerializer.Meta.fields
|
||||||
if n not in ['recipients']
|
if n not in ['recipients']
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -8,8 +8,8 @@ logger = get_logger(__name__)
|
|||||||
|
|
||||||
|
|
||||||
@receiver(pre_save, sender=Account)
|
@receiver(pre_save, sender=Account)
|
||||||
def on_account_pre_save(sender, instance, **kwargs):
|
def on_account_pre_save(sender, instance, created=False, **kwargs):
|
||||||
if instance.version == 0:
|
if created:
|
||||||
instance.version = 1
|
instance.version = 1
|
||||||
else:
|
else:
|
||||||
instance.version = instance.history.count()
|
instance.version = instance.history.count()
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ __all__ = [
|
|||||||
queue="ansible", verbose_name=_('Push accounts to assets'),
|
queue="ansible", verbose_name=_('Push accounts to assets'),
|
||||||
activity_callback=lambda self, account_ids, *args, **kwargs: (account_ids, None)
|
activity_callback=lambda self, account_ids, *args, **kwargs: (account_ids, None)
|
||||||
)
|
)
|
||||||
def push_accounts_to_assets_task(account_ids, params=None):
|
def push_accounts_to_assets_task(account_ids):
|
||||||
from accounts.models import PushAccountAutomation
|
from accounts.models import PushAccountAutomation
|
||||||
from accounts.models import Account
|
from accounts.models import Account
|
||||||
|
|
||||||
@@ -23,11 +23,12 @@ def push_accounts_to_assets_task(account_ids, params=None):
|
|||||||
task_name = gettext_noop("Push accounts to assets")
|
task_name = gettext_noop("Push accounts to assets")
|
||||||
task_name = PushAccountAutomation.generate_unique_name(task_name)
|
task_name = PushAccountAutomation.generate_unique_name(task_name)
|
||||||
|
|
||||||
|
for account in accounts:
|
||||||
task_snapshot = {
|
task_snapshot = {
|
||||||
'accounts': [str(account.id) for account in accounts],
|
'secret': account.secret,
|
||||||
'assets': [str(account.asset_id) for account in accounts],
|
'secret_type': account.secret_type,
|
||||||
'params': params or {},
|
'accounts': [account.username],
|
||||||
|
'assets': [str(account.asset_id)],
|
||||||
}
|
}
|
||||||
|
|
||||||
tp = AutomationTypes.push_account
|
tp = AutomationTypes.push_account
|
||||||
quickstart_automation_by_snapshot(task_name, tp, task_snapshot)
|
quickstart_automation_by_snapshot(task_name, tp, task_snapshot)
|
||||||
|
|||||||
@@ -17,9 +17,9 @@ __all__ = [
|
|||||||
def verify_connectivity_util(assets, tp, accounts, task_name):
|
def verify_connectivity_util(assets, tp, accounts, task_name):
|
||||||
if not assets or not accounts:
|
if not assets or not accounts:
|
||||||
return
|
return
|
||||||
account_ids = [str(account.id) for account in accounts]
|
account_usernames = list(accounts.values_list('username', flat=True))
|
||||||
task_snapshot = {
|
task_snapshot = {
|
||||||
'accounts': account_ids,
|
'accounts': account_usernames,
|
||||||
'assets': [str(asset.id) for asset in assets],
|
'assets': [str(asset.id) for asset in assets],
|
||||||
}
|
}
|
||||||
quickstart_automation_by_snapshot(task_name, tp, task_snapshot)
|
quickstart_automation_by_snapshot(task_name, tp, task_snapshot)
|
||||||
|
|||||||
@@ -25,7 +25,6 @@ router.register(r'push-account-executions', api.PushAccountExecutionViewSet, 'pu
|
|||||||
router.register(r'push-account-records', api.PushAccountRecordViewSet, 'push-account-record')
|
router.register(r'push-account-records', api.PushAccountRecordViewSet, 'push-account-record')
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path('accounts/bulk/', api.AssetAccountBulkCreateApi.as_view(), name='account-bulk-create'),
|
|
||||||
path('accounts/tasks/', api.AccountsTaskCreateAPI.as_view(), name='account-task-create'),
|
path('accounts/tasks/', api.AccountsTaskCreateAPI.as_view(), name='account-task-create'),
|
||||||
path('account-secrets/<uuid:pk>/histories/', api.AccountHistoriesSecretAPI.as_view(),
|
path('account-secrets/<uuid:pk>/histories/', api.AccountHistoriesSecretAPI.as_view(),
|
||||||
name='account-secret-history'),
|
name='account-secret-history'),
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
from django.core.validators import MinValueValidator, MaxValueValidator
|
from django.core.validators import MinValueValidator, MaxValueValidator
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.db.models import Q
|
from django.db.models import Q
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
from common.db.models import JMSBaseModel
|
from common.db.models import JMSBaseModel
|
||||||
from common.utils import contains_ip
|
from common.utils import contains_ip
|
||||||
|
|||||||
@@ -3,12 +3,11 @@ from rest_framework import serializers
|
|||||||
|
|
||||||
from acls.models.base import ActionChoices
|
from acls.models.base import ActionChoices
|
||||||
from common.serializers.fields import LabeledChoiceField, ObjectRelatedField
|
from common.serializers.fields import LabeledChoiceField, ObjectRelatedField
|
||||||
from jumpserver.utils import has_valid_xpack_license
|
|
||||||
from orgs.models import Organization
|
from orgs.models import Organization
|
||||||
from users.models import User
|
from users.models import User
|
||||||
|
|
||||||
common_help_text = _(
|
common_help_text = _(
|
||||||
"With * indicating a match all. "
|
"Format for comma-delimited string, with * indicating a match all. "
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -23,7 +22,7 @@ class ACLUsersSerializer(serializers.Serializer):
|
|||||||
|
|
||||||
class ACLAssestsSerializer(serializers.Serializer):
|
class ACLAssestsSerializer(serializers.Serializer):
|
||||||
address_group_help_text = _(
|
address_group_help_text = _(
|
||||||
"With * indicating a match all. "
|
"Format for comma-delimited string, with * indicating a match all. "
|
||||||
"Such as: "
|
"Such as: "
|
||||||
"192.168.10.1, 192.168.1.0/24, 10.1.1.1-10.1.1.20, 2001:db8:2de::e13, 2001:db8:1a:1110::/64"
|
"192.168.10.1, 192.168.1.0/24, 10.1.1.1-10.1.1.20, 2001:db8:2de::e13, 2001:db8:1a:1110::/64"
|
||||||
" (Domain name support)"
|
" (Domain name support)"
|
||||||
@@ -52,26 +51,7 @@ class ACLAccountsSerializer(serializers.Serializer):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class ActionAclSerializer(serializers.Serializer):
|
class BaseUserAssetAccountACLSerializerMixin(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 BaseUserAssetAccountACLSerializerMixin(ActionAclSerializer, serializers.Serializer):
|
|
||||||
users = ACLUsersSerializer(label=_('User'))
|
users = ACLUsersSerializer(label=_('User'))
|
||||||
assets = ACLAssestsSerializer(label=_('Asset'))
|
assets = ACLAssestsSerializer(label=_('Asset'))
|
||||||
accounts = ACLAccountsSerializer(label=_('Account'))
|
accounts = ACLAccountsSerializer(label=_('Account'))
|
||||||
@@ -97,6 +77,9 @@ class BaseUserAssetAccountACLSerializerMixin(ActionAclSerializer, serializers.Se
|
|||||||
reviewers_amount = serializers.IntegerField(
|
reviewers_amount = serializers.IntegerField(
|
||||||
read_only=True, source="reviewers.count", label=_('Reviewers amount')
|
read_only=True, source="reviewers.count", label=_('Reviewers amount')
|
||||||
)
|
)
|
||||||
|
action = LabeledChoiceField(
|
||||||
|
choices=ActionChoices.choices, default=ActionChoices.reject, label=_("Action")
|
||||||
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
fields_mini = ["id", "name"]
|
fields_mini = ["id", "name"]
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
from django.utils.translation import ugettext as _
|
from django.utils.translation import ugettext as _
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
|
|
||||||
|
from common.serializers.fields import ObjectRelatedField, LabeledChoiceField
|
||||||
from common.serializers import BulkModelSerializer, MethodSerializer
|
from common.serializers import BulkModelSerializer, MethodSerializer
|
||||||
from common.serializers.fields import ObjectRelatedField
|
from jumpserver.utils import has_valid_xpack_license
|
||||||
from users.models import User
|
from users.models import User
|
||||||
from .base import ActionAclSerializer
|
|
||||||
from .rules import RuleSerializer
|
from .rules import RuleSerializer
|
||||||
from ..models import LoginACL
|
from ..models import LoginACL
|
||||||
|
|
||||||
@@ -13,15 +13,16 @@ __all__ = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
common_help_text = _(
|
common_help_text = _(
|
||||||
"With * indicating a match all. "
|
"Format for comma-delimited string, with * indicating a match all. "
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class LoginACLSerializer(ActionAclSerializer, BulkModelSerializer):
|
class LoginACLSerializer(BulkModelSerializer):
|
||||||
user = ObjectRelatedField(queryset=User.objects, label=_("User"))
|
user = ObjectRelatedField(queryset=User.objects, label=_("User"))
|
||||||
reviewers = ObjectRelatedField(
|
reviewers = ObjectRelatedField(
|
||||||
queryset=User.objects, label=_("Reviewers"), many=True, required=False
|
queryset=User.objects, label=_("Reviewers"), many=True, required=False
|
||||||
)
|
)
|
||||||
|
action = LabeledChoiceField(choices=LoginACL.ActionChoices.choices, label=_('Action'))
|
||||||
reviewers_amount = serializers.IntegerField(
|
reviewers_amount = serializers.IntegerField(
|
||||||
read_only=True, source="reviewers.count", label=_("Reviewers amount")
|
read_only=True, source="reviewers.count", label=_("Reviewers amount")
|
||||||
)
|
)
|
||||||
@@ -43,5 +44,18 @@ class LoginACLSerializer(ActionAclSerializer, BulkModelSerializer):
|
|||||||
"is_active": {"default": True},
|
"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
|
||||||
|
|
||||||
def get_rules_serializer(self):
|
def get_rules_serializer(self):
|
||||||
return RuleSerializer()
|
return RuleSerializer()
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ def ip_group_child_validator(ip_group_child):
|
|||||||
|
|
||||||
|
|
||||||
ip_group_help_text = _(
|
ip_group_help_text = _(
|
||||||
'With * indicating a match all. '
|
'Format for comma-delimited string, with * indicating a match all. '
|
||||||
'Such as: '
|
'Such as: '
|
||||||
'192.168.10.1, 192.168.1.0/24, 10.1.1.1-10.1.1.20, 2001:db8:2de::e13, 2001:db8:1a:1110::/64 '
|
'192.168.10.1, 192.168.1.0/24, 10.1.1.1-10.1.1.20, 2001:db8:2de::e13, 2001:db8:1a:1110::/64 '
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
from .asset import *
|
from .asset import *
|
||||||
from .cloud import *
|
|
||||||
from .custom import *
|
|
||||||
from .database import *
|
|
||||||
from .device import *
|
|
||||||
from .host import *
|
from .host import *
|
||||||
from .permission import *
|
from .database import *
|
||||||
from .web import *
|
from .web import *
|
||||||
|
from .cloud import *
|
||||||
|
from .device import *
|
||||||
|
from .permission import *
|
||||||
|
|||||||
@@ -2,17 +2,15 @@
|
|||||||
#
|
#
|
||||||
import django_filters
|
import django_filters
|
||||||
from django.db.models import Q
|
from django.db.models import Q
|
||||||
from django.shortcuts import get_object_or_404
|
|
||||||
from django.utils.translation import gettext as _
|
from django.utils.translation import gettext as _
|
||||||
from rest_framework.decorators import action
|
from rest_framework.decorators import action
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
from rest_framework.status import HTTP_200_OK
|
|
||||||
|
|
||||||
from accounts.tasks import push_accounts_to_assets_task, verify_accounts_connectivity_task
|
from accounts.tasks import push_accounts_to_assets_task, verify_accounts_connectivity_task
|
||||||
from assets import serializers
|
from assets import serializers
|
||||||
from assets.exceptions import NotSupportedTemporarilyError
|
from assets.exceptions import NotSupportedTemporarilyError
|
||||||
from assets.filters import IpInFilterBackend, LabelFilterBackend, NodeFilterBackend
|
from assets.filters import IpInFilterBackend, LabelFilterBackend, NodeFilterBackend
|
||||||
from assets.models import Asset, Gateway, Platform
|
from assets.models import Asset, Gateway
|
||||||
from assets.tasks import test_assets_connectivity_manual, update_assets_hardware_info_manual
|
from assets.tasks import test_assets_connectivity_manual, update_assets_hardware_info_manual
|
||||||
from common.api import SuggestionMixin
|
from common.api import SuggestionMixin
|
||||||
from common.drf.filters import BaseFilterSet
|
from common.drf.filters import BaseFilterSet
|
||||||
@@ -20,7 +18,6 @@ from common.utils import get_logger, is_uuid
|
|||||||
from orgs.mixins import generics
|
from orgs.mixins import generics
|
||||||
from orgs.mixins.api import OrgBulkModelViewSet
|
from orgs.mixins.api import OrgBulkModelViewSet
|
||||||
from ..mixin import NodeFilterMixin
|
from ..mixin import NodeFilterMixin
|
||||||
from ...notifications import BulkUpdatePlatformSkipAssetUserMsg
|
|
||||||
|
|
||||||
logger = get_logger(__file__)
|
logger = get_logger(__file__)
|
||||||
__all__ = [
|
__all__ = [
|
||||||
@@ -96,19 +93,20 @@ class AssetViewSet(SuggestionMixin, NodeFilterMixin, OrgBulkModelViewSet):
|
|||||||
model = Asset
|
model = Asset
|
||||||
filterset_class = AssetFilterSet
|
filterset_class = AssetFilterSet
|
||||||
search_fields = ("name", "address")
|
search_fields = ("name", "address")
|
||||||
ordering_fields = ('name', 'connectivity', 'platform', 'date_updated')
|
ordering = ("name", "connectivity")
|
||||||
serializer_classes = (
|
serializer_classes = (
|
||||||
("default", serializers.AssetSerializer),
|
("default", serializers.AssetSerializer),
|
||||||
("platform", serializers.PlatformSerializer),
|
("platform", serializers.PlatformSerializer),
|
||||||
("suggestion", serializers.MiniAssetSerializer),
|
("suggestion", serializers.MiniAssetSerializer),
|
||||||
("gateways", serializers.GatewaySerializer),
|
("gateways", serializers.GatewaySerializer),
|
||||||
|
("spec_info", serializers.SpecSerializer),
|
||||||
)
|
)
|
||||||
rbac_perms = (
|
rbac_perms = (
|
||||||
("match", "assets.match_asset"),
|
("match", "assets.match_asset"),
|
||||||
("platform", "assets.view_platform"),
|
("platform", "assets.view_platform"),
|
||||||
("gateways", "assets.view_gateway"),
|
("gateways", "assets.view_gateway"),
|
||||||
("spec_info", "assets.view_asset"),
|
("spec_info", "assets.view_asset"),
|
||||||
("gathered_info", "assets.view_asset"),
|
("info", "assets.view_asset"),
|
||||||
)
|
)
|
||||||
extra_filter_backends = [LabelFilterBackend, IpInFilterBackend, NodeFilterBackend]
|
extra_filter_backends = [LabelFilterBackend, IpInFilterBackend, NodeFilterBackend]
|
||||||
|
|
||||||
@@ -126,6 +124,11 @@ class AssetViewSet(SuggestionMixin, NodeFilterMixin, OrgBulkModelViewSet):
|
|||||||
serializer = super().get_serializer(instance=asset.platform)
|
serializer = super().get_serializer(instance=asset.platform)
|
||||||
return Response(serializer.data)
|
return Response(serializer.data)
|
||||||
|
|
||||||
|
@action(methods=["GET"], detail=True, url_path="spec-info")
|
||||||
|
def spec_info(self, *args, **kwargs):
|
||||||
|
asset = super().get_object()
|
||||||
|
return Response(asset.spec_info)
|
||||||
|
|
||||||
@action(methods=["GET"], detail=True, url_path="gateways")
|
@action(methods=["GET"], detail=True, url_path="gateways")
|
||||||
def gateways(self, *args, **kwargs):
|
def gateways(self, *args, **kwargs):
|
||||||
asset = self.get_object()
|
asset = self.get_object()
|
||||||
@@ -141,32 +144,6 @@ class AssetViewSet(SuggestionMixin, NodeFilterMixin, OrgBulkModelViewSet):
|
|||||||
return Response({'error': error}, status=400)
|
return Response({'error': error}, status=400)
|
||||||
return super().create(request, *args, **kwargs)
|
return super().create(request, *args, **kwargs)
|
||||||
|
|
||||||
def filter_bulk_update_data(self):
|
|
||||||
bulk_data = []
|
|
||||||
skip_assets = []
|
|
||||||
for data in self.request.data:
|
|
||||||
pk = data.get('id')
|
|
||||||
platform = data.get('platform')
|
|
||||||
if not platform:
|
|
||||||
bulk_data.append(data)
|
|
||||||
continue
|
|
||||||
asset = get_object_or_404(Asset, pk=pk)
|
|
||||||
platform = get_object_or_404(Platform, **platform)
|
|
||||||
if platform.type == asset.type:
|
|
||||||
bulk_data.append(data)
|
|
||||||
continue
|
|
||||||
skip_assets.append(asset)
|
|
||||||
return bulk_data, skip_assets
|
|
||||||
|
|
||||||
def bulk_update(self, request, *args, **kwargs):
|
|
||||||
bulk_data, skip_assets = self.filter_bulk_update_data()
|
|
||||||
request._full_data = bulk_data
|
|
||||||
response = super().bulk_update(request, *args, **kwargs)
|
|
||||||
if response.status_code == HTTP_200_OK and skip_assets:
|
|
||||||
user = request.user
|
|
||||||
BulkUpdatePlatformSkipAssetUserMsg(user, skip_assets).publish()
|
|
||||||
return response
|
|
||||||
|
|
||||||
|
|
||||||
class AssetsTaskMixin:
|
class AssetsTaskMixin:
|
||||||
def perform_assets_task(self, serializer):
|
def perform_assets_task(self, serializer):
|
||||||
@@ -177,8 +154,8 @@ class AssetsTaskMixin:
|
|||||||
task = update_assets_hardware_info_manual(assets)
|
task = update_assets_hardware_info_manual(assets)
|
||||||
else:
|
else:
|
||||||
asset = assets[0]
|
asset = assets[0]
|
||||||
if not asset.auto_config['ansible_enabled'] or \
|
if not asset.auto_info['ansible_enabled'] or \
|
||||||
not asset.auto_config['ping_enabled']:
|
not asset.auto_info['ping_enabled']:
|
||||||
raise NotSupportedTemporarilyError()
|
raise NotSupportedTemporarilyError()
|
||||||
task = test_assets_connectivity_manual(assets)
|
task = test_assets_connectivity_manual(assets)
|
||||||
return task
|
return task
|
||||||
|
|||||||
@@ -1,16 +0,0 @@
|
|||||||
from assets.models import Custom, Asset
|
|
||||||
from assets.serializers import CustomSerializer
|
|
||||||
|
|
||||||
from .asset import AssetViewSet
|
|
||||||
|
|
||||||
__all__ = ['CustomViewSet']
|
|
||||||
|
|
||||||
|
|
||||||
class CustomViewSet(AssetViewSet):
|
|
||||||
model = Custom
|
|
||||||
perm_model = Asset
|
|
||||||
|
|
||||||
def get_serializer_classes(self):
|
|
||||||
serializer_classes = super().get_serializer_classes()
|
|
||||||
serializer_classes['default'] = CustomSerializer
|
|
||||||
return serializer_classes
|
|
||||||
@@ -1,5 +1,8 @@
|
|||||||
|
from rest_framework.decorators import action
|
||||||
|
from rest_framework.response import Response
|
||||||
|
|
||||||
from assets.models import Host, Asset
|
from assets.models import Host, Asset
|
||||||
from assets.serializers import HostSerializer
|
from assets.serializers import HostSerializer, HostInfoSerializer
|
||||||
from .asset import AssetViewSet
|
from .asset import AssetViewSet
|
||||||
|
|
||||||
__all__ = ['HostViewSet']
|
__all__ = ['HostViewSet']
|
||||||
@@ -12,4 +15,16 @@ class HostViewSet(AssetViewSet):
|
|||||||
def get_serializer_classes(self):
|
def get_serializer_classes(self):
|
||||||
serializer_classes = super().get_serializer_classes()
|
serializer_classes = super().get_serializer_classes()
|
||||||
serializer_classes['default'] = HostSerializer
|
serializer_classes['default'] = HostSerializer
|
||||||
|
serializer_classes['info'] = HostInfoSerializer
|
||||||
return serializer_classes
|
return serializer_classes
|
||||||
|
|
||||||
|
@action(methods=["GET"], detail=True, url_path="info")
|
||||||
|
def info(self, *args, **kwargs):
|
||||||
|
asset = super().get_object()
|
||||||
|
serializer = self.get_serializer(asset.info)
|
||||||
|
data = serializer.data
|
||||||
|
data['asset'] = {
|
||||||
|
'id': asset.id, 'name': asset.name,
|
||||||
|
'address': asset.address
|
||||||
|
}
|
||||||
|
return Response(data)
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ from rest_framework.decorators import action
|
|||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
|
|
||||||
from common.api import JMSGenericViewSet
|
from common.api import JMSGenericViewSet
|
||||||
from common.permissions import IsValidUser
|
|
||||||
from assets.serializers import CategorySerializer, TypeSerializer
|
from assets.serializers import CategorySerializer, TypeSerializer
|
||||||
from assets.const import AllTypes
|
from assets.const import AllTypes
|
||||||
|
|
||||||
@@ -15,7 +14,7 @@ class CategoryViewSet(ListModelMixin, JMSGenericViewSet):
|
|||||||
'default': CategorySerializer,
|
'default': CategorySerializer,
|
||||||
'types': TypeSerializer
|
'types': TypeSerializer
|
||||||
}
|
}
|
||||||
permission_classes = (IsValidUser,)
|
permission_classes = ()
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
return AllTypes.categories()
|
return AllTypes.categories()
|
||||||
|
|||||||
@@ -8,15 +8,6 @@ from common.utils import lazyproperty, timeit
|
|||||||
|
|
||||||
|
|
||||||
class SerializeToTreeNodeMixin:
|
class SerializeToTreeNodeMixin:
|
||||||
request: Request
|
|
||||||
|
|
||||||
@lazyproperty
|
|
||||||
def is_sync(self):
|
|
||||||
sync_paths = ['/api/v1/perms/users/self/nodes/all-with-assets/tree/']
|
|
||||||
for p in sync_paths:
|
|
||||||
if p == self.request.path:
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
@timeit
|
@timeit
|
||||||
def serialize_nodes(self, nodes: List[Node], with_asset_amount=False):
|
def serialize_nodes(self, nodes: List[Node], with_asset_amount=False):
|
||||||
@@ -26,16 +17,6 @@ class SerializeToTreeNodeMixin:
|
|||||||
else:
|
else:
|
||||||
def _name(node: Node):
|
def _name(node: Node):
|
||||||
return node.value
|
return node.value
|
||||||
|
|
||||||
def _open(node):
|
|
||||||
if not self.is_sync:
|
|
||||||
# 异步加载资产树时,默认展开节点
|
|
||||||
return True
|
|
||||||
if not node.parent_key:
|
|
||||||
return True
|
|
||||||
else:
|
|
||||||
return False
|
|
||||||
|
|
||||||
data = [
|
data = [
|
||||||
{
|
{
|
||||||
'id': node.key,
|
'id': node.key,
|
||||||
@@ -43,7 +24,7 @@ class SerializeToTreeNodeMixin:
|
|||||||
'title': _name(node),
|
'title': _name(node),
|
||||||
'pId': node.parent_key,
|
'pId': node.parent_key,
|
||||||
'isParent': True,
|
'isParent': True,
|
||||||
'open': _open(node),
|
'open': True,
|
||||||
'meta': {
|
'meta': {
|
||||||
'data': {
|
'data': {
|
||||||
"id": node.id,
|
"id": node.id,
|
||||||
@@ -57,23 +38,11 @@ class SerializeToTreeNodeMixin:
|
|||||||
]
|
]
|
||||||
return data
|
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
|
@timeit
|
||||||
def serialize_assets(self, assets, node_key=None):
|
def serialize_assets(self, assets, node_key=None):
|
||||||
sftp_enabled_platform = PlatformProtocol.objects \
|
sftp_enabled_platform = PlatformProtocol.objects \
|
||||||
.filter(name='ssh', setting__sftp_enabled=True) \
|
.filter(name='ssh', setting__sftp_enabled=True) \
|
||||||
.values_list('platform', flat=True) \
|
.values_list('platform', flat=True).distinct()
|
||||||
.distinct()
|
|
||||||
if node_key is None:
|
if node_key is None:
|
||||||
get_pid = lambda asset: getattr(asset, 'parent_key', '')
|
get_pid = lambda asset: getattr(asset, 'parent_key', '')
|
||||||
else:
|
else:
|
||||||
@@ -83,11 +52,11 @@ class SerializeToTreeNodeMixin:
|
|||||||
{
|
{
|
||||||
'id': str(asset.id),
|
'id': str(asset.id),
|
||||||
'name': asset.name,
|
'name': asset.name,
|
||||||
'title': f'{asset.address}\n{asset.comment}',
|
'title': asset.address,
|
||||||
'pId': get_pid(asset),
|
'pId': get_pid(asset),
|
||||||
'isParent': False,
|
'isParent': False,
|
||||||
'open': False,
|
'open': False,
|
||||||
'iconSkin': self.get_icon(asset),
|
'iconSkin': asset.type,
|
||||||
'chkDisabled': not asset.is_active,
|
'chkDisabled': not asset.is_active,
|
||||||
'meta': {
|
'meta': {
|
||||||
'type': 'asset',
|
'type': 'asset',
|
||||||
@@ -95,8 +64,6 @@ class SerializeToTreeNodeMixin:
|
|||||||
'platform_type': asset.platform.type,
|
'platform_type': asset.platform.type,
|
||||||
'org_name': asset.org_name,
|
'org_name': asset.org_name,
|
||||||
'sftp': asset.platform_id in sftp_enabled_platform,
|
'sftp': asset.platform_id in sftp_enabled_platform,
|
||||||
'name': asset.name,
|
|
||||||
'address': asset.address
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +1,10 @@
|
|||||||
from rest_framework import generics
|
|
||||||
from rest_framework import serializers
|
|
||||||
from rest_framework.decorators import action
|
|
||||||
from rest_framework.response import Response
|
|
||||||
|
|
||||||
from assets.const import AllTypes
|
from assets.const import AllTypes
|
||||||
from assets.models import Platform, Node, Asset
|
from assets.models import Platform
|
||||||
from assets.serializers import PlatformSerializer
|
from assets.serializers import PlatformSerializer
|
||||||
from common.api import JMSModelViewSet
|
from common.api import JMSModelViewSet
|
||||||
from common.permissions import IsValidUser
|
|
||||||
from common.serializers import GroupedChoiceSerializer
|
from common.serializers import GroupedChoiceSerializer
|
||||||
|
|
||||||
__all__ = ['AssetPlatformViewSet', 'PlatformAutomationMethodsApi']
|
__all__ = ['AssetPlatformViewSet']
|
||||||
|
|
||||||
|
|
||||||
class AssetPlatformViewSet(JMSModelViewSet):
|
class AssetPlatformViewSet(JMSModelViewSet):
|
||||||
@@ -24,13 +18,12 @@ class AssetPlatformViewSet(JMSModelViewSet):
|
|||||||
rbac_perms = {
|
rbac_perms = {
|
||||||
'categories': 'assets.view_platform',
|
'categories': 'assets.view_platform',
|
||||||
'type_constraints': 'assets.view_platform',
|
'type_constraints': 'assets.view_platform',
|
||||||
'ops_methods': 'assets.view_platform',
|
'ops_methods': 'assets.view_platform'
|
||||||
'filter_nodes_assets': 'assets.view_platform'
|
|
||||||
}
|
}
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
queryset = super().get_queryset()
|
queryset = super().get_queryset()
|
||||||
queryset = queryset.filter(type__in=AllTypes.get_types_values())
|
queryset = queryset.filter(type__in=AllTypes.get_types())
|
||||||
return queryset
|
return queryset
|
||||||
|
|
||||||
def get_object(self):
|
def get_object(self):
|
||||||
@@ -45,44 +38,3 @@ class AssetPlatformViewSet(JMSModelViewSet):
|
|||||||
request, message={"detail": "Internal platform"}
|
request, message={"detail": "Internal platform"}
|
||||||
)
|
)
|
||||||
return super().check_object_permissions(request, obj)
|
return super().check_object_permissions(request, obj)
|
||||||
|
|
||||||
@action(methods=['post'], detail=False, url_path='filter-nodes-assets')
|
|
||||||
def filter_nodes_assets(self, request, *args, **kwargs):
|
|
||||||
node_ids = request.data.get('node_ids', [])
|
|
||||||
asset_ids = request.data.get('asset_ids', [])
|
|
||||||
nodes = Node.objects.filter(id__in=node_ids)
|
|
||||||
node_asset_ids = Node.get_nodes_all_assets(*nodes).values_list('id', flat=True)
|
|
||||||
direct_asset_ids = Asset.objects.filter(id__in=asset_ids).values_list('id', flat=True)
|
|
||||||
platform_ids = Asset.objects.filter(
|
|
||||||
id__in=set(list(direct_asset_ids) + list(node_asset_ids))
|
|
||||||
).values_list('platform_id', flat=True)
|
|
||||||
platforms = Platform.objects.filter(id__in=platform_ids)
|
|
||||||
serializer = self.get_serializer(platforms, many=True)
|
|
||||||
return Response(serializer.data)
|
|
||||||
|
|
||||||
|
|
||||||
class PlatformAutomationMethodsApi(generics.ListAPIView):
|
|
||||||
permission_classes = (IsValidUser,)
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def automation_methods():
|
|
||||||
return AllTypes.get_automation_methods()
|
|
||||||
|
|
||||||
def generate_serializer_fields(self):
|
|
||||||
data = self.automation_methods()
|
|
||||||
fields = {
|
|
||||||
i['id']: i['params_serializer'](label=i['name'])
|
|
||||||
if i['params_serializer'] else None
|
|
||||||
for i in data
|
|
||||||
}
|
|
||||||
return fields
|
|
||||||
|
|
||||||
def get_serializer_class(self):
|
|
||||||
fields = self.generate_serializer_fields()
|
|
||||||
serializer_name = 'AutomationMethodsSerializer'
|
|
||||||
return type(serializer_name, (serializers.Serializer,), fields)
|
|
||||||
|
|
||||||
def list(self, request, *args, **kwargs):
|
|
||||||
data = self.generate_serializer_fields()
|
|
||||||
serializer = self.get_serializer(data)
|
|
||||||
return Response(serializer.data)
|
|
||||||
|
|||||||
@@ -163,10 +163,8 @@ class CategoryTreeApi(SerializeToTreeNodeMixin, generics.ListAPIView):
|
|||||||
# 资源数量统计可选项 (asset, account)
|
# 资源数量统计可选项 (asset, account)
|
||||||
count_resource = self.request.query_params.get('count_resource', 'asset')
|
count_resource = self.request.query_params.get('count_resource', 'asset')
|
||||||
|
|
||||||
if not self.request.query_params.get('key'):
|
if include_asset and self.request.query_params.get('key'):
|
||||||
nodes = AllTypes.to_tree_nodes(include_asset, count_resource=count_resource)
|
|
||||||
elif include_asset:
|
|
||||||
nodes = self.get_assets()
|
nodes = self.get_assets()
|
||||||
else:
|
else:
|
||||||
nodes = []
|
nodes = AllTypes.to_tree_nodes(include_asset, count_resource=count_resource)
|
||||||
return Response(data=nodes)
|
return Response(data=nodes)
|
||||||
|
|||||||
@@ -12,7 +12,8 @@ from django.utils.translation import gettext as _
|
|||||||
from sshtunnel import SSHTunnelForwarder, BaseSSHTunnelForwarderError
|
from sshtunnel import SSHTunnelForwarder, BaseSSHTunnelForwarderError
|
||||||
|
|
||||||
from assets.automations.methods import platform_automation_methods
|
from assets.automations.methods import platform_automation_methods
|
||||||
from common.utils import get_logger, lazyproperty, is_openssh_format_key, ssh_pubkey_gen
|
from common.utils import get_logger, lazyproperty
|
||||||
|
from common.utils import ssh_pubkey_gen, ssh_key_string_to_obj
|
||||||
from ops.ansible import JMSInventory, PlaybookRunner, DefaultCallback
|
from ops.ansible import JMSInventory, PlaybookRunner, DefaultCallback
|
||||||
|
|
||||||
logger = get_logger(__name__)
|
logger = get_logger(__name__)
|
||||||
@@ -41,28 +42,6 @@ class BasePlaybookManager:
|
|||||||
self.method_hosts_mapper = defaultdict(list)
|
self.method_hosts_mapper = defaultdict(list)
|
||||||
self.playbooks = []
|
self.playbooks = []
|
||||||
self.gateway_servers = dict()
|
self.gateway_servers = dict()
|
||||||
params = self.execution.snapshot.get('params')
|
|
||||||
self.params = params or {}
|
|
||||||
|
|
||||||
def get_params(self, automation, method_type):
|
|
||||||
method_attr = '{}_method'.format(method_type)
|
|
||||||
method_params = '{}_params'.format(method_type)
|
|
||||||
method_id = getattr(automation, method_attr)
|
|
||||||
automation_params = getattr(automation, method_params)
|
|
||||||
serializer = self.method_id_meta_mapper[method_id]['params_serializer']
|
|
||||||
|
|
||||||
if serializer is None:
|
|
||||||
return {}
|
|
||||||
|
|
||||||
data = self.params.get(method_id)
|
|
||||||
if not data:
|
|
||||||
data = automation_params.get(method_id, {})
|
|
||||||
params = serializer(data).data
|
|
||||||
return {
|
|
||||||
field_name: automation_params.get(field_name, '')
|
|
||||||
if not params[field_name] else params[field_name]
|
|
||||||
for field_name in params
|
|
||||||
}
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def platform_automation_methods(self):
|
def platform_automation_methods(self):
|
||||||
@@ -123,9 +102,8 @@ class BasePlaybookManager:
|
|||||||
return host
|
return host
|
||||||
|
|
||||||
def host_callback(self, host, automation=None, **kwargs):
|
def host_callback(self, host, automation=None, **kwargs):
|
||||||
method_type = self.__class__.method_type()
|
enabled_attr = '{}_enabled'.format(self.__class__.method_type())
|
||||||
enabled_attr = '{}_enabled'.format(method_type)
|
method_attr = '{}_method'.format(self.__class__.method_type())
|
||||||
method_attr = '{}_method'.format(method_type)
|
|
||||||
|
|
||||||
method_enabled = automation and \
|
method_enabled = automation and \
|
||||||
getattr(automation, enabled_attr) and \
|
getattr(automation, enabled_attr) and \
|
||||||
@@ -137,7 +115,6 @@ class BasePlaybookManager:
|
|||||||
return host
|
return host
|
||||||
|
|
||||||
host = self.convert_cert_to_file(host, kwargs.get('path_dir'))
|
host = self.convert_cert_to_file(host, kwargs.get('path_dir'))
|
||||||
host['params'] = self.get_params(automation, method_type)
|
|
||||||
return host
|
return host
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@@ -150,13 +127,7 @@ class BasePlaybookManager:
|
|||||||
key_path = os.path.join(path_dir, key_name)
|
key_path = os.path.join(path_dir, key_name)
|
||||||
|
|
||||||
if not os.path.exists(key_path):
|
if not os.path.exists(key_path):
|
||||||
# https://github.com/ansible/ansible-runner/issues/544
|
ssh_key_string_to_obj(secret, password=None).write_private_key_file(key_path)
|
||||||
# ssh requires OpenSSH format keys to have a full ending newline.
|
|
||||||
# It does not require this for old-style PEM keys.
|
|
||||||
with open(key_path, 'w') as f:
|
|
||||||
f.write(secret)
|
|
||||||
if is_openssh_format_key(secret.encode('utf-8')):
|
|
||||||
f.write("\n")
|
|
||||||
os.chmod(key_path, 0o400)
|
os.chmod(key_path, 0o400)
|
||||||
return key_path
|
return key_path
|
||||||
|
|
||||||
@@ -263,12 +234,10 @@ class BasePlaybookManager:
|
|||||||
jms_asset, jms_gateway = host['jms_asset'], host.get('gateway')
|
jms_asset, jms_gateway = host['jms_asset'], host.get('gateway')
|
||||||
if not jms_gateway:
|
if not jms_gateway:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
server = SSHTunnelForwarder(
|
server = SSHTunnelForwarder(
|
||||||
(jms_gateway['address'], jms_gateway['port']),
|
(jms_gateway['address'], jms_gateway['port']),
|
||||||
ssh_username=jms_gateway['username'],
|
ssh_username=jms_gateway['username'],
|
||||||
ssh_password=jms_gateway['secret'],
|
ssh_password=jms_gateway['secret'],
|
||||||
ssh_pkey=jms_gateway['private_key_path'],
|
|
||||||
remote_bind_address=(jms_asset['address'], jms_asset['port'])
|
remote_bind_address=(jms_asset['address'], jms_asset['port'])
|
||||||
)
|
)
|
||||||
try:
|
try:
|
||||||
@@ -278,8 +247,8 @@ class BasePlaybookManager:
|
|||||||
print('\033[31m %s \033[0m\n' % err_msg)
|
print('\033[31m %s \033[0m\n' % err_msg)
|
||||||
not_valid.append(k)
|
not_valid.append(k)
|
||||||
else:
|
else:
|
||||||
host['ansible_host'] = jms_asset['address'] = '127.0.0.1'
|
jms_asset['address'] = '127.0.0.1'
|
||||||
host['ansible_port'] = jms_asset['port'] = server.local_bind_port
|
jms_asset['port'] = server.local_bind_port
|
||||||
servers.append(server)
|
servers.append(server)
|
||||||
|
|
||||||
# 网域不可连接的,就不继续执行此资源的后续任务了
|
# 网域不可连接的,就不继续执行此资源的后续任务了
|
||||||
|
|||||||
@@ -1,11 +1,6 @@
|
|||||||
id: gather_facts_mongodb
|
id: gather_facts_mongodb
|
||||||
name: "{{ 'Gather facts from MongoDB' | trans }}"
|
name: Gather facts from MongoDB
|
||||||
category: database
|
category: database
|
||||||
type:
|
type:
|
||||||
- mongodb
|
- mongodb
|
||||||
method: gather_facts
|
method: gather_facts
|
||||||
i18n:
|
|
||||||
Gather facts from MongoDB:
|
|
||||||
zh: 从 MongoDB 获取信息
|
|
||||||
en: Gather facts from MongoDB
|
|
||||||
ja: MongoDBから事実を取得する
|
|
||||||
|
|||||||
@@ -1,12 +1,7 @@
|
|||||||
id: gather_facts_mysql
|
id: gather_facts_mysql
|
||||||
name: "{{ 'Gather facts from MySQL' | trans }}"
|
name: Gather facts from MySQL
|
||||||
category: database
|
category: database
|
||||||
type:
|
type:
|
||||||
- mysql
|
- mysql
|
||||||
- mariadb
|
- mariadb
|
||||||
method: gather_facts
|
method: gather_facts
|
||||||
i18n:
|
|
||||||
Gather facts from MySQL:
|
|
||||||
zh: 从 MySQL 获取信息
|
|
||||||
en: Gather facts from MySQL
|
|
||||||
ja: MySQLから事実を取得する
|
|
||||||
|
|||||||
@@ -1,11 +1,6 @@
|
|||||||
id: gather_facts_oracle
|
id: gather_facts_oracle
|
||||||
name: "{{ 'Gather facts from Oracle' | trans }}"
|
name: Gather facts from Oracle
|
||||||
category: database
|
category: database
|
||||||
type:
|
type:
|
||||||
- oracle
|
- oracle
|
||||||
method: gather_facts
|
method: gather_facts
|
||||||
i18n:
|
|
||||||
Gather facts from Oracle:
|
|
||||||
zh: 从 Oracle 获取信息
|
|
||||||
en: Gather facts from Oracle
|
|
||||||
ja: Oracleから事実を取得する
|
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user