mirror of
https://github.com/jumpserver/jumpserver.git
synced 2025-09-02 07:55:16 +00:00
v3.0.0-rc1 (#9322)
* perf:automation * pref: 修改账号推送 * perf: 修改 assets * perf: 修改 accounts * feat: 优化代码 * fix: 修复 ObjectRelatedField 获取 value attr 时先判断是否有 attr 属性 * perf: 增加翻译 * feat: 增加部分翻译 * feat: 去除无用列 * perf: ticket remove app * fix: 修复创建账号备份任务失败的问题 * perf: 添加 accounts app * perf: ticket type serializer (#9252) Co-authored-by: feng <1304903146@qq.com> * perf: ticket * perf: 修改 accounts api * perf: 优化 AssetPermissionSerializer fields 顺序 * perf: 修改 accounts * feat: 限制常用用户名api返回长度 * feat: 限制常用用户名api返回长度 * perf: 修改 LoginAssetACL 序列类,增加 users_username_group, accounts_username_group... 字段 * perf: 修改 CommandFilterACLSerializer 增加 command_groups_amount 字段 * perf: 修改rbac API啥的 (#9254) * perf: migrate * perf: 修改 AssetPermedSerializer domain 字段类型 * perf: 放开push account 权限位 * perf: 修改 accounts * perf: 修改 LoginACLSerializer 字段类型 * pref: 修改数据库 migrations * perf: filter asset systemuser * perf: 修改 SessionSerializer 字段类型 * pref: 修改 applet host * perf: 修改 SessionCommandSerializer 字段类型 * perf: 修改 accounts import * perf: 修改 celery datetime * perf: 修改 asset serializer * pref: 修改 labeled field * feat: 修改翻译 * perf: 修改 JobSerializer 字段类型 * feat: 支持使用 ws 发送终断任务 * perf: add AccessTokenAuthentication * perf: 修改 BaseStorageSerializer 字段类型 * perf: 修改 AppletHostSerializer 字段类型 * perf: signal event * perf: asset types automations (#9259) Co-authored-by: feng <1304903146@qq.com> * perf: 修改下载 rdp 文件时返回的 address 地址信息为空的问题 * perf: 修改 AssetSerializer.accounts.secret 为 write_only; 修改 DomainWithGatewaySerializer.gateways 返回 account 信息及 secret 字段; * perf: automation 干库 (#9260) Co-authored-by: feng <1304903146@qq.com> * perf: account push api * feat: 修改迁移文件 * feat: 删除无用代码 * feat: 优化部分资源无操作日志 * perf: 修改 account * perf: perm tree * perf: asset serializers retrieve * perf: 格式化代码 * perf: AutomationExecution (#9268) Co-authored-by: feng <1304903146@qq.com> * perf: AssetDetailSerializer 和 Asset Model 添加 specific_info 字段; * perf: 修改账号推送 * feat: handle ws heartbeat status * perf: k8s tree (#9269) Co-authored-by: feng <1304903146@qq.com> * perf: 修改账号推送 * perf: 修改 asset detail serializer * fix: 修复 windows 不能运行 powershell 命令的问题 * feat: 支持按照资源时间线查看操作活动 * feat: 翻译 * feat: 优化操作日志 * perf: asset clone * fix: 错误的修改改回去 * perf: create asset account * feat: 增加task 刷新续传功能 * fix: applet host deloypment filter host * perf: 修改了 common 结构,和 push accounts * perf: 整理 common 结构 * perf: 修改 const import * perf: 修改 allow bulk destroy * fix: applet host search fileds * perf: applet bulk delete * fix: applet list 404 * perf: 修改 common view * feat: 增加一些翻译, 修复 playbook 上传的错误 * fix: 修改错别字 * perf: 修改 applets status * perf: 修改网关 api * perf: automateion (#9281) Co-authored-by: feng <1304903146@qq.com> Co-authored-by: feng626 <57284900+feng626@users.noreply.github.com> * perf: 失效 connect methods 当 applet 删除 或者 host 删除 * perf: 网关账号的密码类型改成 LabelField * perf: chrome applet script * perf: verify code ttl (#9282) Co-authored-by: feng <1304903146@qq.com> * perf: database ping * perf: ws * perf: 修改网关创建 * perf: account task org (#9285) Co-authored-by: feng <1304903146@qq.com> * perf: asset test api * perf: port 添加 account * pref: 修改 db mapper permission * fix: db port mapper list api * perf: account change secret (#9286) Co-authored-by: feng <1304903146@qq.com> * perf: 修改 setup_eager_loading * perf: SecretStrategy * feat: 修改 ConnectionToken Create API 支持校验 ACL 逻辑 * feat: 修改 ConnectionToken Create API 支持校验 ACL 逻辑 * feat: 修改 ConnectionToken Create API 支持校验 ACL 逻辑 * pref: web database 信号转发 * perf: account push automation * perf: push filter account * perf: 修改 publish 版本 * perf: 修改网关 * fix: 修改资产 Specific 信息中 JSONField 字段返回 json.loads 对象 * feat: 远程应用内置Navicat Premium 16 * feat: 更新下载链接 * feat: 整理代码格式 * perf: 修改 terminal point * perf: update chrome applet script * fix: 资产 specific 获取 JSONField 时, 判断值的类型不为 list, dict * perf: domain (#9292) Co-authored-by: feng <1304903146@qq.com> * perf: 优化 endpoint 监听端口,仅 oracle 动态 * perf: 修改翻译 * perf: 修改文案 * perf: 修改缺失的翻译 * perf: 修改 endpoint help text * feat: 还原格式 * feat: 去掉基类 * feat: 增加特权账号字段 * perf: decode content * fix: check pid * perf: 修改 smart endpoint * perf: 修改 endpoint mysql default port * feat: 优化 * perf: 修改 endpoint mysql default port * perf: gateway test (#9295) Co-authored-by: feng <1304903146@qq.com> * perf: migrate * perf: 修改 endpoint mysql default port * fix: 修复获取任务执行结果死循环 * feat: 作业审计日志增加字段 * fix: add on_transaction_commit task post save * perf: gateway (#9297) Co-authored-by: feng <1304903146@qq.com> * feat: 过滤 jumpserver 自动产生的用户 * fix: 修复ops节点选择的问题 * fix: 修改 统一 connection-token 和 command 的 review API 返回数据 from_ticket_info * perf: change secret (#9298) Co-authored-by: feng <1304903146@qq.com> * perf: 修改 db port manager * perf: 修改 db port manager * perf: add celery log mark * perf: remove debug log data * fix: navicat use manual type * fix: remove navicate download url * perf: push_account_enabled (#9301) Co-authored-by: feng <1304903146@qq.com> * fix: 修改navicat启动程序MD5值 * perf: push account (#9303) Co-authored-by: feng <1304903146@qq.com> * feat: Redis/MongoDB 支持SSL * fix: 修改授权规则过滤字段 node_name,node_id; 修复获取授权节点下的资产为空的问题; * perf: push account button (#9305) Co-authored-by: feng <1304903146@qq.com> * perf: account push * fix: 修复获取 /user//assets/tree/ 返回用户授权的所有资产 * perf: asset ping (#9307) Co-authored-by: feng <1304903146@qq.com> * perf: asset enabled_info * perf: 优化activity记录都保存至operatelog中 * feat: 远程应用navicat支持试用版连接 * perf: 优化迁移文件 * perf: 修改资产列表 API category type 字段 choices 根据 category 进行返回 * fix * perf: 修改账号列表 API 解决根据 node_id asset_id 搜索账号列表无效的问题 * fix: navicat dba账号登录 * perf: 优化navicat连接 * perf: 修改账号列表 Model Manager 继承自 OrgManager,解决组织过滤问题 * perf: 修改账号列表 Filter 支持根据 platform,category,type 字段搜索 * perf: change secret email (#9312) Co-authored-by: feng <1304903146@qq.com> * feat: 保证认证信息一定清理 * perf: add mariadb * perf: 修改资产类型树数量统计资产或账号 * perf: applet chrome quit * perf: 优化关闭欢迎页面 * fix * perf: executed amount * perf: 修改 built-in applet installation * perf: 修改资产列表增加标签搜索 * perf: 修改资产列表增加标签搜索 * perf: account task automation (#9319) Co-authored-by: feng <1304903146@qq.com> * perf: account trigger * perf: 修改系统设置文案:批量命令执行 -> 作业中心 * perf: 优化migrate (#9320) Co-authored-by: feng <1304903146@qq.com> * perf: 修改资产节点树 API,支持搜索资产、节点 * perf: audit dashboard (#9321) Co-authored-by: feng <1304903146@qq.com> * fix: 修改 has_perm 权限判断兼容 list 和 str 类型 * perf: 修改一些换行 * perf: 修改 ansible config * fix: oracle依赖文件地址错误 (#9324) * perf: ansible mudules * perf: 修改 runner host cwd Co-authored-by: ibuler <ibuler@qq.com> Co-authored-by: Aaron3S <chenyang@fit2cloud.com> Co-authored-by: Bai <baijiangjie@gmail.com> Co-authored-by: feng <1304903146@qq.com> Co-authored-by: feng626 <57284900+feng626@users.noreply.github.com> Co-authored-by: Eric <xplzv@126.com> Co-authored-by: jiangweidong <weidong.jiang@fit2cloud.com> Co-authored-by: jiangweidong <80373698+Hi-JWD@users.noreply.github.com>
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@@ -42,4 +42,4 @@ release/*
|
|||||||
releashe
|
releashe
|
||||||
/apps/script.py
|
/apps/script.py
|
||||||
data/*
|
data/*
|
||||||
|
test.py
|
||||||
|
@@ -99,7 +99,6 @@ VOLUME /opt/jumpserver/data
|
|||||||
VOLUME /opt/jumpserver/logs
|
VOLUME /opt/jumpserver/logs
|
||||||
|
|
||||||
ENV LANG=zh_CN.UTF-8
|
ENV LANG=zh_CN.UTF-8
|
||||||
ENV ANSIBLE_LIBRARY=/opt/jumpserver/apps/ops/ansible/modules
|
|
||||||
|
|
||||||
EXPOSE 8080
|
EXPOSE 8080
|
||||||
|
|
||||||
|
3
apps/accounts/admin.py
Normal file
3
apps/accounts/admin.py
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
from django.contrib import admin
|
||||||
|
|
||||||
|
# Register your models here.
|
2
apps/accounts/api/__init__.py
Normal file
2
apps/accounts/api/__init__.py
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
from .account import *
|
||||||
|
from .automations import *
|
@@ -3,12 +3,13 @@ from rest_framework.decorators import action
|
|||||||
from rest_framework.generics import CreateAPIView, ListAPIView
|
from rest_framework.generics import CreateAPIView, ListAPIView
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
|
|
||||||
from assets import serializers
|
from accounts import serializers
|
||||||
from assets.filters import AccountFilterSet
|
from accounts.filters import AccountFilterSet
|
||||||
from assets.models import Account, Asset
|
from accounts.models import Account
|
||||||
from assets.tasks import verify_accounts_connectivity
|
from accounts.tasks import verify_accounts_connectivity
|
||||||
|
from assets.models import Asset
|
||||||
from authentication.const import ConfirmType
|
from authentication.const import ConfirmType
|
||||||
from common.mixins import RecordViewLogMixin
|
from common.views.mixins import RecordViewLogMixin
|
||||||
from common.permissions import UserConfirmation
|
from common.permissions import UserConfirmation
|
||||||
from orgs.mixins.api import OrgBulkModelViewSet
|
from orgs.mixins.api import OrgBulkModelViewSet
|
||||||
|
|
||||||
@@ -25,10 +26,9 @@ class AccountViewSet(OrgBulkModelViewSet):
|
|||||||
filterset_class = AccountFilterSet
|
filterset_class = AccountFilterSet
|
||||||
serializer_classes = {
|
serializer_classes = {
|
||||||
'default': serializers.AccountSerializer,
|
'default': serializers.AccountSerializer,
|
||||||
'verify': serializers.AssetTaskSerializer
|
|
||||||
}
|
}
|
||||||
rbac_perms = {
|
rbac_perms = {
|
||||||
'verify': 'assets.test_account',
|
'verify_account': 'assets.test_account',
|
||||||
'partial_update': 'assets.change_accountsecret',
|
'partial_update': 'assets.change_accountsecret',
|
||||||
'su_from_accounts': 'assets.view_account',
|
'su_from_accounts': 'assets.view_account',
|
||||||
}
|
}
|
@@ -5,10 +5,10 @@ from rest_framework.response import Response
|
|||||||
|
|
||||||
from orgs.mixins.api import OrgBulkModelViewSet
|
from orgs.mixins.api import OrgBulkModelViewSet
|
||||||
from common.const.choices import Trigger
|
from common.const.choices import Trigger
|
||||||
from assets import serializers
|
from accounts import serializers
|
||||||
from assets.tasks import execute_account_backup_plan
|
from accounts.tasks import execute_account_backup_plan
|
||||||
from assets.models import (
|
from accounts.models import (
|
||||||
AccountBackupPlan, AccountBackupPlanExecution
|
AccountBackupAutomation, AccountBackupExecution
|
||||||
)
|
)
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
@@ -17,12 +17,12 @@ __all__ = [
|
|||||||
|
|
||||||
|
|
||||||
class AccountBackupPlanViewSet(OrgBulkModelViewSet):
|
class AccountBackupPlanViewSet(OrgBulkModelViewSet):
|
||||||
model = AccountBackupPlan
|
model = AccountBackupAutomation
|
||||||
filter_fields = ('name',)
|
filter_fields = ('name',)
|
||||||
search_fields = filter_fields
|
search_fields = filter_fields
|
||||||
ordering_fields = ('name',)
|
ordering_fields = ('name',)
|
||||||
ordering = ('name',)
|
ordering = ('name',)
|
||||||
serializer_class = serializers.AccountBackupPlanSerializer
|
serializer_class = serializers.AccountBackupSerializer
|
||||||
|
|
||||||
|
|
||||||
class AccountBackupPlanExecutionViewSet(viewsets.ModelViewSet):
|
class AccountBackupPlanExecutionViewSet(viewsets.ModelViewSet):
|
||||||
@@ -32,7 +32,7 @@ class AccountBackupPlanExecutionViewSet(viewsets.ModelViewSet):
|
|||||||
http_method_names = ['get', 'post', 'options']
|
http_method_names = ['get', 'post', 'options']
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
queryset = AccountBackupPlanExecution.objects.all()
|
queryset = AccountBackupExecution.objects.all()
|
||||||
return queryset
|
return queryset
|
||||||
|
|
||||||
def create(self, request, *args, **kwargs):
|
def create(self, request, *args, **kwargs):
|
||||||
@@ -41,8 +41,3 @@ class AccountBackupPlanExecutionViewSet(viewsets.ModelViewSet):
|
|||||||
pid = serializer.data.get('plan')
|
pid = serializer.data.get('plan')
|
||||||
task = execute_account_backup_plan.delay(pid=pid, trigger=Trigger.manual)
|
task = execute_account_backup_plan.delay(pid=pid, trigger=Trigger.manual)
|
||||||
return Response({'task': task.id}, status=status.HTTP_201_CREATED)
|
return Response({'task': task.id}, status=status.HTTP_201_CREATED)
|
||||||
|
|
||||||
def filter_queryset(self, queryset):
|
|
||||||
queryset = super().filter_queryset(queryset)
|
|
||||||
queryset = queryset.order_by('-date_start')
|
|
||||||
return queryset
|
|
@@ -1,7 +1,10 @@
|
|||||||
from assets import serializers
|
from rbac.permissions import RBACPermission
|
||||||
from assets.models import AccountTemplate
|
from common.permissions import UserConfirmation, ConfirmType
|
||||||
from common.mixins import RecordViewLogMixin
|
|
||||||
|
from common.views.mixins import RecordViewLogMixin
|
||||||
from orgs.mixins.api import OrgBulkModelViewSet
|
from orgs.mixins.api import OrgBulkModelViewSet
|
||||||
|
from accounts import serializers
|
||||||
|
from accounts.models import AccountTemplate
|
||||||
|
|
||||||
|
|
||||||
class AccountTemplateViewSet(OrgBulkModelViewSet):
|
class AccountTemplateViewSet(OrgBulkModelViewSet):
|
||||||
@@ -18,8 +21,7 @@ class AccountTemplateSecretsViewSet(RecordViewLogMixin, AccountTemplateViewSet):
|
|||||||
'default': serializers.AccountTemplateSecretSerializer,
|
'default': serializers.AccountTemplateSecretSerializer,
|
||||||
}
|
}
|
||||||
http_method_names = ['get', 'options']
|
http_method_names = ['get', 'options']
|
||||||
# Todo: 记得打开
|
permission_classes = [RBACPermission, UserConfirmation.require(ConfirmType.MFA)]
|
||||||
# permission_classes = [RBACPermission, UserConfirmation.require(ConfirmType.MFA)]
|
|
||||||
rbac_perms = {
|
rbac_perms = {
|
||||||
'list': 'assets.view_accounttemplatesecret',
|
'list': 'assets.view_accounttemplatesecret',
|
||||||
'retrieve': 'assets.view_accounttemplatesecret',
|
'retrieve': 'assets.view_accounttemplatesecret',
|
@@ -1,3 +1,4 @@
|
|||||||
from .base import *
|
from .base import *
|
||||||
from .change_secret import *
|
from .change_secret import *
|
||||||
from .gather_accounts import *
|
from .gather_accounts import *
|
||||||
|
from .push_account import *
|
@@ -4,8 +4,9 @@ from rest_framework import status, mixins, viewsets
|
|||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
|
|
||||||
from assets import serializers
|
from assets import serializers
|
||||||
from assets.models import BaseAutomation, AutomationExecution
|
from assets.models import BaseAutomation
|
||||||
from assets.tasks import execute_automation
|
from accounts.tasks import execute_automation
|
||||||
|
from accounts.models import AutomationExecution
|
||||||
from common.const.choices import Trigger
|
from common.const.choices import Trigger
|
||||||
from orgs.mixins import generics
|
from orgs.mixins import generics
|
||||||
|
|
||||||
@@ -17,13 +18,14 @@ __all__ = [
|
|||||||
|
|
||||||
|
|
||||||
class AutomationAssetsListApi(generics.ListAPIView):
|
class AutomationAssetsListApi(generics.ListAPIView):
|
||||||
|
model = BaseAutomation
|
||||||
serializer_class = serializers.AutomationAssetsSerializer
|
serializer_class = serializers.AutomationAssetsSerializer
|
||||||
filter_fields = ("name", "address")
|
filter_fields = ("name", "address")
|
||||||
search_fields = filter_fields
|
search_fields = filter_fields
|
||||||
|
|
||||||
def get_object(self):
|
def get_object(self):
|
||||||
pk = self.kwargs.get('pk')
|
pk = self.kwargs.get('pk')
|
||||||
return get_object_or_404(BaseAutomation, pk=pk)
|
return get_object_or_404(self.model, pk=pk)
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
instance = self.get_object()
|
instance = self.get_object()
|
||||||
@@ -68,7 +70,7 @@ class AutomationAddAssetApi(generics.RetrieveUpdateAPIView):
|
|||||||
|
|
||||||
class AutomationNodeAddRemoveApi(generics.RetrieveUpdateAPIView):
|
class AutomationNodeAddRemoveApi(generics.RetrieveUpdateAPIView):
|
||||||
model = BaseAutomation
|
model = BaseAutomation
|
||||||
serializer_class = serializers.UpdateAssetSerializer
|
serializer_class = serializers.UpdateNodeSerializer
|
||||||
|
|
||||||
def update(self, request, *args, **kwargs):
|
def update(self, request, *args, **kwargs):
|
||||||
action_params = ['add', 'remove']
|
action_params = ['add', 'remove']
|
||||||
@@ -97,21 +99,17 @@ class AutomationExecutionViewSet(
|
|||||||
filterset_fields = ('trigger', 'automation_id')
|
filterset_fields = ('trigger', 'automation_id')
|
||||||
serializer_class = serializers.AutomationExecutionSerializer
|
serializer_class = serializers.AutomationExecutionSerializer
|
||||||
|
|
||||||
|
tp: str
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
queryset = AutomationExecution.objects.all()
|
queryset = AutomationExecution.objects.all()
|
||||||
return queryset
|
return queryset
|
||||||
|
|
||||||
def filter_queryset(self, queryset):
|
|
||||||
queryset = super().filter_queryset(queryset)
|
|
||||||
queryset = queryset.order_by('-date_start')
|
|
||||||
return queryset
|
|
||||||
|
|
||||||
def create(self, request, *args, **kwargs):
|
def create(self, request, *args, **kwargs):
|
||||||
serializer = self.get_serializer(data=request.data)
|
serializer = self.get_serializer(data=request.data)
|
||||||
serializer.is_valid(raise_exception=True)
|
serializer.is_valid(raise_exception=True)
|
||||||
automation = serializer.validated_data.get('automation')
|
automation = serializer.validated_data.get('automation')
|
||||||
tp = serializer.validated_data.get('type')
|
|
||||||
task = execute_automation.delay(
|
task = execute_automation.delay(
|
||||||
pid=automation.pk, trigger=Trigger.manual, tp=tp
|
pid=automation.pk, trigger=Trigger.manual, tp=self.tp
|
||||||
)
|
)
|
||||||
return Response({'task': task.id}, status=status.HTTP_201_CREATED)
|
return Response({'task': task.id}, status=status.HTTP_201_CREATED)
|
82
apps/accounts/api/automations/change_secret.py
Normal file
82
apps/accounts/api/automations/change_secret.py
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
#
|
||||||
|
|
||||||
|
from rest_framework import mixins
|
||||||
|
|
||||||
|
from accounts import serializers
|
||||||
|
from accounts.const import AutomationTypes
|
||||||
|
from accounts.models import ChangeSecretAutomation, ChangeSecretRecord, AutomationExecution
|
||||||
|
from common.utils import get_object_or_none
|
||||||
|
from orgs.mixins.api import OrgBulkModelViewSet, OrgGenericViewSet
|
||||||
|
from .base import (
|
||||||
|
AutomationAssetsListApi, AutomationRemoveAssetApi, AutomationAddAssetApi,
|
||||||
|
AutomationNodeAddRemoveApi, AutomationExecutionViewSet
|
||||||
|
)
|
||||||
|
|
||||||
|
__all__ = [
|
||||||
|
'ChangeSecretAutomationViewSet', 'ChangeSecretRecordViewSet',
|
||||||
|
'ChangSecretExecutionViewSet', 'ChangSecretAssetsListApi',
|
||||||
|
'ChangSecretRemoveAssetApi', 'ChangSecretAddAssetApi',
|
||||||
|
'ChangSecretNodeAddRemoveApi'
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class ChangeSecretAutomationViewSet(OrgBulkModelViewSet):
|
||||||
|
model = ChangeSecretAutomation
|
||||||
|
filter_fields = ('name', 'secret_type', 'secret_strategy')
|
||||||
|
search_fields = filter_fields
|
||||||
|
ordering_fields = ('name',)
|
||||||
|
serializer_class = serializers.ChangeSecretAutomationSerializer
|
||||||
|
|
||||||
|
|
||||||
|
class ChangeSecretRecordViewSet(mixins.ListModelMixin, OrgGenericViewSet):
|
||||||
|
serializer_class = serializers.ChangeSecretRecordSerializer
|
||||||
|
filter_fields = ['asset', 'execution_id']
|
||||||
|
search_fields = ['asset__hostname']
|
||||||
|
|
||||||
|
def get_queryset(self):
|
||||||
|
return ChangeSecretRecord.objects.filter(
|
||||||
|
execution__automation__type=AutomationTypes.change_secret
|
||||||
|
)
|
||||||
|
|
||||||
|
def filter_queryset(self, queryset):
|
||||||
|
queryset = super().filter_queryset(queryset)
|
||||||
|
eid = self.request.query_params.get('execution_id')
|
||||||
|
execution = get_object_or_none(AutomationExecution, pk=eid)
|
||||||
|
if execution:
|
||||||
|
queryset = queryset.filter(execution=execution)
|
||||||
|
return queryset
|
||||||
|
|
||||||
|
|
||||||
|
class ChangSecretExecutionViewSet(AutomationExecutionViewSet):
|
||||||
|
rbac_perms = (
|
||||||
|
("list", "accounts.view_changesecretexecution"),
|
||||||
|
("retrieve", "accounts.view_changesecretexecution"),
|
||||||
|
("create", "accounts.add_changesecretexecution"),
|
||||||
|
)
|
||||||
|
|
||||||
|
tp = AutomationTypes.change_secret
|
||||||
|
|
||||||
|
def get_queryset(self):
|
||||||
|
queryset = super().get_queryset()
|
||||||
|
queryset = queryset.filter(automation__type=self.tp)
|
||||||
|
return queryset
|
||||||
|
|
||||||
|
|
||||||
|
class ChangSecretAssetsListApi(AutomationAssetsListApi):
|
||||||
|
model = ChangeSecretAutomation
|
||||||
|
|
||||||
|
|
||||||
|
class ChangSecretRemoveAssetApi(AutomationRemoveAssetApi):
|
||||||
|
model = ChangeSecretAutomation
|
||||||
|
serializer_class = serializers.ChangeSecretUpdateAssetSerializer
|
||||||
|
|
||||||
|
|
||||||
|
class ChangSecretAddAssetApi(AutomationAddAssetApi):
|
||||||
|
model = ChangeSecretAutomation
|
||||||
|
serializer_class = serializers.ChangeSecretUpdateAssetSerializer
|
||||||
|
|
||||||
|
|
||||||
|
class ChangSecretNodeAddRemoveApi(AutomationNodeAddRemoveApi):
|
||||||
|
model = ChangeSecretAutomation
|
||||||
|
serializer_class = serializers.ChangeSecretUpdateNodeSerializer
|
@@ -1,7 +1,8 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
#
|
#
|
||||||
from assets import serializers
|
from accounts import serializers
|
||||||
from assets.models import GatherAccountsAutomation
|
from accounts.const import AutomationTypes
|
||||||
|
from accounts.models import GatherAccountsAutomation
|
||||||
from orgs.mixins.api import OrgBulkModelViewSet
|
from orgs.mixins.api import OrgBulkModelViewSet
|
||||||
from .base import AutomationExecutionViewSet
|
from .base import AutomationExecutionViewSet
|
||||||
|
|
||||||
@@ -20,7 +21,14 @@ class GatherAccountsAutomationViewSet(OrgBulkModelViewSet):
|
|||||||
|
|
||||||
class GatherAccountsExecutionViewSet(AutomationExecutionViewSet):
|
class GatherAccountsExecutionViewSet(AutomationExecutionViewSet):
|
||||||
rbac_perms = (
|
rbac_perms = (
|
||||||
("list", "assets.view_gatheraccountsexecution"),
|
("list", "accounts.view_gatheraccountsexecution"),
|
||||||
("retrieve", "assets.view_gatheraccountsexecution"),
|
("retrieve", "accounts.view_gatheraccountsexecution"),
|
||||||
("create", "assets.add_gatheraccountsexecution"),
|
("create", "accounts.add_gatheraccountsexecution"),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
tp = AutomationTypes.gather_accounts
|
||||||
|
|
||||||
|
def get_queryset(self):
|
||||||
|
queryset = super().get_queryset()
|
||||||
|
queryset = queryset.filter(automation__type=self.tp)
|
||||||
|
return queryset
|
69
apps/accounts/api/automations/push_account.py
Normal file
69
apps/accounts/api/automations/push_account.py
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
#
|
||||||
|
from accounts import serializers
|
||||||
|
from accounts.const import AutomationTypes
|
||||||
|
from accounts.models import PushAccountAutomation, ChangeSecretRecord
|
||||||
|
from orgs.mixins.api import OrgBulkModelViewSet
|
||||||
|
|
||||||
|
from .base import (
|
||||||
|
AutomationAssetsListApi, AutomationRemoveAssetApi, AutomationAddAssetApi,
|
||||||
|
AutomationNodeAddRemoveApi, AutomationExecutionViewSet
|
||||||
|
)
|
||||||
|
from .change_secret import ChangeSecretRecordViewSet
|
||||||
|
|
||||||
|
__all__ = [
|
||||||
|
'PushAccountAutomationViewSet', 'PushAccountAssetsListApi', 'PushAccountRemoveAssetApi',
|
||||||
|
'PushAccountAddAssetApi', 'PushAccountNodeAddRemoveApi', 'PushAccountExecutionViewSet',
|
||||||
|
'PushAccountRecordViewSet'
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class PushAccountAutomationViewSet(OrgBulkModelViewSet):
|
||||||
|
model = PushAccountAutomation
|
||||||
|
filter_fields = ('name', 'secret_type', 'secret_strategy')
|
||||||
|
search_fields = filter_fields
|
||||||
|
ordering_fields = ('name',)
|
||||||
|
serializer_class = serializers.PushAccountAutomationSerializer
|
||||||
|
|
||||||
|
|
||||||
|
class PushAccountExecutionViewSet(AutomationExecutionViewSet):
|
||||||
|
rbac_perms = (
|
||||||
|
("list", "accounts.view_pushaccountexecution"),
|
||||||
|
("retrieve", "accounts.view_pushaccountexecution"),
|
||||||
|
("create", "accounts.add_pushaccountexecution"),
|
||||||
|
)
|
||||||
|
|
||||||
|
tp = AutomationTypes.push_account
|
||||||
|
|
||||||
|
def get_queryset(self):
|
||||||
|
queryset = super().get_queryset()
|
||||||
|
queryset = queryset.filter(automation__type=self.tp)
|
||||||
|
return queryset
|
||||||
|
|
||||||
|
|
||||||
|
class PushAccountRecordViewSet(ChangeSecretRecordViewSet):
|
||||||
|
serializer_class = serializers.ChangeSecretRecordSerializer
|
||||||
|
|
||||||
|
def get_queryset(self):
|
||||||
|
return ChangeSecretRecord.objects.filter(
|
||||||
|
execution__automation__type=AutomationTypes.push_account
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class PushAccountAssetsListApi(AutomationAssetsListApi):
|
||||||
|
model = PushAccountAutomation
|
||||||
|
|
||||||
|
|
||||||
|
class PushAccountRemoveAssetApi(AutomationRemoveAssetApi):
|
||||||
|
model = PushAccountAutomation
|
||||||
|
serializer_class = serializers.PushAccountUpdateAssetSerializer
|
||||||
|
|
||||||
|
|
||||||
|
class PushAccountAddAssetApi(AutomationAddAssetApi):
|
||||||
|
model = PushAccountAutomation
|
||||||
|
serializer_class = serializers.PushAccountUpdateAssetSerializer
|
||||||
|
|
||||||
|
|
||||||
|
class PushAccountNodeAddRemoveApi(AutomationNodeAddRemoveApi):
|
||||||
|
model = PushAccountAutomation
|
||||||
|
serializer_class = serializers.PushAccountUpdateNodeSerializer
|
10
apps/accounts/apps.py
Normal file
10
apps/accounts/apps.py
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
from django.apps import AppConfig
|
||||||
|
|
||||||
|
|
||||||
|
class AccountsConfig(AppConfig):
|
||||||
|
default_auto_field = 'django.db.models.BigAutoField'
|
||||||
|
name = 'accounts'
|
||||||
|
|
||||||
|
def ready(self):
|
||||||
|
from . import signal_handlers
|
||||||
|
__all__ = signal_handlers
|
2
apps/accounts/automations/__init__.py
Normal file
2
apps/accounts/automations/__init__.py
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
from .endpoint import ExecutionManager
|
||||||
|
from .methods import platform_automation_methods
|
@@ -7,10 +7,10 @@ from django.conf import settings
|
|||||||
from django.db.models import F
|
from django.db.models import F
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
|
|
||||||
from assets.models import Account
|
from accounts.models import Account
|
||||||
from assets.const import AllTypes
|
from assets.const import AllTypes
|
||||||
from assets.serializers import AccountSecretSerializer
|
from accounts.serializers import AccountSecretSerializer
|
||||||
from assets.notifications import AccountBackupExecutionTaskMsg
|
from accounts.notifications import AccountBackupExecutionTaskMsg
|
||||||
from users.models import User
|
from users.models import User
|
||||||
from common.utils import get_logger
|
from common.utils import get_logger
|
||||||
from common.utils.timezone import local_now_display
|
from common.utils.timezone import local_now_display
|
14
apps/accounts/automations/base/base_inventory.txt
Normal file
14
apps/accounts/automations/base/base_inventory.txt
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
## all connection vars
|
||||||
|
hostname asset_name=name asset_type=type asset_primary_protocol=ssh asset_primary_port=22 asset_protocols=[]
|
||||||
|
|
||||||
|
## local connection
|
||||||
|
hostname ansible_connection=local
|
||||||
|
|
||||||
|
## local connection with gateway
|
||||||
|
hostname ansible_connection=ssh ansible_user=gateway.username ansible_port=gateway.port ansible_host=gateway.host ansible_ssh_private_key_file=gateway.key
|
||||||
|
|
||||||
|
## ssh connection for windows
|
||||||
|
hostname ansible_connection=ssh ansible_shell_type=powershell/cmd ansible_user=windows.username ansible_port=windows.port ansible_host=windows.host ansible_ssh_private_key_file=windows.key
|
||||||
|
|
||||||
|
## ssh connection
|
||||||
|
hostname ansible_user=user ansible_password=pass ansible_host=host ansible_port=port ansible_ssh_private_key_file=key ssh_args="-o StrictHostKeyChecking=no"
|
55
apps/accounts/automations/base/manager.py
Normal file
55
apps/accounts/automations/base/manager.py
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
from copy import deepcopy
|
||||||
|
|
||||||
|
from common.utils import get_logger
|
||||||
|
from accounts.const import AutomationTypes, SecretType
|
||||||
|
from assets.automations.base.manager import BasePlaybookManager
|
||||||
|
from accounts.automations.methods import platform_automation_methods
|
||||||
|
|
||||||
|
logger = get_logger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class PushOrVerifyHostCallbackMixin:
|
||||||
|
execution: callable
|
||||||
|
get_accounts: callable
|
||||||
|
host_account_mapper: dict
|
||||||
|
generate_public_key: callable
|
||||||
|
generate_private_key_path: callable
|
||||||
|
|
||||||
|
def host_callback(self, host, asset=None, account=None, automation=None, path_dir=None, **kwargs):
|
||||||
|
host = super().host_callback(host, asset=asset, account=account, automation=automation, **kwargs)
|
||||||
|
if host.get('error'):
|
||||||
|
return host
|
||||||
|
|
||||||
|
accounts = asset.accounts.all()
|
||||||
|
accounts = self.get_accounts(account, accounts)
|
||||||
|
|
||||||
|
inventory_hosts = []
|
||||||
|
for account in accounts:
|
||||||
|
h = deepcopy(host)
|
||||||
|
h['name'] += '_' + account.username
|
||||||
|
self.host_account_mapper[h['name']] = account
|
||||||
|
secret = account.secret
|
||||||
|
|
||||||
|
private_key_path = None
|
||||||
|
if account.secret_type == SecretType.SSH_KEY:
|
||||||
|
private_key_path = self.generate_private_key_path(secret, path_dir)
|
||||||
|
secret = self.generate_public_key(secret)
|
||||||
|
|
||||||
|
h['secret_type'] = account.secret_type
|
||||||
|
h['account'] = {
|
||||||
|
'name': account.name,
|
||||||
|
'username': account.username,
|
||||||
|
'secret_type': account.secret_type,
|
||||||
|
'secret': secret,
|
||||||
|
'private_key_path': private_key_path
|
||||||
|
}
|
||||||
|
inventory_hosts.append(h)
|
||||||
|
return inventory_hosts
|
||||||
|
|
||||||
|
|
||||||
|
class AccountBasePlaybookManager(BasePlaybookManager):
|
||||||
|
pass
|
||||||
|
|
||||||
|
@property
|
||||||
|
def platform_automation_methods(self):
|
||||||
|
return platform_automation_methods
|
@@ -1,5 +1,5 @@
|
|||||||
id: change_secret_mongodb
|
id: change_secret_mongodb
|
||||||
name: Change password for MongoDB
|
name: Change secret for MongoDB
|
||||||
category: database
|
category: database
|
||||||
type:
|
type:
|
||||||
- mongodb
|
- mongodb
|
@@ -1,6 +1,7 @@
|
|||||||
id: change_secret_mysql
|
id: change_secret_mysql
|
||||||
name: Change password for MySQL
|
name: Change secret for MySQL
|
||||||
category: database
|
category: database
|
||||||
type:
|
type:
|
||||||
- mysql
|
- mysql
|
||||||
|
- mariadb
|
||||||
method: change_secret
|
method: change_secret
|
@@ -1,5 +1,5 @@
|
|||||||
id: change_secret_oracle
|
id: change_secret_oracle
|
||||||
name: Change password for Oracle
|
name: Change secret for Oracle
|
||||||
category: database
|
category: database
|
||||||
type:
|
type:
|
||||||
- oracle
|
- oracle
|
@@ -1,5 +1,5 @@
|
|||||||
id: change_secret_postgresql
|
id: change_secret_postgresql
|
||||||
name: Change password for PostgreSQL
|
name: Change secret for PostgreSQL
|
||||||
category: database
|
category: database
|
||||||
type:
|
type:
|
||||||
- postgresql
|
- postgresql
|
@@ -1,5 +1,5 @@
|
|||||||
id: change_secret_sqlserver
|
id: change_secret_sqlserver
|
||||||
name: Change password for SQLServer
|
name: Change secret for SQLServer
|
||||||
category: database
|
category: database
|
||||||
type:
|
type:
|
||||||
- sqlserver
|
- sqlserver
|
@@ -1,5 +1,5 @@
|
|||||||
id: change_secret_local_windows
|
id: change_secret_local_windows
|
||||||
name: Change password local account for Windows
|
name: Change secret local account for Windows
|
||||||
version: 1
|
version: 1
|
||||||
method: change_secret
|
method: change_secret
|
||||||
category: host
|
category: host
|
@@ -1,35 +1,38 @@
|
|||||||
import os
|
import os
|
||||||
import time
|
import time
|
||||||
import random
|
|
||||||
import string
|
|
||||||
from copy import deepcopy
|
from copy import deepcopy
|
||||||
from openpyxl import Workbook
|
from openpyxl import Workbook
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
|
|
||||||
from django.utils import timezone
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
from django.utils import timezone
|
||||||
|
|
||||||
from common.utils.timezone import local_now_display
|
|
||||||
from common.utils.file import encrypt_and_compress_zip_file
|
|
||||||
from common.utils import get_logger, lazyproperty, gen_key_pair
|
|
||||||
from users.models import User
|
from users.models import User
|
||||||
from assets.models import ChangeSecretRecord
|
from accounts.models import ChangeSecretRecord
|
||||||
from assets.notifications import ChangeSecretExecutionTaskMsg
|
from accounts.notifications import ChangeSecretExecutionTaskMsg
|
||||||
from assets.serializers import ChangeSecretRecordBackUpSerializer
|
from accounts.serializers import ChangeSecretRecordBackUpSerializer
|
||||||
from assets.const import (
|
from accounts.const import AutomationTypes, SecretType, SSHKeyStrategy, SecretStrategy
|
||||||
AutomationTypes, SecretType, SecretStrategy, SSHKeyStrategy, DEFAULT_PASSWORD_RULES
|
from common.utils import get_logger, lazyproperty
|
||||||
)
|
from common.utils.file import encrypt_and_compress_zip_file
|
||||||
from ..base.manager import BasePlaybookManager
|
from common.utils.timezone import local_now_display
|
||||||
|
from ...utils import SecretGenerator
|
||||||
|
from ..base.manager import AccountBasePlaybookManager
|
||||||
|
|
||||||
logger = get_logger(__name__)
|
logger = get_logger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class ChangeSecretManager(BasePlaybookManager):
|
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['secret_type']
|
self.secret_type = self.execution.snapshot['secret_type']
|
||||||
self.secret_strategy = self.execution.snapshot['secret_strategy']
|
self.secret_strategy = self.execution.snapshot.get(
|
||||||
|
'secret_strategy', SecretStrategy.custom
|
||||||
|
)
|
||||||
|
self.ssh_key_change_strategy = self.execution.snapshot.get(
|
||||||
|
'ssh_key_change_strategy', SSHKeyStrategy.add
|
||||||
|
)
|
||||||
|
self.snapshot_account_usernames = self.execution.snapshot['accounts']
|
||||||
self._password_generated = None
|
self._password_generated = None
|
||||||
self._ssh_key_generated = None
|
self._ssh_key_generated = None
|
||||||
self.name_recorder_mapper = {} # 做个映射,方便后面处理
|
self.name_recorder_mapper = {} # 做个映射,方便后面处理
|
||||||
@@ -42,74 +45,31 @@ class ChangeSecretManager(BasePlaybookManager):
|
|||||||
def related_accounts(self):
|
def related_accounts(self):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def generate_ssh_key():
|
|
||||||
private_key, public_key = gen_key_pair()
|
|
||||||
return private_key
|
|
||||||
|
|
||||||
def generate_password(self):
|
|
||||||
kwargs = self.execution.snapshot['password_rules'] or {}
|
|
||||||
length = int(kwargs.get('length', DEFAULT_PASSWORD_RULES['length']))
|
|
||||||
symbol_set = kwargs.get('symbol_set')
|
|
||||||
if symbol_set is None:
|
|
||||||
symbol_set = DEFAULT_PASSWORD_RULES['symbol_set']
|
|
||||||
|
|
||||||
no_special_chars = string.ascii_letters + string.digits
|
|
||||||
chars = no_special_chars + symbol_set
|
|
||||||
|
|
||||||
first_char = random.choice(no_special_chars)
|
|
||||||
password = ''.join([random.choice(chars) for _ in range(length - 1)])
|
|
||||||
password = first_char + password
|
|
||||||
return password
|
|
||||||
|
|
||||||
def get_ssh_key(self):
|
|
||||||
if self.secret_strategy == SecretStrategy.custom:
|
|
||||||
secret = self.execution.snapshot['secret']
|
|
||||||
if not secret:
|
|
||||||
raise ValueError("Automation SSH key must be set")
|
|
||||||
return secret
|
|
||||||
elif self.secret_strategy == SecretStrategy.random_one:
|
|
||||||
if not self._ssh_key_generated:
|
|
||||||
self._ssh_key_generated = self.generate_ssh_key()
|
|
||||||
return self._ssh_key_generated
|
|
||||||
else:
|
|
||||||
return self.generate_ssh_key()
|
|
||||||
|
|
||||||
def get_password(self):
|
|
||||||
if self.secret_strategy == SecretStrategy.custom:
|
|
||||||
password = self.execution.snapshot['secret']
|
|
||||||
if not password:
|
|
||||||
raise ValueError("Automation Password must be set")
|
|
||||||
return password
|
|
||||||
elif self.secret_strategy == SecretStrategy.random_one:
|
|
||||||
if not self._password_generated:
|
|
||||||
self._password_generated = self.generate_password()
|
|
||||||
return self._password_generated
|
|
||||||
else:
|
|
||||||
return self.generate_password()
|
|
||||||
|
|
||||||
def get_secret(self):
|
|
||||||
if self.secret_type == SecretType.SSH_KEY:
|
|
||||||
secret = self.get_ssh_key()
|
|
||||||
elif self.secret_type == SecretType.PASSWORD:
|
|
||||||
secret = self.get_password()
|
|
||||||
else:
|
|
||||||
raise ValueError("Secret must be set")
|
|
||||||
return secret
|
|
||||||
|
|
||||||
def get_kwargs(self, account, secret):
|
def get_kwargs(self, account, secret):
|
||||||
kwargs = {}
|
kwargs = {}
|
||||||
if self.secret_type != SecretType.SSH_KEY:
|
if self.secret_type != SecretType.SSH_KEY:
|
||||||
return kwargs
|
return kwargs
|
||||||
kwargs['strategy'] = self.execution.snapshot['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'
|
||||||
|
|
||||||
if kwargs['strategy'] == SSHKeyStrategy.set_jms:
|
if kwargs['strategy'] == SSHKeyStrategy.set_jms:
|
||||||
kwargs['dest'] = '/home/{}/.ssh/authorized_keys'.format(account.username)
|
kwargs['dest'] = '/home/{}/.ssh/authorized_keys'.format(account.username)
|
||||||
kwargs['regexp'] = '.*{}$'.format(secret.split()[2].strip())
|
kwargs['regexp'] = '.*{}$'.format(secret.split()[2].strip())
|
||||||
|
|
||||||
return kwargs
|
return kwargs
|
||||||
|
|
||||||
|
@lazyproperty
|
||||||
|
def secret_generator(self):
|
||||||
|
return SecretGenerator(
|
||||||
|
self.secret_strategy, self.secret_type,
|
||||||
|
self.execution.snapshot.get('password_rules')
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_secret(self):
|
||||||
|
if self.secret_strategy == SecretStrategy.custom:
|
||||||
|
return self.execution.snapshot['secret']
|
||||||
|
else:
|
||||||
|
return self.secret_generator.get_secret()
|
||||||
|
|
||||||
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, asset=asset, account=account, automation=automation, **kwargs)
|
host = super().host_callback(host, asset=asset, account=account, automation=automation, **kwargs)
|
||||||
if host.get('error'):
|
if host.get('error'):
|
||||||
@@ -117,10 +77,10 @@ class ChangeSecretManager(BasePlaybookManager):
|
|||||||
|
|
||||||
accounts = asset.accounts.all()
|
accounts = asset.accounts.all()
|
||||||
if account:
|
if account:
|
||||||
accounts = accounts.exclude(id=account.id)
|
accounts = accounts.exclude(username=account.username)
|
||||||
|
|
||||||
if '*' not in self.execution.snapshot['accounts']:
|
if '*' not in self.snapshot_account_usernames:
|
||||||
accounts = accounts.filter(username__in=self.execution.snapshot['accounts'])
|
accounts = accounts.filter(username__in=self.snapshot_account_usernames)
|
||||||
|
|
||||||
accounts = accounts.filter(secret_type=self.secret_type)
|
accounts = accounts.filter(secret_type=self.secret_type)
|
||||||
method_attr = getattr(automation, self.method_type() + '_method')
|
method_attr = getattr(automation, self.method_type() + '_method')
|
||||||
@@ -128,7 +88,6 @@ class ChangeSecretManager(BasePlaybookManager):
|
|||||||
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
|
host['secret_type'] = self.secret_type
|
||||||
for account in accounts:
|
for account in accounts:
|
||||||
h = deepcopy(host)
|
h = deepcopy(host)
|
||||||
@@ -197,7 +156,8 @@ class ChangeSecretManager(BasePlaybookManager):
|
|||||||
recipients = self.execution.recipients
|
recipients = self.execution.recipients
|
||||||
if not recorders or not recipients:
|
if not recorders or not recipients:
|
||||||
return
|
return
|
||||||
recipients = User.objects.filter(id__in=list(recipients))
|
|
||||||
|
recipients = User.objects.filter(id__in=list(recipients.keys()))
|
||||||
|
|
||||||
name = self.execution.snapshot['name']
|
name = self.execution.snapshot['name']
|
||||||
path = os.path.join(os.path.dirname(settings.BASE_DIR), 'tmp')
|
path = os.path.join(os.path.dirname(settings.BASE_DIR), 'tmp')
|
||||||
@@ -219,7 +179,8 @@ class ChangeSecretManager(BasePlaybookManager):
|
|||||||
def create_file(recorders, filename):
|
def create_file(recorders, filename):
|
||||||
serializer_cls = ChangeSecretRecordBackUpSerializer
|
serializer_cls = ChangeSecretRecordBackUpSerializer
|
||||||
serializer = serializer_cls(recorders, many=True)
|
serializer = serializer_cls(recorders, many=True)
|
||||||
header = [v.label for v in serializer.child.fields.values()]
|
|
||||||
|
header = [str(v.label) for v in serializer.child.fields.values()]
|
||||||
rows = [list(row.values()) for row in serializer.data]
|
rows = [list(row.values()) for row in serializer.data]
|
||||||
if not rows:
|
if not rows:
|
||||||
return False
|
return False
|
24
apps/accounts/automations/endpoint.py
Normal file
24
apps/accounts/automations/endpoint.py
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
from .change_secret.manager import ChangeSecretManager
|
||||||
|
from .gather_accounts.manager import GatherAccountsManager
|
||||||
|
from .verify_account.manager import VerifyAccountManager
|
||||||
|
from .push_account.manager import PushAccountManager
|
||||||
|
from .backup_account.manager import AccountBackupManager
|
||||||
|
from ..const import AutomationTypes
|
||||||
|
|
||||||
|
|
||||||
|
class ExecutionManager:
|
||||||
|
manager_type_mapper = {
|
||||||
|
AutomationTypes.push_account: PushAccountManager,
|
||||||
|
AutomationTypes.change_secret: ChangeSecretManager,
|
||||||
|
AutomationTypes.verify_account: VerifyAccountManager,
|
||||||
|
AutomationTypes.gather_accounts: GatherAccountsManager,
|
||||||
|
# TODO 后期迁移到自动化策略中
|
||||||
|
'backup_account': AccountBackupManager,
|
||||||
|
}
|
||||||
|
|
||||||
|
def __init__(self, execution):
|
||||||
|
self.execution = execution
|
||||||
|
self._runner = self.manager_type_mapper[execution.manager_type](execution)
|
||||||
|
|
||||||
|
def run(self, *args, **kwargs):
|
||||||
|
return self._runner.run(*args, **kwargs)
|
@@ -3,4 +3,5 @@ name: Gather account from MySQL
|
|||||||
category: database
|
category: database
|
||||||
type:
|
type:
|
||||||
- mysql
|
- mysql
|
||||||
|
- mariadb
|
||||||
method: gather_accounts
|
method: gather_accounts
|
@@ -1,15 +1,15 @@
|
|||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
from common.utils import get_logger
|
from common.utils import get_logger
|
||||||
from assets.const import AutomationTypes, Source
|
from accounts.const import AutomationTypes, Source
|
||||||
from orgs.utils import tmp_to_org
|
from orgs.utils import tmp_to_org
|
||||||
from .filter import GatherAccountsFilter
|
from .filter import GatherAccountsFilter
|
||||||
from ..base.manager import BasePlaybookManager
|
from ..base.manager import AccountBasePlaybookManager
|
||||||
|
|
||||||
logger = get_logger(__name__)
|
logger = get_logger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class GatherAccountsManager(BasePlaybookManager):
|
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 = {}
|
30
apps/accounts/automations/methods.py
Normal file
30
apps/accounts/automations/methods.py
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
import os
|
||||||
|
import copy
|
||||||
|
|
||||||
|
from accounts.const import AutomationTypes
|
||||||
|
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__))
|
||||||
|
automation_methods = get_platform_automation_methods(BASE_DIR)
|
||||||
|
|
||||||
|
platform_automation_methods = copy_change_secret_to_push_account(automation_methods)
|
112
apps/accounts/automations/push_account/manager.py
Normal file
112
apps/accounts/automations/push_account/manager.py
Normal file
@@ -0,0 +1,112 @@
|
|||||||
|
from django.db.models import QuerySet
|
||||||
|
|
||||||
|
from common.utils import get_logger
|
||||||
|
from accounts.const import AutomationTypes
|
||||||
|
from accounts.models import Account
|
||||||
|
from ..base.manager import PushOrVerifyHostCallbackMixin, AccountBasePlaybookManager
|
||||||
|
|
||||||
|
logger = get_logger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class PushAccountManager(PushOrVerifyHostCallbackMixin, AccountBasePlaybookManager):
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
self.secret_type = self.execution.snapshot['secret_type']
|
||||||
|
self.host_account_mapper = {}
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def method_type(cls):
|
||||||
|
return AutomationTypes.push_account
|
||||||
|
|
||||||
|
def create_nonlocal_accounts(self, accounts, snapshot_account_usernames, asset):
|
||||||
|
secret = self.execution.snapshot['secret']
|
||||||
|
usernames = accounts.filter(secret_type=self.secret_type).values_list(
|
||||||
|
'username', flat=True
|
||||||
|
)
|
||||||
|
create_usernames = set(snapshot_account_usernames) - set(usernames)
|
||||||
|
create_account_objs = [
|
||||||
|
Account(
|
||||||
|
name=username, username=username, secret=secret,
|
||||||
|
secret_type=self.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:
|
||||||
|
logger.debug(f'not privilege account')
|
||||||
|
return []
|
||||||
|
snapshot_account_usernames = self.execution.snapshot['accounts']
|
||||||
|
accounts = accounts.exclude(username=privilege_account.username)
|
||||||
|
if '*' in snapshot_account_usernames:
|
||||||
|
return accounts
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
# @classmethod
|
||||||
|
# def trigger_by_asset_create(cls, asset):
|
||||||
|
# automations = PushAccountAutomation.objects.filter(
|
||||||
|
# triggers__contains=TriggerChoice.on_asset_create
|
||||||
|
# )
|
||||||
|
# account_automation_map = {auto.username: auto for auto in automations}
|
||||||
|
#
|
||||||
|
# util = AssetPermissionUtil()
|
||||||
|
# permissions = util.get_permissions_for_assets([asset], with_node=True)
|
||||||
|
# account_permission_map = defaultdict(list)
|
||||||
|
# for permission in permissions:
|
||||||
|
# for account in permission.accounts:
|
||||||
|
# account_permission_map[account].append(permission)
|
||||||
|
#
|
||||||
|
# username_automation_map = {}
|
||||||
|
# for username, automation in account_automation_map.items():
|
||||||
|
# if username != '@USER':
|
||||||
|
# username_automation_map[username] = automation
|
||||||
|
# continue
|
||||||
|
#
|
||||||
|
# asset_permissions = account_permission_map.get(username)
|
||||||
|
# if not asset_permissions:
|
||||||
|
# continue
|
||||||
|
# asset_permissions = util.get_permissions([p.id for p in asset_permissions])
|
||||||
|
# usernames = asset_permissions.values_list('users__username', flat=True).distinct()
|
||||||
|
# for _username in usernames:
|
||||||
|
# username_automation_map[_username] = automation
|
||||||
|
#
|
||||||
|
# asset_usernames_exists = asset.accounts.values_list('username', flat=True)
|
||||||
|
# accounts_to_create = []
|
||||||
|
# accounts_to_push = []
|
||||||
|
# for username, automation in username_automation_map.items():
|
||||||
|
# if username in asset_usernames_exists:
|
||||||
|
# continue
|
||||||
|
#
|
||||||
|
# if automation.secret_strategy != SecretStrategy.custom:
|
||||||
|
# secret_generator = SecretGenerator(
|
||||||
|
# automation.secret_strategy, automation.secret_type,
|
||||||
|
# automation.password_rules
|
||||||
|
# )
|
||||||
|
# secret = secret_generator.get_secret()
|
||||||
|
# else:
|
||||||
|
# secret = automation.secret
|
||||||
|
#
|
||||||
|
# account = Account(
|
||||||
|
# username=username, secret=secret,
|
||||||
|
# asset=asset, secret_type=automation.secret_type,
|
||||||
|
# comment='Create by account creation {}'.format(automation.name),
|
||||||
|
# )
|
||||||
|
# accounts_to_create.append(account)
|
||||||
|
# if automation.action == 'create_and_push':
|
||||||
|
# accounts_to_push.append(account)
|
||||||
|
# else:
|
||||||
|
# accounts_to_create.append(account)
|
||||||
|
#
|
||||||
|
# logger.debug(f'Create account {account} for asset {asset}')
|
||||||
|
|
||||||
|
# @classmethod
|
||||||
|
# def trigger_by_permission_accounts_change(cls):
|
||||||
|
# pass
|
@@ -1,12 +1,13 @@
|
|||||||
|
from django.db.models import QuerySet
|
||||||
|
|
||||||
from common.utils import get_logger
|
from common.utils import get_logger
|
||||||
from assets.const import AutomationTypes, Connectivity
|
from accounts.const import AutomationTypes, Connectivity
|
||||||
from ..base.manager import BasePlaybookManager, PushOrVerifyHostCallbackMixin
|
from ..base.manager import PushOrVerifyHostCallbackMixin, AccountBasePlaybookManager
|
||||||
|
|
||||||
logger = get_logger(__name__)
|
logger = get_logger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class VerifyAccountManager(PushOrVerifyHostCallbackMixin, BasePlaybookManager):
|
class VerifyAccountManager(PushOrVerifyHostCallbackMixin, AccountBasePlaybookManager):
|
||||||
need_privilege_account = False
|
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
@@ -16,6 +17,12 @@ class VerifyAccountManager(PushOrVerifyHostCallbackMixin, BasePlaybookManager):
|
|||||||
def method_type(cls):
|
def method_type(cls):
|
||||||
return AutomationTypes.verify_account
|
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)
|
2
apps/accounts/const/__init__.py
Normal file
2
apps/accounts/const/__init__.py
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
from .account import *
|
||||||
|
from .automation import *
|
@@ -2,12 +2,6 @@ from django.db.models import TextChoices
|
|||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
|
|
||||||
class Connectivity(TextChoices):
|
|
||||||
UNKNOWN = 'unknown', _('Unknown')
|
|
||||||
OK = 'ok', _('Ok')
|
|
||||||
FAILED = 'failed', _('Failed')
|
|
||||||
|
|
||||||
|
|
||||||
class SecretType(TextChoices):
|
class SecretType(TextChoices):
|
||||||
PASSWORD = 'password', _('Password')
|
PASSWORD = 'password', _('Password')
|
||||||
SSH_KEY = 'ssh_key', _('SSH key')
|
SSH_KEY = 'ssh_key', _('SSH key')
|
94
apps/accounts/const/automation.py
Normal file
94
apps/accounts/const/automation.py
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
from django.db import models
|
||||||
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
|
from assets.const import Connectivity
|
||||||
|
from common.db.fields import TreeChoices
|
||||||
|
|
||||||
|
string_punctuation = '!#$%&()*+,-.:;<=>?@[]^_~'
|
||||||
|
DEFAULT_PASSWORD_LENGTH = 30
|
||||||
|
DEFAULT_PASSWORD_RULES = {
|
||||||
|
'length': DEFAULT_PASSWORD_LENGTH,
|
||||||
|
'symbol_set': string_punctuation
|
||||||
|
}
|
||||||
|
|
||||||
|
__all__ = [
|
||||||
|
'AutomationTypes', 'SecretStrategy', 'SSHKeyStrategy', 'Connectivity',
|
||||||
|
'DEFAULT_PASSWORD_LENGTH', 'DEFAULT_PASSWORD_RULES', 'TriggerChoice',
|
||||||
|
'PushAccountActionChoice',
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class AutomationTypes(models.TextChoices):
|
||||||
|
push_account = 'push_account', _('Push account')
|
||||||
|
change_secret = 'change_secret', _('Change secret')
|
||||||
|
verify_account = 'verify_account', _('Verify account')
|
||||||
|
gather_accounts = 'gather_accounts', _('Gather accounts')
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_type_model(cls, tp):
|
||||||
|
from accounts.models import (
|
||||||
|
PushAccountAutomation, ChangeSecretAutomation,
|
||||||
|
VerifyAccountAutomation, GatherAccountsAutomation,
|
||||||
|
)
|
||||||
|
type_model_dict = {
|
||||||
|
cls.push_account: PushAccountAutomation,
|
||||||
|
cls.change_secret: ChangeSecretAutomation,
|
||||||
|
cls.verify_account: VerifyAccountAutomation,
|
||||||
|
cls.gather_accounts: GatherAccountsAutomation,
|
||||||
|
}
|
||||||
|
return type_model_dict.get(tp)
|
||||||
|
|
||||||
|
|
||||||
|
class SecretStrategy(models.TextChoices):
|
||||||
|
custom = 'specific', _('Specific password')
|
||||||
|
random = 'random', _('Random')
|
||||||
|
|
||||||
|
|
||||||
|
class SSHKeyStrategy(models.TextChoices):
|
||||||
|
add = 'add', _('Append SSH KEY')
|
||||||
|
set = 'set', _('Empty and append SSH KEY')
|
||||||
|
set_jms = 'set_jms', _('Replace (The key generated by JumpServer) ')
|
||||||
|
|
||||||
|
|
||||||
|
class TriggerChoice(models.TextChoices, TreeChoices):
|
||||||
|
# 当资产创建时,直接创建账号,如果是动态账号,需要从授权中查询该资产被授权过的用户,已用户用户名为账号,创建
|
||||||
|
on_asset_create = 'on_asset_create', _('On asset create')
|
||||||
|
# 授权变化包含,用户加入授权,用户组加入授权,资产加入授权,节点加入授权,账号变化
|
||||||
|
# 当添加用户到授权时,查询所有同名账号 automation, 把本授权上的用户 (用户组), 创建到本授权的资产(节点)上
|
||||||
|
on_perm_add_user = 'on_perm_add_user', _('On perm add user')
|
||||||
|
# 当添加用户组到授权时,查询所有同名账号 automation, 把本授权上的用户 (用户组), 创建到本授权的资产(节点)上
|
||||||
|
on_perm_add_user_group = 'on_perm_add_user_group', _('On perm add user group')
|
||||||
|
# 当添加资产到授权时,查询授权的所有账号 automation, 创建到本授权的资产上
|
||||||
|
on_perm_add_asset = 'on_perm_add_asset', _('On perm add asset')
|
||||||
|
# 当添加节点到授权时,查询授权的所有账号 automation, 创建到本授权的节点的资产上
|
||||||
|
on_perm_add_node = 'on_perm_add_node', _('On perm add node')
|
||||||
|
# 当授权的账号变化时,查询授权的所有账号 automation, 创建到本授权的资产(节点)上
|
||||||
|
on_perm_add_account = 'on_perm_add_account', _('On perm add account')
|
||||||
|
# 当资产添加到节点时,查询节点的授权规则,查询授权的所有账号 automation, 创建到本授权的资产(节点)上
|
||||||
|
on_asset_join_node = 'on_asset_join_node', _('On asset join node')
|
||||||
|
# 当用户加入到用户组时,查询用户组的授权规则,查询授权的所有账号 automation, 创建到本授权的资产(节点)上
|
||||||
|
on_user_join_group = 'on_user_join_group', _('On user join group')
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def branches(cls):
|
||||||
|
# 和用户和用户组相关的都是动态账号
|
||||||
|
#
|
||||||
|
return [
|
||||||
|
cls.on_asset_create,
|
||||||
|
(_("On perm change"), [
|
||||||
|
cls.on_perm_add_user,
|
||||||
|
cls.on_perm_add_user_group,
|
||||||
|
cls.on_perm_add_asset,
|
||||||
|
cls.on_perm_add_node,
|
||||||
|
cls.on_perm_add_account,
|
||||||
|
]),
|
||||||
|
(_("Inherit from group or node"), [
|
||||||
|
cls.on_asset_join_node,
|
||||||
|
cls.on_user_join_group,
|
||||||
|
])
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class PushAccountActionChoice(models.TextChoices):
|
||||||
|
create_and_push = 'create_and_push', _('Create and push')
|
||||||
|
only_create = 'only_create', _('Only create')
|
49
apps/accounts/filters.py
Normal file
49
apps/accounts/filters.py
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
#
|
||||||
|
from django.db.models import Q
|
||||||
|
from django_filters import rest_framework as drf_filters
|
||||||
|
|
||||||
|
from assets.models import Node
|
||||||
|
from common.drf.filters import BaseFilterSet
|
||||||
|
|
||||||
|
from .models import Account
|
||||||
|
|
||||||
|
|
||||||
|
class AccountFilterSet(BaseFilterSet):
|
||||||
|
ip = drf_filters.CharFilter(field_name='address', lookup_expr='exact')
|
||||||
|
hostname = drf_filters.CharFilter(field_name='name', lookup_expr='exact')
|
||||||
|
username = drf_filters.CharFilter(field_name="username", lookup_expr='exact')
|
||||||
|
address = drf_filters.CharFilter(field_name="asset__address", lookup_expr='exact')
|
||||||
|
asset = drf_filters.CharFilter(field_name="asset_id", lookup_expr='exact')
|
||||||
|
assets = drf_filters.CharFilter(field_name='asset_id', lookup_expr='exact')
|
||||||
|
nodes = drf_filters.CharFilter(method='filter_nodes')
|
||||||
|
node_id = drf_filters.CharFilter(method='filter_nodes')
|
||||||
|
has_secret = drf_filters.BooleanFilter(method='filter_has_secret')
|
||||||
|
platform = drf_filters.CharFilter(field_name='asset__platform_id', lookup_expr='exact')
|
||||||
|
category = drf_filters.CharFilter(field_name='asset__platform__category', lookup_expr='exact')
|
||||||
|
type = drf_filters.CharFilter(field_name='asset__platform__type', lookup_expr='exact')
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def filter_has_secret(queryset, name, has_secret):
|
||||||
|
q = Q(secret__isnull=True) | Q(secret='')
|
||||||
|
if has_secret:
|
||||||
|
return queryset.exclude(q)
|
||||||
|
else:
|
||||||
|
return queryset.filter(q)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def filter_nodes(queryset, name, value):
|
||||||
|
nodes = Node.objects.filter(id=value)
|
||||||
|
if not nodes:
|
||||||
|
return queryset
|
||||||
|
|
||||||
|
node_qs = Node.objects.none()
|
||||||
|
for node in nodes:
|
||||||
|
node_qs |= node.get_all_children(with_self=True)
|
||||||
|
node_ids = list(node_qs.values_list('id', flat=True))
|
||||||
|
queryset = queryset.filter(asset__nodes__in=node_ids)
|
||||||
|
return queryset
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = Account
|
||||||
|
fields = ['id', 'asset_id']
|
113
apps/accounts/migrations/0001_initial.py
Normal file
113
apps/accounts/migrations/0001_initial.py
Normal file
@@ -0,0 +1,113 @@
|
|||||||
|
# Generated by Django 3.2.14 on 2022-12-28 07:29
|
||||||
|
|
||||||
|
import common.db.encoder
|
||||||
|
import common.db.fields
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
import simple_history.models
|
||||||
|
import uuid
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
initial = True
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||||
|
('assets', '0098_auto_20220430_2126'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Account',
|
||||||
|
fields=[
|
||||||
|
('created_by', models.CharField(blank=True, max_length=128, null=True, verbose_name='Created by')),
|
||||||
|
('updated_by', models.CharField(blank=True, max_length=128, null=True, verbose_name='Updated by')),
|
||||||
|
('date_created', models.DateTimeField(auto_now_add=True, null=True, verbose_name='Date created')),
|
||||||
|
('date_updated', models.DateTimeField(auto_now=True, verbose_name='Date updated')),
|
||||||
|
('comment', models.TextField(blank=True, default='', verbose_name='Comment')),
|
||||||
|
('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)),
|
||||||
|
('org_id',
|
||||||
|
models.CharField(blank=True, db_index=True, default='', max_length=36, verbose_name='Organization')),
|
||||||
|
('connectivity', models.CharField(choices=[('unknown', 'Unknown'), ('ok', 'Ok'), ('failed', 'Failed')],
|
||||||
|
default='unknown', max_length=16, verbose_name='Connectivity')),
|
||||||
|
('date_verified', models.DateTimeField(null=True, verbose_name='Date verified')),
|
||||||
|
('name', models.CharField(max_length=128, verbose_name='Name')),
|
||||||
|
('username', models.CharField(blank=True, db_index=True, max_length=128, verbose_name='Username')),
|
||||||
|
('secret_type', models.CharField(
|
||||||
|
choices=[('password', 'Password'), ('ssh_key', 'SSH key'), ('access_key', 'Access key'),
|
||||||
|
('token', 'Token')], default='password', max_length=16, verbose_name='Secret type')),
|
||||||
|
('secret', common.db.fields.EncryptTextField(blank=True, null=True, verbose_name='Secret')),
|
||||||
|
('privileged', models.BooleanField(default=False, verbose_name='Privileged')),
|
||||||
|
('is_active', models.BooleanField(default=True, verbose_name='Is active')),
|
||||||
|
('version', models.IntegerField(default=0, verbose_name='Version')),
|
||||||
|
('source', models.CharField(default='local', max_length=30, verbose_name='Source')),
|
||||||
|
('asset', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='accounts',
|
||||||
|
to='assets.asset', verbose_name='Asset')),
|
||||||
|
('su_from',
|
||||||
|
models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='su_to',
|
||||||
|
to='accounts.account', verbose_name='Su from')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'Account',
|
||||||
|
'permissions': [('view_accountsecret', 'Can view asset account secret'),
|
||||||
|
('change_accountsecret', 'Can change asset account secret'),
|
||||||
|
('view_historyaccount', 'Can view asset history account'),
|
||||||
|
('view_historyaccountsecret', 'Can view asset history account secret')],
|
||||||
|
'unique_together': {('username', 'asset', 'secret_type'), ('name', 'asset')},
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='HistoricalAccount',
|
||||||
|
fields=[
|
||||||
|
('id', models.UUIDField(db_index=True, default=uuid.uuid4)),
|
||||||
|
('secret_type', models.CharField(
|
||||||
|
choices=[('password', 'Password'), ('ssh_key', 'SSH key'), ('access_key', 'Access key'),
|
||||||
|
('token', 'Token')], default='password', max_length=16, verbose_name='Secret type')),
|
||||||
|
('secret', common.db.fields.EncryptTextField(blank=True, null=True, verbose_name='Secret')),
|
||||||
|
('version', models.IntegerField(default=0, verbose_name='Version')),
|
||||||
|
('history_id', models.AutoField(primary_key=True, serialize=False)),
|
||||||
|
('history_date', models.DateTimeField(db_index=True)),
|
||||||
|
('history_change_reason', models.CharField(max_length=100, null=True)),
|
||||||
|
('history_type',
|
||||||
|
models.CharField(choices=[('+', 'Created'), ('~', 'Changed'), ('-', 'Deleted')], max_length=1)),
|
||||||
|
('history_user',
|
||||||
|
models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+',
|
||||||
|
to=settings.AUTH_USER_MODEL)),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'historical Account',
|
||||||
|
'verbose_name_plural': 'historical Accounts',
|
||||||
|
'ordering': ('-history_date', '-history_id'),
|
||||||
|
'get_latest_by': ('history_date', 'history_id'),
|
||||||
|
},
|
||||||
|
bases=(simple_history.models.HistoricalChanges, models.Model),
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='AccountTemplate',
|
||||||
|
fields=[
|
||||||
|
('created_by', models.CharField(blank=True, max_length=128, null=True, verbose_name='Created by')),
|
||||||
|
('updated_by', models.CharField(blank=True, max_length=128, null=True, verbose_name='Updated by')),
|
||||||
|
('date_created', models.DateTimeField(auto_now_add=True, null=True, verbose_name='Date created')),
|
||||||
|
('date_updated', models.DateTimeField(auto_now=True, verbose_name='Date updated')),
|
||||||
|
('comment', models.TextField(blank=True, default='', verbose_name='Comment')),
|
||||||
|
('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)),
|
||||||
|
('org_id',
|
||||||
|
models.CharField(blank=True, db_index=True, default='', max_length=36, verbose_name='Organization')),
|
||||||
|
('name', models.CharField(max_length=128, verbose_name='Name')),
|
||||||
|
('username', models.CharField(blank=True, db_index=True, max_length=128, verbose_name='Username')),
|
||||||
|
('secret_type', models.CharField(
|
||||||
|
choices=[('password', 'Password'), ('ssh_key', 'SSH key'), ('access_key', 'Access key'),
|
||||||
|
('token', 'Token')], default='password', max_length=16, verbose_name='Secret type')),
|
||||||
|
('secret', common.db.fields.EncryptTextField(blank=True, null=True, verbose_name='Secret')),
|
||||||
|
('privileged', models.BooleanField(default=False, verbose_name='Privileged')),
|
||||||
|
('is_active', models.BooleanField(default=True, verbose_name='Is active')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'Account template',
|
||||||
|
'permissions': [('view_accounttemplatesecret', 'Can view asset account template secret'),
|
||||||
|
('change_accounttemplatesecret', 'Can change asset account template secret')],
|
||||||
|
'unique_together': {('name', 'org_id')},
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
44
apps/accounts/migrations/0002_auto_20220616_0021.py
Normal file
44
apps/accounts/migrations/0002_auto_20220616_0021.py
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
# Generated by Django 3.2.14 on 2022-12-28 10:39
|
||||||
|
|
||||||
|
import common.db.encoder
|
||||||
|
import common.db.fields
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
import uuid
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
dependencies = [
|
||||||
|
('assets', '0106_auto_20221228_1838'),
|
||||||
|
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||||
|
('accounts', '0001_initial'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='AccountBackupAutomation',
|
||||||
|
fields=[
|
||||||
|
('created_by', models.CharField(blank=True, max_length=128, null=True, verbose_name='Created by')),
|
||||||
|
('updated_by', models.CharField(blank=True, max_length=128, null=True, verbose_name='Updated by')),
|
||||||
|
('date_created', models.DateTimeField(auto_now_add=True, null=True, verbose_name='Date created')),
|
||||||
|
('date_updated', models.DateTimeField(auto_now=True, verbose_name='Date updated')),
|
||||||
|
('comment', models.TextField(blank=True, default='', verbose_name='Comment')),
|
||||||
|
('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)),
|
||||||
|
('org_id',
|
||||||
|
models.CharField(blank=True, db_index=True, default='', max_length=36, verbose_name='Organization')),
|
||||||
|
('name', models.CharField(max_length=128, verbose_name='Name')),
|
||||||
|
('is_periodic', models.BooleanField(default=False, verbose_name='Periodic perform')),
|
||||||
|
('interval', models.IntegerField(blank=True, default=24, null=True, verbose_name='Cycle perform')),
|
||||||
|
('crontab', models.CharField(blank=True, max_length=128, null=True, verbose_name='Regularly perform')),
|
||||||
|
('types', models.JSONField(default=list)),
|
||||||
|
('recipients', models.ManyToManyField(blank=True, related_name='recipient_escape_route_plans',
|
||||||
|
to=settings.AUTH_USER_MODEL, verbose_name='Recipient')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'Account backup plan',
|
||||||
|
'ordering': ['name'],
|
||||||
|
'unique_together': {('name', 'org_id')},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
]
|
194
apps/accounts/migrations/0003_automation.py
Normal file
194
apps/accounts/migrations/0003_automation.py
Normal file
@@ -0,0 +1,194 @@
|
|||||||
|
# Generated by Django 3.2.16 on 2022-12-30 08:08
|
||||||
|
|
||||||
|
import common.db.encoder
|
||||||
|
import common.db.fields
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
import uuid
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
dependencies = [
|
||||||
|
('assets', '0107_automation'),
|
||||||
|
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||||
|
('accounts', '0002_auto_20220616_0021'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='AccountBaseAutomation',
|
||||||
|
fields=[
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'Account automation task',
|
||||||
|
'proxy': True,
|
||||||
|
'indexes': [],
|
||||||
|
'constraints': [],
|
||||||
|
},
|
||||||
|
bases=('assets.baseautomation',),
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='AutomationExecution',
|
||||||
|
fields=[
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'Automation execution',
|
||||||
|
'verbose_name_plural': 'Automation executions',
|
||||||
|
'permissions': [('view_changesecretexecution', 'Can view change secret execution'),
|
||||||
|
('add_changesecretexection', 'Can add change secret execution'),
|
||||||
|
('view_gatheraccountsexecution', 'Can view gather accounts execution'),
|
||||||
|
('add_gatheraccountsexecution', 'Can add gather accounts execution')],
|
||||||
|
'proxy': True,
|
||||||
|
'indexes': [],
|
||||||
|
'constraints': [],
|
||||||
|
},
|
||||||
|
bases=('assets.automationexecution',),
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='PushAccountAutomation',
|
||||||
|
fields=[
|
||||||
|
('baseautomation_ptr',
|
||||||
|
models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True,
|
||||||
|
primary_key=True, serialize=False, to='assets.baseautomation')),
|
||||||
|
('secret_type', models.CharField(
|
||||||
|
choices=[('password', 'Password'), ('ssh_key', 'SSH key'), ('access_key', 'Access key'),
|
||||||
|
('token', 'Token')], default='password', max_length=16, verbose_name='Secret type')),
|
||||||
|
('secret_strategy', models.CharField(choices=[('specific', 'Specific password'),
|
||||||
|
('random_one', 'All assets use the same random password'),
|
||||||
|
('random_all',
|
||||||
|
'All assets use different random password')],
|
||||||
|
default='specific', max_length=16,
|
||||||
|
verbose_name='Secret strategy')),
|
||||||
|
('secret', common.db.fields.EncryptTextField(blank=True, null=True, verbose_name='Secret')),
|
||||||
|
('password_rules', models.JSONField(default=dict, verbose_name='Password rules')),
|
||||||
|
('ssh_key_change_strategy', models.CharField(
|
||||||
|
choices=[('add', 'Append SSH KEY'), ('set', 'Empty and append SSH KEY'),
|
||||||
|
('set_jms', 'Replace (The key generated by JumpServer) ')], default='add', max_length=16,
|
||||||
|
verbose_name='SSH key change strategy')),
|
||||||
|
('triggers', models.JSONField(default=list, max_length=16, verbose_name='Triggers')),
|
||||||
|
('username', models.CharField(max_length=128, verbose_name='Username')),
|
||||||
|
('action', models.CharField(max_length=16, verbose_name='Action')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'Push asset account',
|
||||||
|
},
|
||||||
|
bases=('accounts.accountbaseautomation', models.Model),
|
||||||
|
),
|
||||||
|
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='GatherAccountsAutomation',
|
||||||
|
fields=[
|
||||||
|
('baseautomation_ptr',
|
||||||
|
models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True,
|
||||||
|
primary_key=True, serialize=False, to='assets.baseautomation')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'Gather asset accounts',
|
||||||
|
},
|
||||||
|
bases=('accounts.accountbaseautomation',),
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='VerifyAccountAutomation',
|
||||||
|
fields=[
|
||||||
|
('baseautomation_ptr',
|
||||||
|
models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True,
|
||||||
|
primary_key=True, serialize=False, to='assets.baseautomation')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'Verify asset account',
|
||||||
|
},
|
||||||
|
bases=('accounts.accountbaseautomation',),
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='ChangeSecretRecord',
|
||||||
|
fields=[
|
||||||
|
('created_by', models.CharField(blank=True, max_length=128, null=True, verbose_name='Created by')),
|
||||||
|
('updated_by', models.CharField(blank=True, max_length=128, null=True, verbose_name='Updated by')),
|
||||||
|
('date_created', models.DateTimeField(auto_now_add=True, null=True, verbose_name='Date created')),
|
||||||
|
('date_updated', models.DateTimeField(auto_now=True, verbose_name='Date updated')),
|
||||||
|
('comment', models.TextField(blank=True, default='', verbose_name='Comment')),
|
||||||
|
('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)),
|
||||||
|
('old_secret', common.db.fields.EncryptTextField(blank=True, null=True, verbose_name='Old secret')),
|
||||||
|
('new_secret', common.db.fields.EncryptTextField(blank=True, null=True, verbose_name='Secret')),
|
||||||
|
('date_started', models.DateTimeField(blank=True, null=True, verbose_name='Date started')),
|
||||||
|
('date_finished', models.DateTimeField(blank=True, null=True, verbose_name='Date finished')),
|
||||||
|
('status', models.CharField(default='pending', max_length=16)),
|
||||||
|
('error', models.TextField(blank=True, null=True, verbose_name='Error')),
|
||||||
|
('account',
|
||||||
|
models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='accounts.account')),
|
||||||
|
('asset', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='assets.asset')),
|
||||||
|
('execution',
|
||||||
|
models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='accounts.automationexecution')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'Change secret record',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='AccountBackupExecution',
|
||||||
|
fields=[
|
||||||
|
('org_id',
|
||||||
|
models.CharField(blank=True, db_index=True, default='', max_length=36, verbose_name='Organization')),
|
||||||
|
('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)),
|
||||||
|
('date_start', models.DateTimeField(auto_now_add=True, verbose_name='Date start')),
|
||||||
|
('timedelta', models.FloatField(default=0.0, null=True, verbose_name='Time')),
|
||||||
|
('plan_snapshot',
|
||||||
|
models.JSONField(blank=True, default=dict, encoder=common.db.encoder.ModelJSONFieldEncoder, null=True,
|
||||||
|
verbose_name='Account backup snapshot')),
|
||||||
|
('trigger', models.CharField(choices=[('manual', 'Manual trigger'), ('timing', 'Timing trigger')],
|
||||||
|
default='manual', max_length=128, verbose_name='Trigger mode')),
|
||||||
|
('reason', models.CharField(blank=True, max_length=1024, null=True, verbose_name='Reason')),
|
||||||
|
('is_success', models.BooleanField(default=False, verbose_name='Is success')),
|
||||||
|
('plan', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='execution',
|
||||||
|
to='accounts.accountbackupautomation', verbose_name='Account backup plan')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'Account backup execution',
|
||||||
|
'ordering': ('-date_start',),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='ChangeSecretAutomation',
|
||||||
|
fields=[
|
||||||
|
('baseautomation_ptr',
|
||||||
|
models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True,
|
||||||
|
primary_key=True, serialize=False, to='assets.baseautomation')),
|
||||||
|
('secret_type', models.CharField(
|
||||||
|
choices=[('password', 'Password'), ('ssh_key', 'SSH key'), ('access_key', 'Access key'),
|
||||||
|
('token', 'Token')], default='password', max_length=16, verbose_name='Secret type')),
|
||||||
|
('secret_strategy', models.CharField(choices=[('specific', 'Specific password'),
|
||||||
|
('random_one', 'All assets use the same random password'),
|
||||||
|
('random_all',
|
||||||
|
'All assets use different random password')],
|
||||||
|
default='specific', max_length=16,
|
||||||
|
verbose_name='Secret strategy')),
|
||||||
|
('secret', common.db.fields.EncryptTextField(blank=True, null=True, verbose_name='Secret')),
|
||||||
|
('password_rules', models.JSONField(default=dict, verbose_name='Password rules')),
|
||||||
|
('ssh_key_change_strategy', models.CharField(
|
||||||
|
choices=[('add', 'Append SSH KEY'), ('set', 'Empty and append SSH KEY'),
|
||||||
|
('set_jms', 'Replace (The key generated by JumpServer) ')], default='add', max_length=16,
|
||||||
|
verbose_name='SSH key change strategy')),
|
||||||
|
('recipients',
|
||||||
|
models.ManyToManyField(blank=True, to=settings.AUTH_USER_MODEL, verbose_name='Recipient')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'Change secret automation',
|
||||||
|
},
|
||||||
|
bases=('accounts.accountbaseautomation', models.Model),
|
||||||
|
),
|
||||||
|
migrations.AlterModelOptions(
|
||||||
|
name='automationexecution',
|
||||||
|
options={'permissions': [('view_changesecretexecution', 'Can view change secret execution'),
|
||||||
|
('add_changesecretexection', 'Can add change secret execution'),
|
||||||
|
('view_gatheraccountsexecution', 'Can view gather accounts execution'),
|
||||||
|
('add_gatheraccountsexecution', 'Can add gather accounts execution'),
|
||||||
|
('view_pushaccountexecution', 'Can view push account execution'),
|
||||||
|
('add_pushaccountexecution', 'Can add push account execution')],
|
||||||
|
'verbose_name': 'Automation execution', 'verbose_name_plural': 'Automation executions'},
|
||||||
|
),
|
||||||
|
migrations.AlterModelOptions(
|
||||||
|
name='changesecretrecord',
|
||||||
|
options={'ordering': ('-date_started',), 'verbose_name': 'Change secret record'},
|
||||||
|
),
|
||||||
|
]
|
23
apps/accounts/migrations/0004_auto_20230106_1507.py
Normal file
23
apps/accounts/migrations/0004_auto_20230106_1507.py
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
# Generated by Django 3.2.16 on 2023-01-06 07:07
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('accounts', '0003_automation'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='changesecretautomation',
|
||||||
|
name='secret_strategy',
|
||||||
|
field=models.CharField(choices=[('specific', 'Specific password'), ('random', 'Random')], default='specific', max_length=16, verbose_name='Secret strategy'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='pushaccountautomation',
|
||||||
|
name='secret_strategy',
|
||||||
|
field=models.CharField(choices=[('specific', 'Specific password'), ('random', 'Random')], default='specific', max_length=16, verbose_name='Secret strategy'),
|
||||||
|
),
|
||||||
|
]
|
@@ -0,0 +1,17 @@
|
|||||||
|
# Generated by Django 3.2.16 on 2023-01-10 06:45
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('accounts', '0004_auto_20230106_1507'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterModelOptions(
|
||||||
|
name='changesecretrecord',
|
||||||
|
options={'ordering': ('-date_created',), 'verbose_name': 'Change secret record'},
|
||||||
|
),
|
||||||
|
]
|
0
apps/accounts/migrations/__init__.py
Normal file
0
apps/accounts/migrations/__init__.py
Normal file
3
apps/accounts/models/__init__.py
Normal file
3
apps/accounts/models/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
from .base import *
|
||||||
|
from .account import *
|
||||||
|
from .automations import *
|
@@ -4,7 +4,8 @@ from simple_history.models import HistoricalRecords
|
|||||||
|
|
||||||
from common.utils import lazyproperty
|
from common.utils import lazyproperty
|
||||||
from ..const import AliasAccount, Source
|
from ..const import AliasAccount, Source
|
||||||
from .base import AbsConnectivity, BaseAccount
|
from assets.models.base import AbsConnectivity
|
||||||
|
from .base import BaseAccount
|
||||||
|
|
||||||
__all__ = ['Account', 'AccountTemplate']
|
__all__ = ['Account', 'AccountTemplate']
|
||||||
|
|
||||||
@@ -46,7 +47,7 @@ class Account(AbsConnectivity, BaseAccount):
|
|||||||
on_delete=models.CASCADE, verbose_name=_('Asset')
|
on_delete=models.CASCADE, verbose_name=_('Asset')
|
||||||
)
|
)
|
||||||
su_from = models.ForeignKey(
|
su_from = models.ForeignKey(
|
||||||
'assets.Account', related_name='su_to', null=True,
|
'accounts.Account', related_name='su_to', null=True,
|
||||||
on_delete=models.SET_NULL, verbose_name=_("Su from")
|
on_delete=models.SET_NULL, verbose_name=_("Su from")
|
||||||
)
|
)
|
||||||
version = models.IntegerField(default=0, verbose_name=_('Version'))
|
version = models.IntegerField(default=0, verbose_name=_('Version'))
|
6
apps/accounts/models/automations/__init__.py
Normal file
6
apps/accounts/models/automations/__init__.py
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
from .base import *
|
||||||
|
from .backup_account import *
|
||||||
|
from .change_secret import *
|
||||||
|
from .gather_account import *
|
||||||
|
from .push_account import *
|
||||||
|
from .verify_account import *
|
@@ -13,12 +13,12 @@ from common.utils import get_logger
|
|||||||
from ops.mixin import PeriodTaskModelMixin
|
from ops.mixin import PeriodTaskModelMixin
|
||||||
from orgs.mixins.models import OrgModelMixin, JMSOrgBaseModel
|
from orgs.mixins.models import OrgModelMixin, JMSOrgBaseModel
|
||||||
|
|
||||||
__all__ = ['AccountBackupPlan', 'AccountBackupPlanExecution']
|
__all__ = ['AccountBackupAutomation', 'AccountBackupExecution']
|
||||||
|
|
||||||
logger = get_logger(__file__)
|
logger = get_logger(__file__)
|
||||||
|
|
||||||
|
|
||||||
class AccountBackupPlan(PeriodTaskModelMixin, JMSOrgBaseModel):
|
class AccountBackupAutomation(PeriodTaskModelMixin, JMSOrgBaseModel):
|
||||||
types = models.JSONField(default=list)
|
types = models.JSONField(default=list)
|
||||||
recipients = models.ManyToManyField(
|
recipients = models.ManyToManyField(
|
||||||
'users.User', related_name='recipient_escape_route_plans', blank=True,
|
'users.User', related_name='recipient_escape_route_plans', blank=True,
|
||||||
@@ -34,7 +34,7 @@ class AccountBackupPlan(PeriodTaskModelMixin, JMSOrgBaseModel):
|
|||||||
verbose_name = _('Account backup plan')
|
verbose_name = _('Account backup plan')
|
||||||
|
|
||||||
def get_register_task(self):
|
def get_register_task(self):
|
||||||
from ..tasks import execute_account_backup_plan
|
from ...tasks import execute_account_backup_plan
|
||||||
name = "account_backup_plan_period_{}".format(str(self.id)[:8])
|
name = "account_backup_plan_period_{}".format(str(self.id)[:8])
|
||||||
task = execute_account_backup_plan.name
|
task = execute_account_backup_plan.name
|
||||||
args = (str(self.id), Trigger.timing)
|
args = (str(self.id), Trigger.timing)
|
||||||
@@ -56,18 +56,22 @@ class AccountBackupPlan(PeriodTaskModelMixin, JMSOrgBaseModel):
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@property
|
||||||
|
def executed_amount(self):
|
||||||
|
return self.execution.count()
|
||||||
|
|
||||||
def execute(self, trigger):
|
def execute(self, trigger):
|
||||||
try:
|
try:
|
||||||
hid = current_task.request.id
|
hid = current_task.request.id
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
hid = str(uuid.uuid4())
|
hid = str(uuid.uuid4())
|
||||||
execution = AccountBackupPlanExecution.objects.create(
|
execution = AccountBackupExecution.objects.create(
|
||||||
id=hid, plan=self, plan_snapshot=self.to_attr_json(), trigger=trigger
|
id=hid, plan=self, plan_snapshot=self.to_attr_json(), trigger=trigger
|
||||||
)
|
)
|
||||||
return execution.start()
|
return execution.start()
|
||||||
|
|
||||||
|
|
||||||
class AccountBackupPlanExecution(OrgModelMixin):
|
class AccountBackupExecution(OrgModelMixin):
|
||||||
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
|
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
|
||||||
date_start = models.DateTimeField(
|
date_start = models.DateTimeField(
|
||||||
auto_now_add=True, verbose_name=_('Date start')
|
auto_now_add=True, verbose_name=_('Date start')
|
||||||
@@ -88,11 +92,12 @@ class AccountBackupPlanExecution(OrgModelMixin):
|
|||||||
)
|
)
|
||||||
is_success = models.BooleanField(default=False, verbose_name=_('Is success'))
|
is_success = models.BooleanField(default=False, verbose_name=_('Is success'))
|
||||||
plan = models.ForeignKey(
|
plan = models.ForeignKey(
|
||||||
'AccountBackupPlan', related_name='execution', on_delete=models.CASCADE,
|
'AccountBackupAutomation', related_name='execution', on_delete=models.CASCADE,
|
||||||
verbose_name=_('Account backup plan')
|
verbose_name=_('Account backup plan')
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
|
ordering = ('-date_start',)
|
||||||
verbose_name = _('Account backup execution')
|
verbose_name = _('Account backup execution')
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@@ -112,6 +117,6 @@ class AccountBackupPlanExecution(OrgModelMixin):
|
|||||||
return 'backup_account'
|
return 'backup_account'
|
||||||
|
|
||||||
def start(self):
|
def start(self):
|
||||||
from assets.automations.endpoint import ExecutionManager
|
from accounts.automations.endpoint import ExecutionManager
|
||||||
manager = ExecutionManager(execution=self)
|
manager = ExecutionManager(execution=self)
|
||||||
return manager.run()
|
return manager.run()
|
41
apps/accounts/models/automations/base.py
Normal file
41
apps/accounts/models/automations/base.py
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
from django.db import models
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
|
from assets.models.automations import (
|
||||||
|
BaseAutomation as AssetBaseAutomation,
|
||||||
|
AutomationExecution as AssetAutomationExecution
|
||||||
|
)
|
||||||
|
|
||||||
|
__all__ = ['AccountBaseAutomation', 'AutomationExecution']
|
||||||
|
|
||||||
|
|
||||||
|
class AccountBaseAutomation(AssetBaseAutomation):
|
||||||
|
class Meta:
|
||||||
|
proxy = True
|
||||||
|
verbose_name = _("Account automation task")
|
||||||
|
|
||||||
|
@property
|
||||||
|
def execution_model(self):
|
||||||
|
return AutomationExecution
|
||||||
|
|
||||||
|
|
||||||
|
class AutomationExecution(AssetAutomationExecution):
|
||||||
|
class Meta:
|
||||||
|
proxy = True
|
||||||
|
verbose_name = _("Automation execution")
|
||||||
|
verbose_name_plural = _("Automation executions")
|
||||||
|
permissions = [
|
||||||
|
('view_changesecretexecution', _('Can view change secret execution')),
|
||||||
|
('add_changesecretexection', _('Can add change secret execution')),
|
||||||
|
|
||||||
|
('view_gatheraccountsexecution', _('Can view gather accounts execution')),
|
||||||
|
('add_gatheraccountsexecution', _('Can add gather accounts execution')),
|
||||||
|
|
||||||
|
('view_pushaccountexecution', _('Can view push account execution')),
|
||||||
|
('add_pushaccountexecution', _('Can add push account execution')),
|
||||||
|
]
|
||||||
|
|
||||||
|
def start(self):
|
||||||
|
from accounts.automations.endpoint import ExecutionManager
|
||||||
|
manager = ExecutionManager(execution=self)
|
||||||
|
return manager.run()
|
@@ -1,10 +1,12 @@
|
|||||||
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 assets.const import AutomationTypes, SecretType, SecretStrategy, SSHKeyStrategy
|
|
||||||
from common.db import fields
|
from common.db import fields
|
||||||
from common.db.models import JMSBaseModel
|
from common.db.models import JMSBaseModel
|
||||||
from .base import BaseAutomation
|
from accounts.const import (
|
||||||
|
AutomationTypes, SecretType, SecretStrategy, SSHKeyStrategy
|
||||||
|
)
|
||||||
|
from .base import AccountBaseAutomation
|
||||||
|
|
||||||
__all__ = ['ChangeSecretAutomation', 'ChangeSecretRecord', 'ChangeSecretMixin']
|
__all__ = ['ChangeSecretAutomation', 'ChangeSecretRecord', 'ChangeSecretMixin']
|
||||||
|
|
||||||
@@ -28,8 +30,20 @@ class ChangeSecretMixin(models.Model):
|
|||||||
class Meta:
|
class Meta:
|
||||||
abstract = True
|
abstract = True
|
||||||
|
|
||||||
|
def to_attr_json(self):
|
||||||
|
attr_json = super().to_attr_json()
|
||||||
|
attr_json.update({
|
||||||
|
'secret': self.secret,
|
||||||
|
'secret_type': self.secret_type,
|
||||||
|
'secret_strategy': self.secret_strategy,
|
||||||
|
'password_rules': self.password_rules,
|
||||||
|
'ssh_key_change_strategy': self.ssh_key_change_strategy,
|
||||||
|
|
||||||
class ChangeSecretAutomation(BaseAutomation, ChangeSecretMixin):
|
})
|
||||||
|
return attr_json
|
||||||
|
|
||||||
|
|
||||||
|
class ChangeSecretAutomation(ChangeSecretMixin, AccountBaseAutomation):
|
||||||
recipients = models.ManyToManyField('users.User', verbose_name=_("Recipient"), blank=True)
|
recipients = models.ManyToManyField('users.User', verbose_name=_("Recipient"), blank=True)
|
||||||
|
|
||||||
def save(self, *args, **kwargs):
|
def save(self, *args, **kwargs):
|
||||||
@@ -42,11 +56,6 @@ class ChangeSecretAutomation(BaseAutomation, ChangeSecretMixin):
|
|||||||
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_type': self.secret_type,
|
|
||||||
'secret_strategy': self.secret_strategy,
|
|
||||||
'password_rules': self.password_rules,
|
|
||||||
'ssh_key_change_strategy': self.ssh_key_change_strategy,
|
|
||||||
'recipients': {
|
'recipients': {
|
||||||
str(recipient.id): (str(recipient), bool(recipient.secret_key))
|
str(recipient.id): (str(recipient), bool(recipient.secret_key))
|
||||||
for recipient in self.recipients.all()
|
for recipient in self.recipients.all()
|
||||||
@@ -56,9 +65,9 @@ class ChangeSecretAutomation(BaseAutomation, ChangeSecretMixin):
|
|||||||
|
|
||||||
|
|
||||||
class ChangeSecretRecord(JMSBaseModel):
|
class ChangeSecretRecord(JMSBaseModel):
|
||||||
execution = models.ForeignKey('assets.AutomationExecution', on_delete=models.CASCADE)
|
execution = models.ForeignKey('accounts.AutomationExecution', on_delete=models.CASCADE)
|
||||||
asset = models.ForeignKey('assets.Asset', on_delete=models.CASCADE, null=True)
|
asset = models.ForeignKey('assets.Asset', on_delete=models.CASCADE, null=True)
|
||||||
account = models.ForeignKey('assets.Account', on_delete=models.CASCADE, null=True)
|
account = models.ForeignKey('accounts.Account', on_delete=models.CASCADE, null=True)
|
||||||
old_secret = fields.EncryptTextField(blank=True, null=True, verbose_name=_('Old secret'))
|
old_secret = fields.EncryptTextField(blank=True, null=True, verbose_name=_('Old secret'))
|
||||||
new_secret = fields.EncryptTextField(blank=True, null=True, verbose_name=_('Secret'))
|
new_secret = fields.EncryptTextField(blank=True, null=True, verbose_name=_('Secret'))
|
||||||
date_started = models.DateTimeField(blank=True, null=True, verbose_name=_('Date started'))
|
date_started = models.DateTimeField(blank=True, null=True, verbose_name=_('Date started'))
|
||||||
@@ -67,6 +76,7 @@ class ChangeSecretRecord(JMSBaseModel):
|
|||||||
error = models.TextField(blank=True, null=True, verbose_name=_('Error'))
|
error = models.TextField(blank=True, null=True, verbose_name=_('Error'))
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
|
ordering = ('-date_created',)
|
||||||
verbose_name = _("Change secret record")
|
verbose_name = _("Change secret record")
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
@@ -1,19 +1,15 @@
|
|||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
from assets.const import AutomationTypes
|
from accounts.const import AutomationTypes
|
||||||
from .base import BaseAutomation
|
from .base import AccountBaseAutomation
|
||||||
|
|
||||||
__all__ = ['GatherAccountsAutomation']
|
__all__ = ['GatherAccountsAutomation']
|
||||||
|
|
||||||
|
|
||||||
class GatherAccountsAutomation(BaseAutomation):
|
class GatherAccountsAutomation(AccountBaseAutomation):
|
||||||
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)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = _("Gather asset accounts")
|
verbose_name = _("Gather asset accounts")
|
||||||
|
|
||||||
@property
|
|
||||||
def executed_amount(self):
|
|
||||||
return self.executions.count()
|
|
41
apps/accounts/models/automations/push_account.py
Normal file
41
apps/accounts/models/automations/push_account.py
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
from django.db import models
|
||||||
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
|
from accounts.const import AutomationTypes
|
||||||
|
from .base import AccountBaseAutomation
|
||||||
|
from .change_secret import ChangeSecretMixin
|
||||||
|
|
||||||
|
__all__ = ['PushAccountAutomation']
|
||||||
|
|
||||||
|
|
||||||
|
class PushAccountAutomation(ChangeSecretMixin, AccountBaseAutomation):
|
||||||
|
accounts = None
|
||||||
|
triggers = models.JSONField(max_length=16, default=list, verbose_name=_('Triggers'))
|
||||||
|
username = models.CharField(max_length=128, verbose_name=_('Username'))
|
||||||
|
action = models.CharField(max_length=16, verbose_name=_('Action'))
|
||||||
|
|
||||||
|
def set_period_schedule(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
@property
|
||||||
|
def dynamic_username(self):
|
||||||
|
return self.username == '@USER'
|
||||||
|
|
||||||
|
@dynamic_username.setter
|
||||||
|
def dynamic_username(self, value):
|
||||||
|
if value:
|
||||||
|
self.username = '@USER'
|
||||||
|
|
||||||
|
def save(self, *args, **kwargs):
|
||||||
|
self.type = AutomationTypes.push_account
|
||||||
|
super().save(*args, **kwargs)
|
||||||
|
|
||||||
|
def to_attr_json(self):
|
||||||
|
attr_json = super().to_attr_json()
|
||||||
|
attr_json.update({
|
||||||
|
'username': self.username
|
||||||
|
})
|
||||||
|
return attr_json
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = _("Push asset account")
|
@@ -1,12 +1,12 @@
|
|||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
from assets.const import AutomationTypes
|
from accounts.const import AutomationTypes
|
||||||
from .base import BaseAutomation
|
from .base import AccountBaseAutomation
|
||||||
|
|
||||||
__all__ = ['VerifyAccountAutomation']
|
__all__ = ['VerifyAccountAutomation']
|
||||||
|
|
||||||
|
|
||||||
class VerifyAccountAutomation(BaseAutomation):
|
class VerifyAccountAutomation(AccountBaseAutomation):
|
||||||
def save(self, *args, **kwargs):
|
def save(self, *args, **kwargs):
|
||||||
self.type = AutomationTypes.verify_account
|
self.type = AutomationTypes.verify_account
|
||||||
super().save(*args, **kwargs)
|
super().save(*args, **kwargs)
|
154
apps/accounts/models/base.py
Normal file
154
apps/accounts/models/base.py
Normal file
@@ -0,0 +1,154 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
#
|
||||||
|
import os
|
||||||
|
from hashlib import md5
|
||||||
|
|
||||||
|
import sshpubkeys
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import models
|
||||||
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
|
from accounts.const import SecretType
|
||||||
|
from common.db import fields
|
||||||
|
from common.utils import (
|
||||||
|
ssh_key_string_to_obj, ssh_key_gen, get_logger,
|
||||||
|
random_string, lazyproperty, parse_ssh_public_key_str
|
||||||
|
)
|
||||||
|
from orgs.mixins.models import JMSOrgBaseModel, OrgManager
|
||||||
|
|
||||||
|
logger = get_logger(__file__)
|
||||||
|
|
||||||
|
|
||||||
|
class BaseAccountQuerySet(models.QuerySet):
|
||||||
|
def active(self):
|
||||||
|
return self.filter(is_active=True)
|
||||||
|
|
||||||
|
|
||||||
|
class BaseAccountManager(OrgManager):
|
||||||
|
def active(self):
|
||||||
|
return self.get_queryset().active()
|
||||||
|
|
||||||
|
|
||||||
|
class BaseAccount(JMSOrgBaseModel):
|
||||||
|
name = models.CharField(max_length=128, verbose_name=_("Name"))
|
||||||
|
username = models.CharField(max_length=128, blank=True, verbose_name=_('Username'), db_index=True)
|
||||||
|
secret_type = models.CharField(
|
||||||
|
max_length=16, choices=SecretType.choices, default=SecretType.PASSWORD, verbose_name=_('Secret type')
|
||||||
|
)
|
||||||
|
secret = fields.EncryptTextField(blank=True, null=True, verbose_name=_('Secret'))
|
||||||
|
privileged = models.BooleanField(verbose_name=_("Privileged"), default=False)
|
||||||
|
is_active = models.BooleanField(default=True, verbose_name=_("Is active"))
|
||||||
|
|
||||||
|
objects = BaseAccountManager.from_queryset(BaseAccountQuerySet)()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def has_secret(self):
|
||||||
|
return bool(self.secret)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def has_username(self):
|
||||||
|
return bool(self.username)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def specific(self):
|
||||||
|
data = {}
|
||||||
|
if self.secret_type != SecretType.SSH_KEY:
|
||||||
|
return data
|
||||||
|
data['ssh_key_fingerprint'] = self.ssh_key_fingerprint
|
||||||
|
return data
|
||||||
|
|
||||||
|
@property
|
||||||
|
def password(self):
|
||||||
|
if self.secret_type == SecretType.PASSWORD:
|
||||||
|
return self.secret
|
||||||
|
return None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def private_key(self):
|
||||||
|
if self.secret_type == SecretType.SSH_KEY:
|
||||||
|
return self.secret
|
||||||
|
return None
|
||||||
|
|
||||||
|
@private_key.setter
|
||||||
|
def private_key(self, value):
|
||||||
|
self.secret = value
|
||||||
|
self.secret_type = SecretType.SSH_KEY
|
||||||
|
|
||||||
|
@lazyproperty
|
||||||
|
def public_key(self):
|
||||||
|
if self.secret_type == SecretType.SSH_KEY and self.private_key:
|
||||||
|
return parse_ssh_public_key_str(self.private_key)
|
||||||
|
return None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def ssh_key_fingerprint(self):
|
||||||
|
if self.public_key:
|
||||||
|
public_key = self.public_key
|
||||||
|
elif self.private_key:
|
||||||
|
try:
|
||||||
|
public_key = parse_ssh_public_key_str(self.private_key)
|
||||||
|
except IOError as e:
|
||||||
|
return str(e)
|
||||||
|
else:
|
||||||
|
return ''
|
||||||
|
|
||||||
|
public_key_obj = sshpubkeys.SSHKey(public_key)
|
||||||
|
fingerprint = public_key_obj.hash_md5()
|
||||||
|
return fingerprint
|
||||||
|
|
||||||
|
@property
|
||||||
|
def private_key_obj(self):
|
||||||
|
if self.private_key:
|
||||||
|
key_obj = ssh_key_string_to_obj(self.private_key)
|
||||||
|
return key_obj
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def private_key_path(self):
|
||||||
|
if not self.secret_type != SecretType.SSH_KEY \
|
||||||
|
or not self.secret \
|
||||||
|
or not self.private_key:
|
||||||
|
return None
|
||||||
|
project_dir = settings.PROJECT_DIR
|
||||||
|
tmp_dir = os.path.join(project_dir, 'tmp')
|
||||||
|
key_name = '.' + md5(self.private_key.encode('utf-8')).hexdigest()
|
||||||
|
key_path = os.path.join(tmp_dir, key_name)
|
||||||
|
if not os.path.exists(key_path):
|
||||||
|
self.private_key_obj.write_private_key_file(key_path)
|
||||||
|
os.chmod(key_path, 0o400)
|
||||||
|
return key_path
|
||||||
|
|
||||||
|
def get_private_key(self):
|
||||||
|
if not self.private_key:
|
||||||
|
return None
|
||||||
|
return self.private_key
|
||||||
|
|
||||||
|
@property
|
||||||
|
def public_key_obj(self):
|
||||||
|
if self.public_key:
|
||||||
|
try:
|
||||||
|
return sshpubkeys.SSHKey(self.public_key)
|
||||||
|
except TabError:
|
||||||
|
pass
|
||||||
|
return None
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def gen_password(length=36):
|
||||||
|
return random_string(length, special_char=True)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def gen_key(username):
|
||||||
|
private_key, public_key = ssh_key_gen(username=username)
|
||||||
|
return private_key, public_key
|
||||||
|
|
||||||
|
def _to_secret_json(self):
|
||||||
|
"""Push system user use it"""
|
||||||
|
return {
|
||||||
|
'name': self.name,
|
||||||
|
'username': self.username,
|
||||||
|
'public_key': self.public_key,
|
||||||
|
}
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
abstract = True
|
@@ -1,7 +1,7 @@
|
|||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
from users.models import User
|
|
||||||
from common.tasks import send_mail_attachment_async
|
from common.tasks import send_mail_attachment_async
|
||||||
|
from users.models import User
|
||||||
|
|
||||||
|
|
||||||
class AccountBackupExecutionTaskMsg(object):
|
class AccountBackupExecutionTaskMsg(object):
|
||||||
@@ -15,11 +15,13 @@ class AccountBackupExecutionTaskMsg(object):
|
|||||||
def message(self):
|
def message(self):
|
||||||
name = self.name
|
name = self.name
|
||||||
if self.user.secret_key:
|
if self.user.secret_key:
|
||||||
return _('{} - The account backup passage task has been completed. See the attachment for details').format(
|
return _('{} - The account backup passage task has been completed.'
|
||||||
name)
|
' See the attachment for details').format(name)
|
||||||
return _("{} - The account backup passage task has been completed: the encryption password has not been set - "
|
else:
|
||||||
"please go to personal information -> file encryption password to set the encryption password").format(
|
return _("{} - The account backup passage task has been completed: "
|
||||||
name)
|
"the encryption password has not been set - "
|
||||||
|
"please go to personal information -> file encryption password "
|
||||||
|
"to set the encryption password").format(name)
|
||||||
|
|
||||||
def publish(self, attachment_list=None):
|
def publish(self, attachment_list=None):
|
||||||
send_mail_attachment_async(
|
send_mail_attachment_async(
|
||||||
@@ -38,10 +40,12 @@ class ChangeSecretExecutionTaskMsg(object):
|
|||||||
def message(self):
|
def message(self):
|
||||||
name = self.name
|
name = self.name
|
||||||
if self.user.secret_key:
|
if self.user.secret_key:
|
||||||
return _('{} - The encryption change task has been completed. See the attachment for details').format(name)
|
return _('{} - The encryption change task has been completed. '
|
||||||
return _("{} - The encryption change task has been completed: the encryption password has not been set - "
|
'See the attachment for details').format(name)
|
||||||
"please go to personal information -> file encryption password to set the encryption password").format(
|
else:
|
||||||
name)
|
return _("{} - The encryption change task has been completed: the encryption "
|
||||||
|
"password has not been set - please go to personal information -> "
|
||||||
|
"file encryption password to set the encryption password").format(name)
|
||||||
|
|
||||||
def publish(self, attachments=None):
|
def publish(self, attachments=None):
|
||||||
send_mail_attachment_async(
|
send_mail_attachment_async(
|
2
apps/accounts/serializers/__init__.py
Normal file
2
apps/accounts/serializers/__init__.py
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
from .account import *
|
||||||
|
from .automations import *
|
@@ -1,3 +1,4 @@
|
|||||||
from .account import *
|
from .account import *
|
||||||
from .template import *
|
|
||||||
from .backup import *
|
from .backup import *
|
||||||
|
from .base import *
|
||||||
|
from .template import *
|
@@ -1,11 +1,12 @@
|
|||||||
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 assets.const import SecretType, Source
|
from assets.models import Asset
|
||||||
from assets.models import Account, AccountTemplate, Asset
|
from accounts.const import SecretType, Source
|
||||||
from assets.tasks import push_accounts_to_assets
|
from accounts.models import Account, AccountTemplate
|
||||||
from common.drf.fields import ObjectRelatedField, LabeledChoiceField
|
from accounts.tasks import push_accounts_to_assets
|
||||||
from common.drf.serializers import SecretReadableMixin, BulkModelSerializer
|
from common.serializers.fields import ObjectRelatedField, LabeledChoiceField
|
||||||
|
from common.serializers import SecretReadableMixin, BulkModelSerializer
|
||||||
from .base import BaseAccountSerializer
|
from .base import BaseAccountSerializer
|
||||||
|
|
||||||
|
|
@@ -7,26 +7,28 @@ from orgs.mixins.serializers import BulkOrgResourceModelSerializer
|
|||||||
from ops.mixin import PeriodTaskSerializerMixin
|
from ops.mixin import PeriodTaskSerializerMixin
|
||||||
from common.utils import get_logger
|
from common.utils import get_logger
|
||||||
from common.const.choices import Trigger
|
from common.const.choices import Trigger
|
||||||
from common.drf.fields import LabeledChoiceField
|
from common.serializers.fields import LabeledChoiceField
|
||||||
|
|
||||||
from assets.models import AccountBackupPlan, AccountBackupPlanExecution
|
from accounts.models import AccountBackupAutomation, AccountBackupExecution
|
||||||
|
|
||||||
logger = get_logger(__file__)
|
logger = get_logger(__file__)
|
||||||
|
|
||||||
__all__ = ['AccountBackupPlanSerializer', 'AccountBackupPlanExecutionSerializer']
|
__all__ = ['AccountBackupSerializer', 'AccountBackupPlanExecutionSerializer']
|
||||||
|
|
||||||
|
|
||||||
class AccountBackupPlanSerializer(PeriodTaskSerializerMixin, BulkOrgResourceModelSerializer):
|
class AccountBackupSerializer(PeriodTaskSerializerMixin, BulkOrgResourceModelSerializer):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = AccountBackupPlan
|
model = AccountBackupAutomation
|
||||||
fields = [
|
read_only_fields = [
|
||||||
'id', 'name', 'is_periodic', 'interval', 'crontab', 'date_created',
|
'date_created', 'date_updated', 'created_by', 'periodic_display', 'executed_amount'
|
||||||
'date_updated', 'created_by', 'periodic_display', 'comment',
|
]
|
||||||
'recipients', 'types'
|
fields = read_only_fields + [
|
||||||
|
'id', 'name', 'is_periodic', 'interval', 'crontab', 'comment', 'recipients', 'types'
|
||||||
]
|
]
|
||||||
extra_kwargs = {
|
extra_kwargs = {
|
||||||
'name': {'required': True},
|
'name': {'required': True},
|
||||||
'periodic_display': {'label': _('Periodic perform')},
|
'periodic_display': {'label': _('Periodic perform')},
|
||||||
|
'executed_amount': {'label': _('Executed amount')},
|
||||||
'recipients': {'label': _('Recipient'), 'help_text': _(
|
'recipients': {'label': _('Recipient'), 'help_text': _(
|
||||||
'Currently only mail sending is supported'
|
'Currently only mail sending is supported'
|
||||||
)}
|
)}
|
||||||
@@ -34,9 +36,10 @@ class AccountBackupPlanSerializer(PeriodTaskSerializerMixin, BulkOrgResourceMode
|
|||||||
|
|
||||||
|
|
||||||
class AccountBackupPlanExecutionSerializer(serializers.ModelSerializer):
|
class AccountBackupPlanExecutionSerializer(serializers.ModelSerializer):
|
||||||
|
trigger = LabeledChoiceField(choices=Trigger.choices, label=_("Trigger mode"))
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = AccountBackupPlanExecution
|
model = AccountBackupExecution
|
||||||
read_only_fields = [
|
read_only_fields = [
|
||||||
'id', 'date_start', 'timedelta', 'plan_snapshot', 'trigger', 'reason',
|
'id', 'date_start', 'timedelta', 'plan_snapshot', 'trigger', 'reason',
|
||||||
'is_success', 'org_id', 'recipients'
|
'is_success', 'org_id', 'recipients'
|
81
apps/accounts/serializers/account/base.py
Normal file
81
apps/accounts/serializers/account/base.py
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
from rest_framework import serializers
|
||||||
|
|
||||||
|
from accounts.const import SecretType
|
||||||
|
from accounts.models import BaseAccount
|
||||||
|
from accounts.utils import validate_password_for_ansible, validate_ssh_key
|
||||||
|
from common.serializers.fields import EncryptedField, LabeledChoiceField
|
||||||
|
from orgs.mixins.serializers import BulkOrgResourceModelSerializer
|
||||||
|
|
||||||
|
__all__ = ['AuthValidateMixin', 'BaseAccountSerializer']
|
||||||
|
|
||||||
|
|
||||||
|
class AuthValidateMixin(serializers.Serializer):
|
||||||
|
secret_type = LabeledChoiceField(
|
||||||
|
choices=SecretType.choices, required=True, label=_('Secret type')
|
||||||
|
)
|
||||||
|
secret = EncryptedField(
|
||||||
|
label=_('Secret'), required=False, max_length=40960, allow_blank=True,
|
||||||
|
allow_null=True, write_only=True,
|
||||||
|
)
|
||||||
|
passphrase = serializers.CharField(
|
||||||
|
allow_blank=True, allow_null=True, required=False, max_length=512,
|
||||||
|
write_only=True, label=_('Key password')
|
||||||
|
)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def initial_secret_type(self):
|
||||||
|
secret_type = self.initial_data.get('secret_type')
|
||||||
|
return secret_type
|
||||||
|
|
||||||
|
def validate_secret(self, secret):
|
||||||
|
if not secret:
|
||||||
|
return ''
|
||||||
|
secret_type = self.initial_secret_type
|
||||||
|
if secret_type == SecretType.PASSWORD:
|
||||||
|
validate_password_for_ansible(secret)
|
||||||
|
return secret
|
||||||
|
elif secret_type == SecretType.SSH_KEY:
|
||||||
|
passphrase = self.initial_data.get('passphrase')
|
||||||
|
passphrase = passphrase if passphrase else None
|
||||||
|
return validate_ssh_key(secret, passphrase)
|
||||||
|
else:
|
||||||
|
return secret
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def clean_auth_fields(validated_data):
|
||||||
|
for field in ('secret',):
|
||||||
|
value = validated_data.get(field)
|
||||||
|
if value is None:
|
||||||
|
validated_data.pop(field, None)
|
||||||
|
validated_data.pop('passphrase', None)
|
||||||
|
|
||||||
|
def create(self, validated_data):
|
||||||
|
self.clean_auth_fields(validated_data)
|
||||||
|
return super().create(validated_data)
|
||||||
|
|
||||||
|
def update(self, instance, validated_data):
|
||||||
|
self.clean_auth_fields(validated_data)
|
||||||
|
return super().update(instance, validated_data)
|
||||||
|
|
||||||
|
|
||||||
|
class BaseAccountSerializer(AuthValidateMixin, BulkOrgResourceModelSerializer):
|
||||||
|
has_secret = serializers.BooleanField(label=_("Has secret"), read_only=True)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = BaseAccount
|
||||||
|
fields_mini = ['id', 'name', 'username']
|
||||||
|
fields_small = fields_mini + [
|
||||||
|
'secret_type', 'secret', 'has_secret', 'passphrase',
|
||||||
|
'privileged', 'is_active', 'specific',
|
||||||
|
]
|
||||||
|
fields_other = ['created_by', 'date_created', 'date_updated', 'comment']
|
||||||
|
fields = fields_small + fields_other
|
||||||
|
read_only_fields = [
|
||||||
|
'has_secret', 'specific',
|
||||||
|
'date_verified', 'created_by', 'date_created',
|
||||||
|
]
|
||||||
|
extra_kwargs = {
|
||||||
|
'specific': {'label': _('Specific')},
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user