From cc7424dbfe78570ad504ed81b138575cd79dfa7c Mon Sep 17 00:00:00 2001 From: Bai Date: Mon, 5 Dec 2022 13:27:51 +0800 Subject: [PATCH] =?UTF-8?q?perf:=20=E4=BF=AE=E6=94=B9=20CommandFilterACL,?= =?UTF-8?q?=20CommandGroup=20Model=20=E7=9A=84=20Meta=20=E5=86=85=E9=83=A8?= =?UTF-8?q?=E7=B1=BB;=20=E4=BF=AE=E6=94=B9=20Command=20Model=20=E7=9A=84?= =?UTF-8?q?=20system=5Fuser=20->=20account=20=E5=AD=97=E6=AE=B5;=20?= =?UTF-8?q?=E4=BF=AE=E6=94=B9=20ConnectionToken=20=E7=9A=84=20command=5Ffi?= =?UTF-8?q?lter=5Facls=20=E8=BF=94=E5=9B=9E=E5=AD=97=E6=AE=B5;?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../migrations/0010_auto_20221205_1122.py | 25 ++++++++++++++ apps/acls/models/base.py | 5 +-- apps/acls/models/command_acl.py | 18 ++-------- apps/acls/models/login_acl.py | 4 +-- apps/acls/models/login_asset_acl.py | 5 ++- .../authentication/models/connection_token.py | 6 ++-- .../serializers/connection_token.py | 33 ++++++++++++------- apps/terminal/api/session/command.py | 6 ++-- apps/terminal/backends/command/base.py | 4 +-- apps/terminal/backends/command/db.py | 18 +++++----- apps/terminal/backends/command/es.py | 4 +-- apps/terminal/backends/command/models.py | 2 +- apps/terminal/backends/command/serializers.py | 5 +-- apps/terminal/filters.py | 6 ++-- ...0061_rename_system_user_command_account.py | 23 +++++++++++++ apps/terminal/models/session/command.py | 2 +- 16 files changed, 106 insertions(+), 60 deletions(-) create mode 100644 apps/acls/migrations/0010_auto_20221205_1122.py create mode 100644 apps/terminal/migrations/0061_rename_system_user_command_account.py diff --git a/apps/acls/migrations/0010_auto_20221205_1122.py b/apps/acls/migrations/0010_auto_20221205_1122.py new file mode 100644 index 000000000..78adde93b --- /dev/null +++ b/apps/acls/migrations/0010_auto_20221205_1122.py @@ -0,0 +1,25 @@ +# Generated by Django 3.2.14 on 2022-12-05 03:22 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('acls', '0009_auto_20221204_0001'), + ] + + operations = [ + migrations.AlterModelOptions( + name='commandfilteracl', + options={'ordering': ('priority', 'name'), 'verbose_name': 'Command acl'}, + ), + migrations.AlterModelOptions( + name='loginacl', + options={'ordering': ('priority', 'name'), 'verbose_name': 'Login acl'}, + ), + migrations.AlterModelOptions( + name='loginassetacl', + options={'ordering': ('priority', 'name'), 'verbose_name': 'Login asset acl'}, + ), + ] diff --git a/apps/acls/models/base.py b/apps/acls/models/base.py index 43577d44c..704e3d743 100644 --- a/apps/acls/models/base.py +++ b/apps/acls/models/base.py @@ -83,6 +83,7 @@ class BaseACL(CommonModelMixin): objects = ACLManager.from_queryset(BaseACLQuerySet)() class Meta: + ordering = ('priority', 'name') abstract = True @@ -96,7 +97,8 @@ class UserAssetAccountBaseACL(BaseACL, OrgModelMixin): objects = ACLManager.from_queryset(UserAssetAccountACLQuerySet)() - class Meta: + class Meta(BaseACL.Meta): + unique_together = ('name', 'org_id') abstract = True @classmethod @@ -118,4 +120,3 @@ class UserAssetAccountBaseACL(BaseACL, OrgModelMixin): if kwargs: queryset = queryset.filter(**kwargs) return queryset - diff --git a/apps/acls/models/command_acl.py b/apps/acls/models/command_acl.py index 51e8c7388..f10373a02 100644 --- a/apps/acls/models/command_acl.py +++ b/apps/acls/models/command_acl.py @@ -8,7 +8,7 @@ from django.utils.translation import ugettext_lazy as _ from common.utils import lazyproperty, get_logger from orgs.mixins.models import JMSOrgBaseModel -from .base import UserAssetAccountBaseACL, UserAssetAccountACLQuerySet, ACLManager +from .base import UserAssetAccountBaseACL logger = get_logger(__file__) @@ -94,23 +94,11 @@ class CommandGroup(JMSOrgBaseModel): return '{} % {}'.format(self.name, self.type) -class CommandFilterACLQuerySet(UserAssetAccountACLQuerySet): - def get_command_groups(self): - ids = self.values_list('id', flat=True) - queryset = CommandFilterACL.command_groups.through.objects.filter(commandfilteracl_id__in=ids) - cmd_group_ids = queryset.values_list('commandgroup_id', flat=True) - command_groups = CommandGroup.objects.filter(id__in=cmd_group_ids) - return command_groups - - class CommandFilterACL(UserAssetAccountBaseACL): command_groups = models.ManyToManyField(CommandGroup, verbose_name=_('Commands')) - objects = ACLManager.from_queryset(CommandFilterACLQuerySet)() - - class Meta: - unique_together = ('name', 'org_id') - ordering = ('priority', '-date_updated', 'name') + class Meta(UserAssetAccountBaseACL.Meta): + abstract = False verbose_name = _('Command acl') def __str__(self): diff --git a/apps/acls/models/login_acl.py b/apps/acls/models/login_acl.py index de6a73d1b..1178993c8 100644 --- a/apps/acls/models/login_acl.py +++ b/apps/acls/models/login_acl.py @@ -15,9 +15,9 @@ class LoginACL(BaseACL): # 规则, ip_group, time_period rules = models.JSONField(default=dict, verbose_name=_('Rule')) - class Meta: - ordering = ('priority', '-date_updated', 'name') + class Meta(BaseACL.Meta): verbose_name = _('Login acl') + abstract = False def __str__(self): return self.name diff --git a/apps/acls/models/login_asset_acl.py b/apps/acls/models/login_asset_acl.py index 695b83e05..bdaf9b60c 100644 --- a/apps/acls/models/login_asset_acl.py +++ b/apps/acls/models/login_asset_acl.py @@ -6,10 +6,9 @@ from .base import UserAssetAccountBaseACL class LoginAssetACL(UserAssetAccountBaseACL): - class Meta: - unique_together = ('name', 'org_id') - ordering = ('priority', '-date_updated', 'name') + class Meta(UserAssetAccountBaseACL.Meta): verbose_name = _('Login asset acl') + abstract = False def __str__(self): return self.name diff --git a/apps/authentication/models/connection_token.py b/apps/authentication/models/connection_token.py index ef4fad0ac..3de6f92af 100644 --- a/apps/authentication/models/connection_token.py +++ b/apps/authentication/models/connection_token.py @@ -160,15 +160,15 @@ class ConnectionToken(OrgModelMixin, JMSBaseModel): return self.domain.random_gateway() @lazyproperty - def acl_command_groups(self): + def command_filter_acls(self): from acls.models import CommandFilterACL kwargs = { 'user': self.user, 'asset': self.asset, 'account': self.account, } - command_groups = CommandFilterACL.filter_queryset(**kwargs).get_command_groups() - return command_groups + acls = CommandFilterACL.filter_queryset(**kwargs).valid() + return acls class SuperConnectionToken(ConnectionToken): diff --git a/apps/authentication/serializers/connection_token.py b/apps/authentication/serializers/connection_token.py index 3fefbb5af..070ef2b29 100644 --- a/apps/authentication/serializers/connection_token.py +++ b/apps/authentication/serializers/connection_token.py @@ -1,13 +1,15 @@ from django.utils.translation import ugettext_lazy as _ from rest_framework import serializers -from assets.models import Asset, CommandFilterRule, Account, Platform -from acls.models import CommandGroup -from assets.serializers import PlatformSerializer, AssetProtocolsSerializer -from authentication.models import ConnectionToken -from orgs.mixins.serializers import OrgResourceModelSerializerMixin -from perms.serializers.permission import ActionChoicesField from users.models import User +from assets.models import Asset, Account, Platform +from assets.serializers import PlatformSerializer, AssetProtocolsSerializer +from perms.serializers.permission import ActionChoicesField +from acls.models import CommandGroup, CommandFilterACL +from orgs.mixins.serializers import OrgResourceModelSerializerMixin +from common.drf.fields import ObjectRelatedField + +from ..models import ConnectionToken __all__ = [ 'ConnectionTokenSerializer', 'ConnectionTokenSecretSerializer', @@ -125,13 +127,20 @@ class ConnectionTokenGatewaySerializer(serializers.ModelSerializer): ] -class ConnectionTokenACLCmdGroupSerializer(serializers.ModelSerializer): - """ ACL command group""" +class ConnectionTokenACLSerializer(serializers.ModelSerializer): + command_groups = ObjectRelatedField( + many=True, required=False, queryset=CommandGroup.objects, + attrs=('id', 'name', 'type', 'content', 'ignore_case', 'pattern'), + label=_('Command group') + ) + reviewers = ObjectRelatedField( + many=True, queryset=User.objects, label=_("Reviewers"), required=False + ) class Meta: - model = CommandGroup + model = CommandFilterACL fields = [ - 'id', 'type', 'content', 'ignore_case', 'pattern' + 'id', 'name', 'command_groups', 'action', 'reviewers', 'priority', 'is_active' ] @@ -151,7 +160,7 @@ class ConnectionTokenSecretSerializer(OrgResourceModelSerializerMixin): account = ConnectionTokenAccountSerializer(read_only=True) gateway = ConnectionTokenGatewaySerializer(read_only=True) platform = ConnectionTokenPlatform(read_only=True) - acl_command_groups = ConnectionTokenACLCmdGroupSerializer(read_only=True, many=True) + command_filter_acls = ConnectionTokenACLSerializer(read_only=True, many=True) actions = ActionChoicesField() expire_at = serializers.IntegerField() expire_now = serializers.BooleanField(label=_('Expired now'), write_only=True, default=True) @@ -160,7 +169,7 @@ class ConnectionTokenSecretSerializer(OrgResourceModelSerializerMixin): model = ConnectionToken fields = [ 'id', 'value', 'user', 'asset', 'account', 'platform', - 'acl_command_groups', + 'command_filter_acls', 'protocol', 'gateway', 'actions', 'expire_at', 'expire_now', ] extra_kwargs = { diff --git a/apps/terminal/api/session/command.py b/apps/terminal/api/session/command.py index fec0848a5..53fc0ca8f 100644 --- a/apps/terminal/api/session/command.py +++ b/apps/terminal/api/session/command.py @@ -26,7 +26,7 @@ __all__ = ['CommandViewSet', 'InsecureCommandAlertAPI'] class CommandQueryMixin: command_store = get_command_storage() filterset_fields = [ - "asset", "system_user", "user", "session", + "asset", "account", "user", "session", "risk_level", "input" ] default_days_ago = 5 @@ -56,7 +56,7 @@ class CommandQueryMixin: multi_command_storage = get_multi_command_storage() queryset = multi_command_storage.filter( date_from=date_from, date_to=date_to, - user=q.get("user"), asset=q.get("asset"), system_user=q.get("system_user"), + user=q.get("user"), asset=q.get("asset"), account=q.get("account"), input=q.get("input"), session=q.get("session_id", q.get('session')), risk_level=self.get_query_risk_level(), org_id=self.get_org_id(), ) @@ -91,7 +91,7 @@ class CommandViewSet(JMSBulkModelViewSet): { "user": "admin", "asset": "localhost", - "system_user": "web", + "account": "web", "session": "xxxxxx", "input": "whoami", "output": "d2hvbWFp", # base64.b64encode(s) diff --git a/apps/terminal/backends/command/base.py b/apps/terminal/backends/command/base.py index 4bb85e127..a07c7090a 100644 --- a/apps/terminal/backends/command/base.py +++ b/apps/terminal/backends/command/base.py @@ -15,13 +15,13 @@ class CommandBase(object): @abc.abstractmethod def filter(self, date_from=None, date_to=None, - user=None, asset=None, system_user=None, + user=None, asset=None, account=None, input=None, session=None, risk_level=None, org_id=None): pass @abc.abstractmethod def count(self, date_from=None, date_to=None, - user=None, asset=None, system_user=None, + user=None, asset=None, account=None, input=None, session=None): pass diff --git a/apps/terminal/backends/command/db.py b/apps/terminal/backends/command/db.py index 8b11569e9..1cdc56bda 100644 --- a/apps/terminal/backends/command/db.py +++ b/apps/terminal/backends/command/db.py @@ -21,7 +21,7 @@ class CommandStore(CommandBase): """ self.model.objects.create( user=command["user"], asset=command["asset"], - system_user=command["system_user"], input=command["input"], + account=command["account"], input=command["input"], output=command["output"], session=command["session"], risk_level=command.get("risk_level", 0), org_id=command["org_id"], timestamp=command["timestamp"] @@ -36,7 +36,7 @@ class CommandStore(CommandBase): cmd_input = pretty_string(c['input']) cmd_output = pretty_string(c['output'], max_length=1024) _commands.append(self.model( - user=c["user"], asset=c["asset"], system_user=c["system_user"], + user=c["user"], asset=c["asset"], account=c["account"], input=cmd_input, output=cmd_output, session=c["session"], risk_level=c.get("risk_level", 0), org_id=c["org_id"], timestamp=c["timestamp"] @@ -64,7 +64,7 @@ class CommandStore(CommandBase): @staticmethod def make_filter_kwargs( date_from=None, date_to=None, - user=None, asset=None, system_user=None, + user=None, asset=None, account=None, input=None, session=None, risk_level=None, org_id=None): filter_kwargs = {} date_from_default = timezone.now() - datetime.timedelta(days=7) @@ -87,8 +87,8 @@ class CommandStore(CommandBase): filter_kwargs["user__startswith"] = user if asset: filter_kwargs['asset'] = asset - if system_user: - filter_kwargs['system_user'] = system_user + if account: + filter_kwargs['account'] = account if input: filter_kwargs['input__icontains'] = input if session: @@ -100,22 +100,22 @@ class CommandStore(CommandBase): return filter_kwargs def filter(self, date_from=None, date_to=None, - user=None, asset=None, system_user=None, + user=None, asset=None, account=None, input=None, session=None, risk_level=None, org_id=None): filter_kwargs = self.make_filter_kwargs( date_from=date_from, date_to=date_to, user=user, - asset=asset, system_user=system_user, input=input, + asset=asset, account=account, input=input, session=session, risk_level=risk_level, org_id=org_id, ) queryset = self.model.objects.filter(**filter_kwargs) return queryset def count(self, date_from=None, date_to=None, - user=None, asset=None, system_user=None, + user=None, asset=None, account=None, input=None, session=None): filter_kwargs = self.make_filter_kwargs( date_from=date_from, date_to=date_to, user=user, - asset=asset, system_user=system_user, input=input, + asset=asset, account=account, input=input, session=session, ) count = self.model.objects.filter(**filter_kwargs).count() diff --git a/apps/terminal/backends/command/es.py b/apps/terminal/backends/command/es.py index 20602769c..ea73cf1b6 100644 --- a/apps/terminal/backends/command/es.py +++ b/apps/terminal/backends/command/es.py @@ -49,7 +49,7 @@ class CommandStore(object): self.es = Elasticsearch(hosts=hosts, max_retries=0, **kwargs) self.exact_fields = set() - self.match_fields = {'input', 'risk_level', 'user', 'asset', 'system_user'} + self.match_fields = {'input', 'risk_level', 'user', 'asset', 'account'} may_exact_fields = {'session', 'org_id'} if self.is_new_index_type(): @@ -142,7 +142,7 @@ class CommandStore(object): def make_data(command): data = dict( user=command["user"], asset=command["asset"], - system_user=command["system_user"], input=command["input"], + account=command["account"], input=command["input"], output=command["output"], risk_level=command["risk_level"], session=command["session"], timestamp=command["timestamp"], org_id=command["org_id"] diff --git a/apps/terminal/backends/command/models.py b/apps/terminal/backends/command/models.py index d6eb7a458..bf9c56f81 100644 --- a/apps/terminal/backends/command/models.py +++ b/apps/terminal/backends/command/models.py @@ -19,7 +19,7 @@ class AbstractSessionCommand(OrgModelMixin): id = models.UUIDField(default=uuid.uuid4, primary_key=True) user = models.CharField(max_length=64, db_index=True, verbose_name=_("User")) asset = models.CharField(max_length=128, db_index=True, verbose_name=_("Asset")) - system_user = models.CharField(max_length=64, db_index=True, verbose_name=_("System user")) + account = models.CharField(max_length=64, db_index=True, verbose_name=_("Account")) input = models.CharField(max_length=128, db_index=True, verbose_name=_("Input")) output = models.CharField(max_length=1024, blank=True, verbose_name=_("Output")) session = models.CharField(max_length=36, db_index=True, verbose_name=_("Session")) diff --git a/apps/terminal/backends/command/serializers.py b/apps/terminal/backends/command/serializers.py index 83b9d55d2..ccf6984b8 100644 --- a/apps/terminal/backends/command/serializers.py +++ b/apps/terminal/backends/command/serializers.py @@ -33,7 +33,8 @@ class SessionCommandSerializer(SimpleSessionCommandSerializer): """使用这个类作为基础Command Log Serializer类, 用来序列化""" id = serializers.UUIDField(read_only=True) - system_user = serializers.CharField(label=_("System user")) # 限制 64 字符,不能直接迁移成 128 字符,命令表数据量会比较大 + # 限制 64 字符,不能直接迁移成 128 字符,命令表数据量会比较大 + account = serializers.CharField(label=_("Account ")) output = serializers.CharField(max_length=2048, allow_blank=True, label=_("Output")) risk_level_display = serializers.SerializerMethodField(label=_('Risk level display')) timestamp = serializers.IntegerField(label=_('Timestamp')) @@ -45,7 +46,7 @@ class SessionCommandSerializer(SimpleSessionCommandSerializer): risk_mapper = dict(AbstractSessionCommand.RISK_LEVEL_CHOICES) return risk_mapper.get(obj.risk_level) - def validate_system_user(self, value): + def validate_account(self, value): if len(value) > 64: value = pretty_string(value, 64) return value diff --git a/apps/terminal/filters.py b/apps/terminal/filters.py index 79f4048fe..a0287b5bd 100644 --- a/apps/terminal/filters.py +++ b/apps/terminal/filters.py @@ -16,7 +16,7 @@ class CommandFilter(filters.FilterSet): class Meta: model = Command fields = [ - 'asset', 'system_user', 'user', 'session', 'risk_level', 'input', + 'asset', 'account', 'user', 'session', 'risk_level', 'input', 'date_from', 'date_to', 'session_id', 'risk_level', 'command_storage_id', ] @@ -49,14 +49,14 @@ class CommandFilter(filters.FilterSet): class CommandFilterForStorageTree(CommandFilter): asset = filters.CharFilter(method='do_nothing') - system_user = filters.CharFilter(method='do_nothing') + account = filters.CharFilter(method='do_nothing') session = filters.CharFilter(method='do_nothing') risk_level = filters.NumberFilter(method='do_nothing') class Meta: model = CommandStorage fields = [ - 'asset', 'system_user', 'user', 'session', 'risk_level', 'input', + 'asset', 'account', 'user', 'session', 'risk_level', 'input', 'date_from', 'date_to', 'session_id', 'risk_level', 'command_storage_id', ] diff --git a/apps/terminal/migrations/0061_rename_system_user_command_account.py b/apps/terminal/migrations/0061_rename_system_user_command_account.py new file mode 100644 index 000000000..ab7ee1f32 --- /dev/null +++ b/apps/terminal/migrations/0061_rename_system_user_command_account.py @@ -0,0 +1,23 @@ +# Generated by Django 3.2.14 on 2022-12-05 05:16 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('terminal', '0060_alter_applethostdeployment_options'), + ] + + operations = [ + migrations.RenameField( + model_name='command', + old_name='system_user', + new_name='account', + ), + migrations.AlterField( + model_name='command', + name='account', + field=models.CharField(db_index=True, max_length=64, verbose_name='Account'), + ), + ] diff --git a/apps/terminal/models/session/command.py b/apps/terminal/models/session/command.py index c940e855b..3d377523b 100644 --- a/apps/terminal/models/session/command.py +++ b/apps/terminal/models/session/command.py @@ -33,7 +33,7 @@ class Command(AbstractSessionCommand): cls(**{ 'user': random_string(6), 'asset': random_string(10), - 'system_user': random_string(6), + 'account': random_string(6), 'session': str(uuid.uuid4()), 'input': random_string(16), 'output': random_string(64),