mirror of https://github.com/jumpserver/jumpserver
perf: 修改系统用户
parent
fb0fb71ea3
commit
0dc3d43ee5
|
@ -9,7 +9,7 @@ from orgs.mixins.models import OrgModelMixin
|
||||||
from common.mixins import CommonModelMixin
|
from common.mixins import CommonModelMixin
|
||||||
from common.tree import TreeNode
|
from common.tree import TreeNode
|
||||||
from common.utils import is_uuid
|
from common.utils import is_uuid
|
||||||
from assets.models import Asset, SystemUser
|
from assets.models import Asset
|
||||||
|
|
||||||
from ..utils import KubernetesTree
|
from ..utils import KubernetesTree
|
||||||
from .. import const
|
from .. import const
|
||||||
|
|
|
@ -1,176 +0,0 @@
|
||||||
# ~*~ coding: utf-8 ~*~
|
|
||||||
from django.shortcuts import get_object_or_404
|
|
||||||
from rest_framework.response import Response
|
|
||||||
from rest_framework.decorators import action
|
|
||||||
from rest_framework.viewsets import GenericViewSet
|
|
||||||
|
|
||||||
from common.utils import get_logger, get_object_or_none
|
|
||||||
from common.mixins.api import SuggestionMixin
|
|
||||||
from orgs.mixins.api import OrgBulkModelViewSet
|
|
||||||
from orgs.mixins import generics
|
|
||||||
from ..models import SystemUser, CommandFilterRule, Account
|
|
||||||
from .. import serializers
|
|
||||||
|
|
||||||
logger = get_logger(__file__)
|
|
||||||
__all__ = [
|
|
||||||
'SystemUserViewSet', 'SystemUserAuthInfoApi',
|
|
||||||
'SystemUserCommandFilterRuleListApi',
|
|
||||||
'SystemUserAssetAccountApi',
|
|
||||||
'SystemUserAssetAccountSecretApi',
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
class SystemUserViewSet(SuggestionMixin, OrgBulkModelViewSet):
|
|
||||||
"""
|
|
||||||
System user api set, for add,delete,update,list,retrieve resource
|
|
||||||
"""
|
|
||||||
model = SystemUser
|
|
||||||
filterset_fields = {
|
|
||||||
'name': ['exact'],
|
|
||||||
'username': ['exact'],
|
|
||||||
'protocol': ['exact', 'in'],
|
|
||||||
}
|
|
||||||
search_fields = filterset_fields
|
|
||||||
serializer_class = serializers.SystemUserSerializer
|
|
||||||
serializer_classes = {
|
|
||||||
'default': serializers.SystemUserSerializer,
|
|
||||||
'suggestion': serializers.MiniSystemUserSerializer
|
|
||||||
}
|
|
||||||
ordering_fields = ('name', 'protocol', 'login_mode')
|
|
||||||
ordering = ('name', )
|
|
||||||
rbac_perms = {
|
|
||||||
'su_from': 'assets.view_systemuser',
|
|
||||||
'su_to': 'assets.view_systemuser',
|
|
||||||
'match': 'assets.match_systemuser'
|
|
||||||
}
|
|
||||||
|
|
||||||
@action(methods=['get'], detail=False, url_path='su-from')
|
|
||||||
def su_from(self, request, *args, **kwargs):
|
|
||||||
""" API 获取可选的 su_from 系统用户"""
|
|
||||||
queryset = self.filter_queryset(self.get_queryset())
|
|
||||||
queryset = queryset.filter(
|
|
||||||
protocol=SystemUser.Protocol.ssh, login_mode=SystemUser.LOGIN_AUTO
|
|
||||||
)
|
|
||||||
return self.get_paginate_response_if_need(queryset)
|
|
||||||
|
|
||||||
@action(methods=['get'], detail=True, url_path='su-to')
|
|
||||||
def su_to(self, request, *args, **kwargs):
|
|
||||||
""" 获取系统用户的所有 su_to 系统用户 """
|
|
||||||
pk = kwargs.get('pk')
|
|
||||||
system_user = get_object_or_404(SystemUser, pk=pk)
|
|
||||||
queryset = system_user.su_to.all()
|
|
||||||
queryset = self.filter_queryset(queryset)
|
|
||||||
return self.get_paginate_response_if_need(queryset)
|
|
||||||
|
|
||||||
def get_paginate_response_if_need(self, queryset):
|
|
||||||
page = self.paginate_queryset(queryset)
|
|
||||||
if page is not None:
|
|
||||||
serializer = self.get_serializer(page, many=True)
|
|
||||||
return self.get_paginated_response(serializer.data)
|
|
||||||
serializer = self.get_serializer(queryset, many=True)
|
|
||||||
return Response(serializer.data)
|
|
||||||
|
|
||||||
|
|
||||||
class SystemUserAccountViewSet(GenericViewSet):
|
|
||||||
model = Account
|
|
||||||
serializer_classes = {
|
|
||||||
'default': serializers.AccountSerializer,
|
|
||||||
'account_secret': serializers.AccountSecretSerializer,
|
|
||||||
}
|
|
||||||
|
|
||||||
def get_object(self):
|
|
||||||
system_user_id = self.kwargs.get('pk')
|
|
||||||
asset_id = self.kwargs.get('asset_id')
|
|
||||||
user_id = self.kwargs.get("user_id")
|
|
||||||
system_user = SystemUser.objects.get(id=system_user_id)
|
|
||||||
account = system_user.get_account(user_id, asset_id)
|
|
||||||
return account
|
|
||||||
|
|
||||||
@action(methods=['get'], detail=False, url_path='account')
|
|
||||||
def account(self, request, *args, **kwargs):
|
|
||||||
pass
|
|
||||||
|
|
||||||
@action(methods=['get'], detail=False, url_path='account-secret')
|
|
||||||
def account_secret(self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
@action(methods=['put'], detail=False, url_path='manual-account')
|
|
||||||
def manual_account(self, request, *args, **kwargs):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class SystemUserAssetAccountApi(generics.RetrieveAPIView):
|
|
||||||
model = Account
|
|
||||||
serializer_class = serializers.AccountSerializer
|
|
||||||
|
|
||||||
def get_object(self):
|
|
||||||
system_user_id = self.kwargs.get('pk')
|
|
||||||
asset_id = self.kwargs.get('asset_id')
|
|
||||||
user_id = self.kwargs.get("user_id")
|
|
||||||
system_user = SystemUser.objects.get(id=system_user_id)
|
|
||||||
account = system_user.get_account(user_id, asset_id)
|
|
||||||
return account
|
|
||||||
|
|
||||||
|
|
||||||
class SystemUserAssetAccountSecretApi(SystemUserAssetAccountApi):
|
|
||||||
model = Account
|
|
||||||
serializer_class = serializers.AccountSecretSerializer
|
|
||||||
rbac_perms = {
|
|
||||||
'retrieve': 'assets.view_accountsecret'
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class SystemUserAuthInfoApi(generics.RetrieveUpdateDestroyAPIView):
|
|
||||||
"""
|
|
||||||
Get system user auth info
|
|
||||||
"""
|
|
||||||
model = SystemUser
|
|
||||||
serializer_class = serializers.AccountSerializer
|
|
||||||
rbac_perms = {
|
|
||||||
'retrieve': 'assets.view_systemusersecret',
|
|
||||||
'list': 'assets.view_systemusersecret',
|
|
||||||
'change': 'assets.change_systemuser',
|
|
||||||
'destroy': 'assets.change_systemuser',
|
|
||||||
}
|
|
||||||
|
|
||||||
def get_object(self):
|
|
||||||
system_user_id = self.kwargs.get('pk')
|
|
||||||
asset_id = self.kwargs.get('asset_id')
|
|
||||||
user_id = self.kwargs.get("user_id")
|
|
||||||
system_user = SystemUser.objects.get(id=system_user_id)
|
|
||||||
account = system_user.get_account(user_id, asset_id)
|
|
||||||
return account
|
|
||||||
|
|
||||||
def destroy(self, request, *args, **kwargs):
|
|
||||||
instance = self.get_object()
|
|
||||||
instance.clear_auth()
|
|
||||||
return Response(status=204)
|
|
||||||
|
|
||||||
|
|
||||||
class SystemUserCommandFilterRuleListApi(generics.ListAPIView):
|
|
||||||
rbac_perms = {
|
|
||||||
'list': 'assets.view_commandfilterule'
|
|
||||||
}
|
|
||||||
|
|
||||||
def get_serializer_class(self):
|
|
||||||
from ..serializers import CommandFilterRuleSerializer
|
|
||||||
return CommandFilterRuleSerializer
|
|
||||||
|
|
||||||
def get_queryset(self):
|
|
||||||
user_id = self.request.query_params.get('user_id')
|
|
||||||
user_group_id = self.request.query_params.get('user_group_id')
|
|
||||||
system_user_id = self.kwargs.get('pk', None)
|
|
||||||
system_user = get_object_or_none(SystemUser, pk=system_user_id)
|
|
||||||
if not system_user:
|
|
||||||
system_user_id = self.request.query_params.get('system_user_id')
|
|
||||||
asset_id = self.request.query_params.get('asset_id')
|
|
||||||
application_id = self.request.query_params.get('application_id')
|
|
||||||
rules = CommandFilterRule.get_queryset(
|
|
||||||
user_id=user_id,
|
|
||||||
user_group_id=user_group_id,
|
|
||||||
system_user_id=system_user_id,
|
|
||||||
asset_id=asset_id,
|
|
||||||
application_id=application_id
|
|
||||||
)
|
|
||||||
return rules
|
|
||||||
|
|
|
@ -1,15 +1,12 @@
|
||||||
from .base import *
|
from .base import *
|
||||||
from .asset import *
|
from .asset import *
|
||||||
from .label import Label
|
from .label import Label
|
||||||
from .user import *
|
|
||||||
from .group import *
|
from .group import *
|
||||||
from .domain import *
|
from .domain import *
|
||||||
from .node import *
|
from .node import *
|
||||||
from .cmd_filter import *
|
|
||||||
from .authbook import *
|
|
||||||
from .utils import *
|
from .utils import *
|
||||||
from .authbook import *
|
|
||||||
from .gathered_user import *
|
from .gathered_user import *
|
||||||
from .favorite_asset import *
|
from .favorite_asset import *
|
||||||
from .account import *
|
from .account import *
|
||||||
from .backup import *
|
from .backup import *
|
||||||
|
from .user import *
|
||||||
|
|
|
@ -2,7 +2,7 @@ from django.db import models
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
from simple_history.models import HistoricalRecords
|
from simple_history.models import HistoricalRecords
|
||||||
|
|
||||||
from .user import ProtocolMixin
|
from .protocol import ProtocolMixin
|
||||||
from .base import BaseUser, AbsConnectivity
|
from .base import BaseUser, AbsConnectivity
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,140 +0,0 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
#
|
|
||||||
|
|
||||||
from django.db import models
|
|
||||||
from django.db.models import F
|
|
||||||
from django.utils.translation import ugettext_lazy as _
|
|
||||||
from simple_history.models import HistoricalRecords
|
|
||||||
|
|
||||||
from common.utils import lazyproperty, get_logger
|
|
||||||
from .base import BaseUser, AbsConnectivity
|
|
||||||
|
|
||||||
logger = get_logger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
__all__ = ['AuthBook']
|
|
||||||
|
|
||||||
|
|
||||||
class AuthBook(BaseUser, AbsConnectivity):
|
|
||||||
asset = models.ForeignKey('assets.Asset', on_delete=models.CASCADE, verbose_name=_('Asset'))
|
|
||||||
systemuser = models.ForeignKey('assets.SystemUser', on_delete=models.CASCADE, null=True, verbose_name=_("System user"))
|
|
||||||
version = models.IntegerField(default=1, verbose_name=_('Version'))
|
|
||||||
history = HistoricalRecords()
|
|
||||||
|
|
||||||
auth_attrs = ['username', 'password', 'private_key', 'public_key']
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
verbose_name = _('AuthBook')
|
|
||||||
unique_together = [('username', 'asset', 'systemuser')]
|
|
||||||
permissions = [
|
|
||||||
('test_authbook', _('Can test asset account connectivity')),
|
|
||||||
('view_assetaccountsecret', _('Can view asset account secret')),
|
|
||||||
('change_assetaccountsecret', _('Can change asset account secret')),
|
|
||||||
('view_assethistoryaccount', _('Can view asset history account')),
|
|
||||||
('view_assethistoryaccountsecret', _('Can view asset history account secret')),
|
|
||||||
]
|
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
|
||||||
super().__init__(*args, **kwargs)
|
|
||||||
self.auth_snapshot = {}
|
|
||||||
|
|
||||||
def get_or_systemuser_attr(self, attr):
|
|
||||||
val = getattr(self, attr, None)
|
|
||||||
if val:
|
|
||||||
return val
|
|
||||||
if self.systemuser:
|
|
||||||
return getattr(self.systemuser, attr, '')
|
|
||||||
return ''
|
|
||||||
|
|
||||||
def load_auth(self):
|
|
||||||
for attr in self.auth_attrs:
|
|
||||||
value = self.get_or_systemuser_attr(attr)
|
|
||||||
self.auth_snapshot[attr] = [getattr(self, attr), value]
|
|
||||||
setattr(self, attr, value)
|
|
||||||
|
|
||||||
def unload_auth(self):
|
|
||||||
if not self.systemuser:
|
|
||||||
return
|
|
||||||
|
|
||||||
for attr, values in self.auth_snapshot.items():
|
|
||||||
origin_value, loaded_value = values
|
|
||||||
current_value = getattr(self, attr, '')
|
|
||||||
if current_value == loaded_value:
|
|
||||||
setattr(self, attr, origin_value)
|
|
||||||
|
|
||||||
def save(self, *args, **kwargs):
|
|
||||||
self.unload_auth()
|
|
||||||
instance = super().save(*args, **kwargs)
|
|
||||||
self.load_auth()
|
|
||||||
return instance
|
|
||||||
|
|
||||||
@property
|
|
||||||
def username_display(self):
|
|
||||||
return self.get_or_systemuser_attr('username') or '*'
|
|
||||||
|
|
||||||
@lazyproperty
|
|
||||||
def systemuser_display(self):
|
|
||||||
if not self.systemuser:
|
|
||||||
return ''
|
|
||||||
return str(self.systemuser)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def smart_name(self):
|
|
||||||
username = self.username_display
|
|
||||||
|
|
||||||
if self.asset:
|
|
||||||
asset = str(self.asset)
|
|
||||||
else:
|
|
||||||
asset = '*'
|
|
||||||
return '{}@{}'.format(username, asset)
|
|
||||||
|
|
||||||
def sync_to_system_user_account(self):
|
|
||||||
if self.systemuser:
|
|
||||||
return
|
|
||||||
matched = AuthBook.objects.filter(
|
|
||||||
asset=self.asset, systemuser__username=self.username
|
|
||||||
)
|
|
||||||
if not matched:
|
|
||||||
return
|
|
||||||
|
|
||||||
for i in matched:
|
|
||||||
i.password = self.password
|
|
||||||
i.private_key = self.private_key
|
|
||||||
i.public_key = self.public_key
|
|
||||||
i.comment = 'Update triggered by account {}'.format(self.id)
|
|
||||||
|
|
||||||
# 不触发post_save信号
|
|
||||||
self.__class__.objects.bulk_update(matched, fields=['password', 'private_key', 'public_key'])
|
|
||||||
|
|
||||||
def remove_asset_admin_user_if_need(self):
|
|
||||||
if not self.asset or not self.systemuser:
|
|
||||||
return
|
|
||||||
if not self.systemuser.is_admin_user or self.asset.admin_user != self.systemuser:
|
|
||||||
return
|
|
||||||
self.asset.admin_user = None
|
|
||||||
self.asset.save()
|
|
||||||
logger.debug('Remove asset admin user: {} {}'.format(self.asset, self.systemuser))
|
|
||||||
|
|
||||||
def update_asset_admin_user_if_need(self):
|
|
||||||
if not self.asset or not self.systemuser:
|
|
||||||
return
|
|
||||||
if not self.systemuser.is_admin_user or self.asset.admin_user == self.systemuser:
|
|
||||||
return
|
|
||||||
self.asset.admin_user = self.systemuser
|
|
||||||
self.asset.save()
|
|
||||||
logger.debug('Update asset admin user: {} {}'.format(self.asset, self.systemuser))
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def get_queryset(cls, is_history_model=False):
|
|
||||||
model = cls.history.model if is_history_model else cls
|
|
||||||
queryset = model.objects.all() \
|
|
||||||
.annotate(ip=F('asset__ip')) \
|
|
||||||
.annotate(hostname=F('asset__hostname')) \
|
|
||||||
.annotate(platform=F('asset__platform__name')) \
|
|
||||||
.annotate(protocols=F('asset__protocols'))
|
|
||||||
return queryset
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return self.smart_name
|
|
||||||
|
|
||||||
|
|
|
@ -1,223 +0,0 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
#
|
|
||||||
import uuid
|
|
||||||
import re
|
|
||||||
|
|
||||||
from django.db import models
|
|
||||||
from django.db.models import Q
|
|
||||||
from django.core.validators import MinValueValidator, MaxValueValidator
|
|
||||||
from django.utils.translation import ugettext_lazy as _
|
|
||||||
|
|
||||||
from users.models import User, UserGroup
|
|
||||||
from applications.models import Application
|
|
||||||
from ..models import SystemUser, Asset
|
|
||||||
|
|
||||||
from common.utils import lazyproperty, get_logger, get_object_or_none
|
|
||||||
from orgs.mixins.models import OrgModelMixin
|
|
||||||
|
|
||||||
logger = get_logger(__file__)
|
|
||||||
|
|
||||||
__all__ = [
|
|
||||||
'CommandFilter', 'CommandFilterRule'
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
class CommandFilter(OrgModelMixin):
|
|
||||||
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
|
|
||||||
name = models.CharField(max_length=64, verbose_name=_("Name"))
|
|
||||||
users = models.ManyToManyField(
|
|
||||||
'users.User', related_name='cmd_filters', blank=True,
|
|
||||||
verbose_name=_("User")
|
|
||||||
)
|
|
||||||
user_groups = models.ManyToManyField(
|
|
||||||
'users.UserGroup', related_name='cmd_filters', blank=True,
|
|
||||||
verbose_name=_("User group"),
|
|
||||||
)
|
|
||||||
assets = models.ManyToManyField(
|
|
||||||
'assets.Asset', related_name='cmd_filters', blank=True,
|
|
||||||
verbose_name=_("Asset")
|
|
||||||
)
|
|
||||||
system_users = models.ManyToManyField(
|
|
||||||
'assets.SystemUser', related_name='cmd_filters', blank=True,
|
|
||||||
verbose_name=_("System user"))
|
|
||||||
applications = models.ManyToManyField(
|
|
||||||
'applications.Application', related_name='cmd_filters', blank=True,
|
|
||||||
verbose_name=_("Application")
|
|
||||||
)
|
|
||||||
is_active = models.BooleanField(default=True, verbose_name=_('Is active'))
|
|
||||||
comment = models.TextField(blank=True, default='', verbose_name=_("Comment"))
|
|
||||||
date_created = models.DateTimeField(auto_now_add=True)
|
|
||||||
date_updated = models.DateTimeField(auto_now=True)
|
|
||||||
created_by = models.CharField(
|
|
||||||
max_length=128, blank=True, default='', verbose_name=_('Created by')
|
|
||||||
)
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return self.name
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
unique_together = [('org_id', 'name')]
|
|
||||||
verbose_name = _("Command filter")
|
|
||||||
|
|
||||||
|
|
||||||
class CommandFilterRule(OrgModelMixin):
|
|
||||||
TYPE_REGEX = 'regex'
|
|
||||||
TYPE_COMMAND = 'command'
|
|
||||||
TYPE_CHOICES = (
|
|
||||||
(TYPE_REGEX, _('Regex')),
|
|
||||||
(TYPE_COMMAND, _('Command')),
|
|
||||||
)
|
|
||||||
|
|
||||||
ACTION_UNKNOWN = 10
|
|
||||||
|
|
||||||
class ActionChoices(models.IntegerChoices):
|
|
||||||
deny = 0, _('Deny')
|
|
||||||
allow = 9, _('Allow')
|
|
||||||
confirm = 2, _('Reconfirm')
|
|
||||||
|
|
||||||
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
|
|
||||||
filter = models.ForeignKey(
|
|
||||||
'CommandFilter', on_delete=models.CASCADE, verbose_name=_("Filter"), related_name='rules'
|
|
||||||
)
|
|
||||||
type = models.CharField(max_length=16, default=TYPE_COMMAND, choices=TYPE_CHOICES, verbose_name=_("Type"))
|
|
||||||
priority = models.IntegerField(
|
|
||||||
default=50, verbose_name=_("Priority"), help_text=_("1-100, the lower the value will be match first"),
|
|
||||||
validators=[MinValueValidator(1), MaxValueValidator(100)]
|
|
||||||
)
|
|
||||||
content = models.TextField(verbose_name=_("Content"), help_text=_("One line one command"))
|
|
||||||
ignore_case = models.BooleanField(default=True, verbose_name=_('Ignore case'))
|
|
||||||
action = models.IntegerField(default=ActionChoices.deny, choices=ActionChoices.choices, verbose_name=_("Action"))
|
|
||||||
# 动作: 附加字段
|
|
||||||
# - confirm: 命令复核人
|
|
||||||
reviewers = models.ManyToManyField(
|
|
||||||
'users.User', related_name='review_cmd_filter_rules', blank=True,
|
|
||||||
verbose_name=_("Reviewers")
|
|
||||||
)
|
|
||||||
comment = models.CharField(max_length=64, blank=True, default='', verbose_name=_("Comment"))
|
|
||||||
date_created = models.DateTimeField(auto_now_add=True)
|
|
||||||
date_updated = models.DateTimeField(auto_now=True)
|
|
||||||
created_by = models.CharField(max_length=128, blank=True, default='', verbose_name=_('Created by'))
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
ordering = ('priority', 'action')
|
|
||||||
verbose_name = _("Command filter rule")
|
|
||||||
|
|
||||||
@lazyproperty
|
|
||||||
def pattern(self):
|
|
||||||
if self.type == 'command':
|
|
||||||
s = self.construct_command_regex(content=self.content)
|
|
||||||
else:
|
|
||||||
s = r'{0}'.format(self.content)
|
|
||||||
|
|
||||||
return s
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def construct_command_regex(cls, content):
|
|
||||||
regex = []
|
|
||||||
content = content.replace('\r\n', '\n')
|
|
||||||
for _cmd in content.split('\n'):
|
|
||||||
cmd = re.sub(r'\s+', ' ', _cmd)
|
|
||||||
cmd = re.escape(cmd)
|
|
||||||
cmd = cmd.replace('\\ ', '\s+')
|
|
||||||
|
|
||||||
# 有空格就不能 铆钉单词了
|
|
||||||
if ' ' in _cmd:
|
|
||||||
regex.append(cmd)
|
|
||||||
continue
|
|
||||||
|
|
||||||
# 如果是单个字符
|
|
||||||
if cmd[-1].isalpha():
|
|
||||||
regex.append(r'\b{0}\b'.format(cmd))
|
|
||||||
else:
|
|
||||||
regex.append(r'\b{0}'.format(cmd))
|
|
||||||
s = r'{}'.format('|'.join(regex))
|
|
||||||
return s
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def compile_regex(regex, ignore_case):
|
|
||||||
try:
|
|
||||||
if ignore_case:
|
|
||||||
pattern = re.compile(regex, re.IGNORECASE)
|
|
||||||
else:
|
|
||||||
pattern = re.compile(regex)
|
|
||||||
except Exception as e:
|
|
||||||
error = _('The generated regular expression is incorrect: {}').format(str(e))
|
|
||||||
logger.error(error)
|
|
||||||
return False, error, None
|
|
||||||
return True, '', pattern
|
|
||||||
|
|
||||||
def match(self, data):
|
|
||||||
succeed, error, pattern = self.compile_regex(self.pattern, self.ignore_case)
|
|
||||||
if not succeed:
|
|
||||||
return self.ACTION_UNKNOWN, ''
|
|
||||||
|
|
||||||
found = pattern.search(data)
|
|
||||||
if not found:
|
|
||||||
return self.ACTION_UNKNOWN, ''
|
|
||||||
|
|
||||||
if self.action == self.ActionChoices.allow:
|
|
||||||
return self.ActionChoices.allow, found.group()
|
|
||||||
else:
|
|
||||||
return self.ActionChoices.deny, found.group()
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return '{} % {}'.format(self.type, self.content)
|
|
||||||
|
|
||||||
def create_command_confirm_ticket(self, run_command, session, cmd_filter_rule, org_id):
|
|
||||||
from tickets.const import TicketType
|
|
||||||
from tickets.models import ApplyCommandTicket
|
|
||||||
data = {
|
|
||||||
'title': _('Command confirm') + ' ({})'.format(session.user),
|
|
||||||
'type': TicketType.command_confirm,
|
|
||||||
'applicant': session.user_obj,
|
|
||||||
'apply_run_user_id': session.user_id,
|
|
||||||
'apply_run_asset': str(session.asset),
|
|
||||||
'apply_run_system_user_id': session.system_user_id,
|
|
||||||
'apply_run_command': run_command[:4090],
|
|
||||||
'apply_from_session_id': str(session.id),
|
|
||||||
'apply_from_cmd_filter_rule_id': str(cmd_filter_rule.id),
|
|
||||||
'apply_from_cmd_filter_id': str(cmd_filter_rule.filter.id),
|
|
||||||
'org_id': org_id,
|
|
||||||
}
|
|
||||||
ticket = ApplyCommandTicket.objects.create(**data)
|
|
||||||
assignees = self.reviewers.all()
|
|
||||||
ticket.open_by_system(assignees)
|
|
||||||
return ticket
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def get_queryset(cls, user_id=None, user_group_id=None, system_user_id=None,
|
|
||||||
asset_id=None, application_id=None, org_id=None):
|
|
||||||
user_groups = []
|
|
||||||
user = get_object_or_none(User, pk=user_id)
|
|
||||||
if user:
|
|
||||||
user_groups.extend(list(user.groups.all()))
|
|
||||||
user_group = get_object_or_none(UserGroup, pk=user_group_id)
|
|
||||||
if user_group:
|
|
||||||
org_id = user_group.org_id
|
|
||||||
user_groups.append(user_group)
|
|
||||||
system_user = get_object_or_none(SystemUser, pk=system_user_id)
|
|
||||||
asset = get_object_or_none(Asset, pk=asset_id)
|
|
||||||
application = get_object_or_none(Application, pk=application_id)
|
|
||||||
q = Q()
|
|
||||||
if user:
|
|
||||||
q |= Q(users=user)
|
|
||||||
if user_groups:
|
|
||||||
q |= Q(user_groups__in=set(user_groups))
|
|
||||||
if system_user:
|
|
||||||
org_id = system_user.org_id
|
|
||||||
q |= Q(system_users=system_user)
|
|
||||||
if asset:
|
|
||||||
org_id = asset.org_id
|
|
||||||
q |= Q(assets=asset)
|
|
||||||
if application:
|
|
||||||
org_id = application.org_id
|
|
||||||
q |= Q(applications=application)
|
|
||||||
if q:
|
|
||||||
cmd_filters = CommandFilter.objects.filter(q).filter(is_active=True)
|
|
||||||
if org_id:
|
|
||||||
cmd_filters = cmd_filters.filter(org_id=org_id)
|
|
||||||
rule_ids = cmd_filters.values_list('rules', flat=True)
|
|
||||||
rules = cls.objects.filter(id__in=rule_ids)
|
|
||||||
else:
|
|
||||||
rules = cls.objects.none()
|
|
||||||
return rules
|
|
|
@ -40,23 +40,6 @@ class SystemUser(ProtocolMixin, BaseUser):
|
||||||
username = '*'
|
username = '*'
|
||||||
return '{0.name}({1})'.format(self, username)
|
return '{0.name}({1})'.format(self, username)
|
||||||
|
|
||||||
@property
|
|
||||||
def cmd_filter_rules(self):
|
|
||||||
from .cmd_filter import CommandFilterRule
|
|
||||||
rules = CommandFilterRule.objects.filter(
|
|
||||||
filter__in=self.cmd_filters.all()
|
|
||||||
).distinct()
|
|
||||||
return rules
|
|
||||||
|
|
||||||
def is_command_can_run(self, command):
|
|
||||||
for rule in self.cmd_filter_rules:
|
|
||||||
action, matched_cmd = rule.match(command)
|
|
||||||
if action == rule.ActionChoices.allow:
|
|
||||||
return True, None
|
|
||||||
elif action == rule.ActionChoices.deny:
|
|
||||||
return False, matched_cmd
|
|
||||||
return True, None
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def create_accounts_with_assets(cls, asset_ids, system_user_ids):
|
def create_accounts_with_assets(cls, asset_ids, system_user_ids):
|
||||||
pass
|
pass
|
||||||
|
|
|
@ -1,119 +0,0 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
#
|
|
||||||
from django.db.models.signals import (
|
|
||||||
post_save, m2m_changed
|
|
||||||
)
|
|
||||||
from django.dispatch import receiver
|
|
||||||
|
|
||||||
from common.exceptions import M2MReverseNotAllowed
|
|
||||||
from common.const.signals import POST_ADD
|
|
||||||
from common.utils import get_logger
|
|
||||||
from common.decorator import on_transaction_commit
|
|
||||||
from assets.models import Asset, SystemUser, Node
|
|
||||||
from users.models import User
|
|
||||||
from assets.tasks import (
|
|
||||||
push_system_user_to_assets_manual,
|
|
||||||
push_system_user_to_assets,
|
|
||||||
add_nodes_assets_to_system_users
|
|
||||||
)
|
|
||||||
|
|
||||||
logger = get_logger(__file__)
|
|
||||||
|
|
||||||
|
|
||||||
@receiver(m2m_changed, sender=SystemUser.assets.through)
|
|
||||||
@on_transaction_commit
|
|
||||||
def on_system_user_assets_change(instance, action, model, pk_set, **kwargs):
|
|
||||||
"""
|
|
||||||
当系统用户和资产关系发生变化时,应该重新推送系统用户到新添加的资产中
|
|
||||||
"""
|
|
||||||
logger.debug("System user assets change signal recv: {}".format(instance))
|
|
||||||
|
|
||||||
if not instance:
|
|
||||||
logger.debug('No system user found')
|
|
||||||
return
|
|
||||||
|
|
||||||
if action != POST_ADD:
|
|
||||||
return
|
|
||||||
|
|
||||||
if model == Asset:
|
|
||||||
system_user_ids = [instance.id]
|
|
||||||
asset_ids = pk_set
|
|
||||||
else:
|
|
||||||
system_user_ids = pk_set
|
|
||||||
asset_ids = [instance.id]
|
|
||||||
# todo: Auto create account if need
|
|
||||||
SystemUser.create_accounts_with_assets(asset_ids, system_user_ids)
|
|
||||||
|
|
||||||
|
|
||||||
@receiver(m2m_changed, sender=SystemUser.users.through)
|
|
||||||
@on_transaction_commit
|
|
||||||
def on_system_user_users_change(sender, instance: SystemUser, action, model, pk_set, reverse, **kwargs):
|
|
||||||
"""
|
|
||||||
当系统用户和用户关系发生变化时,应该重新推送系统用户资产中
|
|
||||||
"""
|
|
||||||
if action != POST_ADD:
|
|
||||||
return
|
|
||||||
|
|
||||||
if reverse:
|
|
||||||
raise M2MReverseNotAllowed
|
|
||||||
|
|
||||||
if not instance.username_same_with_user:
|
|
||||||
return
|
|
||||||
|
|
||||||
logger.debug("System user users change signal recv: {}".format(instance))
|
|
||||||
usernames = model.objects.filter(pk__in=pk_set).values_list('username', flat=True)
|
|
||||||
|
|
||||||
for username in usernames:
|
|
||||||
push_system_user_to_assets_manual.delay(instance, username)
|
|
||||||
|
|
||||||
|
|
||||||
@receiver(m2m_changed, sender=SystemUser.nodes.through)
|
|
||||||
@on_transaction_commit
|
|
||||||
def on_system_user_nodes_change(sender, instance=None, action=None, model=None, pk_set=None, **kwargs):
|
|
||||||
"""
|
|
||||||
当系统用户和节点关系发生变化时,应该将节点下资产关联到新的系统用户上
|
|
||||||
"""
|
|
||||||
if action != POST_ADD:
|
|
||||||
return
|
|
||||||
logger.info("System user nodes update signal recv: {}".format(instance))
|
|
||||||
|
|
||||||
queryset = model.objects.filter(pk__in=pk_set)
|
|
||||||
if model == Node:
|
|
||||||
nodes_keys = queryset.values_list('key', flat=True)
|
|
||||||
system_users = [instance]
|
|
||||||
else:
|
|
||||||
nodes_keys = [instance.key]
|
|
||||||
system_users = queryset
|
|
||||||
add_nodes_assets_to_system_users.delay(nodes_keys, system_users)
|
|
||||||
|
|
||||||
|
|
||||||
@receiver(m2m_changed, sender=SystemUser.groups.through)
|
|
||||||
def on_system_user_groups_change(instance, action, pk_set, reverse, **kwargs):
|
|
||||||
"""
|
|
||||||
当系统用户和用户组关系发生变化时,应该将组下用户关联到新的系统用户上
|
|
||||||
"""
|
|
||||||
if action != POST_ADD:
|
|
||||||
return
|
|
||||||
if reverse:
|
|
||||||
raise M2MReverseNotAllowed
|
|
||||||
logger.info("System user groups update signal recv: {}".format(instance))
|
|
||||||
|
|
||||||
users = User.objects.filter(groups__id__in=pk_set).distinct()
|
|
||||||
instance.users.add(*users)
|
|
||||||
|
|
||||||
|
|
||||||
@receiver(post_save, sender=SystemUser, dispatch_uid="jms")
|
|
||||||
@on_transaction_commit
|
|
||||||
def on_system_user_update(instance: SystemUser, created, **kwargs):
|
|
||||||
"""
|
|
||||||
当系统用户更新时,可能更新了密钥,用户名等,这时要自动推送系统用户到资产上,
|
|
||||||
其实应该当 用户名,密码,密钥 sudo等更新时再推送,这里偷个懒,
|
|
||||||
这里直接取了 instance.assets 因为nodes和系统用户发生变化时,会自动将nodes下的资产
|
|
||||||
关联到上面
|
|
||||||
"""
|
|
||||||
if instance and not created:
|
|
||||||
logger.info("System user update signal recv: {}".format(instance))
|
|
||||||
assets = instance.assets.all().valid()
|
|
||||||
push_system_user_to_assets.delay(instance.id, [_asset.id for _asset in assets])
|
|
||||||
# add assets to su_from
|
|
||||||
instance.add_related_assets_to_su_from_if_need(assets)
|
|
|
@ -1,307 +0,0 @@
|
||||||
# ~*~ coding: utf-8 ~*~
|
|
||||||
|
|
||||||
from itertools import groupby
|
|
||||||
from celery import shared_task
|
|
||||||
from common.db.utils import get_object_if_need, get_objects
|
|
||||||
from django.utils.translation import ugettext as _, gettext_noop
|
|
||||||
from django.db.models import Empty
|
|
||||||
|
|
||||||
from common.utils import encrypt_password, get_logger
|
|
||||||
from assets.models import SystemUser, Asset
|
|
||||||
from orgs.utils import org_aware_func, tmp_to_root_org
|
|
||||||
from . import const
|
|
||||||
from .utils import clean_ansible_task_hosts, group_asset_by_platform
|
|
||||||
|
|
||||||
|
|
||||||
logger = get_logger(__file__)
|
|
||||||
__all__ = [
|
|
||||||
'push_system_user_util', 'push_system_user_to_assets',
|
|
||||||
'push_system_user_to_assets_manual', 'push_system_user_a_asset_manual',
|
|
||||||
'push_system_users_a_asset'
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
def _split_by_comma(raw: str):
|
|
||||||
try:
|
|
||||||
return [i.strip() for i in raw.split(',')]
|
|
||||||
except AttributeError:
|
|
||||||
return []
|
|
||||||
|
|
||||||
|
|
||||||
def _dump_args(args: dict):
|
|
||||||
return ' '.join([f'{k}={v}' for k, v in args.items() if v is not Empty])
|
|
||||||
|
|
||||||
|
|
||||||
def get_push_unixlike_system_user_tasks(system_user, username=None, **kwargs):
|
|
||||||
algorithm = kwargs.get('algorithm')
|
|
||||||
if username is None:
|
|
||||||
username = system_user.username
|
|
||||||
|
|
||||||
comment = system_user.name
|
|
||||||
if system_user.username_same_with_user:
|
|
||||||
from users.models import User
|
|
||||||
user = User.objects.filter(username=username).only('name', 'username').first()
|
|
||||||
if user:
|
|
||||||
comment = f'{system_user.name}[{str(user)}]'
|
|
||||||
comment = comment.replace(' ', '')
|
|
||||||
|
|
||||||
password = system_user.password
|
|
||||||
public_key = system_user.public_key
|
|
||||||
|
|
||||||
groups = _split_by_comma(system_user.system_groups)
|
|
||||||
|
|
||||||
if groups:
|
|
||||||
groups = '"%s"' % ','.join(groups)
|
|
||||||
|
|
||||||
add_user_args = {
|
|
||||||
'name': username,
|
|
||||||
'shell': system_user.shell or Empty,
|
|
||||||
'state': 'present',
|
|
||||||
'home': system_user.home or Empty,
|
|
||||||
'expires': -1,
|
|
||||||
'groups': groups or Empty,
|
|
||||||
'comment': comment
|
|
||||||
}
|
|
||||||
|
|
||||||
tasks = [
|
|
||||||
{
|
|
||||||
'name': 'Add user {}'.format(username),
|
|
||||||
'action': {
|
|
||||||
'module': 'user',
|
|
||||||
'args': _dump_args(add_user_args),
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'name': 'Add group {}'.format(username),
|
|
||||||
'action': {
|
|
||||||
'module': 'group',
|
|
||||||
'args': 'name={} state=present'.format(username),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
if not system_user.home:
|
|
||||||
tasks.extend([
|
|
||||||
{
|
|
||||||
'name': 'Check home dir exists',
|
|
||||||
'action': {
|
|
||||||
'module': 'stat',
|
|
||||||
'args': 'path=/home/{}'.format(username)
|
|
||||||
},
|
|
||||||
'register': 'home_existed'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'name': "Set home dir permission",
|
|
||||||
'action': {
|
|
||||||
'module': 'file',
|
|
||||||
'args': "path=/home/{0} owner={0} group={0} mode=700".format(username)
|
|
||||||
},
|
|
||||||
'when': 'home_existed.stat.exists == true'
|
|
||||||
}
|
|
||||||
])
|
|
||||||
if password:
|
|
||||||
tasks.append({
|
|
||||||
'name': 'Set {} password'.format(username),
|
|
||||||
'action': {
|
|
||||||
'module': 'user',
|
|
||||||
'args': 'name={} shell={} state=present password={}'.format(
|
|
||||||
username, system_user.shell,
|
|
||||||
encrypt_password(password, salt="K3mIlKK", algorithm=algorithm),
|
|
||||||
),
|
|
||||||
}
|
|
||||||
})
|
|
||||||
if public_key:
|
|
||||||
tasks.append({
|
|
||||||
'name': 'Set {} authorized key'.format(username),
|
|
||||||
'action': {
|
|
||||||
'module': 'authorized_key',
|
|
||||||
'args': "user={} state=present key='{}'".format(
|
|
||||||
username, public_key
|
|
||||||
)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
if system_user.sudo:
|
|
||||||
sudo = system_user.sudo.replace('\r\n', '\n').replace('\r', '\n')
|
|
||||||
sudo_list = sudo.split('\n')
|
|
||||||
sudo_tmp = []
|
|
||||||
for s in sudo_list:
|
|
||||||
sudo_tmp.append(s.strip(','))
|
|
||||||
sudo = ','.join(sudo_tmp)
|
|
||||||
tasks.append({
|
|
||||||
'name': 'Set {} sudo setting'.format(username),
|
|
||||||
'action': {
|
|
||||||
'module': 'lineinfile',
|
|
||||||
'args': "dest=/etc/sudoers state=present regexp='^{0} ALL=' "
|
|
||||||
"line='{0} ALL=(ALL) NOPASSWD: {1}' "
|
|
||||||
"validate='visudo -cf %s'".format(username, sudo)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
return tasks
|
|
||||||
|
|
||||||
|
|
||||||
def get_push_windows_system_user_tasks(system_user: SystemUser, username=None, **kwargs):
|
|
||||||
if username is None:
|
|
||||||
username = system_user.username
|
|
||||||
password = system_user.password
|
|
||||||
groups = {'Users', 'Remote Desktop Users'}
|
|
||||||
if system_user.system_groups:
|
|
||||||
groups.update(_split_by_comma(system_user.system_groups))
|
|
||||||
groups = ','.join(groups)
|
|
||||||
|
|
||||||
tasks = []
|
|
||||||
if not password:
|
|
||||||
logger.error("Error: no password found")
|
|
||||||
return tasks
|
|
||||||
|
|
||||||
if system_user.ad_domain:
|
|
||||||
logger.error('System user with AD domain do not support push.')
|
|
||||||
return tasks
|
|
||||||
|
|
||||||
task = {
|
|
||||||
'name': 'Add user {}'.format(username),
|
|
||||||
'action': {
|
|
||||||
'module': 'win_user',
|
|
||||||
'args': 'fullname={} '
|
|
||||||
'name={} '
|
|
||||||
'password={} '
|
|
||||||
'state=present '
|
|
||||||
'update_password=always '
|
|
||||||
'password_expired=no '
|
|
||||||
'password_never_expires=yes '
|
|
||||||
'groups="{}" '
|
|
||||||
'groups_action=add '
|
|
||||||
''.format(username, username, password, groups),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
tasks.append(task)
|
|
||||||
return tasks
|
|
||||||
|
|
||||||
|
|
||||||
def get_push_system_user_tasks(system_user, platform="unixlike", username=None, algorithm=None):
|
|
||||||
"""
|
|
||||||
获取推送系统用户的 ansible 命令,跟资产无关
|
|
||||||
:param system_user:
|
|
||||||
:param platform:
|
|
||||||
:param username: 当动态时,近推送某个
|
|
||||||
:return:
|
|
||||||
"""
|
|
||||||
get_task_map = {
|
|
||||||
"unixlike": get_push_unixlike_system_user_tasks,
|
|
||||||
"windows": get_push_windows_system_user_tasks,
|
|
||||||
}
|
|
||||||
get_tasks = get_task_map.get(platform, get_push_unixlike_system_user_tasks)
|
|
||||||
if not system_user.username_same_with_user:
|
|
||||||
return get_tasks(system_user, algorithm=algorithm)
|
|
||||||
tasks = []
|
|
||||||
# 仅推送这个username
|
|
||||||
if username is not None:
|
|
||||||
tasks.extend(get_tasks(system_user, username, algorithm=algorithm))
|
|
||||||
return tasks
|
|
||||||
users = system_user.users.all().values_list('username', flat=True)
|
|
||||||
print(_("System user is dynamic: {}").format(list(users)))
|
|
||||||
for _username in users:
|
|
||||||
tasks.extend(get_tasks(system_user, _username, algorithm=algorithm))
|
|
||||||
return tasks
|
|
||||||
|
|
||||||
|
|
||||||
@org_aware_func("system_user")
|
|
||||||
def push_system_user_util(system_user, assets, task_name, username=None):
|
|
||||||
from ops.utils import update_or_create_ansible_task
|
|
||||||
assets = clean_ansible_task_hosts(assets, system_user=system_user)
|
|
||||||
if not assets:
|
|
||||||
return {}
|
|
||||||
|
|
||||||
# 资产按平台分类
|
|
||||||
assets_sorted = sorted(assets, key=group_asset_by_platform)
|
|
||||||
platform_hosts = groupby(assets_sorted, key=group_asset_by_platform)
|
|
||||||
|
|
||||||
if system_user.username_same_with_user:
|
|
||||||
if username is None:
|
|
||||||
# 动态系统用户,但是没有指定 username
|
|
||||||
usernames = list(system_user.users.all().values_list('username', flat=True).distinct())
|
|
||||||
else:
|
|
||||||
usernames = [username]
|
|
||||||
else:
|
|
||||||
# 非动态系统用户指定 username 无效
|
|
||||||
assert username is None, 'Only Dynamic user can assign `username`'
|
|
||||||
usernames = [system_user.username]
|
|
||||||
|
|
||||||
def run_task(_tasks, _hosts):
|
|
||||||
if not _tasks:
|
|
||||||
return
|
|
||||||
task, created = update_or_create_ansible_task(
|
|
||||||
task_name=task_name, hosts=_hosts, tasks=_tasks, pattern='all',
|
|
||||||
options=const.TASK_OPTIONS, run_as_admin=True,
|
|
||||||
)
|
|
||||||
task.run()
|
|
||||||
|
|
||||||
for platform, _assets in platform_hosts:
|
|
||||||
_assets = list(_assets)
|
|
||||||
if not _assets:
|
|
||||||
continue
|
|
||||||
print(_("Start push system user for platform: [{}]").format(platform))
|
|
||||||
print(_("Hosts count: {}").format(len(_assets)))
|
|
||||||
|
|
||||||
for u in usernames:
|
|
||||||
for a in _assets:
|
|
||||||
system_user.load_asset_special_auth(a, u)
|
|
||||||
algorithm = 'des' if a.platform.name == 'AIX' else 'sha512'
|
|
||||||
tasks = get_push_system_user_tasks(
|
|
||||||
system_user, platform, username=u,
|
|
||||||
algorithm=algorithm
|
|
||||||
)
|
|
||||||
run_task(tasks, [a])
|
|
||||||
|
|
||||||
|
|
||||||
@shared_task(queue="ansible")
|
|
||||||
@tmp_to_root_org()
|
|
||||||
def push_system_user_to_assets_manual(system_user, username=None):
|
|
||||||
"""
|
|
||||||
将系统用户推送到与它关联的所有资产上
|
|
||||||
"""
|
|
||||||
system_user = get_object_if_need(SystemUser, system_user)
|
|
||||||
assets = system_user.get_related_assets()
|
|
||||||
task_name = gettext_noop("Push system users to assets: ") + system_user.name
|
|
||||||
return push_system_user_util(system_user, assets, task_name=task_name, username=username)
|
|
||||||
|
|
||||||
|
|
||||||
@shared_task(queue="ansible")
|
|
||||||
@tmp_to_root_org()
|
|
||||||
def push_system_user_a_asset_manual(system_user, asset, username=None):
|
|
||||||
"""
|
|
||||||
将系统用户推送到一个资产上
|
|
||||||
"""
|
|
||||||
# if username is None:
|
|
||||||
# username = system_user.username
|
|
||||||
task_name = gettext_noop("Push system users to asset: ") + "{}({}) => {}".format(
|
|
||||||
system_user.name, username or system_user.username, asset
|
|
||||||
)
|
|
||||||
return push_system_user_util(system_user, [asset], task_name=task_name, username=username)
|
|
||||||
|
|
||||||
|
|
||||||
@shared_task(queue="ansible")
|
|
||||||
@tmp_to_root_org()
|
|
||||||
def push_system_users_a_asset(system_users, asset):
|
|
||||||
for system_user in system_users:
|
|
||||||
push_system_user_a_asset_manual(system_user, asset)
|
|
||||||
|
|
||||||
|
|
||||||
@shared_task(queue="ansible")
|
|
||||||
@tmp_to_root_org()
|
|
||||||
def push_system_user_to_assets(system_user_id, asset_ids, username=None):
|
|
||||||
"""
|
|
||||||
推送系统用户到指定的若干资产上
|
|
||||||
"""
|
|
||||||
system_user = SystemUser.objects.get(id=system_user_id)
|
|
||||||
assets = get_objects(Asset, asset_ids)
|
|
||||||
task_name = gettext_noop("Push system users to assets: ") + system_user.name
|
|
||||||
|
|
||||||
return push_system_user_util(system_user, assets, task_name, username=username)
|
|
||||||
|
|
||||||
# @shared_task
|
|
||||||
# @register_as_period_task(interval=3600)
|
|
||||||
# @after_app_ready_start
|
|
||||||
# @after_app_shutdown_clean_periodic
|
|
||||||
# def push_system_user_period():
|
|
||||||
# for system_user in SystemUser.objects.all():
|
|
||||||
# push_system_user_related_nodes(system_user)
|
|
|
@ -1,151 +0,0 @@
|
||||||
|
|
||||||
from itertools import groupby
|
|
||||||
from collections import defaultdict
|
|
||||||
|
|
||||||
from celery import shared_task
|
|
||||||
from django.utils.translation import ugettext as _, gettext_noop
|
|
||||||
|
|
||||||
from assets.models import Asset
|
|
||||||
from common.utils import get_logger
|
|
||||||
from orgs.utils import tmp_to_org, org_aware_func
|
|
||||||
from ..models import SystemUser, Connectivity, Account
|
|
||||||
from . import const
|
|
||||||
from .utils import (
|
|
||||||
clean_ansible_task_hosts, group_asset_by_platform
|
|
||||||
)
|
|
||||||
|
|
||||||
logger = get_logger(__name__)
|
|
||||||
__all__ = [
|
|
||||||
'test_system_user_connectivity_util', 'test_system_user_connectivity_manual',
|
|
||||||
'test_system_user_connectivity_period', 'test_system_user_connectivity_a_asset',
|
|
||||||
'test_system_users_connectivity_a_asset'
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
def set_assets_accounts_connectivity(system_user, assets, results_summary):
|
|
||||||
asset_ids_ok = set()
|
|
||||||
asset_ids_failed = set()
|
|
||||||
|
|
||||||
asset_hostnames_ok = results_summary.get('contacted', {}).keys()
|
|
||||||
|
|
||||||
for asset in assets:
|
|
||||||
if asset.hostname in asset_hostnames_ok:
|
|
||||||
asset_ids_ok.add(asset.id)
|
|
||||||
else:
|
|
||||||
asset_ids_failed.add(asset.id)
|
|
||||||
|
|
||||||
accounts_ok = Account.objects.filter(asset_id__in=asset_ids_ok, systemuser=system_user)
|
|
||||||
accounts_failed = Account.objects.filter(asset_id__in=asset_ids_failed, systemuser=system_user)
|
|
||||||
|
|
||||||
Account.bulk_set_connectivity(accounts_ok, Connectivity.ok)
|
|
||||||
Account.bulk_set_connectivity(accounts_failed, Connectivity.failed)
|
|
||||||
|
|
||||||
|
|
||||||
@org_aware_func("system_user")
|
|
||||||
def test_system_user_connectivity_util(system_user, assets, task_name):
|
|
||||||
"""
|
|
||||||
Test system cant connect his assets or not.
|
|
||||||
:param system_user:
|
|
||||||
:param assets:
|
|
||||||
:param task_name:
|
|
||||||
:return:
|
|
||||||
"""
|
|
||||||
from ops.utils import update_or_create_ansible_task
|
|
||||||
|
|
||||||
if system_user.username_same_with_user:
|
|
||||||
logger.error(_("Dynamic system user not support test"))
|
|
||||||
return
|
|
||||||
|
|
||||||
# hosts = clean_ansible_task_hosts(assets, system_user=system_user)
|
|
||||||
# TODO: 这里不传递系统用户,因为clean_ansible_task_hosts会通过system_user来判断是否可以推送,
|
|
||||||
# 不符合测试可连接性逻辑, 后面需要优化此逻辑
|
|
||||||
hosts = clean_ansible_task_hosts(assets)
|
|
||||||
if not hosts:
|
|
||||||
return {}
|
|
||||||
platform_hosts_map = {}
|
|
||||||
hosts_sorted = sorted(hosts, key=group_asset_by_platform)
|
|
||||||
platform_hosts = groupby(hosts_sorted, key=group_asset_by_platform)
|
|
||||||
for i in platform_hosts:
|
|
||||||
platform_hosts_map[i[0]] = list(i[1])
|
|
||||||
|
|
||||||
platform_tasks_map = {
|
|
||||||
"unixlike": const.PING_UNIXLIKE_TASKS,
|
|
||||||
"windows": const.PING_WINDOWS_TASKS
|
|
||||||
}
|
|
||||||
|
|
||||||
results_summary = dict(
|
|
||||||
contacted=defaultdict(dict), dark=defaultdict(dict), success=True
|
|
||||||
)
|
|
||||||
|
|
||||||
def run_task(_tasks, _hosts, _username):
|
|
||||||
old_name = "{}".format(system_user)
|
|
||||||
new_name = "{}({})".format(system_user.name, _username)
|
|
||||||
_task_name = task_name.replace(old_name, new_name)
|
|
||||||
_task, created = update_or_create_ansible_task(
|
|
||||||
task_name=_task_name, hosts=_hosts, tasks=_tasks,
|
|
||||||
pattern='all', options=const.TASK_OPTIONS,
|
|
||||||
run_as=_username, system_user=system_user
|
|
||||||
)
|
|
||||||
raw, summary = _task.run()
|
|
||||||
success = summary.get('success', False)
|
|
||||||
contacted = summary.get('contacted', {})
|
|
||||||
dark = summary.get('dark', {})
|
|
||||||
|
|
||||||
results_summary['success'] &= success
|
|
||||||
results_summary['contacted'].update(contacted)
|
|
||||||
results_summary['dark'].update(dark)
|
|
||||||
|
|
||||||
for platform, _hosts in platform_hosts_map.items():
|
|
||||||
if not _hosts:
|
|
||||||
continue
|
|
||||||
if platform not in ["unixlike", "windows"]:
|
|
||||||
continue
|
|
||||||
|
|
||||||
tasks = platform_tasks_map[platform]
|
|
||||||
print(_("Start test system user connectivity for platform: [{}]").format(platform))
|
|
||||||
print(_("Hosts count: {}").format(len(_hosts)))
|
|
||||||
# 用户名不是动态的,用户名则是一个
|
|
||||||
logger.debug("System user not has special auth")
|
|
||||||
run_task(tasks, _hosts, system_user.username)
|
|
||||||
|
|
||||||
set_assets_accounts_connectivity(system_user, hosts, results_summary)
|
|
||||||
return results_summary
|
|
||||||
|
|
||||||
|
|
||||||
@shared_task(queue="ansible")
|
|
||||||
@org_aware_func("system_user")
|
|
||||||
def test_system_user_connectivity_manual(system_user, asset_ids=None):
|
|
||||||
task_name = gettext_noop("Test system user connectivity: ") + str(system_user)
|
|
||||||
if asset_ids:
|
|
||||||
assets = Asset.objects.filter(id__in=asset_ids)
|
|
||||||
else:
|
|
||||||
assets = system_user.get_related_assets()
|
|
||||||
test_system_user_connectivity_util(system_user, assets, task_name)
|
|
||||||
|
|
||||||
|
|
||||||
@shared_task(queue="ansible")
|
|
||||||
@org_aware_func("system_user")
|
|
||||||
def test_system_user_connectivity_a_asset(system_user, asset):
|
|
||||||
task_name = gettext_noop("Test system user connectivity: ") + "{} => {}".format(
|
|
||||||
system_user, asset
|
|
||||||
)
|
|
||||||
test_system_user_connectivity_util(system_user, [asset], task_name)
|
|
||||||
|
|
||||||
|
|
||||||
@shared_task(queue="ansible")
|
|
||||||
def test_system_users_connectivity_a_asset(system_users, asset):
|
|
||||||
for system_user in system_users:
|
|
||||||
test_system_user_connectivity_a_asset(system_user, asset)
|
|
||||||
|
|
||||||
|
|
||||||
@shared_task(queue="ansible")
|
|
||||||
def test_system_user_connectivity_period():
|
|
||||||
if not const.PERIOD_TASK_ENABLED:
|
|
||||||
logger.debug("Period task disabled, test system user connectivity pass")
|
|
||||||
return
|
|
||||||
queryset_map = SystemUser.objects.all_group_by_org()
|
|
||||||
for org, system_user in queryset_map.items():
|
|
||||||
task_name = gettext_noop("Test system user connectivity period: ") + str(system_user)
|
|
||||||
with tmp_to_org(org):
|
|
||||||
assets = system_user.get_related_assets()
|
|
||||||
test_system_user_connectivity_util(system_user, assets, task_name)
|
|
|
@ -16,7 +16,6 @@ router.register(r'account-secrets', api.AccountSecretsViewSet, 'account-secret')
|
||||||
router.register(r'accounts-history', api.AccountHistoryViewSet, 'account-history')
|
router.register(r'accounts-history', api.AccountHistoryViewSet, 'account-history')
|
||||||
router.register(r'account-history-secrets', api.AccountHistorySecretsViewSet, 'account-history-secret')
|
router.register(r'account-history-secrets', api.AccountHistorySecretsViewSet, 'account-history-secret')
|
||||||
router.register(r'platforms', api.AssetPlatformViewSet, 'platform')
|
router.register(r'platforms', api.AssetPlatformViewSet, 'platform')
|
||||||
router.register(r'system-users', api.SystemUserViewSet, 'system-user')
|
|
||||||
router.register(r'labels', api.LabelViewSet, 'label')
|
router.register(r'labels', api.LabelViewSet, 'label')
|
||||||
router.register(r'nodes', api.NodeViewSet, 'node')
|
router.register(r'nodes', api.NodeViewSet, 'node')
|
||||||
router.register(r'domains', api.DomainViewSet, 'domain')
|
router.register(r'domains', api.DomainViewSet, 'domain')
|
||||||
|
@ -41,11 +40,6 @@ urlpatterns = [
|
||||||
path('assets/<uuid:pk>/perm-user-groups/', api.AssetPermUserGroupListApi.as_view(), name='asset-perm-user-group-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('assets/<uuid:pk>/perm-user-groups/<uuid:perm_user_group_id>/permissions/', api.AssetPermUserGroupPermissionsListApi.as_view(), name='asset-perm-user-group-permission-list'),
|
||||||
|
|
||||||
path('system-users/<uuid:pk>/assets/<uuid:asset_id>/users/<uuid:user_id>/account/', api.SystemUserAssetAccountApi.as_view(), name='system-user-asset-account'),
|
|
||||||
path('system-users/<uuid:pk>/assets/<uuid:asset_id>/users/<uuid:user_id>/account-secret/', api.SystemUserAssetAccountSecretApi.as_view(), name='system-user-asset-account-secret'),
|
|
||||||
path('system-users/<uuid:pk>/cmd-filter-rules/', api.SystemUserCommandFilterRuleListApi.as_view(), name='system-user-cmd-filter-rule-list'),
|
|
||||||
path('cmd-filter-rules/', api.SystemUserCommandFilterRuleListApi.as_view(), name='cmd-filter-rules'),
|
|
||||||
|
|
||||||
path('accounts/tasks/', api.AccountTaskCreateAPI.as_view(), name='account-task-create'),
|
path('accounts/tasks/', api.AccountTaskCreateAPI.as_view(), name='account-task-create'),
|
||||||
|
|
||||||
path('nodes/tree/', api.NodeListAsTreeApi.as_view(), name='node-tree'),
|
path('nodes/tree/', api.NodeListAsTreeApi.as_view(), name='node-tree'),
|
||||||
|
@ -65,9 +59,5 @@ urlpatterns = [
|
||||||
|
|
||||||
]
|
]
|
||||||
|
|
||||||
old_version_urlpatterns = [
|
urlpatterns += router.urls + cmd_filter_router.urls
|
||||||
re_path('(?P<resource>admin-user|system-user|domain|gateway|cmd-filter|asset-user)/.*', capi.redirect_plural_name_api)
|
|
||||||
]
|
|
||||||
|
|
||||||
urlpatterns += router.urls + cmd_filter_router.urls + old_version_urlpatterns
|
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,7 @@ from django.db.models import F, TextChoices
|
||||||
from orgs.mixins.models import OrgModelMixin
|
from orgs.mixins.models import OrgModelMixin
|
||||||
from common.db import models
|
from common.db import models
|
||||||
from common.utils import lazyproperty
|
from common.utils import lazyproperty
|
||||||
from assets.models import Asset, SystemUser, Node, FamilyMixin
|
from assets.models import Asset, Node, FamilyMixin
|
||||||
|
|
||||||
from .base import BasePermission
|
from .base import BasePermission
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue