From ebfc3b7b38e98ee6eca4d0c5ab24f564c05264f7 Mon Sep 17 00:00:00 2001 From: fit2bot <68588906+fit2bot@users.noreply.github.com> Date: Thu, 3 Nov 2022 22:39:48 +0800 Subject: [PATCH] perf: change secret (#9014) Co-authored-by: feng <1304903146@qq.com> --- .../automations/change_secret/manager.py | 64 ++++++++++++++++++- .../0110_changesecretrecord_asset.py | 19 ++++++ apps/assets/models/automations/base.py | 7 ++ .../models/automations/change_secret.py | 1 + apps/assets/models/base.py | 1 - apps/assets/notifications.py | 28 +++++++- apps/assets/serializers/__init__.py | 1 + apps/assets/serializers/automation.py | 35 ++++++++++ 8 files changed, 150 insertions(+), 6 deletions(-) create mode 100644 apps/assets/migrations/0110_changesecretrecord_asset.py create mode 100644 apps/assets/serializers/automation.py diff --git a/apps/assets/automations/change_secret/manager.py b/apps/assets/automations/change_secret/manager.py index 4f77dfe85..fd289b12f 100644 --- a/apps/assets/automations/change_secret/manager.py +++ b/apps/assets/automations/change_secret/manager.py @@ -1,17 +1,28 @@ +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 common.utils import lazyproperty, gen_key_pair +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 +logger = get_logger(__name__) + class ChangeSecretManager(BasePlaybookManager): def __init__(self, *args, **kwargs): @@ -125,7 +136,7 @@ class ChangeSecretManager(BasePlaybookManager): new_secret = self.get_secret() recorder = ChangeSecretRecord( - account=account, execution=self.execution, + asset=asset, account=account, execution=self.execution, old_secret=account.secret, new_secret=new_secret, ) records.append(recorder) @@ -172,4 +183,51 @@ class ChangeSecretManager(BasePlaybookManager): recorder.save() def on_runner_failed(self, runner, e): - pass + logger.error("Change secret error: ", e) + + def run(self, *args, **kwargs): + super().run(*args, **kwargs) + recorders = self.name_recorder_mapper.values() + recorders = list(recorders) + self.send_recorder_mail(recorders) + + def send_recorder_mail(self, recorders): + if not recorders: + return + recipients = self.execution.recipients + if not recipients: + return + recipients = User.objects.filter(id__in=list(recipients)) + + name = self.execution.snapshot['name'] + path = os.path.join(os.path.dirname(settings.BASE_DIR), 'tmp') + filename = os.path.join(path, f'{name}-{local_now_display()}-{time.time()}.xlsx') + if not self.create_file(recorders, filename): + return + + for user in recipients: + attachments = [] + if user.secret_key: + password = user.secret_key.encode('utf8') + attachment = os.path.join(path, f'{name}-{local_now_display()}-{time.time()}.zip') + encrypt_and_compress_zip_file(attachment, password, [filename]) + attachments = [attachment] + ChangeSecretExecutionTaskMsg(name, user).publish(attachments) + os.remove(filename) + + @staticmethod + def create_file(recorders, filename): + serializer_cls = ChangeSecretRecordBackUpSerializer + serializer = serializer_cls(recorders, many=True) + header = [v.label for v in serializer.child.fields.values()] + rows = [list(row.values()) for row in serializer.data] + if not rows: + return False + + rows.insert(0, header) + wb = Workbook(filename) + ws = wb.create_sheet('Sheet1') + for row in rows: + ws.append(row) + wb.save(filename) + return True diff --git a/apps/assets/migrations/0110_changesecretrecord_asset.py b/apps/assets/migrations/0110_changesecretrecord_asset.py new file mode 100644 index 000000000..7a4e862ff --- /dev/null +++ b/apps/assets/migrations/0110_changesecretrecord_asset.py @@ -0,0 +1,19 @@ +# Generated by Django 3.2.14 on 2022-11-03 13:57 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('assets', '0109_rename_categories_to_types'), + ] + + operations = [ + migrations.AddField( + model_name='changesecretrecord', + name='asset', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='assets.asset'), + ), + ] diff --git a/apps/assets/models/automations/base.py b/apps/assets/models/automations/base.py index ac1fdb046..5eadca8c4 100644 --- a/apps/assets/models/automations/base.py +++ b/apps/assets/models/automations/base.py @@ -111,6 +111,13 @@ class AutomationExecution(OrgModelMixin): def manager_type(self): return self.snapshot['type'] + @property + def recipients(self): + recipients = self.snapshot.get('recipients') + if not recipients: + return [] + return recipients.values() + def start(self): from assets.automations.endpoint import ExecutionManager manager = ExecutionManager(execution=self) diff --git a/apps/assets/models/automations/change_secret.py b/apps/assets/models/automations/change_secret.py index 53ca08aba..81871fb3b 100644 --- a/apps/assets/models/automations/change_secret.py +++ b/apps/assets/models/automations/change_secret.py @@ -51,6 +51,7 @@ class ChangeSecretAutomation(BaseAutomation): class ChangeSecretRecord(JMSBaseModel): execution = models.ForeignKey('assets.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) old_secret = fields.EncryptTextField(blank=True, null=True, verbose_name=_('Old secret')) new_secret = fields.EncryptTextField(blank=True, null=True, verbose_name=_('Secret')) diff --git a/apps/assets/models/base.py b/apps/assets/models/base.py index 9f4f05649..8fa91b2cb 100644 --- a/apps/assets/models/base.py +++ b/apps/assets/models/base.py @@ -158,7 +158,6 @@ class BaseAccount(OrgModelMixin): return { 'name': self.name, 'username': self.username, - 'password': self.password, 'public_key': self.public_key, } diff --git a/apps/assets/notifications.py b/apps/assets/notifications.py index 58c02686c..6a67878c9 100644 --- a/apps/assets/notifications.py +++ b/apps/assets/notifications.py @@ -15,11 +15,35 @@ 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. 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) + "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.delay( self.subject, self.message, [self.user.email], attachment_list ) + + +class ChangeSecretExecutionTaskMsg(object): + subject = _('Notification of implementation result of encryption change plan') + + def __init__(self, name: str, user: User): + self.name = name + self.user = user + + @property + 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) + + def publish(self, attachments=None): + send_mail_attachment_async.delay( + self.subject, self.message, [self.user.email], attachments + ) diff --git a/apps/assets/serializers/__init__.py b/apps/assets/serializers/__init__.py index 586c6f2e5..252b2dc64 100644 --- a/apps/assets/serializers/__init__.py +++ b/apps/assets/serializers/__init__.py @@ -11,3 +11,4 @@ from .account import * from assets.serializers.account.backup import * from .platform import * from .cagegory import * +from .automation import * diff --git a/apps/assets/serializers/automation.py b/apps/assets/serializers/automation.py new file mode 100644 index 000000000..482f95fc8 --- /dev/null +++ b/apps/assets/serializers/automation.py @@ -0,0 +1,35 @@ +from django.utils.translation import ugettext as _ +from rest_framework import serializers + +from common.utils import get_logger + +from assets.models import ChangeSecretRecord + +logger = get_logger(__file__) + + +class ChangeSecretRecordBackUpSerializer(serializers.ModelSerializer): + asset = serializers.SerializerMethodField(label=_('Asset')) + account = serializers.SerializerMethodField(label=_('Account')) + is_success = serializers.SerializerMethodField(label=_('Is success')) + + class Meta: + model = ChangeSecretRecord + fields = [ + 'id', 'asset', 'account', 'old_secret', 'new_secret', + 'status', 'error', 'is_success' + ] + + @staticmethod + def get_asset(instance): + return str(instance.asset) + + @staticmethod + def get_account(instance): + return str(instance.account) + + @staticmethod + def get_is_success(obj): + if obj.status == 'success': + return _("Success") + return _("Failed")