mirror of https://github.com/jumpserver/jumpserver
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>pull/9325/head
parent
3f264ae999
commit
56d533c802
|
@ -42,4 +42,4 @@ release/*
|
|||
releashe
|
||||
/apps/script.py
|
||||
data/*
|
||||
|
||||
test.py
|
||||
|
|
|
@ -99,7 +99,6 @@ VOLUME /opt/jumpserver/data
|
|||
VOLUME /opt/jumpserver/logs
|
||||
|
||||
ENV LANG=zh_CN.UTF-8
|
||||
ENV ANSIBLE_LIBRARY=/opt/jumpserver/apps/ops/ansible/modules
|
||||
|
||||
EXPOSE 8080
|
||||
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
from django.contrib import admin
|
||||
|
||||
# Register your models here.
|
|
@ -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.response import Response
|
||||
|
||||
from assets import serializers
|
||||
from assets.filters import AccountFilterSet
|
||||
from assets.models import Account, Asset
|
||||
from assets.tasks import verify_accounts_connectivity
|
||||
from accounts import serializers
|
||||
from accounts.filters import AccountFilterSet
|
||||
from accounts.models import Account
|
||||
from accounts.tasks import verify_accounts_connectivity
|
||||
from assets.models import Asset
|
||||
from authentication.const import ConfirmType
|
||||
from common.mixins import RecordViewLogMixin
|
||||
from common.views.mixins import RecordViewLogMixin
|
||||
from common.permissions import UserConfirmation
|
||||
from orgs.mixins.api import OrgBulkModelViewSet
|
||||
|
||||
|
@ -25,10 +26,9 @@ class AccountViewSet(OrgBulkModelViewSet):
|
|||
filterset_class = AccountFilterSet
|
||||
serializer_classes = {
|
||||
'default': serializers.AccountSerializer,
|
||||
'verify': serializers.AssetTaskSerializer
|
||||
}
|
||||
rbac_perms = {
|
||||
'verify': 'assets.test_account',
|
||||
'verify_account': 'assets.test_account',
|
||||
'partial_update': 'assets.change_accountsecret',
|
||||
'su_from_accounts': 'assets.view_account',
|
||||
}
|
|
@ -5,10 +5,10 @@ from rest_framework.response import Response
|
|||
|
||||
from orgs.mixins.api import OrgBulkModelViewSet
|
||||
from common.const.choices import Trigger
|
||||
from assets import serializers
|
||||
from assets.tasks import execute_account_backup_plan
|
||||
from assets.models import (
|
||||
AccountBackupPlan, AccountBackupPlanExecution
|
||||
from accounts import serializers
|
||||
from accounts.tasks import execute_account_backup_plan
|
||||
from accounts.models import (
|
||||
AccountBackupAutomation, AccountBackupExecution
|
||||
)
|
||||
|
||||
__all__ = [
|
||||
|
@ -17,12 +17,12 @@ __all__ = [
|
|||
|
||||
|
||||
class AccountBackupPlanViewSet(OrgBulkModelViewSet):
|
||||
model = AccountBackupPlan
|
||||
model = AccountBackupAutomation
|
||||
filter_fields = ('name',)
|
||||
search_fields = filter_fields
|
||||
ordering_fields = ('name',)
|
||||
ordering = ('name',)
|
||||
serializer_class = serializers.AccountBackupPlanSerializer
|
||||
serializer_class = serializers.AccountBackupSerializer
|
||||
|
||||
|
||||
class AccountBackupPlanExecutionViewSet(viewsets.ModelViewSet):
|
||||
|
@ -32,7 +32,7 @@ class AccountBackupPlanExecutionViewSet(viewsets.ModelViewSet):
|
|||
http_method_names = ['get', 'post', 'options']
|
||||
|
||||
def get_queryset(self):
|
||||
queryset = AccountBackupPlanExecution.objects.all()
|
||||
queryset = AccountBackupExecution.objects.all()
|
||||
return queryset
|
||||
|
||||
def create(self, request, *args, **kwargs):
|
||||
|
@ -41,8 +41,3 @@ class AccountBackupPlanExecutionViewSet(viewsets.ModelViewSet):
|
|||
pid = serializer.data.get('plan')
|
||||
task = execute_account_backup_plan.delay(pid=pid, trigger=Trigger.manual)
|
||||
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 assets.models import AccountTemplate
|
||||
from common.mixins import RecordViewLogMixin
|
||||
from rbac.permissions import RBACPermission
|
||||
from common.permissions import UserConfirmation, ConfirmType
|
||||
|
||||
from common.views.mixins import RecordViewLogMixin
|
||||
from orgs.mixins.api import OrgBulkModelViewSet
|
||||
from accounts import serializers
|
||||
from accounts.models import AccountTemplate
|
||||
|
||||
|
||||
class AccountTemplateViewSet(OrgBulkModelViewSet):
|
||||
|
@ -18,8 +21,7 @@ class AccountTemplateSecretsViewSet(RecordViewLogMixin, AccountTemplateViewSet):
|
|||
'default': serializers.AccountTemplateSecretSerializer,
|
||||
}
|
||||
http_method_names = ['get', 'options']
|
||||
# Todo: 记得打开
|
||||
# permission_classes = [RBACPermission, UserConfirmation.require(ConfirmType.MFA)]
|
||||
permission_classes = [RBACPermission, UserConfirmation.require(ConfirmType.MFA)]
|
||||
rbac_perms = {
|
||||
'list': 'assets.view_accounttemplatesecret',
|
||||
'retrieve': 'assets.view_accounttemplatesecret',
|
|
@ -1,3 +1,4 @@
|
|||
from .base import *
|
||||
from .change_secret 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 assets import serializers
|
||||
from assets.models import BaseAutomation, AutomationExecution
|
||||
from assets.tasks import execute_automation
|
||||
from assets.models import BaseAutomation
|
||||
from accounts.tasks import execute_automation
|
||||
from accounts.models import AutomationExecution
|
||||
from common.const.choices import Trigger
|
||||
from orgs.mixins import generics
|
||||
|
||||
|
@ -17,13 +18,14 @@ __all__ = [
|
|||
|
||||
|
||||
class AutomationAssetsListApi(generics.ListAPIView):
|
||||
model = BaseAutomation
|
||||
serializer_class = serializers.AutomationAssetsSerializer
|
||||
filter_fields = ("name", "address")
|
||||
search_fields = filter_fields
|
||||
|
||||
def get_object(self):
|
||||
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):
|
||||
instance = self.get_object()
|
||||
|
@ -68,7 +70,7 @@ class AutomationAddAssetApi(generics.RetrieveUpdateAPIView):
|
|||
|
||||
class AutomationNodeAddRemoveApi(generics.RetrieveUpdateAPIView):
|
||||
model = BaseAutomation
|
||||
serializer_class = serializers.UpdateAssetSerializer
|
||||
serializer_class = serializers.UpdateNodeSerializer
|
||||
|
||||
def update(self, request, *args, **kwargs):
|
||||
action_params = ['add', 'remove']
|
||||
|
@ -97,21 +99,17 @@ class AutomationExecutionViewSet(
|
|||
filterset_fields = ('trigger', 'automation_id')
|
||||
serializer_class = serializers.AutomationExecutionSerializer
|
||||
|
||||
tp: str
|
||||
|
||||
def get_queryset(self):
|
||||
queryset = AutomationExecution.objects.all()
|
||||
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):
|
||||
serializer = self.get_serializer(data=request.data)
|
||||
serializer.is_valid(raise_exception=True)
|
||||
automation = serializer.validated_data.get('automation')
|
||||
tp = serializer.validated_data.get('type')
|
||||
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)
|
|
@ -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 -*-
|
||||
#
|
||||
from assets import serializers
|
||||
from assets.models import GatherAccountsAutomation
|
||||
from accounts import serializers
|
||||
from accounts.const import AutomationTypes
|
||||
from accounts.models import GatherAccountsAutomation
|
||||
from orgs.mixins.api import OrgBulkModelViewSet
|
||||
from .base import AutomationExecutionViewSet
|
||||
|
||||
|
@ -20,7 +21,14 @@ class GatherAccountsAutomationViewSet(OrgBulkModelViewSet):
|
|||
|
||||
class GatherAccountsExecutionViewSet(AutomationExecutionViewSet):
|
||||
rbac_perms = (
|
||||
("list", "assets.view_gatheraccountsexecution"),
|
||||
("retrieve", "assets.view_gatheraccountsexecution"),
|
||||
("create", "assets.add_gatheraccountsexecution"),
|
||||
("list", "accounts.view_gatheraccountsexecution"),
|
||||
("retrieve", "accounts.view_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
|
|
@ -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
|
|
@ -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
|
|
@ -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 rest_framework import serializers
|
||||
|
||||
from assets.models import Account
|
||||
from accounts.models import Account
|
||||
from assets.const import AllTypes
|
||||
from assets.serializers import AccountSecretSerializer
|
||||
from assets.notifications import AccountBackupExecutionTaskMsg
|
||||
from accounts.serializers import AccountSecretSerializer
|
||||
from accounts.notifications import AccountBackupExecutionTaskMsg
|
||||
from users.models import User
|
||||
from common.utils import get_logger
|
||||
from common.utils.timezone import local_now_display
|
|
@ -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"
|
|
@ -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
|
||||
name: Change password for MongoDB
|
||||
name: Change secret for MongoDB
|
||||
category: database
|
||||
type:
|
||||
- mongodb
|
|
@ -1,6 +1,7 @@
|
|||
id: change_secret_mysql
|
||||
name: Change password for MySQL
|
||||
name: Change secret for MySQL
|
||||
category: database
|
||||
type:
|
||||
- mysql
|
||||
- mariadb
|
||||
method: change_secret
|
|
@ -1,5 +1,5 @@
|
|||
id: change_secret_oracle
|
||||
name: Change password for Oracle
|
||||
name: Change secret for Oracle
|
||||
category: database
|
||||
type:
|
||||
- oracle
|
|
@ -1,5 +1,5 @@
|
|||
id: change_secret_postgresql
|
||||
name: Change password for PostgreSQL
|
||||
name: Change secret for PostgreSQL
|
||||
category: database
|
||||
type:
|
||||
- postgresql
|
|
@ -1,5 +1,5 @@
|
|||
id: change_secret_sqlserver
|
||||
name: Change password for SQLServer
|
||||
name: Change secret for SQLServer
|
||||
category: database
|
||||
type:
|
||||
- sqlserver
|
|
@ -1,5 +1,5 @@
|
|||
id: change_secret_local_windows
|
||||
name: Change password local account for Windows
|
||||
name: Change secret local account for Windows
|
||||
version: 1
|
||||
method: change_secret
|
||||
category: host
|
|
@ -1,35 +1,38 @@
|
|||
import os
|
||||
import time
|
||||
import random
|
||||
import string
|
||||
from copy import deepcopy
|
||||
from openpyxl import Workbook
|
||||
from collections import defaultdict
|
||||
|
||||
from django.utils import timezone
|
||||
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 assets.models import ChangeSecretRecord
|
||||
from assets.notifications import ChangeSecretExecutionTaskMsg
|
||||
from assets.serializers import ChangeSecretRecordBackUpSerializer
|
||||
from assets.const import (
|
||||
AutomationTypes, SecretType, SecretStrategy, SSHKeyStrategy, DEFAULT_PASSWORD_RULES
|
||||
)
|
||||
from ..base.manager import BasePlaybookManager
|
||||
from accounts.models import ChangeSecretRecord
|
||||
from accounts.notifications import ChangeSecretExecutionTaskMsg
|
||||
from accounts.serializers import ChangeSecretRecordBackUpSerializer
|
||||
from accounts.const import AutomationTypes, SecretType, SSHKeyStrategy, SecretStrategy
|
||||
from common.utils import get_logger, lazyproperty
|
||||
from common.utils.file import encrypt_and_compress_zip_file
|
||||
from common.utils.timezone import local_now_display
|
||||
from ...utils import SecretGenerator
|
||||
from ..base.manager import AccountBasePlaybookManager
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
||||
|
||||
class ChangeSecretManager(BasePlaybookManager):
|
||||
class ChangeSecretManager(AccountBasePlaybookManager):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.method_hosts_mapper = defaultdict(list)
|
||||
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._ssh_key_generated = None
|
||||
self.name_recorder_mapper = {} # 做个映射,方便后面处理
|
||||
|
@ -42,74 +45,31 @@ class ChangeSecretManager(BasePlaybookManager):
|
|||
def related_accounts(self):
|
||||
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):
|
||||
kwargs = {}
|
||||
if self.secret_type != SecretType.SSH_KEY:
|
||||
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'
|
||||
|
||||
if kwargs['strategy'] == SSHKeyStrategy.set_jms:
|
||||
kwargs['dest'] = '/home/{}/.ssh/authorized_keys'.format(account.username)
|
||||
kwargs['regexp'] = '.*{}$'.format(secret.split()[2].strip())
|
||||
|
||||
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):
|
||||
host = super().host_callback(host, asset=asset, account=account, automation=automation, **kwargs)
|
||||
if host.get('error'):
|
||||
|
@ -117,10 +77,10 @@ class ChangeSecretManager(BasePlaybookManager):
|
|||
|
||||
accounts = asset.accounts.all()
|
||||
if account:
|
||||
accounts = accounts.exclude(id=account.id)
|
||||
accounts = accounts.exclude(username=account.username)
|
||||
|
||||
if '*' not in self.execution.snapshot['accounts']:
|
||||
accounts = accounts.filter(username__in=self.execution.snapshot['accounts'])
|
||||
if '*' not in self.snapshot_account_usernames:
|
||||
accounts = accounts.filter(username__in=self.snapshot_account_usernames)
|
||||
|
||||
accounts = accounts.filter(secret_type=self.secret_type)
|
||||
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']]
|
||||
inventory_hosts = []
|
||||
records = []
|
||||
|
||||
host['secret_type'] = self.secret_type
|
||||
for account in accounts:
|
||||
h = deepcopy(host)
|
||||
|
@ -197,7 +156,8 @@ class ChangeSecretManager(BasePlaybookManager):
|
|||
recipients = self.execution.recipients
|
||||
if not recorders or not recipients:
|
||||
return
|
||||
recipients = User.objects.filter(id__in=list(recipients))
|
||||
|
||||
recipients = User.objects.filter(id__in=list(recipients.keys()))
|
||||
|
||||
name = self.execution.snapshot['name']
|
||||
path = os.path.join(os.path.dirname(settings.BASE_DIR), 'tmp')
|
||||
|
@ -219,7 +179,8 @@ class ChangeSecretManager(BasePlaybookManager):
|
|||
def create_file(recorders, filename):
|
||||
serializer_cls = ChangeSecretRecordBackUpSerializer
|
||||
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]
|
||||
if not rows:
|
||||
return False
|
|
@ -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
|
||||
type:
|
||||
- mysql
|
||||
- mariadb
|
||||
method: gather_accounts
|
|
@ -1,15 +1,15 @@
|
|||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
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 .filter import GatherAccountsFilter
|
||||
from ..base.manager import BasePlaybookManager
|
||||
from ..base.manager import AccountBasePlaybookManager
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
||||
|
||||
class GatherAccountsManager(BasePlaybookManager):
|
||||
class GatherAccountsManager(AccountBasePlaybookManager):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.host_asset_mapper = {}
|
|
@ -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)
|
|
@ -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 assets.const import AutomationTypes, Connectivity
|
||||
from ..base.manager import BasePlaybookManager, PushOrVerifyHostCallbackMixin
|
||||
from accounts.const import AutomationTypes, Connectivity
|
||||
from ..base.manager import PushOrVerifyHostCallbackMixin, AccountBasePlaybookManager
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
||||
|
||||
class VerifyAccountManager(PushOrVerifyHostCallbackMixin, BasePlaybookManager):
|
||||
need_privilege_account = False
|
||||
class VerifyAccountManager(PushOrVerifyHostCallbackMixin, AccountBasePlaybookManager):
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
@ -16,6 +17,12 @@ class VerifyAccountManager(PushOrVerifyHostCallbackMixin, BasePlaybookManager):
|
|||
def method_type(cls):
|
||||
return AutomationTypes.verify_account
|
||||
|
||||
def get_accounts(self, privilege_account, accounts: QuerySet):
|
||||
snapshot_account_usernames = self.execution.snapshot['accounts']
|
||||
if '*' not in snapshot_account_usernames:
|
||||
accounts = accounts.filter(username__in=snapshot_account_usernames)
|
||||
return accounts
|
||||
|
||||
def on_host_success(self, host, result):
|
||||
account = self.host_account_mapper.get(host)
|
||||
account.set_connectivity(Connectivity.OK)
|
|
@ -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 _
|
||||
|
||||
|
||||
class Connectivity(TextChoices):
|
||||
UNKNOWN = 'unknown', _('Unknown')
|
||||
OK = 'ok', _('Ok')
|
||||
FAILED = 'failed', _('Failed')
|
||||
|
||||
|
||||
class SecretType(TextChoices):
|
||||
PASSWORD = 'password', _('Password')
|
||||
SSH_KEY = 'ssh_key', _('SSH key')
|
|
@ -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')
|
|
@ -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']
|
|
@ -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')},
|
||||
},
|
||||
),
|
||||
]
|
|
@ -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')},
|
||||
},
|
||||
)
|
||||
]
|
|
@ -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'},
|
||||
),
|
||||
]
|
|
@ -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,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 ..const import AliasAccount, Source
|
||||
from .base import AbsConnectivity, BaseAccount
|
||||
from assets.models.base import AbsConnectivity
|
||||
from .base import BaseAccount
|
||||
|
||||
__all__ = ['Account', 'AccountTemplate']
|
||||
|
||||
|
@ -46,7 +47,7 @@ class Account(AbsConnectivity, BaseAccount):
|
|||
on_delete=models.CASCADE, verbose_name=_('Asset')
|
||||
)
|
||||
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")
|
||||
)
|
||||
version = models.IntegerField(default=0, verbose_name=_('Version'))
|
|
@ -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 orgs.mixins.models import OrgModelMixin, JMSOrgBaseModel
|
||||
|
||||
__all__ = ['AccountBackupPlan', 'AccountBackupPlanExecution']
|
||||
__all__ = ['AccountBackupAutomation', 'AccountBackupExecution']
|
||||
|
||||
logger = get_logger(__file__)
|
||||
|
||||
|
||||
class AccountBackupPlan(PeriodTaskModelMixin, JMSOrgBaseModel):
|
||||
class AccountBackupAutomation(PeriodTaskModelMixin, JMSOrgBaseModel):
|
||||
types = models.JSONField(default=list)
|
||||
recipients = models.ManyToManyField(
|
||||
'users.User', related_name='recipient_escape_route_plans', blank=True,
|
||||
|
@ -34,7 +34,7 @@ class AccountBackupPlan(PeriodTaskModelMixin, JMSOrgBaseModel):
|
|||
verbose_name = _('Account backup plan')
|
||||
|
||||
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])
|
||||
task = execute_account_backup_plan.name
|
||||
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):
|
||||
try:
|
||||
hid = current_task.request.id
|
||||
except AttributeError:
|
||||
hid = str(uuid.uuid4())
|
||||
execution = AccountBackupPlanExecution.objects.create(
|
||||
execution = AccountBackupExecution.objects.create(
|
||||
id=hid, plan=self, plan_snapshot=self.to_attr_json(), trigger=trigger
|
||||
)
|
||||
return execution.start()
|
||||
|
||||
|
||||
class AccountBackupPlanExecution(OrgModelMixin):
|
||||
class AccountBackupExecution(OrgModelMixin):
|
||||
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
|
||||
date_start = models.DateTimeField(
|
||||
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'))
|
||||
plan = models.ForeignKey(
|
||||
'AccountBackupPlan', related_name='execution', on_delete=models.CASCADE,
|
||||
'AccountBackupAutomation', related_name='execution', on_delete=models.CASCADE,
|
||||
verbose_name=_('Account backup plan')
|
||||
)
|
||||
|
||||
class Meta:
|
||||
ordering = ('-date_start',)
|
||||
verbose_name = _('Account backup execution')
|
||||
|
||||
@property
|
||||
|
@ -112,6 +117,6 @@ class AccountBackupPlanExecution(OrgModelMixin):
|
|||
return 'backup_account'
|
||||
|
||||
def start(self):
|
||||
from assets.automations.endpoint import ExecutionManager
|
||||
from accounts.automations.endpoint import ExecutionManager
|
||||
manager = ExecutionManager(execution=self)
|
||||
return manager.run()
|
|
@ -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.utils.translation import ugettext_lazy as _
|
||||
|
||||
from assets.const import AutomationTypes, SecretType, SecretStrategy, SSHKeyStrategy
|
||||
from common.db import fields
|
||||
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']
|
||||
|
||||
|
@ -28,8 +30,20 @@ class ChangeSecretMixin(models.Model):
|
|||
class Meta:
|
||||
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)
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
|
@ -42,11 +56,6 @@ class ChangeSecretAutomation(BaseAutomation, ChangeSecretMixin):
|
|||
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,
|
||||
'recipients': {
|
||||
str(recipient.id): (str(recipient), bool(recipient.secret_key))
|
||||
for recipient in self.recipients.all()
|
||||
|
@ -56,9 +65,9 @@ class ChangeSecretAutomation(BaseAutomation, ChangeSecretMixin):
|
|||
|
||||
|
||||
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)
|
||||
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'))
|
||||
new_secret = fields.EncryptTextField(blank=True, null=True, verbose_name=_('Secret'))
|
||||
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'))
|
||||
|
||||
class Meta:
|
||||
ordering = ('-date_created',)
|
||||
verbose_name = _("Change secret record")
|
||||
|
||||
def __str__(self):
|
|
@ -1,19 +1,15 @@
|
|||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from assets.const import AutomationTypes
|
||||
from .base import BaseAutomation
|
||||
from accounts.const import AutomationTypes
|
||||
from .base import AccountBaseAutomation
|
||||
|
||||
__all__ = ['GatherAccountsAutomation']
|
||||
|
||||
|
||||
class GatherAccountsAutomation(BaseAutomation):
|
||||
class GatherAccountsAutomation(AccountBaseAutomation):
|
||||
def save(self, *args, **kwargs):
|
||||
self.type = AutomationTypes.gather_accounts
|
||||
super().save(*args, **kwargs)
|
||||
|
||||
class Meta:
|
||||
verbose_name = _("Gather asset accounts")
|
||||
|
||||
@property
|
||||
def executed_amount(self):
|
||||
return self.executions.count()
|
|
@ -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 assets.const import AutomationTypes
|
||||
from .base import BaseAutomation
|
||||
from accounts.const import AutomationTypes
|
||||
from .base import AccountBaseAutomation
|
||||
|
||||
__all__ = ['VerifyAccountAutomation']
|
||||
|
||||
|
||||
class VerifyAccountAutomation(BaseAutomation):
|
||||
class VerifyAccountAutomation(AccountBaseAutomation):
|
||||
def save(self, *args, **kwargs):
|
||||
self.type = AutomationTypes.verify_account
|
||||
super().save(*args, **kwargs)
|
|
@ -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 users.models import User
|
||||
from common.tasks import send_mail_attachment_async
|
||||
from users.models import User
|
||||
|
||||
|
||||
class AccountBackupExecutionTaskMsg(object):
|
||||
|
@ -15,11 +15,13 @@ class AccountBackupExecutionTaskMsg(object):
|
|||
def message(self):
|
||||
name = self.name
|
||||
if self.user.secret_key:
|
||||
return _('{} - The account backup passage task has been completed. See the attachment for details').format(
|
||||
name)
|
||||
return _("{} - The account backup passage 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)
|
||||
return _('{} - The account backup passage task has been completed.'
|
||||
' See the attachment for details').format(name)
|
||||
else:
|
||||
return _("{} - The account backup passage 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, attachment_list=None):
|
||||
send_mail_attachment_async(
|
||||
|
@ -38,10 +40,12 @@ class ChangeSecretExecutionTaskMsg(object):
|
|||
def message(self):
|
||||
name = self.name
|
||||
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: the encryption password has not been set - "
|
||||
"please go to personal information -> file encryption password to set the encryption password").format(
|
||||
name)
|
||||
return _('{} - The encryption change task has been completed. '
|
||||
'See the attachment for details').format(name)
|
||||
else:
|
||||
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):
|
||||
send_mail_attachment_async(
|
|
@ -0,0 +1,2 @@
|
|||
from .account import *
|
||||
from .automations import *
|
|
@ -1,3 +1,4 @@
|
|||
from .account import *
|
||||
from .template import *
|
||||
from .backup import *
|
||||
from .base import *
|
||||
from .template import *
|
|
@ -1,11 +1,12 @@
|
|||
from django.utils.translation import ugettext_lazy as _
|
||||
from rest_framework import serializers
|
||||
|
||||
from assets.const import SecretType, Source
|
||||
from assets.models import Account, AccountTemplate, Asset
|
||||
from assets.tasks import push_accounts_to_assets
|
||||
from common.drf.fields import ObjectRelatedField, LabeledChoiceField
|
||||
from common.drf.serializers import SecretReadableMixin, BulkModelSerializer
|
||||
from assets.models import Asset
|
||||
from accounts.const import SecretType, Source
|
||||
from accounts.models import Account, AccountTemplate
|
||||
from accounts.tasks import push_accounts_to_assets
|
||||
from common.serializers.fields import ObjectRelatedField, LabeledChoiceField
|
||||
from common.serializers import SecretReadableMixin, BulkModelSerializer
|
||||
from .base import BaseAccountSerializer
|
||||
|
||||
|
|
@ -7,26 +7,28 @@ from orgs.mixins.serializers import BulkOrgResourceModelSerializer
|
|||
from ops.mixin import PeriodTaskSerializerMixin
|
||||
from common.utils import get_logger
|
||||
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__)
|
||||
|
||||
__all__ = ['AccountBackupPlanSerializer', 'AccountBackupPlanExecutionSerializer']
|
||||
__all__ = ['AccountBackupSerializer', 'AccountBackupPlanExecutionSerializer']
|
||||
|
||||
|
||||
class AccountBackupPlanSerializer(PeriodTaskSerializerMixin, BulkOrgResourceModelSerializer):
|
||||
class AccountBackupSerializer(PeriodTaskSerializerMixin, BulkOrgResourceModelSerializer):
|
||||
class Meta:
|
||||
model = AccountBackupPlan
|
||||
fields = [
|
||||
'id', 'name', 'is_periodic', 'interval', 'crontab', 'date_created',
|
||||
'date_updated', 'created_by', 'periodic_display', 'comment',
|
||||
'recipients', 'types'
|
||||
model = AccountBackupAutomation
|
||||
read_only_fields = [
|
||||
'date_created', 'date_updated', 'created_by', 'periodic_display', 'executed_amount'
|
||||
]
|
||||
fields = read_only_fields + [
|
||||
'id', 'name', 'is_periodic', 'interval', 'crontab', 'comment', 'recipients', 'types'
|
||||
]
|
||||
extra_kwargs = {
|
||||
'name': {'required': True},
|
||||
'periodic_display': {'label': _('Periodic perform')},
|
||||
'executed_amount': {'label': _('Executed amount')},
|
||||
'recipients': {'label': _('Recipient'), 'help_text': _(
|
||||
'Currently only mail sending is supported'
|
||||
)}
|
||||
|
@ -34,9 +36,10 @@ class AccountBackupPlanSerializer(PeriodTaskSerializerMixin, BulkOrgResourceMode
|
|||
|
||||
|
||||
class AccountBackupPlanExecutionSerializer(serializers.ModelSerializer):
|
||||
trigger = LabeledChoiceField(choices=Trigger.choices, label=_("Trigger mode"))
|
||||
|
||||
class Meta:
|
||||
model = AccountBackupPlanExecution
|
||||
model = AccountBackupExecution
|
||||
read_only_fields = [
|
||||
'id', 'date_start', 'timedelta', 'plan_snapshot', 'trigger', 'reason',
|
||||
'is_success', 'org_id', 'recipients'
|
|
@ -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
Loading…
Reference in New Issue