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
fit2bot 2023-01-16 19:02:09 +08:00 committed by GitHub
parent 3f264ae999
commit 56d533c802
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
459 changed files with 8461 additions and 6595 deletions

2
.gitignore vendored
View File

@ -42,4 +42,4 @@ release/*
releashe
/apps/script.py
data/*
test.py

View File

@ -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

3
apps/accounts/admin.py Normal file
View File

@ -0,0 +1,3 @@
from django.contrib import admin
# Register your models here.

View File

@ -0,0 +1,2 @@
from .account import *
from .automations import *

View File

@ -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',
}

View File

@ -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

View File

@ -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',

View File

@ -1,3 +1,4 @@
from .base import *
from .change_secret import *
from .gather_accounts import *
from .push_account import *

View File

@ -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)

View File

@ -0,0 +1,82 @@
# -*- coding: utf-8 -*-
#
from rest_framework import mixins
from accounts import serializers
from accounts.const import AutomationTypes
from accounts.models import ChangeSecretAutomation, ChangeSecretRecord, AutomationExecution
from common.utils import get_object_or_none
from orgs.mixins.api import OrgBulkModelViewSet, OrgGenericViewSet
from .base import (
AutomationAssetsListApi, AutomationRemoveAssetApi, AutomationAddAssetApi,
AutomationNodeAddRemoveApi, AutomationExecutionViewSet
)
__all__ = [
'ChangeSecretAutomationViewSet', 'ChangeSecretRecordViewSet',
'ChangSecretExecutionViewSet', 'ChangSecretAssetsListApi',
'ChangSecretRemoveAssetApi', 'ChangSecretAddAssetApi',
'ChangSecretNodeAddRemoveApi'
]
class ChangeSecretAutomationViewSet(OrgBulkModelViewSet):
model = ChangeSecretAutomation
filter_fields = ('name', 'secret_type', 'secret_strategy')
search_fields = filter_fields
ordering_fields = ('name',)
serializer_class = serializers.ChangeSecretAutomationSerializer
class ChangeSecretRecordViewSet(mixins.ListModelMixin, OrgGenericViewSet):
serializer_class = serializers.ChangeSecretRecordSerializer
filter_fields = ['asset', 'execution_id']
search_fields = ['asset__hostname']
def get_queryset(self):
return ChangeSecretRecord.objects.filter(
execution__automation__type=AutomationTypes.change_secret
)
def filter_queryset(self, queryset):
queryset = super().filter_queryset(queryset)
eid = self.request.query_params.get('execution_id')
execution = get_object_or_none(AutomationExecution, pk=eid)
if execution:
queryset = queryset.filter(execution=execution)
return queryset
class ChangSecretExecutionViewSet(AutomationExecutionViewSet):
rbac_perms = (
("list", "accounts.view_changesecretexecution"),
("retrieve", "accounts.view_changesecretexecution"),
("create", "accounts.add_changesecretexecution"),
)
tp = AutomationTypes.change_secret
def get_queryset(self):
queryset = super().get_queryset()
queryset = queryset.filter(automation__type=self.tp)
return queryset
class ChangSecretAssetsListApi(AutomationAssetsListApi):
model = ChangeSecretAutomation
class ChangSecretRemoveAssetApi(AutomationRemoveAssetApi):
model = ChangeSecretAutomation
serializer_class = serializers.ChangeSecretUpdateAssetSerializer
class ChangSecretAddAssetApi(AutomationAddAssetApi):
model = ChangeSecretAutomation
serializer_class = serializers.ChangeSecretUpdateAssetSerializer
class ChangSecretNodeAddRemoveApi(AutomationNodeAddRemoveApi):
model = ChangeSecretAutomation
serializer_class = serializers.ChangeSecretUpdateNodeSerializer

View File

@ -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

View File

@ -0,0 +1,69 @@
# -*- coding: utf-8 -*-
#
from accounts import serializers
from accounts.const import AutomationTypes
from accounts.models import PushAccountAutomation, ChangeSecretRecord
from orgs.mixins.api import OrgBulkModelViewSet
from .base import (
AutomationAssetsListApi, AutomationRemoveAssetApi, AutomationAddAssetApi,
AutomationNodeAddRemoveApi, AutomationExecutionViewSet
)
from .change_secret import ChangeSecretRecordViewSet
__all__ = [
'PushAccountAutomationViewSet', 'PushAccountAssetsListApi', 'PushAccountRemoveAssetApi',
'PushAccountAddAssetApi', 'PushAccountNodeAddRemoveApi', 'PushAccountExecutionViewSet',
'PushAccountRecordViewSet'
]
class PushAccountAutomationViewSet(OrgBulkModelViewSet):
model = PushAccountAutomation
filter_fields = ('name', 'secret_type', 'secret_strategy')
search_fields = filter_fields
ordering_fields = ('name',)
serializer_class = serializers.PushAccountAutomationSerializer
class PushAccountExecutionViewSet(AutomationExecutionViewSet):
rbac_perms = (
("list", "accounts.view_pushaccountexecution"),
("retrieve", "accounts.view_pushaccountexecution"),
("create", "accounts.add_pushaccountexecution"),
)
tp = AutomationTypes.push_account
def get_queryset(self):
queryset = super().get_queryset()
queryset = queryset.filter(automation__type=self.tp)
return queryset
class PushAccountRecordViewSet(ChangeSecretRecordViewSet):
serializer_class = serializers.ChangeSecretRecordSerializer
def get_queryset(self):
return ChangeSecretRecord.objects.filter(
execution__automation__type=AutomationTypes.push_account
)
class PushAccountAssetsListApi(AutomationAssetsListApi):
model = PushAccountAutomation
class PushAccountRemoveAssetApi(AutomationRemoveAssetApi):
model = PushAccountAutomation
serializer_class = serializers.PushAccountUpdateAssetSerializer
class PushAccountAddAssetApi(AutomationAddAssetApi):
model = PushAccountAutomation
serializer_class = serializers.PushAccountUpdateAssetSerializer
class PushAccountNodeAddRemoveApi(AutomationNodeAddRemoveApi):
model = PushAccountAutomation
serializer_class = serializers.PushAccountUpdateNodeSerializer

10
apps/accounts/apps.py Normal file
View File

@ -0,0 +1,10 @@
from django.apps import AppConfig
class AccountsConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'accounts'
def ready(self):
from . import signal_handlers
__all__ = signal_handlers

View File

@ -0,0 +1,2 @@
from .endpoint import ExecutionManager
from .methods import platform_automation_methods

View File

@ -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

View File

@ -0,0 +1,14 @@
## all connection vars
hostname asset_name=name asset_type=type asset_primary_protocol=ssh asset_primary_port=22 asset_protocols=[]
## local connection
hostname ansible_connection=local
## local connection with gateway
hostname ansible_connection=ssh ansible_user=gateway.username ansible_port=gateway.port ansible_host=gateway.host ansible_ssh_private_key_file=gateway.key
## ssh connection for windows
hostname ansible_connection=ssh ansible_shell_type=powershell/cmd ansible_user=windows.username ansible_port=windows.port ansible_host=windows.host ansible_ssh_private_key_file=windows.key
## ssh connection
hostname ansible_user=user ansible_password=pass ansible_host=host ansible_port=port ansible_ssh_private_key_file=key ssh_args="-o StrictHostKeyChecking=no"

View File

@ -0,0 +1,55 @@
from copy import deepcopy
from common.utils import get_logger
from accounts.const import AutomationTypes, SecretType
from assets.automations.base.manager import BasePlaybookManager
from accounts.automations.methods import platform_automation_methods
logger = get_logger(__name__)
class PushOrVerifyHostCallbackMixin:
execution: callable
get_accounts: callable
host_account_mapper: dict
generate_public_key: callable
generate_private_key_path: callable
def host_callback(self, host, asset=None, account=None, automation=None, path_dir=None, **kwargs):
host = super().host_callback(host, asset=asset, account=account, automation=automation, **kwargs)
if host.get('error'):
return host
accounts = asset.accounts.all()
accounts = self.get_accounts(account, accounts)
inventory_hosts = []
for account in accounts:
h = deepcopy(host)
h['name'] += '_' + account.username
self.host_account_mapper[h['name']] = account
secret = account.secret
private_key_path = None
if account.secret_type == SecretType.SSH_KEY:
private_key_path = self.generate_private_key_path(secret, path_dir)
secret = self.generate_public_key(secret)
h['secret_type'] = account.secret_type
h['account'] = {
'name': account.name,
'username': account.username,
'secret_type': account.secret_type,
'secret': secret,
'private_key_path': private_key_path
}
inventory_hosts.append(h)
return inventory_hosts
class AccountBasePlaybookManager(BasePlaybookManager):
pass
@property
def platform_automation_methods(self):
return platform_automation_methods

View File

@ -1,5 +1,5 @@
id: change_secret_mongodb
name: Change password for MongoDB
name: Change secret for MongoDB
category: database
type:
- mongodb

View File

@ -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

View File

@ -1,5 +1,5 @@
id: change_secret_oracle
name: Change password for Oracle
name: Change secret for Oracle
category: database
type:
- oracle

View File

@ -1,5 +1,5 @@
id: change_secret_postgresql
name: Change password for PostgreSQL
name: Change secret for PostgreSQL
category: database
type:
- postgresql

View File

@ -1,5 +1,5 @@
id: change_secret_sqlserver
name: Change password for SQLServer
name: Change secret for SQLServer
category: database
type:
- sqlserver

View File

@ -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

View File

@ -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

View File

@ -0,0 +1,24 @@
from .change_secret.manager import ChangeSecretManager
from .gather_accounts.manager import GatherAccountsManager
from .verify_account.manager import VerifyAccountManager
from .push_account.manager import PushAccountManager
from .backup_account.manager import AccountBackupManager
from ..const import AutomationTypes
class ExecutionManager:
manager_type_mapper = {
AutomationTypes.push_account: PushAccountManager,
AutomationTypes.change_secret: ChangeSecretManager,
AutomationTypes.verify_account: VerifyAccountManager,
AutomationTypes.gather_accounts: GatherAccountsManager,
# TODO 后期迁移到自动化策略中
'backup_account': AccountBackupManager,
}
def __init__(self, execution):
self.execution = execution
self._runner = self.manager_type_mapper[execution.manager_type](execution)
def run(self, *args, **kwargs):
return self._runner.run(*args, **kwargs)

View File

@ -3,4 +3,5 @@ name: Gather account from MySQL
category: database
type:
- mysql
- mariadb
method: gather_accounts

View File

@ -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 = {}

View File

@ -0,0 +1,30 @@
import os
import copy
from accounts.const import AutomationTypes
from assets.automations.methods import get_platform_automation_methods
def copy_change_secret_to_push_account(methods):
push_account = AutomationTypes.push_account
change_secret = AutomationTypes.change_secret
copy_methods = copy.deepcopy(methods)
for method in copy_methods:
if not method['id'].startswith(change_secret):
continue
copy_method = copy.deepcopy(method)
copy_method['method'] = push_account.value
copy_method['id'] = copy_method['id'].replace(
change_secret, push_account
)
copy_method['name'] = copy_method['name'].replace(
'Change secret', 'Push account'
)
methods.append(copy_method)
return methods
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
automation_methods = get_platform_automation_methods(BASE_DIR)
platform_automation_methods = copy_change_secret_to_push_account(automation_methods)

View File

@ -0,0 +1,112 @@
from django.db.models import QuerySet
from common.utils import get_logger
from accounts.const import AutomationTypes
from accounts.models import Account
from ..base.manager import PushOrVerifyHostCallbackMixin, AccountBasePlaybookManager
logger = get_logger(__name__)
class PushAccountManager(PushOrVerifyHostCallbackMixin, AccountBasePlaybookManager):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.secret_type = self.execution.snapshot['secret_type']
self.host_account_mapper = {}
@classmethod
def method_type(cls):
return AutomationTypes.push_account
def create_nonlocal_accounts(self, accounts, snapshot_account_usernames, asset):
secret = self.execution.snapshot['secret']
usernames = accounts.filter(secret_type=self.secret_type).values_list(
'username', flat=True
)
create_usernames = set(snapshot_account_usernames) - set(usernames)
create_account_objs = [
Account(
name=username, username=username, secret=secret,
secret_type=self.secret_type, asset=asset,
)
for username in create_usernames
]
Account.objects.bulk_create(create_account_objs)
def get_accounts(self, privilege_account, accounts: QuerySet):
if not privilege_account:
logger.debug(f'not privilege account')
return []
snapshot_account_usernames = self.execution.snapshot['accounts']
accounts = accounts.exclude(username=privilege_account.username)
if '*' in snapshot_account_usernames:
return accounts
asset = privilege_account.asset
self.create_nonlocal_accounts(accounts, snapshot_account_usernames, asset)
accounts = asset.accounts.exclude(username=privilege_account.username).filter(
username__in=snapshot_account_usernames, secret_type=self.secret_type
)
return accounts
# @classmethod
# def trigger_by_asset_create(cls, asset):
# automations = PushAccountAutomation.objects.filter(
# triggers__contains=TriggerChoice.on_asset_create
# )
# account_automation_map = {auto.username: auto for auto in automations}
#
# util = AssetPermissionUtil()
# permissions = util.get_permissions_for_assets([asset], with_node=True)
# account_permission_map = defaultdict(list)
# for permission in permissions:
# for account in permission.accounts:
# account_permission_map[account].append(permission)
#
# username_automation_map = {}
# for username, automation in account_automation_map.items():
# if username != '@USER':
# username_automation_map[username] = automation
# continue
#
# asset_permissions = account_permission_map.get(username)
# if not asset_permissions:
# continue
# asset_permissions = util.get_permissions([p.id for p in asset_permissions])
# usernames = asset_permissions.values_list('users__username', flat=True).distinct()
# for _username in usernames:
# username_automation_map[_username] = automation
#
# asset_usernames_exists = asset.accounts.values_list('username', flat=True)
# accounts_to_create = []
# accounts_to_push = []
# for username, automation in username_automation_map.items():
# if username in asset_usernames_exists:
# continue
#
# if automation.secret_strategy != SecretStrategy.custom:
# secret_generator = SecretGenerator(
# automation.secret_strategy, automation.secret_type,
# automation.password_rules
# )
# secret = secret_generator.get_secret()
# else:
# secret = automation.secret
#
# account = Account(
# username=username, secret=secret,
# asset=asset, secret_type=automation.secret_type,
# comment='Create by account creation {}'.format(automation.name),
# )
# accounts_to_create.append(account)
# if automation.action == 'create_and_push':
# accounts_to_push.append(account)
# else:
# accounts_to_create.append(account)
#
# logger.debug(f'Create account {account} for asset {asset}')
# @classmethod
# def trigger_by_permission_accounts_change(cls):
# pass

View File

@ -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)

View File

@ -0,0 +1,2 @@
from .account import *
from .automation import *

View File

@ -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')

View File

@ -0,0 +1,94 @@
from django.db import models
from django.utils.translation import ugettext_lazy as _
from assets.const import Connectivity
from common.db.fields import TreeChoices
string_punctuation = '!#$%&()*+,-.:;<=>?@[]^_~'
DEFAULT_PASSWORD_LENGTH = 30
DEFAULT_PASSWORD_RULES = {
'length': DEFAULT_PASSWORD_LENGTH,
'symbol_set': string_punctuation
}
__all__ = [
'AutomationTypes', 'SecretStrategy', 'SSHKeyStrategy', 'Connectivity',
'DEFAULT_PASSWORD_LENGTH', 'DEFAULT_PASSWORD_RULES', 'TriggerChoice',
'PushAccountActionChoice',
]
class AutomationTypes(models.TextChoices):
push_account = 'push_account', _('Push account')
change_secret = 'change_secret', _('Change secret')
verify_account = 'verify_account', _('Verify account')
gather_accounts = 'gather_accounts', _('Gather accounts')
@classmethod
def get_type_model(cls, tp):
from accounts.models import (
PushAccountAutomation, ChangeSecretAutomation,
VerifyAccountAutomation, GatherAccountsAutomation,
)
type_model_dict = {
cls.push_account: PushAccountAutomation,
cls.change_secret: ChangeSecretAutomation,
cls.verify_account: VerifyAccountAutomation,
cls.gather_accounts: GatherAccountsAutomation,
}
return type_model_dict.get(tp)
class SecretStrategy(models.TextChoices):
custom = 'specific', _('Specific password')
random = 'random', _('Random')
class SSHKeyStrategy(models.TextChoices):
add = 'add', _('Append SSH KEY')
set = 'set', _('Empty and append SSH KEY')
set_jms = 'set_jms', _('Replace (The key generated by JumpServer) ')
class TriggerChoice(models.TextChoices, TreeChoices):
# 当资产创建时,直接创建账号,如果是动态账号,需要从授权中查询该资产被授权过的用户,已用户用户名为账号,创建
on_asset_create = 'on_asset_create', _('On asset create')
# 授权变化包含,用户加入授权,用户组加入授权,资产加入授权,节点加入授权,账号变化
# 当添加用户到授权时,查询所有同名账号 automation, 把本授权上的用户 (用户组), 创建到本授权的资产(节点)上
on_perm_add_user = 'on_perm_add_user', _('On perm add user')
# 当添加用户组到授权时,查询所有同名账号 automation, 把本授权上的用户 (用户组), 创建到本授权的资产(节点)上
on_perm_add_user_group = 'on_perm_add_user_group', _('On perm add user group')
# 当添加资产到授权时,查询授权的所有账号 automation, 创建到本授权的资产上
on_perm_add_asset = 'on_perm_add_asset', _('On perm add asset')
# 当添加节点到授权时,查询授权的所有账号 automation, 创建到本授权的节点的资产上
on_perm_add_node = 'on_perm_add_node', _('On perm add node')
# 当授权的账号变化时,查询授权的所有账号 automation, 创建到本授权的资产(节点)上
on_perm_add_account = 'on_perm_add_account', _('On perm add account')
# 当资产添加到节点时,查询节点的授权规则,查询授权的所有账号 automation, 创建到本授权的资产(节点)上
on_asset_join_node = 'on_asset_join_node', _('On asset join node')
# 当用户加入到用户组时,查询用户组的授权规则,查询授权的所有账号 automation, 创建到本授权的资产(节点)上
on_user_join_group = 'on_user_join_group', _('On user join group')
@classmethod
def branches(cls):
# 和用户和用户组相关的都是动态账号
#
return [
cls.on_asset_create,
(_("On perm change"), [
cls.on_perm_add_user,
cls.on_perm_add_user_group,
cls.on_perm_add_asset,
cls.on_perm_add_node,
cls.on_perm_add_account,
]),
(_("Inherit from group or node"), [
cls.on_asset_join_node,
cls.on_user_join_group,
])
]
class PushAccountActionChoice(models.TextChoices):
create_and_push = 'create_and_push', _('Create and push')
only_create = 'only_create', _('Only create')

49
apps/accounts/filters.py Normal file
View File

@ -0,0 +1,49 @@
# -*- coding: utf-8 -*-
#
from django.db.models import Q
from django_filters import rest_framework as drf_filters
from assets.models import Node
from common.drf.filters import BaseFilterSet
from .models import Account
class AccountFilterSet(BaseFilterSet):
ip = drf_filters.CharFilter(field_name='address', lookup_expr='exact')
hostname = drf_filters.CharFilter(field_name='name', lookup_expr='exact')
username = drf_filters.CharFilter(field_name="username", lookup_expr='exact')
address = drf_filters.CharFilter(field_name="asset__address", lookup_expr='exact')
asset = drf_filters.CharFilter(field_name="asset_id", lookup_expr='exact')
assets = drf_filters.CharFilter(field_name='asset_id', lookup_expr='exact')
nodes = drf_filters.CharFilter(method='filter_nodes')
node_id = drf_filters.CharFilter(method='filter_nodes')
has_secret = drf_filters.BooleanFilter(method='filter_has_secret')
platform = drf_filters.CharFilter(field_name='asset__platform_id', lookup_expr='exact')
category = drf_filters.CharFilter(field_name='asset__platform__category', lookup_expr='exact')
type = drf_filters.CharFilter(field_name='asset__platform__type', lookup_expr='exact')
@staticmethod
def filter_has_secret(queryset, name, has_secret):
q = Q(secret__isnull=True) | Q(secret='')
if has_secret:
return queryset.exclude(q)
else:
return queryset.filter(q)
@staticmethod
def filter_nodes(queryset, name, value):
nodes = Node.objects.filter(id=value)
if not nodes:
return queryset
node_qs = Node.objects.none()
for node in nodes:
node_qs |= node.get_all_children(with_self=True)
node_ids = list(node_qs.values_list('id', flat=True))
queryset = queryset.filter(asset__nodes__in=node_ids)
return queryset
class Meta:
model = Account
fields = ['id', 'asset_id']

View File

@ -0,0 +1,113 @@
# Generated by Django 3.2.14 on 2022-12-28 07:29
import common.db.encoder
import common.db.fields
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
import simple_history.models
import uuid
class Migration(migrations.Migration):
initial = True
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('assets', '0098_auto_20220430_2126'),
]
operations = [
migrations.CreateModel(
name='Account',
fields=[
('created_by', models.CharField(blank=True, max_length=128, null=True, verbose_name='Created by')),
('updated_by', models.CharField(blank=True, max_length=128, null=True, verbose_name='Updated by')),
('date_created', models.DateTimeField(auto_now_add=True, null=True, verbose_name='Date created')),
('date_updated', models.DateTimeField(auto_now=True, verbose_name='Date updated')),
('comment', models.TextField(blank=True, default='', verbose_name='Comment')),
('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)),
('org_id',
models.CharField(blank=True, db_index=True, default='', max_length=36, verbose_name='Organization')),
('connectivity', models.CharField(choices=[('unknown', 'Unknown'), ('ok', 'Ok'), ('failed', 'Failed')],
default='unknown', max_length=16, verbose_name='Connectivity')),
('date_verified', models.DateTimeField(null=True, verbose_name='Date verified')),
('name', models.CharField(max_length=128, verbose_name='Name')),
('username', models.CharField(blank=True, db_index=True, max_length=128, verbose_name='Username')),
('secret_type', models.CharField(
choices=[('password', 'Password'), ('ssh_key', 'SSH key'), ('access_key', 'Access key'),
('token', 'Token')], default='password', max_length=16, verbose_name='Secret type')),
('secret', common.db.fields.EncryptTextField(blank=True, null=True, verbose_name='Secret')),
('privileged', models.BooleanField(default=False, verbose_name='Privileged')),
('is_active', models.BooleanField(default=True, verbose_name='Is active')),
('version', models.IntegerField(default=0, verbose_name='Version')),
('source', models.CharField(default='local', max_length=30, verbose_name='Source')),
('asset', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='accounts',
to='assets.asset', verbose_name='Asset')),
('su_from',
models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='su_to',
to='accounts.account', verbose_name='Su from')),
],
options={
'verbose_name': 'Account',
'permissions': [('view_accountsecret', 'Can view asset account secret'),
('change_accountsecret', 'Can change asset account secret'),
('view_historyaccount', 'Can view asset history account'),
('view_historyaccountsecret', 'Can view asset history account secret')],
'unique_together': {('username', 'asset', 'secret_type'), ('name', 'asset')},
},
),
migrations.CreateModel(
name='HistoricalAccount',
fields=[
('id', models.UUIDField(db_index=True, default=uuid.uuid4)),
('secret_type', models.CharField(
choices=[('password', 'Password'), ('ssh_key', 'SSH key'), ('access_key', 'Access key'),
('token', 'Token')], default='password', max_length=16, verbose_name='Secret type')),
('secret', common.db.fields.EncryptTextField(blank=True, null=True, verbose_name='Secret')),
('version', models.IntegerField(default=0, verbose_name='Version')),
('history_id', models.AutoField(primary_key=True, serialize=False)),
('history_date', models.DateTimeField(db_index=True)),
('history_change_reason', models.CharField(max_length=100, null=True)),
('history_type',
models.CharField(choices=[('+', 'Created'), ('~', 'Changed'), ('-', 'Deleted')], max_length=1)),
('history_user',
models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+',
to=settings.AUTH_USER_MODEL)),
],
options={
'verbose_name': 'historical Account',
'verbose_name_plural': 'historical Accounts',
'ordering': ('-history_date', '-history_id'),
'get_latest_by': ('history_date', 'history_id'),
},
bases=(simple_history.models.HistoricalChanges, models.Model),
),
migrations.CreateModel(
name='AccountTemplate',
fields=[
('created_by', models.CharField(blank=True, max_length=128, null=True, verbose_name='Created by')),
('updated_by', models.CharField(blank=True, max_length=128, null=True, verbose_name='Updated by')),
('date_created', models.DateTimeField(auto_now_add=True, null=True, verbose_name='Date created')),
('date_updated', models.DateTimeField(auto_now=True, verbose_name='Date updated')),
('comment', models.TextField(blank=True, default='', verbose_name='Comment')),
('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)),
('org_id',
models.CharField(blank=True, db_index=True, default='', max_length=36, verbose_name='Organization')),
('name', models.CharField(max_length=128, verbose_name='Name')),
('username', models.CharField(blank=True, db_index=True, max_length=128, verbose_name='Username')),
('secret_type', models.CharField(
choices=[('password', 'Password'), ('ssh_key', 'SSH key'), ('access_key', 'Access key'),
('token', 'Token')], default='password', max_length=16, verbose_name='Secret type')),
('secret', common.db.fields.EncryptTextField(blank=True, null=True, verbose_name='Secret')),
('privileged', models.BooleanField(default=False, verbose_name='Privileged')),
('is_active', models.BooleanField(default=True, verbose_name='Is active')),
],
options={
'verbose_name': 'Account template',
'permissions': [('view_accounttemplatesecret', 'Can view asset account template secret'),
('change_accounttemplatesecret', 'Can change asset account template secret')],
'unique_together': {('name', 'org_id')},
},
),
]

View File

@ -0,0 +1,44 @@
# Generated by Django 3.2.14 on 2022-12-28 10:39
import common.db.encoder
import common.db.fields
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
import uuid
class Migration(migrations.Migration):
dependencies = [
('assets', '0106_auto_20221228_1838'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('accounts', '0001_initial'),
]
operations = [
migrations.CreateModel(
name='AccountBackupAutomation',
fields=[
('created_by', models.CharField(blank=True, max_length=128, null=True, verbose_name='Created by')),
('updated_by', models.CharField(blank=True, max_length=128, null=True, verbose_name='Updated by')),
('date_created', models.DateTimeField(auto_now_add=True, null=True, verbose_name='Date created')),
('date_updated', models.DateTimeField(auto_now=True, verbose_name='Date updated')),
('comment', models.TextField(blank=True, default='', verbose_name='Comment')),
('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)),
('org_id',
models.CharField(blank=True, db_index=True, default='', max_length=36, verbose_name='Organization')),
('name', models.CharField(max_length=128, verbose_name='Name')),
('is_periodic', models.BooleanField(default=False, verbose_name='Periodic perform')),
('interval', models.IntegerField(blank=True, default=24, null=True, verbose_name='Cycle perform')),
('crontab', models.CharField(blank=True, max_length=128, null=True, verbose_name='Regularly perform')),
('types', models.JSONField(default=list)),
('recipients', models.ManyToManyField(blank=True, related_name='recipient_escape_route_plans',
to=settings.AUTH_USER_MODEL, verbose_name='Recipient')),
],
options={
'verbose_name': 'Account backup plan',
'ordering': ['name'],
'unique_together': {('name', 'org_id')},
},
)
]

View File

@ -0,0 +1,194 @@
# Generated by Django 3.2.16 on 2022-12-30 08:08
import common.db.encoder
import common.db.fields
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
import uuid
class Migration(migrations.Migration):
dependencies = [
('assets', '0107_automation'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('accounts', '0002_auto_20220616_0021'),
]
operations = [
migrations.CreateModel(
name='AccountBaseAutomation',
fields=[
],
options={
'verbose_name': 'Account automation task',
'proxy': True,
'indexes': [],
'constraints': [],
},
bases=('assets.baseautomation',),
),
migrations.CreateModel(
name='AutomationExecution',
fields=[
],
options={
'verbose_name': 'Automation execution',
'verbose_name_plural': 'Automation executions',
'permissions': [('view_changesecretexecution', 'Can view change secret execution'),
('add_changesecretexection', 'Can add change secret execution'),
('view_gatheraccountsexecution', 'Can view gather accounts execution'),
('add_gatheraccountsexecution', 'Can add gather accounts execution')],
'proxy': True,
'indexes': [],
'constraints': [],
},
bases=('assets.automationexecution',),
),
migrations.CreateModel(
name='PushAccountAutomation',
fields=[
('baseautomation_ptr',
models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True,
primary_key=True, serialize=False, to='assets.baseautomation')),
('secret_type', models.CharField(
choices=[('password', 'Password'), ('ssh_key', 'SSH key'), ('access_key', 'Access key'),
('token', 'Token')], default='password', max_length=16, verbose_name='Secret type')),
('secret_strategy', models.CharField(choices=[('specific', 'Specific password'),
('random_one', 'All assets use the same random password'),
('random_all',
'All assets use different random password')],
default='specific', max_length=16,
verbose_name='Secret strategy')),
('secret', common.db.fields.EncryptTextField(blank=True, null=True, verbose_name='Secret')),
('password_rules', models.JSONField(default=dict, verbose_name='Password rules')),
('ssh_key_change_strategy', models.CharField(
choices=[('add', 'Append SSH KEY'), ('set', 'Empty and append SSH KEY'),
('set_jms', 'Replace (The key generated by JumpServer) ')], default='add', max_length=16,
verbose_name='SSH key change strategy')),
('triggers', models.JSONField(default=list, max_length=16, verbose_name='Triggers')),
('username', models.CharField(max_length=128, verbose_name='Username')),
('action', models.CharField(max_length=16, verbose_name='Action')),
],
options={
'verbose_name': 'Push asset account',
},
bases=('accounts.accountbaseautomation', models.Model),
),
migrations.CreateModel(
name='GatherAccountsAutomation',
fields=[
('baseautomation_ptr',
models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True,
primary_key=True, serialize=False, to='assets.baseautomation')),
],
options={
'verbose_name': 'Gather asset accounts',
},
bases=('accounts.accountbaseautomation',),
),
migrations.CreateModel(
name='VerifyAccountAutomation',
fields=[
('baseautomation_ptr',
models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True,
primary_key=True, serialize=False, to='assets.baseautomation')),
],
options={
'verbose_name': 'Verify asset account',
},
bases=('accounts.accountbaseautomation',),
),
migrations.CreateModel(
name='ChangeSecretRecord',
fields=[
('created_by', models.CharField(blank=True, max_length=128, null=True, verbose_name='Created by')),
('updated_by', models.CharField(blank=True, max_length=128, null=True, verbose_name='Updated by')),
('date_created', models.DateTimeField(auto_now_add=True, null=True, verbose_name='Date created')),
('date_updated', models.DateTimeField(auto_now=True, verbose_name='Date updated')),
('comment', models.TextField(blank=True, default='', verbose_name='Comment')),
('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)),
('old_secret', common.db.fields.EncryptTextField(blank=True, null=True, verbose_name='Old secret')),
('new_secret', common.db.fields.EncryptTextField(blank=True, null=True, verbose_name='Secret')),
('date_started', models.DateTimeField(blank=True, null=True, verbose_name='Date started')),
('date_finished', models.DateTimeField(blank=True, null=True, verbose_name='Date finished')),
('status', models.CharField(default='pending', max_length=16)),
('error', models.TextField(blank=True, null=True, verbose_name='Error')),
('account',
models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='accounts.account')),
('asset', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='assets.asset')),
('execution',
models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='accounts.automationexecution')),
],
options={
'verbose_name': 'Change secret record',
},
),
migrations.CreateModel(
name='AccountBackupExecution',
fields=[
('org_id',
models.CharField(blank=True, db_index=True, default='', max_length=36, verbose_name='Organization')),
('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)),
('date_start', models.DateTimeField(auto_now_add=True, verbose_name='Date start')),
('timedelta', models.FloatField(default=0.0, null=True, verbose_name='Time')),
('plan_snapshot',
models.JSONField(blank=True, default=dict, encoder=common.db.encoder.ModelJSONFieldEncoder, null=True,
verbose_name='Account backup snapshot')),
('trigger', models.CharField(choices=[('manual', 'Manual trigger'), ('timing', 'Timing trigger')],
default='manual', max_length=128, verbose_name='Trigger mode')),
('reason', models.CharField(blank=True, max_length=1024, null=True, verbose_name='Reason')),
('is_success', models.BooleanField(default=False, verbose_name='Is success')),
('plan', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='execution',
to='accounts.accountbackupautomation', verbose_name='Account backup plan')),
],
options={
'verbose_name': 'Account backup execution',
'ordering': ('-date_start',),
},
),
migrations.CreateModel(
name='ChangeSecretAutomation',
fields=[
('baseautomation_ptr',
models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True,
primary_key=True, serialize=False, to='assets.baseautomation')),
('secret_type', models.CharField(
choices=[('password', 'Password'), ('ssh_key', 'SSH key'), ('access_key', 'Access key'),
('token', 'Token')], default='password', max_length=16, verbose_name='Secret type')),
('secret_strategy', models.CharField(choices=[('specific', 'Specific password'),
('random_one', 'All assets use the same random password'),
('random_all',
'All assets use different random password')],
default='specific', max_length=16,
verbose_name='Secret strategy')),
('secret', common.db.fields.EncryptTextField(blank=True, null=True, verbose_name='Secret')),
('password_rules', models.JSONField(default=dict, verbose_name='Password rules')),
('ssh_key_change_strategy', models.CharField(
choices=[('add', 'Append SSH KEY'), ('set', 'Empty and append SSH KEY'),
('set_jms', 'Replace (The key generated by JumpServer) ')], default='add', max_length=16,
verbose_name='SSH key change strategy')),
('recipients',
models.ManyToManyField(blank=True, to=settings.AUTH_USER_MODEL, verbose_name='Recipient')),
],
options={
'verbose_name': 'Change secret automation',
},
bases=('accounts.accountbaseautomation', models.Model),
),
migrations.AlterModelOptions(
name='automationexecution',
options={'permissions': [('view_changesecretexecution', 'Can view change secret execution'),
('add_changesecretexection', 'Can add change secret execution'),
('view_gatheraccountsexecution', 'Can view gather accounts execution'),
('add_gatheraccountsexecution', 'Can add gather accounts execution'),
('view_pushaccountexecution', 'Can view push account execution'),
('add_pushaccountexecution', 'Can add push account execution')],
'verbose_name': 'Automation execution', 'verbose_name_plural': 'Automation executions'},
),
migrations.AlterModelOptions(
name='changesecretrecord',
options={'ordering': ('-date_started',), 'verbose_name': 'Change secret record'},
),
]

View File

@ -0,0 +1,23 @@
# Generated by Django 3.2.16 on 2023-01-06 07:07
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('accounts', '0003_automation'),
]
operations = [
migrations.AlterField(
model_name='changesecretautomation',
name='secret_strategy',
field=models.CharField(choices=[('specific', 'Specific password'), ('random', 'Random')], default='specific', max_length=16, verbose_name='Secret strategy'),
),
migrations.AlterField(
model_name='pushaccountautomation',
name='secret_strategy',
field=models.CharField(choices=[('specific', 'Specific password'), ('random', 'Random')], default='specific', max_length=16, verbose_name='Secret strategy'),
),
]

View File

@ -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'},
),
]

View File

View File

@ -0,0 +1,3 @@
from .base import *
from .account import *
from .automations import *

View File

@ -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'))

View File

@ -0,0 +1,6 @@
from .base import *
from .backup_account import *
from .change_secret import *
from .gather_account import *
from .push_account import *
from .verify_account import *

View File

@ -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()

View File

@ -0,0 +1,41 @@
from django.db import models
from django.utils.translation import gettext_lazy as _
from assets.models.automations import (
BaseAutomation as AssetBaseAutomation,
AutomationExecution as AssetAutomationExecution
)
__all__ = ['AccountBaseAutomation', 'AutomationExecution']
class AccountBaseAutomation(AssetBaseAutomation):
class Meta:
proxy = True
verbose_name = _("Account automation task")
@property
def execution_model(self):
return AutomationExecution
class AutomationExecution(AssetAutomationExecution):
class Meta:
proxy = True
verbose_name = _("Automation execution")
verbose_name_plural = _("Automation executions")
permissions = [
('view_changesecretexecution', _('Can view change secret execution')),
('add_changesecretexection', _('Can add change secret execution')),
('view_gatheraccountsexecution', _('Can view gather accounts execution')),
('add_gatheraccountsexecution', _('Can add gather accounts execution')),
('view_pushaccountexecution', _('Can view push account execution')),
('add_pushaccountexecution', _('Can add push account execution')),
]
def start(self):
from accounts.automations.endpoint import ExecutionManager
manager = ExecutionManager(execution=self)
return manager.run()

View File

@ -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):

View File

@ -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()

View File

@ -0,0 +1,41 @@
from django.db import models
from django.utils.translation import ugettext_lazy as _
from accounts.const import AutomationTypes
from .base import AccountBaseAutomation
from .change_secret import ChangeSecretMixin
__all__ = ['PushAccountAutomation']
class PushAccountAutomation(ChangeSecretMixin, AccountBaseAutomation):
accounts = None
triggers = models.JSONField(max_length=16, default=list, verbose_name=_('Triggers'))
username = models.CharField(max_length=128, verbose_name=_('Username'))
action = models.CharField(max_length=16, verbose_name=_('Action'))
def set_period_schedule(self):
pass
@property
def dynamic_username(self):
return self.username == '@USER'
@dynamic_username.setter
def dynamic_username(self, value):
if value:
self.username = '@USER'
def save(self, *args, **kwargs):
self.type = AutomationTypes.push_account
super().save(*args, **kwargs)
def to_attr_json(self):
attr_json = super().to_attr_json()
attr_json.update({
'username': self.username
})
return attr_json
class Meta:
verbose_name = _("Push asset account")

View File

@ -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)

View File

@ -0,0 +1,154 @@
# -*- coding: utf-8 -*-
#
import os
from hashlib import md5
import sshpubkeys
from django.conf import settings
from django.db import models
from django.utils.translation import ugettext_lazy as _
from accounts.const import SecretType
from common.db import fields
from common.utils import (
ssh_key_string_to_obj, ssh_key_gen, get_logger,
random_string, lazyproperty, parse_ssh_public_key_str
)
from orgs.mixins.models import JMSOrgBaseModel, OrgManager
logger = get_logger(__file__)
class BaseAccountQuerySet(models.QuerySet):
def active(self):
return self.filter(is_active=True)
class BaseAccountManager(OrgManager):
def active(self):
return self.get_queryset().active()
class BaseAccount(JMSOrgBaseModel):
name = models.CharField(max_length=128, verbose_name=_("Name"))
username = models.CharField(max_length=128, blank=True, verbose_name=_('Username'), db_index=True)
secret_type = models.CharField(
max_length=16, choices=SecretType.choices, default=SecretType.PASSWORD, verbose_name=_('Secret type')
)
secret = fields.EncryptTextField(blank=True, null=True, verbose_name=_('Secret'))
privileged = models.BooleanField(verbose_name=_("Privileged"), default=False)
is_active = models.BooleanField(default=True, verbose_name=_("Is active"))
objects = BaseAccountManager.from_queryset(BaseAccountQuerySet)()
@property
def has_secret(self):
return bool(self.secret)
@property
def has_username(self):
return bool(self.username)
@property
def specific(self):
data = {}
if self.secret_type != SecretType.SSH_KEY:
return data
data['ssh_key_fingerprint'] = self.ssh_key_fingerprint
return data
@property
def password(self):
if self.secret_type == SecretType.PASSWORD:
return self.secret
return None
@property
def private_key(self):
if self.secret_type == SecretType.SSH_KEY:
return self.secret
return None
@private_key.setter
def private_key(self, value):
self.secret = value
self.secret_type = SecretType.SSH_KEY
@lazyproperty
def public_key(self):
if self.secret_type == SecretType.SSH_KEY and self.private_key:
return parse_ssh_public_key_str(self.private_key)
return None
@property
def ssh_key_fingerprint(self):
if self.public_key:
public_key = self.public_key
elif self.private_key:
try:
public_key = parse_ssh_public_key_str(self.private_key)
except IOError as e:
return str(e)
else:
return ''
public_key_obj = sshpubkeys.SSHKey(public_key)
fingerprint = public_key_obj.hash_md5()
return fingerprint
@property
def private_key_obj(self):
if self.private_key:
key_obj = ssh_key_string_to_obj(self.private_key)
return key_obj
else:
return None
@property
def private_key_path(self):
if not self.secret_type != SecretType.SSH_KEY \
or not self.secret \
or not self.private_key:
return None
project_dir = settings.PROJECT_DIR
tmp_dir = os.path.join(project_dir, 'tmp')
key_name = '.' + md5(self.private_key.encode('utf-8')).hexdigest()
key_path = os.path.join(tmp_dir, key_name)
if not os.path.exists(key_path):
self.private_key_obj.write_private_key_file(key_path)
os.chmod(key_path, 0o400)
return key_path
def get_private_key(self):
if not self.private_key:
return None
return self.private_key
@property
def public_key_obj(self):
if self.public_key:
try:
return sshpubkeys.SSHKey(self.public_key)
except TabError:
pass
return None
@staticmethod
def gen_password(length=36):
return random_string(length, special_char=True)
@staticmethod
def gen_key(username):
private_key, public_key = ssh_key_gen(username=username)
return private_key, public_key
def _to_secret_json(self):
"""Push system user use it"""
return {
'name': self.name,
'username': self.username,
'public_key': self.public_key,
}
class Meta:
abstract = True

View File

@ -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(

View File

@ -0,0 +1,2 @@
from .account import *
from .automations import *

View File

@ -1,3 +1,4 @@
from .account import *
from .template import *
from .backup import *
from .base import *
from .template import *

View File

@ -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

View File

@ -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'

View File

@ -0,0 +1,81 @@
# -*- coding: utf-8 -*-
from django.utils.translation import ugettext_lazy as _
from rest_framework import serializers
from accounts.const import SecretType
from accounts.models import BaseAccount
from accounts.utils import validate_password_for_ansible, validate_ssh_key
from common.serializers.fields import EncryptedField, LabeledChoiceField
from orgs.mixins.serializers import BulkOrgResourceModelSerializer
__all__ = ['AuthValidateMixin', 'BaseAccountSerializer']
class AuthValidateMixin(serializers.Serializer):
secret_type = LabeledChoiceField(
choices=SecretType.choices, required=True, label=_('Secret type')
)
secret = EncryptedField(
label=_('Secret'), required=False, max_length=40960, allow_blank=True,
allow_null=True, write_only=True,
)
passphrase = serializers.CharField(
allow_blank=True, allow_null=True, required=False, max_length=512,
write_only=True, label=_('Key password')
)
@property
def initial_secret_type(self):
secret_type = self.initial_data.get('secret_type')
return secret_type
def validate_secret(self, secret):
if not secret:
return ''
secret_type = self.initial_secret_type
if secret_type == SecretType.PASSWORD:
validate_password_for_ansible(secret)
return secret
elif secret_type == SecretType.SSH_KEY:
passphrase = self.initial_data.get('passphrase')
passphrase = passphrase if passphrase else None
return validate_ssh_key(secret, passphrase)
else:
return secret
@staticmethod
def clean_auth_fields(validated_data):
for field in ('secret',):
value = validated_data.get(field)
if value is None:
validated_data.pop(field, None)
validated_data.pop('passphrase', None)
def create(self, validated_data):
self.clean_auth_fields(validated_data)
return super().create(validated_data)
def update(self, instance, validated_data):
self.clean_auth_fields(validated_data)
return super().update(instance, validated_data)
class BaseAccountSerializer(AuthValidateMixin, BulkOrgResourceModelSerializer):
has_secret = serializers.BooleanField(label=_("Has secret"), read_only=True)
class Meta:
model = BaseAccount
fields_mini = ['id', 'name', 'username']
fields_small = fields_mini + [
'secret_type', 'secret', 'has_secret', 'passphrase',
'privileged', 'is_active', 'specific',
]
fields_other = ['created_by', 'date_created', 'date_updated', 'comment']
fields = fields_small + fields_other
read_only_fields = [
'has_secret', 'specific',
'date_verified', 'created_by', 'date_created',
]
extra_kwargs = {
'specific': {'label': _('Specific')},
}

Some files were not shown because too many files have changed in this diff Show More