mirror of https://github.com/jumpserver/jumpserver
parent
e69bb9f83e
commit
ce9ebd94ec
|
@ -6,5 +6,6 @@ from .label import *
|
||||||
from .account import *
|
from .account import *
|
||||||
from .node import *
|
from .node import *
|
||||||
from .domain import *
|
from .domain import *
|
||||||
|
from .automations import *
|
||||||
from .gathered_user import *
|
from .gathered_user import *
|
||||||
from .favorite_asset import *
|
from .favorite_asset import *
|
||||||
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
from .base import *
|
||||||
|
from .change_secret import *
|
||||||
|
from .gather_accounts import *
|
|
@ -0,0 +1,118 @@
|
||||||
|
from django.shortcuts import get_object_or_404
|
||||||
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
from rest_framework.response import Response
|
||||||
|
from rest_framework import status, mixins, viewsets
|
||||||
|
|
||||||
|
from orgs.mixins import generics
|
||||||
|
from assets import serializers
|
||||||
|
from assets.const import AutomationTypes
|
||||||
|
from assets.tasks import execute_automation
|
||||||
|
from assets.models import BaseAutomation, AutomationExecution
|
||||||
|
from common.const.choices import Trigger
|
||||||
|
|
||||||
|
__all__ = [
|
||||||
|
'AutomationAssetsListApi', 'AutomationRemoveAssetApi',
|
||||||
|
'AutomationAddAssetApi', 'AutomationNodeAddRemoveApi', 'AutomationExecutionViewSet'
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class AutomationAssetsListApi(generics.ListAPIView):
|
||||||
|
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)
|
||||||
|
|
||||||
|
def get_queryset(self):
|
||||||
|
instance = self.get_object()
|
||||||
|
assets = instance.get_all_assets().only(
|
||||||
|
*self.serializer_class.Meta.only_fields
|
||||||
|
)
|
||||||
|
return assets
|
||||||
|
|
||||||
|
|
||||||
|
class AutomationRemoveAssetApi(generics.RetrieveUpdateAPIView):
|
||||||
|
model = BaseAutomation
|
||||||
|
serializer_class = serializers.UpdateAssetSerializer
|
||||||
|
|
||||||
|
def update(self, request, *args, **kwargs):
|
||||||
|
instance = self.get_object()
|
||||||
|
serializer = self.serializer_class(data=request.data)
|
||||||
|
|
||||||
|
if not serializer.is_valid():
|
||||||
|
return Response({'error': serializer.errors})
|
||||||
|
|
||||||
|
assets = serializer.validated_data.get('assets')
|
||||||
|
if assets:
|
||||||
|
instance.assets.remove(*tuple(assets))
|
||||||
|
return Response({'msg': 'ok'})
|
||||||
|
|
||||||
|
|
||||||
|
class AutomationAddAssetApi(generics.RetrieveUpdateAPIView):
|
||||||
|
model = BaseAutomation
|
||||||
|
serializer_class = serializers.UpdateAssetSerializer
|
||||||
|
|
||||||
|
def update(self, request, *args, **kwargs):
|
||||||
|
instance = self.get_object()
|
||||||
|
serializer = self.serializer_class(data=request.data)
|
||||||
|
if serializer.is_valid():
|
||||||
|
assets = serializer.validated_data.get('assets')
|
||||||
|
if assets:
|
||||||
|
instance.assets.add(*tuple(assets))
|
||||||
|
return Response({"msg": "ok"})
|
||||||
|
else:
|
||||||
|
return Response({"error": serializer.errors})
|
||||||
|
|
||||||
|
|
||||||
|
class AutomationNodeAddRemoveApi(generics.RetrieveUpdateAPIView):
|
||||||
|
model = BaseAutomation
|
||||||
|
serializer_class = serializers.UpdateAssetSerializer
|
||||||
|
|
||||||
|
def update(self, request, *args, **kwargs):
|
||||||
|
action_params = ['add', 'remove']
|
||||||
|
action = request.query_params.get('action')
|
||||||
|
if action not in action_params:
|
||||||
|
err_info = _("The parameter 'action' must be [{}]".format(','.join(action_params)))
|
||||||
|
return Response({"error": err_info})
|
||||||
|
|
||||||
|
instance = self.get_object()
|
||||||
|
serializer = self.serializer_class(data=request.data)
|
||||||
|
if serializer.is_valid():
|
||||||
|
nodes = serializer.validated_data.get('nodes')
|
||||||
|
if nodes:
|
||||||
|
# eg: plan.nodes.add(*tuple(assets))
|
||||||
|
getattr(instance.nodes, action)(*tuple(nodes))
|
||||||
|
return Response({"msg": "ok"})
|
||||||
|
else:
|
||||||
|
return Response({"error": serializer.errors})
|
||||||
|
|
||||||
|
|
||||||
|
class AutomationExecutionViewSet(
|
||||||
|
mixins.CreateModelMixin, mixins.ListModelMixin,
|
||||||
|
mixins.RetrieveModelMixin, viewsets.GenericViewSet
|
||||||
|
):
|
||||||
|
search_fields = ('trigger',)
|
||||||
|
filterset_fields = ('trigger', 'automation_id')
|
||||||
|
serializer_class = serializers.AutomationExecutionSerializer
|
||||||
|
|
||||||
|
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')
|
||||||
|
model = AutomationTypes.get_model(tp)
|
||||||
|
task = execute_automation.delay(
|
||||||
|
pid=automation.ok, trigger=Trigger.manual, model=model
|
||||||
|
)
|
||||||
|
return Response({'task': task.id}, status=status.HTTP_201_CREATED)
|
|
@ -0,0 +1,40 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
#
|
||||||
|
|
||||||
|
from rest_framework import mixins
|
||||||
|
|
||||||
|
from common.utils import get_object_or_none
|
||||||
|
from orgs.mixins.api import OrgBulkModelViewSet, OrgGenericViewSet
|
||||||
|
|
||||||
|
from assets.models import ChangeSecretAutomation, ChangeSecretRecord, AutomationExecution
|
||||||
|
from assets import serializers
|
||||||
|
|
||||||
|
__all__ = [
|
||||||
|
'ChangeSecretAutomationViewSet', 'ChangeSecretRecordViewSet'
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
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 = ['username', 'asset', 'reason', 'execution']
|
||||||
|
search_fields = ['username', 'reason', 'asset__hostname']
|
||||||
|
|
||||||
|
def get_queryset(self):
|
||||||
|
return ChangeSecretRecord.objects.all()
|
||||||
|
|
||||||
|
def filter_queryset(self, queryset):
|
||||||
|
queryset = super().filter_queryset(queryset)
|
||||||
|
eid = self.request.GET.get('execution_id')
|
||||||
|
execution = get_object_or_none(AutomationExecution, pk=eid)
|
||||||
|
if execution:
|
||||||
|
queryset = queryset.filter(execution=execution)
|
||||||
|
queryset = queryset.order_by('is_success', '-date_start')
|
||||||
|
return queryset
|
|
@ -47,7 +47,7 @@ class PushOrVerifyHostCallbackMixin:
|
||||||
secret = account.secret
|
secret = account.secret
|
||||||
|
|
||||||
private_key_path = None
|
private_key_path = None
|
||||||
if account.secret_type == SecretType.ssh_key:
|
if account.secret_type == SecretType.SSH_KEY:
|
||||||
private_key_path = self.generate_private_key_path(secret, path_dir)
|
private_key_path = self.generate_private_key_path(secret, path_dir)
|
||||||
secret = self.generate_public_key(secret)
|
secret = self.generate_public_key(secret)
|
||||||
|
|
||||||
|
|
|
@ -89,9 +89,9 @@ class ChangeSecretManager(BasePlaybookManager):
|
||||||
return self.generate_password()
|
return self.generate_password()
|
||||||
|
|
||||||
def get_secret(self):
|
def get_secret(self):
|
||||||
if self.secret_type == SecretType.ssh_key:
|
if self.secret_type == SecretType.SSH_KEY:
|
||||||
secret = self.get_ssh_key()
|
secret = self.get_ssh_key()
|
||||||
elif self.secret_type == SecretType.password:
|
elif self.secret_type == SecretType.PASSWORD:
|
||||||
secret = self.get_password()
|
secret = self.get_password()
|
||||||
else:
|
else:
|
||||||
raise ValueError("Secret must be set")
|
raise ValueError("Secret must be set")
|
||||||
|
@ -99,7 +99,7 @@ class ChangeSecretManager(BasePlaybookManager):
|
||||||
|
|
||||||
def get_kwargs(self, account, secret):
|
def get_kwargs(self, account, secret):
|
||||||
kwargs = {}
|
kwargs = {}
|
||||||
if self.secret_type != SecretType.ssh_key:
|
if self.secret_type != SecretType.SSH_KEY:
|
||||||
return kwargs
|
return kwargs
|
||||||
kwargs['strategy'] = self.execution.snapshot['ssh_key_change_strategy']
|
kwargs['strategy'] = self.execution.snapshot['ssh_key_change_strategy']
|
||||||
kwargs['exclusive'] = 'yes' if kwargs['strategy'] == SSHKeyStrategy.set else 'no'
|
kwargs['exclusive'] = 'yes' if kwargs['strategy'] == SSHKeyStrategy.set else 'no'
|
||||||
|
@ -143,7 +143,7 @@ class ChangeSecretManager(BasePlaybookManager):
|
||||||
self.name_recorder_mapper[h['name']] = recorder
|
self.name_recorder_mapper[h['name']] = recorder
|
||||||
|
|
||||||
private_key_path = None
|
private_key_path = None
|
||||||
if self.secret_type == SecretType.ssh_key:
|
if self.secret_type == SecretType.SSH_KEY:
|
||||||
private_key_path = self.generate_private_key_path(new_secret, path_dir)
|
private_key_path = self.generate_private_key_path(new_secret, path_dir)
|
||||||
new_secret = self.generate_public_key(new_secret)
|
new_secret = self.generate_public_key(new_secret)
|
||||||
|
|
||||||
|
|
|
@ -21,14 +21,14 @@ class PingManager(BasePlaybookManager):
|
||||||
|
|
||||||
def on_host_success(self, host, result):
|
def on_host_success(self, host, result):
|
||||||
asset, account = self.host_asset_and_account_mapper.get(host)
|
asset, account = self.host_asset_and_account_mapper.get(host)
|
||||||
asset.set_connectivity(Connectivity.ok)
|
asset.set_connectivity(Connectivity.OK)
|
||||||
if not account:
|
if not account:
|
||||||
return
|
return
|
||||||
account.set_connectivity(Connectivity.ok)
|
account.set_connectivity(Connectivity.OK)
|
||||||
|
|
||||||
def on_host_error(self, host, error, result):
|
def on_host_error(self, host, error, result):
|
||||||
asset, account = self.host_asset_and_account_mapper.get(host)
|
asset, account = self.host_asset_and_account_mapper.get(host)
|
||||||
asset.set_connectivity(Connectivity.failed)
|
asset.set_connectivity(Connectivity.FAILED)
|
||||||
if not account:
|
if not account:
|
||||||
return
|
return
|
||||||
account.set_connectivity(Connectivity.failed)
|
account.set_connectivity(Connectivity.FAILED)
|
||||||
|
|
|
@ -18,8 +18,8 @@ class VerifyAccountManager(PushOrVerifyHostCallbackMixin, BasePlaybookManager):
|
||||||
|
|
||||||
def on_host_success(self, host, result):
|
def on_host_success(self, host, result):
|
||||||
account = self.host_account_mapper.get(host)
|
account = self.host_account_mapper.get(host)
|
||||||
account.set_connectivity(Connectivity.ok)
|
account.set_connectivity(Connectivity.OK)
|
||||||
|
|
||||||
def on_host_error(self, host, error, result):
|
def on_host_error(self, host, error, result):
|
||||||
account = self.host_account_mapper.get(host)
|
account = self.host_account_mapper.get(host)
|
||||||
account.set_connectivity(Connectivity.failed)
|
account.set_connectivity(Connectivity.FAILED)
|
||||||
|
|
|
@ -3,13 +3,13 @@ from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
|
|
||||||
class Connectivity(TextChoices):
|
class Connectivity(TextChoices):
|
||||||
unknown = 'unknown', _('Unknown')
|
UNKNOWN = 'unknown', _('Unknown')
|
||||||
ok = 'ok', _('Ok')
|
OK = 'ok', _('Ok')
|
||||||
failed = 'failed', _('Failed')
|
FAILED = 'failed', _('Failed')
|
||||||
|
|
||||||
|
|
||||||
class SecretType(TextChoices):
|
class SecretType(TextChoices):
|
||||||
password = 'password', _('Password')
|
PASSWORD = 'password', _('Password')
|
||||||
ssh_key = 'ssh_key', _('SSH key')
|
SSH_KEY = 'ssh_key', _('SSH key')
|
||||||
access_key = 'access_key', _('Access key')
|
ACCESS_KEY = 'access_key', _('Access key')
|
||||||
token = 'token', _('Token')
|
TOKEN = 'token', _('Token')
|
||||||
|
|
|
@ -17,6 +17,22 @@ class AutomationTypes(TextChoices):
|
||||||
verify_account = 'verify_account', _('Verify account')
|
verify_account = 'verify_account', _('Verify account')
|
||||||
gather_accounts = 'gather_accounts', _('Gather accounts')
|
gather_accounts = 'gather_accounts', _('Gather accounts')
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_type_model(cls, tp):
|
||||||
|
from assets.models import (
|
||||||
|
PingAutomation, GatherFactsAutomation, PushAccountAutomation,
|
||||||
|
ChangeSecretAutomation, VerifyAccountAutomation, GatherAccountsAutomation,
|
||||||
|
)
|
||||||
|
type_model_dict = {
|
||||||
|
cls.ping: PingAutomation,
|
||||||
|
cls.gather_facts: GatherFactsAutomation,
|
||||||
|
cls.push_account: PushAccountAutomation,
|
||||||
|
cls.change_secret: ChangeSecretAutomation,
|
||||||
|
cls.verify_account: VerifyAccountAutomation,
|
||||||
|
cls.gather_accounts: GatherAccountsAutomation,
|
||||||
|
}
|
||||||
|
return type_model_dict.get(tp)
|
||||||
|
|
||||||
|
|
||||||
class SecretStrategy(TextChoices):
|
class SecretStrategy(TextChoices):
|
||||||
custom = 'specific', _('Specific')
|
custom = 'specific', _('Specific')
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
from .change_secret import *
|
from .ping import *
|
||||||
from .discovery_account import *
|
from .base import *
|
||||||
from .push_account import *
|
from .push_account import *
|
||||||
from .gather_facts import *
|
from .gather_facts import *
|
||||||
from .gather_accounts import *
|
from .change_secret import *
|
||||||
from .verify_account import *
|
from .verify_account import *
|
||||||
from .ping import *
|
from .gather_accounts import *
|
||||||
|
from .discovery_account import *
|
||||||
|
|
|
@ -3,7 +3,7 @@ from celery import current_task
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
from common.const.choices import Trigger, Status
|
from common.const.choices import Trigger
|
||||||
from common.mixins.models import CommonModelMixin
|
from common.mixins.models import CommonModelMixin
|
||||||
from common.db.fields import EncryptJsonDictTextField
|
from common.db.fields import EncryptJsonDictTextField
|
||||||
from orgs.mixins.models import OrgModelMixin
|
from orgs.mixins.models import OrgModelMixin
|
||||||
|
|
|
@ -12,7 +12,7 @@ __all__ = ['ChangeSecretAutomation', 'ChangeSecretRecord']
|
||||||
class ChangeSecretAutomation(BaseAutomation):
|
class ChangeSecretAutomation(BaseAutomation):
|
||||||
secret_type = models.CharField(
|
secret_type = models.CharField(
|
||||||
choices=SecretType.choices, max_length=16,
|
choices=SecretType.choices, max_length=16,
|
||||||
default=SecretType.password, verbose_name=_('Secret type')
|
default=SecretType.PASSWORD, verbose_name=_('Secret type')
|
||||||
)
|
)
|
||||||
secret_strategy = models.CharField(
|
secret_strategy = models.CharField(
|
||||||
choices=SecretStrategy.choices, max_length=16,
|
choices=SecretStrategy.choices, max_length=16,
|
||||||
|
@ -24,7 +24,7 @@ class ChangeSecretAutomation(BaseAutomation):
|
||||||
choices=SSHKeyStrategy.choices, max_length=16,
|
choices=SSHKeyStrategy.choices, max_length=16,
|
||||||
default=SSHKeyStrategy.add, verbose_name=_('SSH key change strategy')
|
default=SSHKeyStrategy.add, verbose_name=_('SSH key change strategy')
|
||||||
)
|
)
|
||||||
recipients = models.ManyToManyField('users.User', blank=True, verbose_name=_("Recipient"))
|
recipients = models.ManyToManyField('users.User', verbose_name=_("Recipient"), blank=True)
|
||||||
|
|
||||||
def save(self, *args, **kwargs):
|
def save(self, *args, **kwargs):
|
||||||
self.type = AutomationTypes.change_secret
|
self.type = AutomationTypes.change_secret
|
||||||
|
|
|
@ -24,7 +24,7 @@ logger = get_logger(__file__)
|
||||||
|
|
||||||
class AbsConnectivity(models.Model):
|
class AbsConnectivity(models.Model):
|
||||||
connectivity = models.CharField(
|
connectivity = models.CharField(
|
||||||
choices=Connectivity.choices, default=Connectivity.unknown,
|
choices=Connectivity.choices, default=Connectivity.UNKNOWN,
|
||||||
max_length=16, verbose_name=_('Connectivity')
|
max_length=16, verbose_name=_('Connectivity')
|
||||||
)
|
)
|
||||||
date_verified = models.DateTimeField(null=True, verbose_name=_("Date verified"))
|
date_verified = models.DateTimeField(null=True, verbose_name=_("Date verified"))
|
||||||
|
@ -50,7 +50,7 @@ class BaseAccount(JMSOrgBaseModel):
|
||||||
name = models.CharField(max_length=128, verbose_name=_("Name"))
|
name = models.CharField(max_length=128, verbose_name=_("Name"))
|
||||||
username = models.CharField(max_length=128, blank=True, verbose_name=_('Username'), db_index=True)
|
username = models.CharField(max_length=128, blank=True, verbose_name=_('Username'), db_index=True)
|
||||||
secret_type = models.CharField(
|
secret_type = models.CharField(
|
||||||
max_length=16, choices=SecretType.choices, default=SecretType.password, verbose_name=_('Secret type')
|
max_length=16, choices=SecretType.choices, default=SecretType.PASSWORD, verbose_name=_('Secret type')
|
||||||
)
|
)
|
||||||
secret = fields.EncryptTextField(blank=True, null=True, verbose_name=_('Secret'))
|
secret = fields.EncryptTextField(blank=True, null=True, verbose_name=_('Secret'))
|
||||||
privileged = models.BooleanField(verbose_name=_("Privileged"), default=False)
|
privileged = models.BooleanField(verbose_name=_("Privileged"), default=False)
|
||||||
|
@ -65,25 +65,25 @@ class BaseAccount(JMSOrgBaseModel):
|
||||||
@property
|
@property
|
||||||
def specific(self):
|
def specific(self):
|
||||||
data = {}
|
data = {}
|
||||||
if self.secret_type != SecretType.ssh_key:
|
if self.secret_type != SecretType.SSH_KEY:
|
||||||
return data
|
return data
|
||||||
data['ssh_key_fingerprint'] = self.ssh_key_fingerprint
|
data['ssh_key_fingerprint'] = self.ssh_key_fingerprint
|
||||||
return data
|
return data
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def private_key(self):
|
def private_key(self):
|
||||||
if self.secret_type == SecretType.ssh_key:
|
if self.secret_type == SecretType.SSH_KEY:
|
||||||
return self.secret
|
return self.secret
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@private_key.setter
|
@private_key.setter
|
||||||
def private_key(self, value):
|
def private_key(self, value):
|
||||||
self.secret = value
|
self.secret = value
|
||||||
self.secret_type = SecretType.ssh_key
|
self.secret_type = SecretType.SSH_KEY
|
||||||
|
|
||||||
@lazyproperty
|
@lazyproperty
|
||||||
def public_key(self):
|
def public_key(self):
|
||||||
if self.secret_type == SecretType.ssh_key:
|
if self.secret_type == SecretType.SSH_KEY:
|
||||||
return ssh_pubkey_gen(private_key=self.private_key)
|
return ssh_pubkey_gen(private_key=self.private_key)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
@ -113,7 +113,7 @@ class BaseAccount(JMSOrgBaseModel):
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def private_key_path(self):
|
def private_key_path(self):
|
||||||
if not self.secret_type != SecretType.ssh_key or not self.secret:
|
if not self.secret_type != SecretType.SSH_KEY or not self.secret:
|
||||||
return None
|
return None
|
||||||
project_dir = settings.PROJECT_DIR
|
project_dir = settings.PROJECT_DIR
|
||||||
tmp_dir = os.path.join(project_dir, 'tmp')
|
tmp_dir = os.path.join(project_dir, 'tmp')
|
||||||
|
|
|
@ -11,4 +11,4 @@ from .account import *
|
||||||
from assets.serializers.account.backup import *
|
from assets.serializers.account.backup import *
|
||||||
from .platform import *
|
from .platform import *
|
||||||
from .cagegory import *
|
from .cagegory import *
|
||||||
from .automation import *
|
from .automations import *
|
||||||
|
|
|
@ -1,35 +0,0 @@
|
||||||
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")
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
from .base import *
|
||||||
|
from .change_secret import *
|
||||||
|
from .gather_accounts import *
|
|
@ -0,0 +1,76 @@
|
||||||
|
from django.utils.translation import ugettext as _
|
||||||
|
from rest_framework import serializers
|
||||||
|
|
||||||
|
from ops.mixin import PeriodTaskSerializerMixin
|
||||||
|
from assets.const import AutomationTypes
|
||||||
|
from assets.models import Asset, BaseAutomation, AutomationExecution
|
||||||
|
from orgs.mixins.serializers import BulkOrgResourceModelSerializer
|
||||||
|
from common.utils import get_logger
|
||||||
|
|
||||||
|
logger = get_logger(__file__)
|
||||||
|
|
||||||
|
__all__ = [
|
||||||
|
'BaseAutomationSerializer', 'AutomationExecutionSerializer',
|
||||||
|
'UpdateAssetSerializer', 'UpdateNodeSerializer', 'AutomationAssetsSerializer',
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class BaseAutomationSerializer(PeriodTaskSerializerMixin, BulkOrgResourceModelSerializer):
|
||||||
|
class Meta:
|
||||||
|
read_only_fields = [
|
||||||
|
'date_created', 'date_updated', 'created_by', 'periodic_display'
|
||||||
|
]
|
||||||
|
fields = read_only_fields + [
|
||||||
|
'id', 'name', 'is_periodic', 'interval', 'crontab', 'comment',
|
||||||
|
'type', 'accounts', 'nodes', 'assets', 'is_active'
|
||||||
|
]
|
||||||
|
extra_kwargs = {
|
||||||
|
'name': {'required': True},
|
||||||
|
'periodic_display': {'label': _('Periodic perform')},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class AutomationExecutionSerializer(serializers.ModelSerializer):
|
||||||
|
snapshot = serializers.SerializerMethodField(label=_('Automation snapshot'))
|
||||||
|
type = serializers.ChoiceField(choices=AutomationTypes.choices, write_only=True, label=_('Type'))
|
||||||
|
trigger_display = serializers.ReadOnlyField(source='get_trigger_display', label=_('Trigger mode'))
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = AutomationExecution
|
||||||
|
fields = [
|
||||||
|
'id', 'automation', 'trigger', 'trigger_display',
|
||||||
|
'date_start', 'date_finished', 'snapshot', 'type'
|
||||||
|
]
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_snapshot(obj):
|
||||||
|
tp = obj.snapshot['type']
|
||||||
|
snapshot = {
|
||||||
|
'type': tp,
|
||||||
|
'name': obj.snapshot['name'],
|
||||||
|
'comment': obj.snapshot['comment'],
|
||||||
|
'accounts': obj.snapshot['accounts'],
|
||||||
|
'node_amount': len(obj.snapshot['nodes']),
|
||||||
|
'asset_amount': len(obj.snapshot['assets']),
|
||||||
|
'type_display': getattr(AutomationTypes, tp).label,
|
||||||
|
}
|
||||||
|
return snapshot
|
||||||
|
|
||||||
|
|
||||||
|
class UpdateAssetSerializer(serializers.ModelSerializer):
|
||||||
|
class Meta:
|
||||||
|
model = BaseAutomation
|
||||||
|
fields = ['id', 'assets']
|
||||||
|
|
||||||
|
|
||||||
|
class UpdateNodeSerializer(serializers.ModelSerializer):
|
||||||
|
class Meta:
|
||||||
|
model = BaseAutomation
|
||||||
|
fields = ['id', 'nodes']
|
||||||
|
|
||||||
|
|
||||||
|
class AutomationAssetsSerializer(serializers.ModelSerializer):
|
||||||
|
class Meta:
|
||||||
|
model = Asset
|
||||||
|
only_fields = ['id', 'name', 'address']
|
||||||
|
fields = tuple(only_fields)
|
|
@ -0,0 +1,139 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
#
|
||||||
|
from django.utils.translation import ugettext as _
|
||||||
|
from rest_framework import serializers
|
||||||
|
|
||||||
|
from assets.serializers.base import AuthValidateMixin
|
||||||
|
from assets.models import ChangeSecretAutomation, ChangeSecretRecord
|
||||||
|
from assets.const import DEFAULT_PASSWORD_RULES, SecretType, SecretStrategy
|
||||||
|
from common.utils import get_logger
|
||||||
|
|
||||||
|
from .base import BaseAutomationSerializer
|
||||||
|
|
||||||
|
logger = get_logger(__file__)
|
||||||
|
|
||||||
|
__all__ = [
|
||||||
|
'ChangeSecretAutomationSerializer',
|
||||||
|
'ChangeSecretRecordSerializer',
|
||||||
|
'ChangeSecretRecordBackUpSerializer'
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class ChangeSecretAutomationSerializer(AuthValidateMixin, BaseAutomationSerializer):
|
||||||
|
password_rules = serializers.DictField(default=DEFAULT_PASSWORD_RULES)
|
||||||
|
secret_strategy_display = serializers.ReadOnlyField(
|
||||||
|
source='get_secret_strategy_display', label=_('Secret strategy')
|
||||||
|
)
|
||||||
|
ssh_key_change_strategy_display = serializers.ReadOnlyField(
|
||||||
|
source='get_ssh_key_strategy_display', label=_('SSH Key strategy')
|
||||||
|
)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = ChangeSecretAutomation
|
||||||
|
read_only_fields = BaseAutomationSerializer.Meta.read_only_fields + [
|
||||||
|
'secret_strategy_display', 'ssh_key_change_strategy_display'
|
||||||
|
]
|
||||||
|
fields = BaseAutomationSerializer.Meta.fields + read_only_fields + [
|
||||||
|
'secret_type', 'secret_strategy', 'secret', 'password_rules',
|
||||||
|
'ssh_key_change_strategy', 'passphrase', 'recipients',
|
||||||
|
]
|
||||||
|
extra_kwargs = {**BaseAutomationSerializer.Meta.extra_kwargs, **{
|
||||||
|
'recipients': {'label': _('Recipient'), 'help_text': _(
|
||||||
|
"Currently only mail sending is supported"
|
||||||
|
)},
|
||||||
|
}}
|
||||||
|
|
||||||
|
def validate_password_rules(self, password_rules):
|
||||||
|
secret_type = self.initial_secret_type
|
||||||
|
if secret_type != SecretType.PASSWORD:
|
||||||
|
return password_rules
|
||||||
|
|
||||||
|
length = password_rules.get('length')
|
||||||
|
symbol_set = password_rules.get('symbol_set', '')
|
||||||
|
|
||||||
|
try:
|
||||||
|
length = int(length)
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(e)
|
||||||
|
msg = _("* Please enter the correct password length")
|
||||||
|
raise serializers.ValidationError(msg)
|
||||||
|
if length < 6 or length > 30:
|
||||||
|
msg = _('* Password length range 6-30 bits')
|
||||||
|
raise serializers.ValidationError(msg)
|
||||||
|
|
||||||
|
if not isinstance(symbol_set, str):
|
||||||
|
symbol_set = str(symbol_set)
|
||||||
|
|
||||||
|
password_rules = {'length': length, 'symbol_set': ''.join(symbol_set)}
|
||||||
|
return password_rules
|
||||||
|
|
||||||
|
def validate(self, attrs):
|
||||||
|
secret_type = attrs.get('secret_type')
|
||||||
|
secret_strategy = attrs.get('secret_strategy')
|
||||||
|
if secret_type == SecretType.PASSWORD:
|
||||||
|
attrs.pop('ssh_key_change_strategy', None)
|
||||||
|
if secret_strategy == SecretStrategy.custom:
|
||||||
|
attrs.pop('password_rules', None)
|
||||||
|
else:
|
||||||
|
attrs.pop('secret', None)
|
||||||
|
elif secret_type == SecretType.SSH_KEY:
|
||||||
|
attrs.pop('password_rules', None)
|
||||||
|
if secret_strategy != SecretStrategy.custom:
|
||||||
|
attrs.pop('secret', None)
|
||||||
|
return attrs
|
||||||
|
|
||||||
|
|
||||||
|
class ChangeSecretRecordSerializer(serializers.ModelSerializer):
|
||||||
|
asset_display = serializers.SerializerMethodField(label=_('Asset display'))
|
||||||
|
account_display = serializers.SerializerMethodField(label=_('Account display'))
|
||||||
|
is_success = serializers.SerializerMethodField(label=_('Is success'))
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = ChangeSecretRecord
|
||||||
|
fields = [
|
||||||
|
'id', 'asset', 'account', 'date_started', 'date_finished',
|
||||||
|
'is_success', 'error', 'execution', 'asset_display', 'account_display'
|
||||||
|
]
|
||||||
|
read_only_fields = fields
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_asset_display(instance):
|
||||||
|
return str(instance.asset)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_account_display(instance):
|
||||||
|
return str(instance.account)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_is_success(obj):
|
||||||
|
if obj.status == 'success':
|
||||||
|
return _("Success")
|
||||||
|
return _("Failed")
|
||||||
|
|
||||||
|
|
||||||
|
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'
|
||||||
|
]
|
||||||
|
read_only_fields = fields
|
||||||
|
|
||||||
|
@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")
|
|
@ -1,68 +1,51 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
#
|
#
|
||||||
from io import StringIO
|
|
||||||
|
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
|
|
||||||
from common.utils import ssh_pubkey_gen, ssh_private_key_gen, validate_ssh_private_key
|
|
||||||
from common.drf.fields import EncryptedField
|
from common.drf.fields import EncryptedField
|
||||||
from .utils import validate_password_for_ansible
|
from assets.const import SecretType
|
||||||
|
from .utils import validate_password_for_ansible, validate_ssh_key
|
||||||
|
|
||||||
|
|
||||||
class AuthValidateMixin(serializers.Serializer):
|
class AuthValidateMixin(serializers.Serializer):
|
||||||
password = EncryptedField(
|
secret_type = serializers.CharField(label=_('Secret type'), max_length=16, required=True)
|
||||||
label=_('Password'), required=False, allow_blank=True, allow_null=True,
|
secret = EncryptedField(
|
||||||
max_length=1024, validators=[validate_password_for_ansible]
|
label=_('Secret'), required=False, max_length=16384, allow_blank=True,
|
||||||
)
|
allow_null=True, write_only=True,
|
||||||
private_key = EncryptedField(
|
|
||||||
label=_('SSH private key'), required=False, allow_blank=True,
|
|
||||||
allow_null=True, max_length=16384
|
|
||||||
)
|
)
|
||||||
passphrase = serializers.CharField(
|
passphrase = serializers.CharField(
|
||||||
allow_blank=True, allow_null=True, required=False, max_length=512,
|
allow_blank=True, allow_null=True, required=False, max_length=512,
|
||||||
write_only=True, label=_('Key password')
|
write_only=True, label=_('Key password')
|
||||||
)
|
)
|
||||||
|
|
||||||
def validate_private_key(self, private_key):
|
@property
|
||||||
if not private_key:
|
def initial_secret_type(self):
|
||||||
return
|
secret_type = self.initial_data.get('secret_type')
|
||||||
passphrase = self.initial_data.get('passphrase')
|
return secret_type
|
||||||
passphrase = passphrase if passphrase else None
|
|
||||||
valid = validate_ssh_private_key(private_key, password=passphrase)
|
|
||||||
if not valid:
|
|
||||||
raise serializers.ValidationError(_("private key invalid or passphrase error"))
|
|
||||||
|
|
||||||
private_key = ssh_private_key_gen(private_key, password=passphrase)
|
def validate_secret(self, secret):
|
||||||
string_io = StringIO()
|
if not secret:
|
||||||
private_key.write_private_key(string_io)
|
return
|
||||||
private_key = string_io.getvalue()
|
secret_type = self.initial_secret_type
|
||||||
return private_key
|
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
|
@staticmethod
|
||||||
def clean_auth_fields(validated_data):
|
def clean_auth_fields(validated_data):
|
||||||
for field in ('password', 'private_key', 'public_key'):
|
for field in ('secret',):
|
||||||
value = validated_data.get(field)
|
value = validated_data.get(field)
|
||||||
if not value:
|
if not value:
|
||||||
validated_data.pop(field, None)
|
validated_data.pop(field, None)
|
||||||
validated_data.pop('passphrase', None)
|
validated_data.pop('passphrase', None)
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def _validate_gen_key(attrs):
|
|
||||||
private_key = attrs.get('private_key')
|
|
||||||
if not private_key:
|
|
||||||
return attrs
|
|
||||||
|
|
||||||
password = attrs.get('passphrase')
|
|
||||||
username = attrs.get('username')
|
|
||||||
public_key = ssh_pubkey_gen(private_key, password=password, username=username)
|
|
||||||
attrs['public_key'] = public_key
|
|
||||||
return attrs
|
|
||||||
|
|
||||||
def validate(self, attrs):
|
|
||||||
attrs = self._validate_gen_key(attrs)
|
|
||||||
return super().validate(attrs)
|
|
||||||
|
|
||||||
def create(self, validated_data):
|
def create(self, validated_data):
|
||||||
self.clean_auth_fields(validated_data)
|
self.clean_auth_fields(validated_data)
|
||||||
return super().create(validated_data)
|
return super().create(validated_data)
|
||||||
|
|
|
@ -1,6 +1,10 @@
|
||||||
|
from io import StringIO
|
||||||
|
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
|
|
||||||
|
from common.utils import ssh_private_key_gen, validate_ssh_private_key
|
||||||
|
|
||||||
|
|
||||||
def validate_password_for_ansible(password):
|
def validate_password_for_ansible(password):
|
||||||
""" 校验 Ansible 不支持的特殊字符 """
|
""" 校验 Ansible 不支持的特殊字符 """
|
||||||
|
@ -15,3 +19,14 @@ def validate_password_for_ansible(password):
|
||||||
if '"' in password:
|
if '"' in password:
|
||||||
raise serializers.ValidationError(_('Password can not contains `"` '))
|
raise serializers.ValidationError(_('Password can not contains `"` '))
|
||||||
|
|
||||||
|
|
||||||
|
def validate_ssh_key(ssh_key, passphrase=None):
|
||||||
|
valid = validate_ssh_private_key(ssh_key, password=passphrase)
|
||||||
|
if not valid:
|
||||||
|
raise serializers.ValidationError(_("private key invalid or passphrase error"))
|
||||||
|
|
||||||
|
ssh_key = ssh_private_key_gen(ssh_key, password=passphrase)
|
||||||
|
string_io = StringIO()
|
||||||
|
ssh_key.write_private_key(string_io)
|
||||||
|
ssh_key = string_io.getvalue()
|
||||||
|
return ssh_key
|
||||||
|
|
|
@ -7,9 +7,9 @@ logger = get_logger(__file__)
|
||||||
|
|
||||||
|
|
||||||
@shared_task(queue='ansible')
|
@shared_task(queue='ansible')
|
||||||
def execute_automation(pid, trigger, mode):
|
def execute_automation(pid, trigger, model):
|
||||||
with tmp_to_root_org():
|
with tmp_to_root_org():
|
||||||
instance = get_object_or_none(mode, pk=pid)
|
instance = get_object_or_none(model, pk=pid)
|
||||||
if not instance:
|
if not instance:
|
||||||
logger.error("No automation task found: {}".format(pid))
|
logger.error("No automation task found: {}".format(pid))
|
||||||
return
|
return
|
||||||
|
|
|
@ -27,17 +27,25 @@ router.register(r'favorite-assets', api.FavoriteAssetViewSet, 'favorite-asset')
|
||||||
router.register(r'account-backup-plans', api.AccountBackupPlanViewSet, 'account-backup')
|
router.register(r'account-backup-plans', api.AccountBackupPlanViewSet, 'account-backup')
|
||||||
router.register(r'account-backup-plan-executions', api.AccountBackupPlanExecutionViewSet, 'account-backup-execution')
|
router.register(r'account-backup-plan-executions', api.AccountBackupPlanExecutionViewSet, 'account-backup-execution')
|
||||||
|
|
||||||
|
router.register(r'change-secret-automations', api.ChangeSecretAutomationViewSet, 'change-secret-automations')
|
||||||
|
router.register(r'automation-executions', api.AutomationExecutionViewSet, 'automation-execution')
|
||||||
|
router.register(r'change-secret-records', api.ChangeSecretRecordViewSet, 'change-secret-records')
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
# path('assets/<uuid:pk>/gateways/', api.AssetGatewayListApi.as_view(), name='asset-gateway-list'),
|
# path('assets/<uuid:pk>/gateways/', api.AssetGatewayListApi.as_view(), name='asset-gateway-list'),
|
||||||
path('assets/<uuid:pk>/tasks/', api.AssetTaskCreateApi.as_view(), name='asset-task-create'),
|
path('assets/<uuid:pk>/tasks/', api.AssetTaskCreateApi.as_view(), name='asset-task-create'),
|
||||||
path('assets/tasks/', api.AssetsTaskCreateApi.as_view(), name='assets-task-create'),
|
path('assets/tasks/', api.AssetsTaskCreateApi.as_view(), name='assets-task-create'),
|
||||||
path('assets/<uuid:pk>/perm-users/', api.AssetPermUserListApi.as_view(), name='asset-perm-user-list'),
|
path('assets/<uuid:pk>/perm-users/', api.AssetPermUserListApi.as_view(), name='asset-perm-user-list'),
|
||||||
path('assets/<uuid:pk>/perm-users/<uuid:perm_user_id>/permissions/', api.AssetPermUserPermissionsListApi.as_view(), name='asset-perm-user-permission-list'),
|
path('assets/<uuid:pk>/perm-users/<uuid:perm_user_id>/permissions/', api.AssetPermUserPermissionsListApi.as_view(),
|
||||||
path('assets/<uuid:pk>/perm-user-groups/', api.AssetPermUserGroupListApi.as_view(), name='asset-perm-user-group-list'),
|
name='asset-perm-user-permission-list'),
|
||||||
path('assets/<uuid:pk>/perm-user-groups/<uuid:perm_user_group_id>/permissions/', api.AssetPermUserGroupPermissionsListApi.as_view(), name='asset-perm-user-group-permission-list'),
|
path('assets/<uuid:pk>/perm-user-groups/', api.AssetPermUserGroupListApi.as_view(),
|
||||||
|
name='asset-perm-user-group-list'),
|
||||||
|
path('assets/<uuid:pk>/perm-user-groups/<uuid:perm_user_group_id>/permissions/',
|
||||||
|
api.AssetPermUserGroupPermissionsListApi.as_view(), name='asset-perm-user-group-permission-list'),
|
||||||
|
|
||||||
path('accounts/tasks/', api.AccountTaskCreateAPI.as_view(), name='account-task-create'),
|
path('accounts/tasks/', api.AccountTaskCreateAPI.as_view(), name='account-task-create'),
|
||||||
path('account-secrets/<uuid:pk>/histories/', api.AccountHistoriesSecretAPI.as_view(), name='account-secret-history'),
|
path('account-secrets/<uuid:pk>/histories/', api.AccountHistoriesSecretAPI.as_view(),
|
||||||
|
name='account-secret-history'),
|
||||||
|
|
||||||
path('nodes/category/tree/', api.CategoryTreeApi.as_view(), name='asset-category-tree'),
|
path('nodes/category/tree/', api.CategoryTreeApi.as_view(), name='asset-category-tree'),
|
||||||
path('nodes/tree/', api.NodeListAsTreeApi.as_view(), name='node-tree'),
|
path('nodes/tree/', api.NodeListAsTreeApi.as_view(), name='node-tree'),
|
||||||
|
@ -52,7 +60,11 @@ urlpatterns = [
|
||||||
path('nodes/<uuid:pk>/tasks/', api.NodeTaskCreateApi.as_view(), name='node-task-create'),
|
path('nodes/<uuid:pk>/tasks/', api.NodeTaskCreateApi.as_view(), name='node-task-create'),
|
||||||
|
|
||||||
path('gateways/<uuid:pk>/test-connective/', api.GatewayTestConnectionApi.as_view(), name='test-gateway-connective'),
|
path('gateways/<uuid:pk>/test-connective/', api.GatewayTestConnectionApi.as_view(), name='test-gateway-connective'),
|
||||||
|
|
||||||
|
path('automation/<uuid:pk>/asset/remove/', api.AutomationRemoveAssetApi.as_view(), name='automation-remove-asset'),
|
||||||
|
path('automation/<uuid:pk>/asset/add/', api.AutomationAddAssetApi.as_view(), name='automation-add-asset'),
|
||||||
|
path('automation/<uuid:pk>/nodes/', api.AutomationNodeAddRemoveApi.as_view(), name='automation-add-or-remove-node'),
|
||||||
|
path('automation/<uuid:pk>/assets/', api.AutomationAssetsListApi.as_view(), name='automation-assets'),
|
||||||
]
|
]
|
||||||
|
|
||||||
urlpatterns += router.urls
|
urlpatterns += router.urls
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue