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

View File

@ -1,4 +1,11 @@
from django.db import models
from django.utils.translation import ugettext_lazy as _
ADMIN = 'Admin' ADMIN = 'Admin'
USER = 'User' USER = 'User'
AUDITOR = 'Auditor' 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 .adhoc import *
from .celery import * from .celery import *
from .command 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))() res = chain(*tuple(s))()
return res 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 django.utils.translation import ugettext_lazy as _
from common.utils import get_logger, get_object_or_none 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 orgs.utils import org_aware_func
from jumpserver.const import PROJECT_DIR from jumpserver.const import PROJECT_DIR
from .models import Task, AdHoc from .models import Task, AdHoc
from .const import DEFAULT_PASSWORD_RULES
logger = get_logger(__file__) 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) path = os.path.join(base_path, rel_path)
os.makedirs(os.path.dirname(path), exist_ok=True) os.makedirs(os.path.dirname(path), exist_ok=True)
return path 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