mirror of https://github.com/jumpserver/jumpserver
Merge branch 'v3' of github.com:jumpserver/jumpserver into v3
commit
706488d293
|
@ -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')
|
||||
|
|
|
@ -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')
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -4,3 +4,4 @@
|
|||
from .adhoc import *
|
||||
from .celery import *
|
||||
from .command import *
|
||||
from .automation import *
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
from .change_auth import *
|
||||
from .collect import *
|
||||
from .push import *
|
||||
from .verify import *
|
||||
from .common import *
|
|
@ -0,0 +1,2 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
|
@ -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
|
|
@ -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
|
|
@ -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']
|
|
@ -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
|
|
@ -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
|
|
@ -0,0 +1 @@
|
|||
from .endpoint import *
|
|
@ -0,0 +1,2 @@
|
|||
from .manager import *
|
||||
from .handlers import *
|
|
@ -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
|
|
@ -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()
|
|
@ -0,0 +1,2 @@
|
|||
from .manager import *
|
||||
from .handlers import *
|
|
@ -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
|
|
@ -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
|
|
@ -0,0 +1,2 @@
|
|||
from .manager import *
|
||||
from .handlers import *
|
|
@ -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
|
|
@ -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
|
|
@ -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)
|
|
@ -0,0 +1,2 @@
|
|||
from .manager import *
|
||||
from .handlers import *
|
|
@ -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
|
|
@ -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
|
|
@ -0,0 +1,2 @@
|
|||
from .manager import *
|
||||
from .handlers import *
|
|
@ -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
|
|
@ -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
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue