Merge branch 'v3' of github.com:jumpserver/jumpserver into v3

pull/8873/head
ibuler 2022-09-08 10:04:32 +08:00
commit 706488d293
30 changed files with 520 additions and 7 deletions

View File

@ -14,6 +14,7 @@ from common.utils import get_logger
from common.db.encoder import ModelJSONFieldEncoder
from common.db.models import BitOperationChoice
from common.mixins.models import CommonModelMixin
from common.const.choices import Trigger
from ..const import AllTypes, Category
__all__ = ['AccountBackupPlan', 'AccountBackupPlanExecution', 'Type']
@ -89,7 +90,7 @@ class AccountBackupPlan(CommonModelMixin, PeriodTaskModelMixin, OrgModelMixin):
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), AccountBackupPlanExecution.Trigger.timing)
args = (str(self.id), Trigger.timing)
kwargs = {}
return name, task, args, kwargs
@ -120,10 +121,6 @@ class AccountBackupPlan(CommonModelMixin, PeriodTaskModelMixin, OrgModelMixin):
class AccountBackupPlanExecution(OrgModelMixin):
class Trigger(models.TextChoices):
manual = 'manual', _('Manual trigger')
timing = 'timing', _('Timing trigger')
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
date_start = models.DateTimeField(
auto_now_add=True, verbose_name=_('Date start')

View File

@ -1,4 +1,11 @@
from django.db import models
from django.utils.translation import ugettext_lazy as _
ADMIN = 'Admin'
USER = 'User'
AUDITOR = 'Auditor'
class Trigger(models.TextChoices):
manual = 'manual', _('Manual trigger')
timing = 'timing', _('Timing trigger')

29
apps/ops/const.py Normal file
View File

@ -0,0 +1,29 @@
from django.db import models
from django.utils.translation import ugettext_lazy as _
class StrategyChoice(models.TextChoices):
push = 'push', _('Push')
verify = 'verify', _('Verify')
collect = 'collect', _('Collect')
change_auth = 'change_auth', _('Change auth')
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 PasswordStrategy(models.TextChoices):
custom = 'custom', _('Custom password')
random_one = 'random_one', _('All assets use the same random password')
random_all = 'random_all', _('All assets use different random password')
string_punctuation = '!#$%&()*+,-.:;<=>?@[]^_~'
DEFAULT_PASSWORD_LENGTH = 30
DEFAULT_PASSWORD_RULES = {
'length': DEFAULT_PASSWORD_LENGTH,
'symbol_set': string_punctuation
}

View File

@ -4,3 +4,4 @@
from .adhoc import *
from .celery import *
from .command import *
from .automation import *

View File

@ -0,0 +1,5 @@
from .change_auth import *
from .collect import *
from .push import *
from .verify import *
from .common import *

View File

@ -0,0 +1,2 @@
# -*- coding: utf-8 -*-
#

View File

@ -0,0 +1,71 @@
from django.db import models
from django.utils.translation import ugettext_lazy as _
from ops.const import SSHKeyStrategy, PasswordStrategy, StrategyChoice
from ops.utils import generate_random_password
from common.db.fields import (
EncryptCharField, EncryptTextField, JsonDictCharField
)
from .common import AutomationStrategy
class ChangeAuthStrategy(AutomationStrategy):
is_password = models.BooleanField(default=True)
password_strategy = models.CharField(
max_length=128, blank=True, null=True, choices=PasswordStrategy.choices,
verbose_name=_('Password strategy')
)
password_rules = JsonDictCharField(
max_length=2048, blank=True, null=True, verbose_name=_('Password rules')
)
password = EncryptCharField(
max_length=256, blank=True, null=True, verbose_name=_('Password')
)
is_ssh_key = models.BooleanField(default=False)
ssh_key_strategy = models.CharField(
max_length=128, blank=True, null=True, choices=SSHKeyStrategy.choices,
verbose_name=_('SSH Key strategy')
)
private_key = EncryptTextField(
max_length=4096, blank=True, null=True, verbose_name=_('SSH private key')
)
public_key = EncryptTextField(
max_length=4096, blank=True, null=True, verbose_name=_('SSH public key')
)
recipients = models.ManyToManyField(
'users.User', related_name='recipients_change_auth_strategy', blank=True,
verbose_name=_("Recipient")
)
class Meta:
verbose_name = _("Change auth strategy")
def gen_execute_password(self):
if self.password_strategy == PasswordStrategy.custom:
return self.password
elif self.password_strategy == PasswordStrategy.random_one:
return generate_random_password(**self.password_rules)
else:
return None
def to_attr_json(self):
attr_json = super().to_attr_json()
attr_json.update({
'type': StrategyChoice.change_auth,
'password': self.gen_execute_password(),
'is_password': self.is_password,
'password_rules': self.password_rules,
'password_strategy': self.password_strategy,
'is_ssh_key': self.is_ssh_key,
'public_key': self.public_key,
'private_key': self.private_key,
'ssh_key_strategy': self.ssh_key_strategy,
'recipients': {
str(recipient.id): (str(recipient), bool(recipient.secret_key))
for recipient in self.recipients.all()
}
})
return attr_json

View File

@ -0,0 +1,16 @@
from django.utils.translation import ugettext_lazy as _
from ops.const import StrategyChoice
from .common import AutomationStrategy
class CollectStrategy(AutomationStrategy):
class Meta:
verbose_name = _("Collect strategy")
def to_attr_json(self):
attr_json = super().to_attr_json()
attr_json.update({
'type': StrategyChoice.collect
})
return attr_json

View File

@ -0,0 +1,111 @@
import uuid
from celery import current_task
from django.db import models
from django.utils.translation import ugettext_lazy as _
from common.const.choices import Trigger
from common.mixins.models import CommonModelMixin
from common.db.fields import EncryptJsonDictTextField
from orgs.mixins.models import OrgModelMixin
from ops.mixin import PeriodTaskModelMixin
from ops.tasks import execute_automation_strategy
from ops.task_handlers import ExecutionManager
class AutomationStrategy(CommonModelMixin, PeriodTaskModelMixin, OrgModelMixin):
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
accounts = models.JSONField(default=list, verbose_name=_("Accounts"))
nodes = models.ManyToManyField(
'assets.Node', related_name='automation_strategy', blank=True, verbose_name=_("Nodes")
)
assets = models.ManyToManyField(
'assets.Asset', related_name='automation_strategy', blank=True, verbose_name=_("Assets")
)
comment = models.TextField(blank=True, verbose_name=_('Comment'))
def __str__(self):
return self.name + '@' + str(self.created_by)
def get_register_task(self):
name = "automation_strategy_period_{}".format(str(self.id)[:8])
task = execute_automation_strategy.name
args = (str(self.id), Trigger.timing)
kwargs = {}
return name, task, args, kwargs
def to_attr_json(self):
return {
'name': self.name,
'accounts': self.accounts,
'assets': list(self.assets.all().values_list('id', flat=True)),
'nodes': list(self.assets.all().values_list('id', flat=True)),
}
def execute(self, trigger):
try:
eid = current_task.request.id
except AttributeError:
eid = str(uuid.uuid4())
execution = AutomationStrategyExecution.objects.create(
id=eid, strategy=self, snapshot=self.to_attr_json(), trigger=trigger
)
return execution.start()
class Meta:
unique_together = [('org_id', 'name')]
verbose_name = _("Automation plan")
class AutomationStrategyExecution(OrgModelMixin):
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
date_created = models.DateTimeField(auto_now_add=True)
timedelta = models.FloatField(default=0.0, verbose_name=_('Time'), null=True)
date_start = models.DateTimeField(auto_now_add=True, verbose_name=_('Date start'))
snapshot = EncryptJsonDictTextField(
default=dict, blank=True, null=True, verbose_name=_('Automation snapshot')
)
strategy = models.ForeignKey(
'AutomationStrategy', related_name='execution', on_delete=models.CASCADE,
verbose_name=_('Automation strategy')
)
trigger = models.CharField(
max_length=128, default=Trigger.manual, choices=Trigger.choices, verbose_name=_('Trigger mode')
)
class Meta:
verbose_name = _('Automation strategy execution')
@property
def manager_type(self):
return self.snapshot['type']
def start(self):
manager = ExecutionManager(execution=self)
return manager.run()
class AutomationStrategyTask(OrgModelMixin):
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
asset = models.ForeignKey(
'assets.Asset', on_delete=models.CASCADE, verbose_name=_('Asset')
)
account = models.ForeignKey(
'assets.Account', on_delete=models.CASCADE, verbose_name=_('Account')
)
is_success = models.BooleanField(default=False, verbose_name=_('Is success'))
timedelta = models.FloatField(default=0.0, null=True, verbose_name=_('Time'))
date_start = models.DateTimeField(auto_now_add=True, verbose_name=_('Date start'))
reason = models.CharField(max_length=1024, blank=True, null=True, verbose_name=_('Reason'))
execution = models.ForeignKey(
'AutomationStrategyExecution', related_name='task', on_delete=models.CASCADE,
verbose_name=_('Automation strategy execution')
)
class Meta:
verbose_name = _('Automation strategy task')
@property
def handler_type(self):
return self.execution.snapshot['type']

View File

@ -0,0 +1,16 @@
from django.utils.translation import ugettext_lazy as _
from ops.const import StrategyChoice
from .common import AutomationStrategy
class PushStrategy(AutomationStrategy):
class Meta:
verbose_name = _("Push strategy")
def to_attr_json(self):
attr_json = super().to_attr_json()
attr_json.update({
'type': StrategyChoice.push
})
return attr_json

View File

@ -0,0 +1,16 @@
from django.utils.translation import ugettext_lazy as _
from ops.const import StrategyChoice
from .common import AutomationStrategy
class VerifyStrategy(AutomationStrategy):
class Meta:
verbose_name = _("Verify strategy")
def to_attr_json(self):
attr_json = super().to_attr_json()
attr_json.update({
'type': StrategyChoice.verify
})
return attr_json

View File

@ -0,0 +1 @@
from .endpoint import *

View File

@ -0,0 +1,2 @@
from .manager import *
from .handlers import *

View File

@ -0,0 +1,16 @@
"""
执行改密计划的基类
"""
from common.utils import get_logger
logger = get_logger(__file__)
class BaseHandler:
def __init__(self, task, show_step_info=True):
self.task = task
self.conn = None
self.retry_times = 3
self.current_step = 0
self.is_frozen = False # 任务状态冻结标志
self.show_step_info = show_step_info

View File

@ -0,0 +1,78 @@
# -*- coding: utf-8 -*-
#
import time
from openpyxl import Workbook
from django.utils import timezone
from common.utils import get_logger
from common.utils.timezone import local_now_display
logger = get_logger(__file__)
class BaseExecutionManager:
task_back_up_serializer: None
def __init__(self, execution):
self.execution = execution
self.date_start = timezone.now()
self.time_start = time.time()
self.date_end = None
self.time_end = None
self.timedelta = 0
self.total_tasks = []
def on_tasks_pre_run(self, tasks):
raise NotImplementedError
def on_per_task_pre_run(self, task, total, index):
raise NotImplementedError
def create_csv_file(self, tasks, file_name):
raise NotImplementedError
def get_handler_cls(self):
raise NotImplemented
def do_run(self):
tasks = self.total_tasks = self.execution.create_plan_tasks()
self.on_tasks_pre_run(tasks)
total = len(tasks)
for index, task in enumerate(tasks, start=1):
self.on_per_task_pre_run(task, total, index)
task.start(show_step_info=False)
def pre_run(self):
self.execution.date_start = self.date_start
self.execution.save()
self.show_execution_steps()
def show_execution_steps(self):
pass
def show_summary(self):
split_line = '#' * 40
summary = self.execution.result_summary
logger.info(f'\n{split_line} 改密计划执行结果汇总 {split_line}')
logger.info(
'\n成功: {succeed}, 失败: {failed}, 总数: {total}\n'
''.format(**summary)
)
def post_run(self):
self.time_end = time.time()
self.date_end = timezone.now()
logger.info('\n\n' + '-' * 80)
logger.info('任务执行结束 {}\n'.format(local_now_display()))
self.timedelta = int(self.time_end - self.time_start)
logger.info('用时: {}s'.format(self.timedelta))
self.execution.timedelta = self.timedelta
self.execution.save()
self.show_summary()
def run(self):
self.pre_run()
self.do_run()
self.post_run()

View File

@ -0,0 +1,2 @@
from .manager import *
from .handlers import *

View File

@ -0,0 +1,10 @@
# -*- coding: utf-8 -*-
#
from common.utils import get_logger
from ..base import BaseHandler
logger = get_logger(__name__)
class ChangeAuthHandler(BaseHandler):
pass

View File

@ -0,0 +1,12 @@
# -*- coding: utf-8 -*-
#
from common.utils import get_logger
from ..base import BaseExecutionManager
from .handlers import ChangeAuthHandler
logger = get_logger(__name__)
class ChangeAuthExecutionManager(BaseExecutionManager):
def get_handler_cls(self):
return ChangeAuthHandler

View File

@ -0,0 +1,2 @@
from .manager import *
from .handlers import *

View File

@ -0,0 +1,10 @@
# -*- coding: utf-8 -*-
#
from common.utils import get_logger
from ..base import BaseHandler
logger = get_logger(__name__)
class CollectHandler(BaseHandler):
pass

View File

@ -0,0 +1,10 @@
# -*- coding: utf-8 -*-
#
from common.utils import get_logger
from ..base import BaseExecutionManager
logger = get_logger(__name__)
class CollectExecutionManager(object):
pass

View File

@ -0,0 +1,31 @@
from ops.const import StrategyChoice
from .push import PushExecutionManager, PushHandler
from .verify import VerifyExecutionManager, VerifyHandler
from .collect import CollectExecutionManager, CollectHandler
from .change_auth import ChangeAuthExecutionManager, ChangeAuthHandler
class ExecutionManager:
manager_type = {
StrategyChoice.push: PushExecutionManager,
StrategyChoice.verify: VerifyExecutionManager,
StrategyChoice.collect: CollectExecutionManager,
StrategyChoice.change_auth: ChangeAuthExecutionManager,
}
def __new__(cls, execution):
manager = cls.manager_type[execution.manager_type]
return manager(execution)
class TaskHandler:
handler_type = {
StrategyChoice.push: PushHandler,
StrategyChoice.verify: VerifyHandler,
StrategyChoice.collect: CollectHandler,
StrategyChoice.change_auth: ChangeAuthHandler,
}
def __new__(cls, task, show_step_info):
handler = cls.handler_type[task.handler_type]
return handler(task, show_step_info)

View File

@ -0,0 +1,2 @@
from .manager import *
from .handlers import *

View File

@ -0,0 +1,10 @@
# -*- coding: utf-8 -*-
#
from common.utils import get_logger
from ..base import BaseHandler
logger = get_logger(__name__)
class PushHandler(BaseHandler):
pass

View File

@ -0,0 +1,10 @@
# -*- coding: utf-8 -*-
#
from common.utils import get_logger
from ..base import BaseExecutionManager
logger = get_logger(__name__)
class PushExecutionManager(BaseExecutionManager):
pass

View File

@ -0,0 +1,2 @@
from .manager import *
from .handlers import *

View File

@ -0,0 +1,10 @@
# -*- coding: utf-8 -*-
#
from common.utils import get_logger
from ..base import BaseHandler
logger = get_logger(__name__)
class VerifyHandler(BaseHandler):
pass

View File

@ -0,0 +1,10 @@
# -*- coding: utf-8 -*-
#
from common.utils import get_logger
from ..base import BaseExecutionManager
logger = get_logger(__name__)
class VerifyExecutionManager(BaseExecutionManager):
pass

View File

@ -179,3 +179,15 @@ def add_m(x):
res = chain(*tuple(s))()
return res
@shared_task
def execute_automation_strategy(pid, trigger):
from .models import AutomationStrategy
with tmp_to_root_org():
instance = get_object_or_none(AutomationStrategy, pk=pid)
if not instance:
logger.error("No automation plan found: {}".format(pid))
return
with tmp_to_org(instance.org):
instance.execute(trigger)

View File

@ -5,11 +5,11 @@ import uuid
from django.utils.translation import ugettext_lazy as _
from common.utils import get_logger, get_object_or_none
from common.tasks import send_mail_async
from orgs.utils import org_aware_func
from jumpserver.const import PROJECT_DIR
from .models import Task, AdHoc
from .const import DEFAULT_PASSWORD_RULES
logger = get_logger(__file__)
@ -80,3 +80,15 @@ def get_task_log_path(base_path, task_id, level=2):
path = os.path.join(base_path, rel_path)
os.makedirs(os.path.dirname(path), exist_ok=True)
return path
def generate_random_password(**kwargs):
import random
import string
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']
chars = string.ascii_letters + string.digits + symbol_set
password = ''.join([random.choice(chars) for _ in range(length)])
return password